@@ -40,7 +40,11 @@ import {
4040 SessionMode
4141} from './types'
4242import { ServerAddress } from './internal/server-address'
43- import BookmarkManager from './bookmark-manager'
43+ import BookmarkManager , { bookmarkManager } from './bookmark-manager'
44+ import EagerResult , { createEagerResultFromResult } from './result-eager'
45+ import ManagedTransaction from './transaction-managed'
46+ import Result from './result'
47+ import { Dict } from './record'
4448
4549const DEFAULT_MAX_CONNECTION_LIFETIME : number = 60 * 60 * 1000 // 1 hour
4650
@@ -159,7 +163,7 @@ class SessionConfig {
159163 * Enabling it is done by supplying an BookmarkManager implementation instance to this param.
160164 * A default implementation could be acquired by calling the factory function {@link bookmarkManager}.
161165 *
162- * **Warning**: Share the same BookmarkManager instance accross all session can have a negative impact
166+ * **Warning**: Share the same BookmarkManager instance across all session can have a negative impact
163167 * on performance since all the queries will wait for the latest changes being propagated across the cluster.
164168 * For keeping consistency between a group of queries, use {@link Session} for grouping them.
165169 * For keeping consistency between a group of sessions, use {@link BookmarkManager} instance for groupping them.
@@ -179,7 +183,7 @@ class SessionConfig {
179183 *
180184 * // Reading Driver User will wait of the changes being propagated to the server before RUN the query
181185 * // So the 'Driver User' person should exist in the Result, unless deleted.
182- * const linkedSesssion2 = await linkedSession2.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Driver User'})
186+ * const linkedSession2 = await linkedSession2.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Driver User'})
183187 *
184188 * await linkedSession1.close()
185189 * await linkedSession2.close()
@@ -193,6 +197,85 @@ class SessionConfig {
193197 }
194198}
195199
200+ type RoutingControl = 'WRITERS' | 'READERS'
201+ const WRITERS : RoutingControl = 'WRITERS'
202+ const READERS : RoutingControl = 'READERS'
203+ /**
204+ * @typedef {'WRITERS'|'READERS' } RoutingControl
205+ */
206+ /**
207+ * Constant that represents writers routing control.
208+ *
209+ * @example
210+ * driver.query("<QUERY>", <PARAMETERS>, { routing: neo4j.routing.WRITERS })
211+ */
212+ const routing = {
213+ WRITERS ,
214+ READERS
215+ }
216+
217+ Object . freeze ( routing )
218+
219+ /**
220+ * The query configuration
221+ * @interface
222+ */
223+ class QueryConfig < Entries extends Dict = Dict , T = EagerResult < Entries > > {
224+ routing ?: RoutingControl
225+ database ?: string
226+ impersonatedUser ?: string
227+ bookmarkManager ?: BookmarkManager
228+ resultTransformer ?: ( result : Result ) => Promise < T >
229+
230+ /**
231+ * @constructor
232+ * @private
233+ */
234+ private constructor ( ) {
235+ /**
236+ * Define the type of cluster member the query will be routed to.
237+ *
238+ * @type {RoutingControl }
239+ */
240+ this . routing = routing . WRITERS
241+
242+ /**
243+ * Define the transformation will be applied to the Result before return from the
244+ * query method.
245+ *
246+ * @type {function<T>(result:Result): Promise<T> }
247+ */
248+ this . resultTransformer = undefined
249+
250+ /**
251+ * The database this session will operate on.
252+ *
253+ * @type {string|undefined }
254+ */
255+ this . database = ''
256+
257+ /**
258+ * The username which the user wants to impersonate for the duration of the query.
259+ *
260+ * @type {string|undefined }
261+ */
262+ this . impersonatedUser = undefined
263+
264+ /**
265+ * Configure a BookmarkManager for the session to use
266+ *
267+ * A BookmarkManager is a piece of software responsible for keeping casual consistency between different sessions by sharing bookmarks
268+ * between the them.
269+ *
270+ * By default, it uses the drivers non mutable driver level bookmark manager.
271+ *
272+ * Can be set to null to disable causal chaining.
273+ * @type {BookmarkManager }
274+ */
275+ this . bookmarkManager = undefined
276+ }
277+ }
278+
196279/**
197280 * A driver maintains one or more {@link Session}s with a remote
198281 * Neo4j instance. Through the {@link Session}s you can send queries
@@ -211,20 +294,21 @@ class Driver {
211294 private readonly _createConnectionProvider : CreateConnectionProvider
212295 private _connectionProvider : ConnectionProvider | null
213296 private readonly _createSession : CreateSession
297+ private readonly _queryBookmarkManager : BookmarkManager
214298
215299 /**
216300 * You should not be calling this directly, instead use {@link driver}.
217301 * @constructor
218302 * @protected
219303 * @param {Object } meta Metainformation about the driver
220304 * @param {Object } config
221- * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectonProvider Creates the connection provider
305+ * @param {function(id: number, config:Object, log:Logger, hostNameResolver: ConfiguredCustomResolver): ConnectionProvider } createConnectionProvider Creates the connection provider
222306 * @param {function(args): Session } createSession Creates the a session
223307 */
224308 constructor (
225309 meta : MetaInfo ,
226310 config : DriverConfig = { } ,
227- createConnectonProvider : CreateConnectionProvider ,
311+ createConnectionProvider : CreateConnectionProvider ,
228312 createSession : CreateSession = args => new Session ( args )
229313 ) {
230314 sanitizeConfig ( config )
@@ -237,8 +321,9 @@ class Driver {
237321 this . _meta = meta
238322 this . _config = config
239323 this . _log = log
240- this . _createConnectionProvider = createConnectonProvider
324+ this . _createConnectionProvider = createConnectionProvider
241325 this . _createSession = createSession
326+ this . _queryBookmarkManager = bookmarkManager ( )
242327
243328 /**
244329 * Reference to the connection provider. Initialized lazily by {@link _getOrCreateConnectionProvider}.
@@ -250,6 +335,85 @@ class Driver {
250335 this . _afterConstruction ( )
251336 }
252337
338+ /**
339+ * The bookmark managed used by {@link Driver.executeQuery}
340+ *
341+ * @type {BookmarkManager }
342+ * @returns {BookmarkManager }
343+ */
344+ get queryBookmarkManager ( ) : BookmarkManager {
345+ return this . _queryBookmarkManager
346+ }
347+
348+ /**
349+ * Executes a query in a retriable context and returns a {@link EagerResult}.
350+ *
351+ * This method is a shortcut for a transaction function
352+ *
353+ * @example
354+ * // Run a simple write query
355+ * const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
356+ *
357+ * @example
358+ * // Run a read query
359+ * const { keys, records, summary } = await driver.executeQuery(
360+ * 'MATCH (p:Person{ name: $name }) RETURN p',
361+ * { name: 'Person1'},
362+ * { routing: neo4j.routing.READERS})
363+ *
364+ * @example
365+ * // this lines
366+ * const transformedResult = await driver.executeQuery(
367+ * "<QUERY>",
368+ * <PARAMETERS>,
369+ * QueryConfig {
370+ * routing: neo4j.routing.WRITERS,
371+ * resultTransformer: transformer,
372+ * database: "<DATABASE>",
373+ * impersonatedUser: "<USER>",
374+ * bookmarkManager: bookmarkManager
375+ * }
376+ * )
377+ * // are equivalent to this ones
378+ * const session = driver.session({
379+ * database: "<DATABASE>",
380+ * impersonatedUser: "<USER>",
381+ * bookmarkManager: bookmarkManager
382+ * })
383+ *
384+ * try {
385+ * const transformedResult = await session.executeWrite(tx => {
386+ * const result = tx.run("<QUERY>", <PARAMETERS>)
387+ * return transformer(result)
388+ * })
389+ * } finally {
390+ * await session.close()
391+ * }
392+ *
393+ * @public
394+ * @param {string } query - Cypher query to execute
395+ * @param {Object } parameters - Map with parameters to use in query
396+ * @param {QueryConfig<T> } config - The query configuration
397+ * @returns {Promise<T> }
398+ */
399+ async executeQuery < Entries extends Dict = Dict , T = EagerResult < Entries > > ( query : string , parameters ?: any , config : QueryConfig < T > = { } ) : Promise < T > {
400+ const bookmarkManager = config . bookmarkManager === null ? undefined : config . bookmarkManager ?? this . queryBookmarkManager
401+ const session = this . session ( { database : config . database , bookmarkManager } )
402+ try {
403+ const execute = config . routing === routing . READERS
404+ ? session . executeRead . bind ( session )
405+ : session . executeWrite . bind ( session )
406+ const transformer = config . resultTransformer ?? createEagerResultFromResult
407+
408+ return ( await execute ( ( tx : ManagedTransaction ) => {
409+ const result = tx . run ( query , parameters )
410+ return transformer ( result )
411+ } ) ) as unknown as T
412+ } finally {
413+ await session . close ( )
414+ }
415+ }
416+
253417 /**
254418 * Verifies connectivity of this driver by trying to open a connection with the provided driver options.
255419 *
@@ -418,6 +582,7 @@ class Driver {
418582
419583 /**
420584 * @protected
585+ * @returns {void }
421586 */
422587 _afterConstruction ( ) : void {
423588 this . _log . info (
@@ -589,6 +754,6 @@ function createHostNameResolver (config: any): ConfiguredCustomResolver {
589754 return new ConfiguredCustomResolver ( config . resolver )
590755}
591756
592- export { Driver , READ , WRITE }
593- export type { SessionConfig }
757+ export { Driver , READ , WRITE , routing }
758+ export type { SessionConfig , QueryConfig , RoutingControl }
594759export default Driver
0 commit comments