UNPKG

@ultipa-graph/ultipa-driver

Version:

NodeJS SDK for Ultipa GQL

527 lines (526 loc) 27.3 kB
/** * Main client for GQLDB Node.js driver. */ import { EventEmitter } from 'events'; import { GqldbConfig } from './config'; import { Response, InsertNodesResult, InsertEdgesResult, ExportNodesResult, ExportEdgesResult, ExportConfig, ExportChunk } from './response'; import { Session } from './session'; import { Transaction } from './transaction'; import { GraphInfo, GraphType, EdgeIdMode, HealthStatus, CacheStats, CacheType, Statistics, CompactResult, ComputeTopologyResult, SystemMetrics, NodeData, EdgeData, BulkCreateNodesOptions, BulkCreateEdgesOptions, BulkImportOptions, BulkImportSession, CheckpointResult, EndBulkImportResult, AbortBulkImportResult, BulkImportStatus, TransactionInfo, TransactionRow, DBType, InsertType, LabelInfo, NodeTypeInfo, EdgeTypeInfo, ConvPropertyDef, IndexProperty, IndexInfo, FulltextInfo, TaskInfo, ProcessInfo, GraphStats, AlgoInfo, AiReadResult } from './types'; /** Configuration for a query */ export interface QueryConfig { graphName?: string; parameters?: Record<string, any>; transactionId?: number; timeout?: number; readOnly?: boolean; /** Max paths to return from path queries (0 = unlimited) */ maxPathResults?: number; } /** * Configuration for insertNodes / insertEdges convenience methods. * Extends QueryConfig with insert-specific options. */ export interface InsertConfig extends QueryConfig { /** Insert mode. Defaults to InsertType.Normal when omitted. */ insertType?: InsertType; } /** * Configuration for the new GQL-emitter delete API * (deleteNodesByIds / deleteNodesByCondition / * deleteEdgesByIds / deleteEdgesByCondition). * * Extends QueryConfig so per-call `graphName` works the same as * `InsertConfig`. Two delete-specific knobs: * - `returnDeleted` (default true) — emit `RETURN ...` so the * response carries the full deleted node/edge data. Set to false * on large bulk deletes to save bandwidth; `rowsAffected` still * carries the count. * - `allowDeleteAll` (default false) — safety latch. With empty * labels/ids AND empty where, the SDK would otherwise emit a * graph-wide delete; this flag must be explicitly true to opt in. */ export interface DeleteConfig extends QueryConfig { returnDeleted?: boolean; allowDeleteAll?: boolean; } /** Configuration for insert nodes */ export interface InsertNodesConfig { options?: BulkCreateNodesOptions; bulkImportSessionId?: string; } /** Configuration for insert edges */ export interface InsertEdgesConfig { options?: BulkCreateEdgesOptions; bulkImportSessionId?: string; } /** * Health watcher interface for streaming health updates. * Emits 'status' events with HealthStatus and 'error' events on failure. */ export interface HealthWatcher extends EventEmitter { /** Stop watching health updates */ stop(): void; } /** Main client for interacting with GQLDB */ export declare class GqldbClient { private config; private clients; private sessions; private txManager; private closed; private storedUsername; private storedPassword; private storedGraph; private grpcHost; private grpcCredentials; private grpcChannelOptions; private ctx; private sessionService; private queryService; private graphService; private transactionService; private dataService; private healthService; private adminService; private bulkImportService; /** Get session metadata for authenticated requests */ private getSessionMetadata; /** * Stable per-client logical session id surfaced under the * transaction-branch model. See TRANSACTIONS_DRIVER_GUIDE.md §2.0–2.1. * Falls back to a UUID v4 hex generated at construction when * `config.sessionId` is not set. */ readonly clientSessionId: string; constructor(config: GqldbConfig); /** Close the client and all connections */ close(): Promise<void>; /** Authenticate the user and create a session */ login(username: string, password: string): Promise<Session>; /** * Rebuild every gRPC client used by this connection so the next call * gets a fresh channel. Called by withAutoReconnect when a call * fails with UNAVAILABLE / connection reset. Does NOT close old * clients synchronously — in-flight calls hold them alive and gRPC * closes them once those calls complete. */ private forceReconnectAll; /** * Execute fn with two recovery behaviours: * - UNAUTHENTICATED — if stored credentials are available, re-login * and retry fn once. * - UNAVAILABLE / connection reset — rebuild every gRPC client so * the NEXT call gets a fresh channel. The current call is NOT * retried; caller decides (write-side calls are often not * idempotent). */ private withAutoReconnect; /** Terminate the current session */ logout(): Promise<void>; /** Check the connection and return the latency in nanoseconds */ ping(): Promise<number>; /** Execute a GQL query and return the result */ gql(query: string, config?: QueryConfig): Promise<Response>; /** Execute a GQL query and stream the results */ gqlStream(query: string, config?: QueryConfig, callback?: (response: Response) => void): Promise<void>; /** Return the execution plan for a query */ explain(query: string, config?: QueryConfig): Promise<string>; /** Execute a query with profiling and return statistics */ profile(query: string, config?: QueryConfig): Promise<string>; /** * Create a new graph. * * When `edgeId` is provided, the driver issues a follow-up * `ALTER GRAPH <name> SET EDGE_ID ENABLED|DISABLED` via GQL after the * gRPC create call. When `edgeId` is undefined, behavior is unchanged * and no ALTER statement is sent. */ createGraph(name: string, graphType?: GraphType, description?: string, edgeId?: EdgeIdMode): Promise<void>; /** Delete a graph */ dropGraph(name: string, ifExists?: boolean): Promise<void>; /** Set the current graph for the session */ useGraph(name: string): Promise<void>; /** Return all available graphs */ listGraphs(): Promise<GraphInfo[]>; /** Return information about a specific graph */ getGraphInfo(name: string): Promise<GraphInfo>; /** Start a new transaction */ beginTransaction(graphName: string, readOnly?: boolean, timeout?: number): Promise<Transaction>; /** Commit a transaction */ commit(transactionId: number): Promise<boolean>; /** Rollback a transaction */ rollback(transactionId: number): Promise<boolean>; /** Return active transactions */ listTransactions(): Promise<TransactionInfo[]>; /** Execute a function within a transaction */ withTransaction<T>(graphName: string, fn: (txId: number) => Promise<T>, readOnly?: boolean): Promise<T>; /** * Return active transactions via the GQL admin DDL `SHOW TRANSACTIONS`. * Each row mirrors the 5-column server-side schema: * `transactionId / status / readOnly / startTime / sessionId`. * `sessionId` is `''` unless the server has auto-derived one from peer * info or the driver explicitly surfaces `x-ultipa-session-id` metadata. * * Distinct from `listTransactions()` which uses the legacy gRPC. */ showTransactions(): Promise<TransactionRow[]>; /** * Roll back a single transaction by id via `KILL TRANSACTION '<id>'`. * `transactionId` here is the **string** id surfaced by * `showTransactions()` (e.g. `'tx_a396c531-...'`), distinct from the * `number` id returned by `begin()`. */ killTransaction(transactionId: string): Promise<Response>; /** * Roll back every active transaction via `RESET TRANSACTIONS`. Admin-only. * Intended as an escape hatch when an orphan tx blocks new BEGINs. */ resetTransactions(): Promise<Response>; /** Insert multiple nodes into a graph using gRPC bulk insert */ insertNodesBatchAuto(graphName: string, nodes: NodeData[], config?: InsertNodesConfig): Promise<InsertNodesResult>; /** Insert multiple edges into a graph using gRPC bulk insert */ insertEdgesBatchAuto(graphName: string, edges: EdgeData[], config?: InsertEdgesConfig): Promise<InsertEdgesResult>; /** * Delete nodes by id list. Emits * `MATCH (n) WHERE id(n) IN [...] DETACH DELETE n RETURN n`. * * Empty/null `nodeIds` short-circuits without contacting the server. * * @returns Response with one row per actually-deleted node, column * "n" holding the GqldbNode. Use `response.alias("n").asNodes()`. * With `returnDeleted=false` the response carries only * `rowsAffected`; rows is empty. */ deleteNodesByIds(nodeIds: string[], config?: DeleteConfig): Promise<Response>; /** * Delete nodes matching labels and/or where clause. Emits * `MATCH (n:L1|L2) WHERE <where> DETACH DELETE n RETURN n`. * * Throws if both labels and where are empty unless * `config.allowDeleteAll = true`. */ deleteNodesByCondition(labels: string[] | undefined, where: string | undefined, limit?: number, config?: DeleteConfig): Promise<Response>; /** * Delete edges by id list. Emits 5-column GQL with `id(e)` so the * same emit works on graphs with or without EDGE_ID enabled, then * reshapes the response into a single "e" column holding GqldbEdge. */ deleteEdgesByIds(edgeIds: string[], config?: DeleteConfig): Promise<Response>; /** * Delete edges matching label and/or where. Emits 5-column GQL, * then reshapes into a single "e" column. * * Throws if both label and where are empty unless * `config.allowDeleteAll = true`. */ deleteEdgesByCondition(label: string | undefined, where: string | undefined, limit?: number, config?: DeleteConfig): Promise<Response>; /** * Export graph data in JSON Lines format (streaming). * @param config Export configuration * @param callback Callback for each exported chunk */ export(config: ExportConfig, callback?: (chunk: ExportChunk) => void): Promise<void>; /** * Stream nodes from a graph * @deprecated Use export() with ExportConfig instead */ exportNodes(graphName: string, labels?: string[], limit?: number, callback?: (result: ExportNodesResult) => void): Promise<void>; /** * Stream edges from a graph * @deprecated Use export() with ExportConfig instead */ exportEdges(graphName: string, labels?: string[], limit?: number, callback?: (result: ExportEdgesResult) => void): Promise<void>; /** Check the health of a service */ healthCheck(service?: string): Promise<HealthStatus>; /** * Watch health status changes via server-side streaming. * Returns a HealthWatcher that emits 'status' events with HealthStatus values. * Call stop() to cancel the stream. * * @param service - Optional service name to watch * @returns HealthWatcher - EventEmitter with stop() method * * @example * ```typescript * const watcher = client.watch(); * watcher.on('status', (status: HealthStatus) => { * console.log('Health status:', status); * }); * watcher.on('error', (err) => { * console.error('Watch error:', err); * }); * watcher.on('end', () => { * console.log('Watch stream ended'); * }); * // Later, to stop watching: * watcher.stop(); * ``` */ watch(service?: string): HealthWatcher; /** Pre-allocate parser instances */ warmupParser(count: number): Promise<void>; /** Return cache statistics */ getCacheStats(cacheType?: CacheType): Promise<CacheStats>; /** Clear the specified cache */ clearCache(cacheType?: CacheType): Promise<void>; /** Return database statistics */ getStatistics(graphName?: string): Promise<Statistics>; /** Invalidate the RBAC permission cache */ invalidatePermissionCache(username?: string): Promise<void>; /** Return system-level metrics (CPU, memory, disk I/O, storage, network) */ getSystemMetrics(): Promise<SystemMetrics>; /** Trigger manual compaction of the database storage */ compact(): Promise<CompactResult>; /** Wait for the computing engine topology to be ready */ waitForComputeTopology(graphName: string, timeoutMs?: number): Promise<ComputeTopologyResult>; /** * Start a bulk import session for optimized high-throughput inserts. * @param graphName Target graph name * @param options Optional bulk import configuration */ startBulkImport(graphName: string, options?: BulkImportOptions): Promise<BulkImportSession>; /** * @deprecated Checkpoint is no longer needed. Use endBulkImport() which performs a final flush. */ checkpoint(sessionId: string): Promise<CheckpointResult>; /** * End the bulk import session with a final checkpoint. * @param sessionId Bulk import session ID */ endBulkImport(sessionId: string): Promise<EndBulkImportResult>; /** * Cancel the bulk import session without final sync. * @param sessionId Bulk import session ID */ abortBulkImport(sessionId: string): Promise<AbortBulkImportResult>; /** * Return the current status of a bulk import session. * @param sessionId Bulk import session ID */ getBulkImportStatus(sessionId: string): Promise<BulkImportStatus>; /** Get the current session */ getSession(): Session | null; /** Check if there is an active session */ isLoggedIn(): boolean; /** Get the client configuration */ getConfig(): GqldbConfig; /** Whether this client is connected to a cluster deployment */ get isCluster(): boolean; /** Get the cluster ID (empty string if not a cluster) */ get clusterId(): string; /** Get the partition count (0 if not a cluster) */ get partitionCount(): number; /** Wrap a name in backticks for safe use in GQL statements. * Label names and property names should be quoted; graph names, index names, * and fulltext names should NOT be wrapped in backticks. */ private static quoteLabel; /** Wrap each label name in backticks and join with ", " */ private static quoteLabels; /** Join names with ", " without backtick wrapping */ private static joinNames; /** Create a new open (schema-less) graph * * v6 GQL: `CREATE GRAPH g` -> OPEN; `CREATE GRAPH g {}` -> CLOSED. The * brace-less form is required to actually get an OPEN graph. */ createOpenGraph(name: string, config?: QueryConfig): Promise<Response>; /** Create a new closed (schema-enforced) graph with node and edge label definitions */ createClosedGraph(name: string, config?: QueryConfig): Promise<Response>; /** * Create a graph if it does not already exist. * Returns true if the graph was created, false if it already existed. */ createGraphIfNotExist(name: string, graphType?: GraphType, description?: string, edgeId?: EdgeIdMode): Promise<boolean>; /** Check whether a graph with the given name exists */ hasGraph(name: string): Promise<boolean>; /** Rename a graph */ alterGraph(graphName: string, newName: string, config?: QueryConfig): Promise<Response>; /** Remove all data from a graph while keeping its structure */ truncate(graphName: string, config?: QueryConfig): Promise<Response>; /** Return all labels (node and edge) in the current graph */ showLabels(config?: QueryConfig): Promise<LabelInfo[]>; /** Return all node labels in the current graph */ showNodeLabels(config?: QueryConfig): Promise<LabelInfo[]>; /** Return all edge labels in the current graph */ showEdgeLabels(config?: QueryConfig): Promise<LabelInfo[]>; /** Return all node types with their properties */ showNodeTypes(config?: QueryConfig): Promise<NodeTypeInfo[]>; /** Return all edge types with their properties */ showEdgeTypes(config?: QueryConfig): Promise<EdgeTypeInfo[]>; /** * Return a specific label by name from the current graph, or null if not found. * Matches entries where `name` is one of the .labels (supports multi-label groups). */ getLabel(name: string, config?: QueryConfig): Promise<LabelInfo | null>; /** Return a specific node type by name, or null if not found */ getNodeLabel(name: string, config?: QueryConfig): Promise<NodeTypeInfo | null>; /** Return a specific edge type by name, or null if not found */ getEdgeLabel(name: string, config?: QueryConfig): Promise<EdgeTypeInfo | null>; /** Create a node label in the current graph (closed graph) */ createNodeLabel(name: string, props?: ConvPropertyDef[], config?: QueryConfig): Promise<Response>; /** Create an edge label in the current graph (closed graph) */ createEdgeLabel(name: string, props?: ConvPropertyDef[], config?: QueryConfig): Promise<Response>; /** Drop a node label from the current graph (closed graph) */ dropNodeLabel(name: string, config?: QueryConfig): Promise<Response>; /** Drop one or more edge labels from the current graph (closed graph) */ dropEdgeLabel(...namesOrConfig: Array<string | QueryConfig | undefined>): Promise<Response>; /** * Create a label if it does not already exist. * Returns true if created, false if it already existed. */ createLabelIfNotExist(nodeOrEdge: DBType, name: string, props?: ConvPropertyDef[], config?: QueryConfig): Promise<boolean>; /** Rename a node label */ alterNodeLabel(oldName: string, newName: string, config?: QueryConfig): Promise<Response>; /** Rename an edge label */ alterEdgeLabel(oldName: string, newName: string, config?: QueryConfig): Promise<Response>; /** Return all available algorithms */ showAlgos(config?: QueryConfig): Promise<AlgoInfo[]>; /** Return properties for a label (node or edge) in the current graph */ showProperty(nodeOrEdge: DBType, labelName: string, config?: QueryConfig): Promise<ConvPropertyDef[]>; /** Return properties for a node label */ showNodeProperty(labelName: string, config?: QueryConfig): Promise<ConvPropertyDef[]>; /** Return properties for an edge label */ showEdgeProperty(labelName: string, config?: QueryConfig): Promise<ConvPropertyDef[]>; /** Return a specific property definition for a label, or null if not found */ getProperty(nodeOrEdge: DBType, labelName: string, propName: string, config?: QueryConfig): Promise<ConvPropertyDef | null>; /** Return a specific property definition for a node label, or null if not found */ getNodeProperty(labelName: string, propName: string, config?: QueryConfig): Promise<ConvPropertyDef | null>; /** Return a specific property definition for an edge label, or null if not found */ getEdgeProperty(labelName: string, propName: string, config?: QueryConfig): Promise<ConvPropertyDef | null>; /** Add properties to a label (node or edge) */ createProperty(nodeOrEdge: DBType, labelName: string, props: ConvPropertyDef[], config?: QueryConfig): Promise<Response>; /** Add properties to a node label */ createNodeProperty(labelName: string, props: ConvPropertyDef[], config?: QueryConfig): Promise<Response>; /** Add properties to an edge label */ createEdgeProperty(labelName: string, props: ConvPropertyDef[], config?: QueryConfig): Promise<Response>; /** Drop properties from a label (node or edge) */ dropProperty(nodeOrEdge: DBType, labelName: string, ...propNamesOrConfig: Array<string | QueryConfig | undefined>): Promise<Response>; /** Drop properties from a node label */ dropNodeProperty(labelName: string, ...propNamesOrConfig: Array<string | QueryConfig | undefined>): Promise<Response>; /** Drop properties from an edge label */ dropEdgeProperty(labelName: string, ...propNamesOrConfig: Array<string | QueryConfig | undefined>): Promise<Response>; /** * Create properties on a label if they do not already exist. * Returns true if any properties were created, false if all already existed. */ createPropertyIfNotExist(nodeOrEdge: DBType, labelName: string, props: ConvPropertyDef[], config?: QueryConfig): Promise<boolean>; private static constraintName; private static constraintTarget; /** Create a NOT NULL constraint on a property */ createNotNullConstraint(nodeOrEdge: DBType, labelName: string, propName: string, config?: QueryConfig): Promise<Response>; /** Create a UNIQUE constraint on one or more properties */ createUniqueConstraint(nodeOrEdge: DBType, labelName: string, ...propNamesOrConfig: Array<string | QueryConfig | undefined>): Promise<Response>; /** Remove a NOT NULL constraint from a property */ dropNotNullConstraint(nodeOrEdge: DBType, labelName: string, propName: string, config?: QueryConfig): Promise<Response>; /** Remove a UNIQUE constraint from one or more properties */ dropUniqueConstraint(nodeOrEdge: DBType, labelName: string, ...propNamesOrConfig: Array<string | QueryConfig | undefined>): Promise<Response>; /** Return all indexes in the current graph */ showIndex(config?: QueryConfig): Promise<IndexInfo[]>; /** Return all node indexes in the current graph */ showNodeIndex(config?: QueryConfig): Promise<IndexInfo[]>; /** Return all edge indexes in the current graph */ showEdgeIndex(config?: QueryConfig): Promise<IndexInfo[]>; /** Create an index on a node label */ createNodeIndex(indexName: string, labelName: string, props: IndexProperty[], config?: QueryConfig): Promise<Response>; /** Create an index on an edge label */ createEdgeIndex(indexName: string, labelName: string, props: IndexProperty[], config?: QueryConfig): Promise<Response>; /** Drop a node index by name */ dropNodeIndex(indexName: string, config?: QueryConfig): Promise<Response>; /** Drop an edge index by name */ dropEdgeIndex(indexName: string, config?: QueryConfig): Promise<Response>; /** Return all fulltext indexes in the current graph */ showFulltext(config?: QueryConfig): Promise<FulltextInfo[]>; /** Return all node fulltext indexes */ showNodeFulltext(config?: QueryConfig): Promise<FulltextInfo[]>; /** Return all edge fulltext indexes */ showEdgeFulltext(config?: QueryConfig): Promise<FulltextInfo[]>; /** Create a fulltext index on a node label */ createNodeFulltext(indexName: string, labelName: string, props: string[], config?: QueryConfig): Promise<Response>; /** Create a fulltext index on an edge label */ createEdgeFulltext(indexName: string, labelName: string, props: string[], config?: QueryConfig): Promise<Response>; /** Drop a node fulltext index by name */ dropNodeFulltext(indexName: string, config?: QueryConfig): Promise<Response>; /** Drop an edge fulltext index by name */ dropEdgeFulltext(indexName: string, config?: QueryConfig): Promise<Response>; /** Return all tasks in the current graph */ showTasks(config?: QueryConfig): Promise<TaskInfo[]>; /** Delete a task by ID */ deleteTask(taskId: string, config?: QueryConfig): Promise<Response>; /** Stop a running task by ID */ stopTask(taskId: string, config?: QueryConfig): Promise<Response>; /** * Insert nodes — backward-compatible overloaded entry point. * * Two call shapes are accepted; the runtime dispatches on the first * argument's type: * * 1. `insertNodes(graphName: string, nodes, config?)` — original * 6.0.0 signature, routes through the bulk-import gRPC path * (`DataService.InsertNodes`). Returns `InsertNodesResult` with * gRPC-level metadata. Preserves source compatibility for * callers written against 6.0.0 (e.g. gqldb-manager). * * 2. `insertNodes(nodes, config?)` — convenience GQL emitter (added * after 6.0.0). Synthesizes * `INSERT (n0:Label {...}), (n1:Label {...}) RETURN n0, n1`, * with `INSERT OVERWRITE` when `config.insertType` is * `InsertType.Overwrite`. Returns the raw `Response`. * `NodeData.id`, when set, is emitted as the `_id` property. */ insertNodes(graphName: string, nodes: NodeData[], config?: InsertNodesConfig): Promise<InsertNodesResult>; insertNodes(nodes: NodeData[], config?: InsertConfig): Promise<Response>; /** * Insert edges — backward-compatible overloaded entry point. * * Two call shapes are accepted; the runtime dispatches on the first * argument's type: * * 1. `insertEdges(graphName: string, edges, config?)` — original * 6.0.0 signature, routes through the bulk-import gRPC path * (`DataService.InsertEdges`). Returns `InsertEdgesResult` with * gRPC-level metadata. Preserves source compatibility for * 6.0.0 callers. * * 2. `insertEdges(edges, config?)` — convenience GQL emitter * (added after 6.0.0). Per edge synthesizes * `MATCH (src WHERE id(src) = 'from'), (dst WHERE id(dst) = 'to') INSERT (src)-[e0:Label {...}]->(dst) RETURN e0`. * Returns a merged `Response` with columns `e0, e1, ...`. */ insertEdges(graphName: string, edges: EdgeData[], config?: InsertEdgesConfig): Promise<InsertEdgesResult>; insertEdges(edges: EdgeData[], config?: InsertConfig): Promise<Response>; /** Return currently running processes (queries) */ top(config?: QueryConfig): Promise<ProcessInfo[]>; /** Terminate a running query by its query ID */ kill(queryId: string, config?: QueryConfig): Promise<Response>; /** Return statistics for the current graph using db.stats() */ stats(config?: QueryConfig): Promise<GraphStats>; /** Send a ping to the server and return the latency in nanoseconds */ test(): Promise<number>; /** YIELD columns emitted by CALL ai.read / ai.gql (kept in sync with server). */ private static readonly AI_YIELD_COLS; /** * Ask the AI to generate a GQL statement and auto-execute it. * * Wraps `CALL ai.read("<prompt>") YIELD ...`, streams stage rows from the * server, and re-runs the synthesized GQL so `AiReadResult.data` holds the * real query {@link Response} (rows / columns) — matching what Manager UI * displays. * * Only transport-level errors reject the Promise. AI-side errors set * `success=false` and populate `error`, with `data=null`. */ aiRead(prompt: string, config?: QueryConfig): Promise<AiReadResult>; /** * Ask the AI to generate a GQL statement *without* executing it. * * The generated GQL is exposed via `AiReadResult.generatedGql` so the * caller can review / edit it before running it manually via `client.gql()`. * `AiReadResult.data` is always null for `aiGql`. */ aiGql(prompt: string, config?: QueryConfig): Promise<AiReadResult>; /** Escape a prompt so it can be safely embedded in a double-quoted GQL string. */ private static escapeAiPrompt; /** Parse a CALL ai.read / ai.gql YIELD response into an AiReadResult. */ private parseAiResult; }