Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Misc improvements #344

Draft
wants to merge 8 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,18 @@
"recommended": true,
"suspicious": {
"noConsoleLog": "error"
},
"style": {
"useFilenamingConvention": {
"level": "error",
"options": {
"filenameCases": ["kebab-case"]
}
}
}
}
},
"files": {
"ignore": ["dist/**"]
"ignore": ["dist/**", "npm/**"]
}
}
8 changes: 8 additions & 0 deletions src/cbor/gap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
// Why is the default value being stored in an array? undefined, null, false, etc... are all valid defaults,
// and specifying a field on a class as optional will make it undefined by default.

/**
* Represents a Gap and its intended value.
*/
export type Fill<T = unknown> = [Gap<T>, T];

/**
* A Gap represents a placeholder value that can be filled
* at a later point in time.
*/
export class Gap<T = unknown> {
readonly args: [T?] = [];
constructor(...args: [T?]) {
Expand Down
11 changes: 7 additions & 4 deletions src/data/cbor.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
import { Tagged, decode, encode } from "../cbor";
import {
cborCustomDateToDate,
dateToCborCustomDate,
} from "./types/datetime.ts";
import { Decimal } from "./types/decimal.ts";
import { Duration } from "./types/duration.ts";
import { Future } from "./types/future.ts";

import {
GeometryCollection,
GeometryLine,
Expand All @@ -15,12 +12,18 @@ import {
GeometryPoint,
GeometryPolygon,
} from "./types/geometry.ts";

import {
Range,
RecordIdRange,
cborToRange,
rangeToCbor,
} from "./types/range.ts";

import { Tagged, decode, encode } from "../cbor";
import { Decimal } from "./types/decimal.ts";
import { Duration } from "./types/duration.ts";
import { Future } from "./types/future.ts";
import { RecordId, StringRecordId } from "./types/recordid.ts";
import { Table } from "./types/table.ts";
import { Uuid } from "./types/uuid.ts";
Expand Down
14 changes: 9 additions & 5 deletions src/data/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,15 @@ export {
type RecordIdValue,
escape_ident,
} from "./types/recordid.ts";

export {
Range,
type Bound,
BoundIncluded,
BoundExcluded,
RecordIdRange,
} from "./types/range.ts";
export { Future } from "./types/future.ts";
export { Uuid } from "./types/uuid.ts";
export { Duration } from "./types/duration.ts";
export { Decimal } from "./types/decimal.ts";
export { Table } from "./types/table.ts";

export {
Geometry,
GeometryCollection,
Expand All @@ -26,4 +23,11 @@ export {
GeometryPoint,
GeometryPolygon,
} from "./types/geometry.ts";

export { Future } from "./types/future.ts";
export { Uuid } from "./types/uuid.ts";
export { Duration } from "./types/duration.ts";
export { Decimal } from "./types/decimal.ts";
export { Table } from "./types/table.ts";

export { encodeCbor, decodeCbor } from "./cbor.ts";
9 changes: 5 additions & 4 deletions src/data/types/range.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import { Tagged } from "../../cbor";
import { SurrealDbError } from "../../errors";
import { toSurrealqlString } from "../../util/to-surrealql-string";
import { TAG_BOUND_EXCLUDED, TAG_BOUND_INCLUDED, TAG_RANGE } from "../cbor";
import {
type RecordIdValue,
escape_id_part,
escape_ident,
isValidIdPart,
} from "./recordid";

import { Tagged } from "../../cbor";
import { SurrealDbError } from "../../errors";
import { toSurrealqlString } from "../../util/to-surrealql-string";
import { TAG_BOUND_EXCLUDED, TAG_BOUND_INCLUDED } from "../cbor";

export class Range<Beg, End> {
constructor(
readonly beg: Bound<Beg>,
Expand Down
11 changes: 3 additions & 8 deletions src/engines/abstract.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import type { Encoded } from "../cbor";
import type { LiveHandlerArguments, RpcRequest, RpcResponse } from "../types";

import type { EngineDisconnected } from "../errors";
import type {
LiveAction,
LiveHandlerArguments,
Patch,
RpcRequest,
RpcResponse,
} from "../types";
import type { Emitter } from "../util/emitter";

export type Engine = new (context: EngineContext) => AbstractEngine;
Expand All @@ -16,6 +10,7 @@ export type EngineEvents = {
connecting: [];
connected: [];
disconnected: [];
reconnecting: [];
error: [Error];

[K: `rpc-${string | number}`]: [RpcResponse | EngineDisconnected];
Expand Down
8 changes: 5 additions & 3 deletions src/engines/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import {
HttpConnectionError,
MissingNamespaceDatabase,
} from "../errors";
import type { RpcRequest, RpcResponse } from "../types";
import { getIncrementalID } from "../util/getIncrementalID";
import { retrieveRemoteVersion } from "../util/versionCheck";

import {
AbstractEngine,
ConnectionStatus,
type EngineEvents,
} from "./abstract";

import type { RpcRequest, RpcResponse } from "../types";
import { getIncrementalID } from "../util/get-incremental-id";
import { retrieveRemoteVersion } from "../util/version-check";

const ALWAYS_ALLOW = new Set([
"signin",
"signup",
Expand Down
10 changes: 6 additions & 4 deletions src/engines/ws.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
import { WebSocket } from "isows";
import {
ConnectionUnavailable,
EngineDisconnected,
ResponseError,
UnexpectedConnectionError,
UnexpectedServerResponse,
} from "../errors";
import { type RpcRequest, type RpcResponse, isLiveResult } from "../types";
import { getIncrementalID } from "../util/getIncrementalID";
import { retrieveRemoteVersion } from "../util/versionCheck";

import {
AbstractEngine,
ConnectionStatus,
type EngineContext,
type EngineEvents,
} from "./abstract";

import { WebSocket } from "isows";
import { type RpcRequest, type RpcResponse, isLiveResult } from "../types";
import { getIncrementalID } from "../util/get-incremental-id";
import { retrieveRemoteVersion } from "../util/version-check";

export class WebsocketEngine extends AbstractEngine {
private pinger?: Pinger;
private socket?: WebSocket;
Expand Down
10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,25 @@
export { Emitter, type Listener, type UnknownEvents } from "./util/emitter.ts";
export { surql, surrealql } from "./util/tagged-template.ts";
export { PreparedQuery } from "./util/PreparedQuery.ts";
export { PreparedQuery } from "./util/prepared-query.ts";

export * as cbor from "./cbor";
export * from "./cbor/gap";
export * from "./cbor/error";
export * from "./data";
export * from "./errors.ts";
export * from "./types.ts";
export * from "./util/jsonify.ts";
export * from "./util/versionCheck.ts";
export * from "./util/getIncrementalID.ts";
export * from "./util/version-check.ts";
export * from "./util/get-incremental-id.ts";
export * from "./util/string-prefixes.ts";
export * from "./util/to-surrealql-string.ts";

export {
ConnectionStatus,
AbstractEngine,
type Engine,
type Engines,
type EngineEvents,
} from "./engines/abstract.ts";

export { Surreal, Surreal as default } from "./surreal.ts";
43 changes: 31 additions & 12 deletions src/surreal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,14 @@ import {
decodeCbor,
encodeCbor,
} from "./data";

import {
type AbstractEngine,
ConnectionStatus,
EngineContext,
type EngineEvents,
type Engines,
} from "./engines/abstract.ts";
import { PreparedQuery } from "./util/PreparedQuery.ts";
import { Emitter } from "./util/emitter.ts";
import { processAuthVars } from "./util/processAuthVars.ts";
import { versionCheck } from "./util/versionCheck.ts";

import {
type AccessRecordAuth,
Expand All @@ -33,29 +30,34 @@ import {
convertAuth,
} from "./types.ts";

import { type Fill, partiallyEncodeObject } from "./cbor";
import { replacer } from "./data/cbor.ts";
import type { RecordIdRange } from "./data/types/range.ts";
import { HttpEngine } from "./engines/http.ts";
import { WebsocketEngine } from "./engines/ws.ts";
import {
EngineDisconnected,
NoActiveSocket,
NoDatabaseSpecified,
NoNamespaceSpecified,
NoTokenReturned,
ResponseError,
SurrealDbError,
UnsupportedEngine,
} from "./errors.ts";

import { type Fill, partiallyEncodeObject } from "./cbor";
import { replacer } from "./data/cbor.ts";
import type { RecordIdRange } from "./data/types/range.ts";
import { HttpEngine } from "./engines/http.ts";
import { WebsocketEngine } from "./engines/ws.ts";
import { Emitter } from "./util/emitter.ts";
import { PreparedQuery } from "./util/prepared-query.ts";
import { processAuthVars } from "./util/process-auth-vars.ts";
import { versionCheck } from "./util/version-check.ts";

type R = Prettify<Record<string, unknown>>;
type RecordId<Tb extends string = string> = _RecordId<Tb> | StringRecordId;

export class Surreal {
public connection: AbstractEngine | undefined;
ready?: Promise<void>;
emitter: Emitter<EngineEvents>;
terminated = false;
reconnector?: unknown;
protected engines: Engines = {
ws: WebsocketEngine,
wss: WebsocketEngine,
Expand All @@ -82,7 +84,9 @@ export class Surreal {

/**
* Establish a socket connection to the database
* @param connection - Connection details
*
* @param url - The URL of the SurrealDB instance.
* @param opts - Options for the connection.
*/
async connect(
url: string | URL,
Expand All @@ -93,6 +97,8 @@ export class Surreal {
prepare?: (connection: Surreal) => unknown;
versionCheck?: boolean;
versionCheckTimeout?: number;
reconnect?: boolean;
reconnectTimeout?: number;
} = {},
): Promise<true> {
// biome-ignore lint/style/noParameterAssign: Need to ensure it's a URL
Expand Down Expand Up @@ -130,6 +136,17 @@ export class Surreal {
versionCheck(version);
}

// Schedule reconnect
if (opts.reconnect) {
const delay = opts.reconnectTimeout ?? 5000;
this.emitter.subscribeOnce(ConnectionStatus.Disconnected).then(() => {
if (this.terminated) return;
this.emitter.emit("reconnecting", []);
this.reconnector = setTimeout(() => this.connect(url, opts), delay);
});
}

this.terminated = false;
this.connection = connection;
this.ready = new Promise((resolve, reject) =>
connection
Expand Down Expand Up @@ -162,6 +179,8 @@ export class Surreal {
* Disconnect the socket to the database
*/
async close(): Promise<true> {
clearTimeout(this.reconnector as number);
this.terminated = true;
this.clean();
await this.connection?.disconnect();
return true;
Expand Down
4 changes: 2 additions & 2 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Encoded, Fill } from "./cbor";
import type { Fill } from "./cbor";
import { type RecordId, Uuid } from "./data";
import { SurrealDbError } from "./errors";
import type { PreparedQuery } from "./util/PreparedQuery";
import type { PreparedQuery } from "./util/prepared-query";

export type ActionResult<T extends Record<string, unknown>> = Prettify<
T["id"] extends RecordId ? T : { id: RecordId } & T
Expand Down
9 changes: 8 additions & 1 deletion src/util/emitter.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
export type UnknownEvents = Record<string, unknown[]>;

/**
* A listener which can be used to subscribe to events.
*/
export type Listener<Args extends unknown[] = unknown[]> = (
...args: Args
) => unknown;
export type UnknownEvents = Record<string, unknown[]>;

/**
* An event emitter which can be used to subscribe to and emit events.
*/
export class Emitter<Events extends UnknownEvents = UnknownEvents> {
private collectable: Partial<{
[K in keyof Events]: Events[K][];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
let id = 0;

export function getIncrementalID(): string {
id = (id + 1) % Number.MAX_SAFE_INTEGER;
return id.toString();
Expand Down
6 changes: 6 additions & 0 deletions src/util/jsonify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ export type Jsonify<T> = T extends
? `${Tb}`
: T;

/**
* Recursively converts SurrealQL values to a JSON serializable representation.
*
* @param input The structure to convert recursively.
* @returns JSON serializable representation of the input.
*/
export function jsonify<T>(input: T): Jsonify<T> {
if (typeof input === "object") {
if (input === null) return null as Jsonify<T>;
Expand Down
11 changes: 11 additions & 0 deletions src/util/PreparedQuery.ts → src/util/prepared-query.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,22 @@ import {
encode,
partiallyEncodeObject,
} from "../cbor";

import { replacer } from "../data/cbor";

let textEncoder: TextEncoder;

export type ConvertMethod<T = unknown> = (result: unknown[]) => T;

/**
* A prepared query that encapulates a query and its bindings.
*
* While bound values are safely encoded into the query, you can
* pass a `Gap` instance to later set or override it when sending the query.
*
* Prepared queries can be extended with the `append` method, which
* allows you to add more query segments and bindings.
*/
export class PreparedQuery {
private _query: Uint8Array;
private _bindings: Record<string, PartiallyEncoded>;
Expand Down
File renamed without changes.
Loading
Loading