@tai-kun/surrealdb
Version:
The SurrealDB SDK for JavaScript
801 lines (706 loc) • 23 kB
text/typescript
import Base, { type ClientRpcOptions } from "@tai-kun/surrealdb/basic-client";
import { QueryFailedError } from "@tai-kun/surrealdb/errors";
import type {
Auth,
LivePayload,
Patch,
PreparedQueryLike,
QueryResult,
ReadonlyPatch,
RecordAccessAuth,
RpcGraphqlRequest,
RpcResultMapping,
SlotLike,
} from "@tai-kun/surrealdb/types";
import { isTable, type TaskListener } from "@tai-kun/surrealdb/utils";
import type { Simplify, UnionToIntersection } from "type-fest";
import type { DataType } from "../../surreal/data-types";
import Jwt from "./jwt";
// re-exports
export type * from "@tai-kun/surrealdb/basic-client";
type Override<T, U> = Simplify<Omit<T, keyof U> & U>;
export type InferSlotVars<TSlot extends SlotLike> = UnionToIntersection<
// dprint-ignore
{
[TName in TSlot["name"]]:
TSlot extends SlotLike<TName, infer TRequired, infer TValue>
? TRequired extends false
? { readonly [_ in TName]?: TValue }
: { readonly [_ in TName]: TValue } // boolean の場合も必須で。
: never;
}[TSlot["name"]]
>;
export interface LiveOptions extends ClientRpcOptions {
readonly diff?: boolean | undefined;
}
export type LiveHandler<TPayload extends LivePayload<any, any> = LivePayload> =
TaskListener<[payload: TPayload]>;
export type InferLivePayload<
TUuid,
TData extends Readonly<RecordData> = RecordData,
TPatch extends Patch[] = Patch[],
> = TUuid extends { __diff: false }
? LivePayload.Data<TData, DataType.Thing | string>
: TUuid extends { __diff: true }
? LivePayload.Diff<TData, TPatch, DataType.Thing | string>
: LivePayload<TData, TPatch>;
export type ActionResult<
TData extends Readonly<RecordData> = RecordData,
> = [Extract<keyof TData, "id">] extends [never]
? ({ id: DataType.Thing | string } & TData)
: TData;
type _In<T> = [Extract<keyof T, "in">] extends [never]
? ({ in: DataType.Thing | string } & T)
: T;
type _Out<T> = [Extract<keyof T, "out">] extends [never]
? ({ out: DataType.Thing | string } & T)
: T;
export type RelateResult<
TData extends Readonly<RecordData> = RecordData,
> = ActionResult<_In<_Out<TData>>>;
export interface PatchOptions extends ClientRpcOptions {
readonly diff?: boolean | undefined;
}
export interface RunOptions extends ClientRpcOptions {
readonly version?: string | undefined;
}
export interface GraphqlOptions
extends ClientRpcOptions, NonNullable<RpcGraphqlRequest["params"][1]>
{}
type RecordData = {
[p: string]: unknown;
};
// DEFINE TABLE ... TYPE NORMAL
type NormalRecord = {
id: DataType.Thing | string;
// RecordData
[p: string]: unknown;
};
export default class Client extends Base {
async ping(options?: ClientRpcOptions | undefined): Promise<void> {
await this.rpc("ping", [], options);
}
async use(
ns: string | null | undefined,
options?: ClientRpcOptions | undefined,
): Promise<void>;
async use(
ns: string | null | undefined,
db?: string | null | undefined,
options?: ClientRpcOptions | undefined,
): Promise<void>;
async use(
target: [ns: string | null | undefined, db?: string | null | undefined],
options?: ClientRpcOptions | undefined,
): Promise<void>;
async use(
target: {
readonly ns?: string | null | undefined;
readonly db?: string | null | undefined;
},
options?: ClientRpcOptions | undefined,
): Promise<void>;
async use(
target: {
readonly namespace?: string | null | undefined;
readonly database?: string | null | undefined;
},
options?: ClientRpcOptions | undefined,
): Promise<void>;
async use(
...args:
| [
string | null | undefined,
(ClientRpcOptions | undefined)?,
]
| [
string | null | undefined,
(string | null | undefined)?,
(ClientRpcOptions | undefined)?,
]
| [
[string | null | undefined, (string | null | undefined)?],
(ClientRpcOptions | undefined)?,
]
| [
{
readonly ns?: string | null | undefined;
readonly db?: string | null | undefined;
},
(ClientRpcOptions | undefined)?,
]
| [
{
readonly namespace?: string | null | undefined;
readonly database?: string | null | undefined;
},
(ClientRpcOptions | undefined)?,
]
): Promise<void> {
let namespace: string | null | undefined;
let database: string | null | undefined;
let options: ClientRpcOptions | undefined;
if (typeof args[0] === "string" || args[0] == null) {
const [ns, arg1, arg2] = args;
const [db, opts] = typeof arg1 === "string" || arg1 == null
? [arg1, arg2]
: [undefined, arg2];
namespace = ns;
database = db;
options = opts;
} else if (Array.isArray(args[0])) {
const [[ns, db], opts] = args;
namespace = ns;
database = db;
options = opts as ClientRpcOptions | undefined;
} else if ("ns" in args[0] || "db" in args[0]) {
const [{ ns, db }, opts] = args;
namespace = ns;
database = db;
options = opts as ClientRpcOptions | undefined;
} else if ("namespace" in args[0] || "database" in args[0]) {
const [{ namespace: ns, database: db }, opts] = args;
namespace = ns;
database = db;
options = opts as ClientRpcOptions | undefined;
}
await this.rpc("use", [namespace, database], options);
}
async info<T extends RpcResultMapping["info"] = RpcResultMapping["info"]>(
options?: ClientRpcOptions | undefined,
): Promise<T> {
return await this.rpc("info", [], options);
}
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/connecting/#signup)
*/
async signup(
auth: RecordAccessAuth,
options?: ClientRpcOptions | undefined,
): Promise<Jwt> {
return new Jwt(await this.rpc("signup", [auth], options));
}
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/connecting/#signin)
*/
async signin(
auth: Auth,
options?: ClientRpcOptions | undefined,
): Promise<Jwt> {
return new Jwt(await this.rpc("signin", [auth], options));
}
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/connecting/#authenticate)
*/
async authenticate(
token: string | Jwt,
options?: ClientRpcOptions | undefined,
): Promise<void> {
if (typeof token !== "string") {
token = token.raw;
}
await this.rpc("authenticate", [token], options);
}
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/connecting/#invalidate)
*/
async invalidate(
options?: ClientRpcOptions | undefined,
): Promise<void> {
await this.rpc("invalidate", [], options);
}
async live<T extends DataType.Uuid | string = DataType.Uuid | string>(
table: DataType.Table | string,
options: ClientRpcOptions & { readonly diff: true },
): Promise<T & { __diff: true }>;
async live<T extends DataType.Uuid | string = DataType.Uuid | string>(
table: DataType.Table | string,
options?:
| (ClientRpcOptions & { readonly diff?: false | undefined })
| undefined,
): Promise<T & { __diff: false }>;
async live<T extends DataType.Uuid | string = DataType.Uuid | string>(
table: DataType.Table | string,
options?: LiveOptions | undefined,
): Promise<T & { __diff: boolean }>;
// TODO(tai-kun): バッファリングオプションを追加
async live(
table: DataType.Table | string,
options: LiveOptions | undefined = {},
) {
const { diff, ...rest } = options;
const queryUuid = await this.rpc("live", [table, diff], rest);
return queryUuid;
}
subscribe<
TUuid,
TPayload extends LivePayload<any, any> = InferLivePayload<TUuid>,
>(
queryUuid: TUuid,
callback: LiveHandler<TPayload>,
): void {
this.ee.on(`live_${queryUuid}`, callback as LiveHandler<any>);
}
unsubscribe(
queryUuid: DataType.Uuid | string,
callback: LiveHandler<any>,
): void {
this.ee.off(`live_${queryUuid}`, callback);
}
async kill(
queryUuid: DataType.Uuid | string | readonly (DataType.Uuid | string)[],
options?: ClientRpcOptions | undefined,
): Promise<void> {
if (Array.isArray(queryUuid)) {
await Promise.all(queryUuid.map(async uuid => {
await this.kill(uuid, options);
}));
} else {
this.ee.off(`live_${queryUuid}`);
await this.rpc("kill", [queryUuid], options);
}
}
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/querying/#queryraw)
*/
async queryRaw<TResults extends readonly QueryResult[] = QueryResult[]>(
surql: string | PreparedQueryLike,
vars?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
): Promise<TResults> {
const results: readonly QueryResult[] = await this.rpc(
"query",
[surql, vars],
options,
);
return results as TResults;
}
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/querying/#query)
*/
async query<TReturns extends readonly unknown[] = unknown[]>(
surql: string,
vars?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
): Promise<TReturns>;
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/querying/#query)
*/
async query<TResult>(
surql: Override<PreparedQueryLike, {
readonly slots: readonly SlotLike<any, false, any>[];
readonly __type: TResult;
}>,
vars?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
): Promise<TResult>;
/**
* [API Reference](https://tai-kun.github.io/surrealdb.js/v2/guides/querying/#query)
*/
async query<TSlot extends SlotLike, TResult>(
surql: Override<PreparedQueryLike, {
readonly slots: readonly TSlot[];
readonly __type: TResult;
}>,
vars: Simplify<InferSlotVars<TSlot>> & Readonly<RecordData>,
options?: ClientRpcOptions | undefined,
): Promise<TResult>;
async query(
surql: string | PreparedQueryLike,
vars?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
): Promise<unknown> {
const results = await this.queryRaw(surql, vars, options);
const output: unknown[] = [];
const errors: string[] = [];
for (const result of results) {
if (result.status === "OK") {
output.push(result.result);
} else {
errors.push(result.result);
}
}
if (errors.length > 0) {
throw new QueryFailedError(errors);
}
if (typeof surql === "string") {
return output;
}
return surql._trans(surql._parse(output));
}
async let(
name: string,
value: unknown,
options?: ClientRpcOptions | undefined,
): Promise<void> {
await this.rpc("let", [name, value], options);
}
async unset(
name: string,
options?: ClientRpcOptions | undefined,
): Promise<void> {
await this.rpc("unset", [name], options);
}
async select<
TResult extends Readonly<RecordData> = RecordData,
>(
table: DataType.Table | string,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async select<
TResult extends Readonly<RecordData> = RecordData,
>(
thing: DataType.Thing,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async select(
target:
| (DataType.Table | string)
| DataType.Thing,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("select", [target], options);
}
async create<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
table: DataType.Table | string,
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async create<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
thing: DataType.Thing,
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async create(
target:
| (DataType.Table | string)
| DataType.Thing,
data?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("create", [target, data], options);
}
async insert<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
table: DataType.Table | string,
data?: TData | readonly TData[] | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async insert<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<NormalRecord> = (
TResult extends { readonly id: any } ? TResult
: ({ id: DataType.Thing | string } & TResult)
),
>(
table: DataType.Table | string | null | undefined,
data?: TData | readonly TData[] | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async insert<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<NormalRecord> = (
TResult extends { readonly id: any } ? TResult
: ({ id: DataType.Thing | string } & TResult)
),
>(
data: TData | readonly TData[],
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async insert(
...args:
| [
table: DataType.Table | string | null | undefined,
data?:
| Readonly<RecordData>
| readonly Readonly<RecordData>[]
| undefined,
options?: ClientRpcOptions | undefined,
]
| [
data: Readonly<NormalRecord> | readonly Readonly<NormalRecord>[],
options?: ClientRpcOptions | undefined,
]
) {
const [table, data, options]: [any?, any?, any?] =
isTable<DataType.Table>(args[0])
|| typeof args[0] === "string"
|| typeof args[0] == null
? args
: [null, args[0], args[1]];
return await this.rpc("insert", [table, data], options);
}
async insert_relation<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
table: DataType.Table | string,
data?: TData | readonly TData[] | undefined,
options?: ClientRpcOptions | undefined,
): Promise<RelateResult<TResult>[]>;
async insert_relation<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<NormalRecord> = (
TResult extends { readonly id: any } ? TResult
: ({ id: DataType.Thing | string } & TResult)
),
>(
table: DataType.Table | string | null | undefined,
data?: TData | readonly TData[] | undefined,
options?: ClientRpcOptions | undefined,
): Promise<RelateResult<TResult>[]>;
async insert_relation<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<NormalRecord> = (
TResult extends { readonly id: any } ? TResult
: ({ id: DataType.Thing | string } & TResult)
),
>(
data: TData | readonly TData[],
options?: ClientRpcOptions | undefined,
): Promise<RelateResult<TResult>[]>;
async insert_relation(
...args:
| [
table: DataType.Table | string | null | undefined,
data?:
| Readonly<RecordData>
| readonly Readonly<RecordData>[]
| undefined,
options?: ClientRpcOptions | undefined,
]
| [
data: Readonly<NormalRecord> | readonly Readonly<NormalRecord>[],
options?: ClientRpcOptions | undefined,
]
) {
const [table, data, options]: [any?, any?, any?] =
isTable<DataType.Table>(args[0])
|| typeof args[0] === "string"
|| typeof args[0] == null
? args
: [null, args[0], args[1]];
return await this.rpc("insert_relation", [table, data], options);
}
async update<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
table: DataType.Table | string,
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async update<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
thing: DataType.Thing,
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async update(
target:
| (DataType.Table | string)
| DataType.Thing,
data?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("update", [target, data], options);
}
async upsert<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
table: DataType.Table | string,
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async upsert<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
thing: DataType.Thing,
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async upsert(
target:
| (DataType.Table | string)
| DataType.Thing,
data?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("upsert", [target, data], options);
}
async merge<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
table: DataType.Table | string,
data: TData,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async merge<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
thing: DataType.Thing,
data: TData,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async merge(
target:
| (DataType.Table | string)
| DataType.Thing,
data: Readonly<RecordData>,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("merge", [target, data], options);
}
async patch<
TResult extends Readonly<RecordData> = RecordData,
>(
table: DataType.Table | string,
patches: readonly ReadonlyPatch[],
options?:
| (ClientRpcOptions & { readonly diff?: false | undefined })
| undefined,
): Promise<ActionResult<TResult>[]>;
async patch<
TResult extends Readonly<RecordData> = RecordData,
>(
thing: DataType.Thing,
patches: readonly ReadonlyPatch[],
options?:
| (ClientRpcOptions & { readonly diff?: false | undefined })
| undefined,
): Promise<ActionResult<TResult>>;
async patch(
table: DataType.Table | string,
patches: readonly ReadonlyPatch[],
options: ClientRpcOptions & { readonly diff: true },
): Promise<Patch[][]>;
async patch(
thing: DataType.Thing,
patches: readonly ReadonlyPatch[],
options: ClientRpcOptions & { readonly diff: true },
): Promise<Patch[]>;
async patch(
target:
| (DataType.Table | string)
| DataType.Thing,
patches: readonly ReadonlyPatch[],
options?: PatchOptions | undefined,
) {
const { diff, ...rest } = options || {};
return await this.rpc("patch", [target, patches, diff], rest);
}
async delete<
TResult extends Readonly<RecordData> = RecordData,
>(
table: DataType.Table | string,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>[]>;
async delete<
TResult extends Readonly<RecordData> = RecordData,
>(
thing: DataType.Thing,
options?: ClientRpcOptions | undefined,
): Promise<ActionResult<TResult>>;
async delete(
target:
| (DataType.Table | string)
| DataType.Thing,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("delete", [target], options);
}
async version(options?: ClientRpcOptions | undefined): Promise<string> {
return await this.rpc("version", [], options);
}
async run<T = unknown>(
funcName: string,
options?: RunOptions | undefined,
): Promise<T>;
async run<T = unknown>(
funcName: string,
args?: readonly unknown[] | undefined,
options?: RunOptions | undefined,
): Promise<T>;
async run(
funcName: string,
arg1?: readonly unknown[] | RunOptions | undefined,
arg2?: RunOptions | undefined,
) {
const [args, opts] = Array.isArray(arg1) || arg1 === undefined
? [arg1, arg2] as const
: [, arg1] as const;
const { version, ...rest } = opts || {};
return await this.rpc("run", [funcName, version, args], rest);
}
/**
* @experimental
*/
async graphql(
// `vars` of `variables`
// `operation` or `operationName`
query: string | {
readonly query: string;
readonly vars?: Readonly<RecordData> | undefined;
readonly operation?: string | undefined;
} | {
readonly query: string;
readonly vars?: Readonly<RecordData> | undefined;
readonly operationName?: string | undefined;
} | {
readonly query: string;
readonly variables?: Readonly<RecordData> | undefined;
readonly operation?: string | undefined;
} | {
readonly query: string;
readonly variables?: Readonly<RecordData> | undefined;
readonly operationName?: string | undefined;
},
options: GraphqlOptions | undefined = {},
): Promise<string> {
const { signal, ...gqlOptions } = options;
return await this.rpc("graphql", [query, gqlOptions], { signal });
}
async relate<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
from: DataType.Thing | string | readonly (DataType.Thing | string)[],
thing: string,
to: DataType.Thing | string | readonly (DataType.Thing | string)[],
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<RelateResult<TResult>[]>;
async relate<
TResult extends Readonly<RecordData> = RecordData,
TData extends Readonly<RecordData> = TResult,
>(
from: DataType.Thing | string | readonly (DataType.Thing | string)[],
thing: DataType.Thing,
to: DataType.Thing | string | readonly (DataType.Thing | string)[],
data?: TData | undefined,
options?: ClientRpcOptions | undefined,
): Promise<RelateResult<TResult>>;
async relate(
from: DataType.Thing | string | readonly (DataType.Thing | string)[],
thing: DataType.Thing | string,
to: DataType.Thing | string | readonly (DataType.Thing | string)[],
data?: Readonly<RecordData> | undefined,
options?: ClientRpcOptions | undefined,
) {
return await this.rpc("relate", [from, thing, to, data], options);
}
}