Skip to content

Commit 4bc2221

Browse files
committed
Introduce resultTransformer.summary
**⚠️ This API is released as preview.** This function enables fetching only the summary of the Result. The result will be consumed and records won't be streamed. Examples: ```javascript // using in the execute query const summary = await driver.executeQuery('MATCH (p:Person{ age: $age }) RETURN p.name as name', { age: 25 }, { database: 'neo4j, resultTransformer: neo4j.resultTransformers.summary() }) ``` **⚠️ This API is released as preview.**
1 parent 75d3040 commit 4bc2221

File tree

6 files changed

+215
-10
lines changed

6 files changed

+215
-10
lines changed

packages/core/src/result-transformers.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import Result from './result'
2020
import EagerResult from './result-eager'
2121
import ResultSummary from './result-summary'
2222
import { newError } from './error'
23+
import { NumberOrInteger } from './graph-types'
24+
import Integer from './integer'
2325

2426
async function createEagerResultFromResult<Entries extends RecordShape> (result: Result): Promise<EagerResult<Entries>> {
2527
const { summary, records } = await result
@@ -162,6 +164,24 @@ class ResultTransformers {
162164
})
163165
}
164166
}
167+
168+
/**
169+
* Creates a {@link ResultTransformer} which consumes the result and returns the {@link ResultSummary}.
170+
*
171+
* This result transformer is a shortcut to `(result) => result.summary()`.
172+
*
173+
* @example
174+
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
175+
* resultTransformer: neo4j.resultTransformers.summary()
176+
* })
177+
*
178+
* @returns {ResultTransformer<ResultSummary<T>>} The result transformer
179+
* @see {@link Driver#executeQuery}
180+
* @experimental This is a preview feature
181+
*/
182+
summary <T extends NumberOrInteger = Integer> (): ResultTransformer<ResultSummary<T>> {
183+
return summary
184+
}
165185
}
166186

167187
/**
@@ -176,3 +196,7 @@ export default resultTransformers
176196
export type {
177197
ResultTransformer
178198
}
199+
200+
async function summary<T extends NumberOrInteger = Integer> (result: Result): Promise<ResultSummary<T>> {
201+
return await result.summary()
202+
}

packages/core/src/result.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Query, PeekableAsyncIterator } from './types'
2323
import { observer, util, connectionHolder } from './internal'
2424
import { newError, PROTOCOL_ERROR } from './error'
2525
import { NumberOrInteger } from './graph-types'
26+
import Integer from './integer'
2627

2728
const { EMPTY_CONNECTION_HOLDER } = connectionHolder
2829

@@ -182,12 +183,14 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
182183
* *Should not be combined with {@link Result#subscribe} function.*
183184
*
184185
* @public
185-
* @returns {Promise<ResultSummary>} - Result summary.
186+
* @returns {Promise<ResultSummary<T>>} - Result summary.
186187
*
187188
*/
188-
summary (): Promise<ResultSummary> {
189+
summary<T extends NumberOrInteger = Integer> (): Promise<ResultSummary<T>> {
189190
if (this._summary !== null) {
190-
return Promise.resolve(this._summary)
191+
// This type casting is needed since we are defining the number type of
192+
// summary in Result template
193+
return Promise.resolve(this._summary as unknown as ResultSummary<T>)
191194
} else if (this._error !== null) {
192195
return Promise.reject(this._error)
193196
}
@@ -196,7 +199,9 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
196199
.then(o => {
197200
o.cancel()
198201
o.subscribe(this._decorateObserver({
199-
onCompleted: summary => resolve(summary),
202+
// This type casting is needed since we are defining the number type of
203+
// summary in Result template
204+
onCompleted: summary => resolve(summary as unknown as ResultSummary<T>),
200205
onError: err => reject(err)
201206
}))
202207
})

packages/core/test/result-transformers.test.ts

Lines changed: 148 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { EagerResult, newError, Record, Result, ResultSummary } from '../src'
18+
import { EagerResult, Integer, newError, Record, Result, ResultSummary } from '../src'
1919
import resultTransformers from '../src/result-transformers'
2020
import ResultStreamObserverMock from './utils/result-stream-observer.mock'
2121

@@ -267,4 +267,151 @@ describe('resultTransformers', () => {
267267
})
268268
})
269269
})
270+
271+
describe('.summary()', () => {
272+
describe('with a valid result', () => {
273+
it('it should return an ResultSummary', async () => {
274+
const resultStreamObserverMock = new ResultStreamObserverMock()
275+
const query = 'Query'
276+
const params = { a: 1 }
277+
const meta = { db: 'adb' }
278+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
279+
const keys = ['a', 'b']
280+
const rawRecord1 = [1, 2]
281+
const rawRecord2 = [3, 4]
282+
resultStreamObserverMock.onKeys(keys)
283+
resultStreamObserverMock.onNext(rawRecord1)
284+
resultStreamObserverMock.onNext(rawRecord2)
285+
resultStreamObserverMock.onCompleted(meta)
286+
287+
const summary: ResultSummary = await resultTransformers.summary()(result)
288+
289+
expect(summary).toEqual(
290+
new ResultSummary(query, params, meta)
291+
)
292+
})
293+
294+
it('it should cancel stream', async () => {
295+
const meta = { db: 'adb' }
296+
const resultStreamObserverMock = new ResultStreamObserverMock()
297+
const cancelSpy = jest.spyOn(resultStreamObserverMock, 'cancel')
298+
cancelSpy.mockImplementation(() => resultStreamObserverMock.onCompleted(meta))
299+
const query = 'Query'
300+
const params = { a: 1 }
301+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
302+
const keys = ['a', 'b']
303+
const rawRecord1 = [1, 2]
304+
const rawRecord2 = [3, 4]
305+
resultStreamObserverMock.onKeys(keys)
306+
resultStreamObserverMock.onNext(rawRecord1)
307+
resultStreamObserverMock.onNext(rawRecord2)
308+
309+
const summary: ResultSummary = await resultTransformers.summary()(result)
310+
311+
expect(cancelSpy).toHaveBeenCalledTimes(1)
312+
expect(summary).toEqual(
313+
new ResultSummary(query, params, meta)
314+
)
315+
})
316+
317+
it('it should return a ResultSummary<number>', async () => {
318+
const resultStreamObserverMock = new ResultStreamObserverMock()
319+
const query = 'Query'
320+
const params = { a: 1 }
321+
const meta = { db: 'adb' }
322+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
323+
const keys = ['model', 'year']
324+
const rawRecord1 = ['Beautiful Sedan', 1987]
325+
const rawRecord2 = ['Hot Hatch', 1995]
326+
327+
resultStreamObserverMock.onKeys(keys)
328+
resultStreamObserverMock.onNext(rawRecord1)
329+
resultStreamObserverMock.onNext(rawRecord2)
330+
resultStreamObserverMock.onCompleted(meta)
331+
const summary = await resultTransformers.summary<number>()(result)
332+
333+
// @ts-expect-error
334+
const typeAssertionInteger: ResultSummary<Integer> = summary
335+
// @ts-expect-error
336+
const typeAssertionBigInt: ResultSummary<bigint> = summary
337+
338+
expect(typeAssertionInteger).toEqual(
339+
new ResultSummary<Integer>(query, params, meta)
340+
)
341+
342+
expect(typeAssertionBigInt).toEqual(
343+
new ResultSummary<Integer>(query, params, meta)
344+
)
345+
})
346+
347+
it('it should return a ResultSummary<bigint>', async () => {
348+
const resultStreamObserverMock = new ResultStreamObserverMock()
349+
const query = 'Query'
350+
const params = { a: 1 }
351+
const meta = { db: 'adb' }
352+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
353+
const keys = ['model', 'year']
354+
const rawRecord1 = ['Beautiful Sedan', 1987]
355+
const rawRecord2 = ['Hot Hatch', 1995]
356+
357+
resultStreamObserverMock.onKeys(keys)
358+
resultStreamObserverMock.onNext(rawRecord1)
359+
resultStreamObserverMock.onNext(rawRecord2)
360+
resultStreamObserverMock.onCompleted(meta)
361+
const summary = await resultTransformers.summary<bigint>()(result)
362+
363+
// @ts-expect-error
364+
const typeAssertionNumber: ResultSummary<number> = summary
365+
// @ts-expect-error
366+
const typeAssertionInteger: ResultSummary<Integer> = summary
367+
368+
expect(typeAssertionNumber).toEqual(
369+
new ResultSummary<Integer>(query, params, meta)
370+
)
371+
372+
expect(typeAssertionInteger).toEqual(
373+
new ResultSummary<Integer>(query, params, meta)
374+
)
375+
})
376+
377+
it('it should return a ResultSummary<Integer>', async () => {
378+
const resultStreamObserverMock = new ResultStreamObserverMock()
379+
const query = 'Query'
380+
const params = { a: 1 }
381+
const meta = { db: 'adb' }
382+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
383+
const keys = ['model', 'year']
384+
const rawRecord1 = ['Beautiful Sedan', 1987]
385+
const rawRecord2 = ['Hot Hatch', 1995]
386+
387+
resultStreamObserverMock.onKeys(keys)
388+
resultStreamObserverMock.onNext(rawRecord1)
389+
resultStreamObserverMock.onNext(rawRecord2)
390+
resultStreamObserverMock.onCompleted(meta)
391+
const summary = await resultTransformers.summary<Integer>()(result)
392+
393+
// @ts-expect-error
394+
const typeAssertionNumber: ResultSummary<number> = summary
395+
// @ts-expect-error
396+
const typeAssertionBigInt: ResultSummary<bigint> = summary
397+
398+
expect(typeAssertionNumber).toEqual(
399+
new ResultSummary<Integer>(query, params, meta)
400+
)
401+
402+
expect(typeAssertionBigInt).toEqual(
403+
new ResultSummary<Integer>(query, params, meta)
404+
)
405+
})
406+
})
407+
408+
describe('when results fail', () => {
409+
it('should propagate the exception', async () => {
410+
const expectedError = newError('expected error')
411+
const result = new Result(Promise.reject(expectedError), 'query')
412+
413+
await expect(resultTransformers.eagerResultTransformer()(result)).rejects.toThrow(expectedError)
414+
})
415+
})
416+
})
270417
})

packages/core/test/result.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ describe('Result', () => {
240240
await expect(result.summary()).rejects.toThrow(expectedError)
241241
})
242242

243-
it('should resolve summary pushe afterwards', done => {
243+
it('should resolve summary push afterwards', done => {
244244
const metadata = {
245245
resultConsumedAfter: 20,
246246
resultAvailableAfter: 124,

packages/neo4j-driver-deno/lib/core/result-transformers.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import Result from './result.ts'
2020
import EagerResult from './result-eager.ts'
2121
import ResultSummary from './result-summary.ts'
2222
import { newError } from './error.ts'
23+
import { NumberOrInteger } from './graph-types.ts'
24+
import Integer from './integer.ts'
2325

2426
async function createEagerResultFromResult<Entries extends RecordShape> (result: Result): Promise<EagerResult<Entries>> {
2527
const { summary, records } = await result
@@ -162,6 +164,24 @@ class ResultTransformers {
162164
})
163165
}
164166
}
167+
168+
/**
169+
* Creates a {@link ResultTransformer} which consumes the result and returns the {@link ResultSummary}.
170+
*
171+
* This result transformer is a shortcut to `(result) => result.summary()`.
172+
*
173+
* @example
174+
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
175+
* resultTransformer: neo4j.resultTransformers.summary()
176+
* })
177+
*
178+
* @returns {ResultTransformer<ResultSummary<T>>} The result transformer
179+
* @see {@link Driver#executeQuery}
180+
* @experimental This is a preview feature
181+
*/
182+
summary <T extends NumberOrInteger = Integer> (): ResultTransformer<ResultSummary<T>> {
183+
return summary
184+
}
165185
}
166186

167187
/**
@@ -176,3 +196,7 @@ export default resultTransformers
176196
export type {
177197
ResultTransformer
178198
}
199+
200+
async function summary<T extends NumberOrInteger = Integer> (result: Result): Promise<ResultSummary<T>> {
201+
return await result.summary()
202+
}

packages/neo4j-driver-deno/lib/core/result.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import { Query, PeekableAsyncIterator } from './types.ts'
2323
import { observer, util, connectionHolder } from './internal/index.ts'
2424
import { newError, PROTOCOL_ERROR } from './error.ts'
2525
import { NumberOrInteger } from './graph-types.ts'
26+
import Integer from './integer.ts'
2627

2728
const { EMPTY_CONNECTION_HOLDER } = connectionHolder
2829

@@ -182,12 +183,14 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
182183
* *Should not be combined with {@link Result#subscribe} function.*
183184
*
184185
* @public
185-
* @returns {Promise<ResultSummary>} - Result summary.
186+
* @returns {Promise<ResultSummary<T>>} - Result summary.
186187
*
187188
*/
188-
summary (): Promise<ResultSummary> {
189+
summary<T extends NumberOrInteger = Integer> (): Promise<ResultSummary<T>> {
189190
if (this._summary !== null) {
190-
return Promise.resolve(this._summary)
191+
// This type casting is needed since we are defining the number type of
192+
// summary in Result template
193+
return Promise.resolve(this._summary as unknown as ResultSummary<T>)
191194
} else if (this._error !== null) {
192195
return Promise.reject(this._error)
193196
}
@@ -196,7 +199,9 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
196199
.then(o => {
197200
o.cancel()
198201
o.subscribe(this._decorateObserver({
199-
onCompleted: summary => resolve(summary),
202+
// This type casting is needed since we are defining the number type of
203+
// summary in Result template
204+
onCompleted: summary => resolve(summary as unknown as ResultSummary<T>),
200205
onError: err => reject(err)
201206
}))
202207
})

0 commit comments

Comments
 (0)