UNPKG

@tai-kun/surrealdb

Version:

The SurrealDB SDK for JavaScript

801 lines (706 loc) 23 kB
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); } }