Skip to content

Commit 50aa6aa

Browse files
authored
Introduce resultTransformer.summary (#1201)
**⚠️ 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 a7efcf7 commit 50aa6aa

File tree

6 files changed

+230
-10
lines changed

6 files changed

+230
-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
type ResultTransformer<T> = (result: Result) => Promise<T>
2527
/**
@@ -181,6 +183,24 @@ class ResultTransformers {
181183
first<Entries extends RecordShape = RecordShape>(): ResultTransformer<Record<Entries> | undefined> {
182184
return first
183185
}
186+
187+
/**
188+
* Creates a {@link ResultTransformer} which consumes the result and returns the {@link ResultSummary}.
189+
*
190+
* This result transformer is a shortcut to `(result) => result.summary()`.
191+
*
192+
* @example
193+
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
194+
* resultTransformer: neo4j.resultTransformers.summary()
195+
* })
196+
*
197+
* @returns {ResultTransformer<ResultSummary<T>>} The result transformer
198+
* @see {@link Driver#executeQuery}
199+
* @experimental This is a preview feature
200+
*/
201+
summary <T extends NumberOrInteger = Integer> (): ResultTransformer<ResultSummary<T>> {
202+
return summary
203+
}
184204
}
185205

186206
/**
@@ -221,3 +241,7 @@ async function first<Entries extends RecordShape> (result: Result): Promise<Reco
221241
}
222242
}
223243
}
244+
245+
async function summary<T extends NumberOrInteger = Integer> (result: Result): Promise<ResultSummary<T>> {
246+
return await result.summary()
247+
}

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: 163 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

@@ -268,6 +268,168 @@ describe('resultTransformers', () => {
268268
})
269269
})
270270

271+
describe('.summary()', () => {
272+
describe('with a valid result', () => {
273+
it('should return a 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('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('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+
const typeAssertionNumber: ResultSummary<number> = summary
334+
// @ts-expect-error
335+
const typeAssertionInteger: ResultSummary<Integer> = summary
336+
// @ts-expect-error
337+
const typeAssertionBigInt: ResultSummary<bigint> = summary
338+
339+
expect(typeAssertionNumber).toEqual(
340+
new ResultSummary<Integer>(query, params, meta)
341+
)
342+
343+
expect(typeAssertionInteger).toEqual(
344+
new ResultSummary<Integer>(query, params, meta)
345+
)
346+
347+
expect(typeAssertionBigInt).toEqual(
348+
new ResultSummary<Integer>(query, params, meta)
349+
)
350+
})
351+
352+
it('should return a ResultSummary<bigint>', async () => {
353+
const resultStreamObserverMock = new ResultStreamObserverMock()
354+
const query = 'Query'
355+
const params = { a: 1 }
356+
const meta = { db: 'adb' }
357+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
358+
const keys = ['model', 'year']
359+
const rawRecord1 = ['Beautiful Sedan', 1987]
360+
const rawRecord2 = ['Hot Hatch', 1995]
361+
362+
resultStreamObserverMock.onKeys(keys)
363+
resultStreamObserverMock.onNext(rawRecord1)
364+
resultStreamObserverMock.onNext(rawRecord2)
365+
resultStreamObserverMock.onCompleted(meta)
366+
const summary = await resultTransformers.summary<bigint>()(result)
367+
368+
const typeAssertionBigInt: ResultSummary<bigint> = summary
369+
// @ts-expect-error
370+
const typeAssertionNumber: ResultSummary<number> = summary
371+
// @ts-expect-error
372+
const typeAssertionInteger: ResultSummary<Integer> = summary
373+
374+
expect(typeAssertionNumber).toEqual(
375+
new ResultSummary<Integer>(query, params, meta)
376+
)
377+
378+
expect(typeAssertionInteger).toEqual(
379+
new ResultSummary<Integer>(query, params, meta)
380+
)
381+
382+
expect(typeAssertionBigInt).toEqual(
383+
new ResultSummary<Integer>(query, params, meta)
384+
)
385+
})
386+
387+
it('should return a ResultSummary<Integer>', async () => {
388+
const resultStreamObserverMock = new ResultStreamObserverMock()
389+
const query = 'Query'
390+
const params = { a: 1 }
391+
const meta = { db: 'adb' }
392+
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
393+
const keys = ['model', 'year']
394+
const rawRecord1 = ['Beautiful Sedan', 1987]
395+
const rawRecord2 = ['Hot Hatch', 1995]
396+
397+
resultStreamObserverMock.onKeys(keys)
398+
resultStreamObserverMock.onNext(rawRecord1)
399+
resultStreamObserverMock.onNext(rawRecord2)
400+
resultStreamObserverMock.onCompleted(meta)
401+
const summary = await resultTransformers.summary<Integer>()(result)
402+
403+
const typeAssertionInteger: ResultSummary<Integer> = summary
404+
// @ts-expect-error
405+
const typeAssertionNumber: ResultSummary<number> = summary
406+
// @ts-expect-error
407+
const typeAssertionBigInt: ResultSummary<bigint> = summary
408+
409+
expect(typeAssertionInteger).toEqual(
410+
new ResultSummary<Integer>(query, params, meta)
411+
)
412+
413+
expect(typeAssertionNumber).toEqual(
414+
new ResultSummary<Integer>(query, params, meta)
415+
)
416+
417+
expect(typeAssertionBigInt).toEqual(
418+
new ResultSummary<Integer>(query, params, meta)
419+
)
420+
})
421+
})
422+
423+
describe('when results fail', () => {
424+
it('should propagate the exception', async () => {
425+
const expectedError = newError('expected error')
426+
const result = new Result(Promise.reject(expectedError), 'query')
427+
428+
await expect(resultTransformers.summary()(result)).rejects.toThrow(expectedError)
429+
})
430+
})
431+
})
432+
271433
describe('.first', () => {
272434
describe('with a valid result', () => {
273435
it('should return an single Record', async () => {

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
type ResultTransformer<T> = (result: Result) => Promise<T>
2527
/**
@@ -181,6 +183,24 @@ class ResultTransformers {
181183
first<Entries extends RecordShape = RecordShape>(): ResultTransformer<Record<Entries> | undefined> {
182184
return first
183185
}
186+
187+
/**
188+
* Creates a {@link ResultTransformer} which consumes the result and returns the {@link ResultSummary}.
189+
*
190+
* This result transformer is a shortcut to `(result) => result.summary()`.
191+
*
192+
* @example
193+
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
194+
* resultTransformer: neo4j.resultTransformers.summary()
195+
* })
196+
*
197+
* @returns {ResultTransformer<ResultSummary<T>>} The result transformer
198+
* @see {@link Driver#executeQuery}
199+
* @experimental This is a preview feature
200+
*/
201+
summary <T extends NumberOrInteger = Integer> (): ResultTransformer<ResultSummary<T>> {
202+
return summary
203+
}
184204
}
185205

186206
/**
@@ -221,3 +241,7 @@ async function first<Entries extends RecordShape> (result: Result): Promise<Reco
221241
}
222242
}
223243
}
244+
245+
async function summary<T extends NumberOrInteger = Integer> (result: Result): Promise<ResultSummary<T>> {
246+
return await result.summary()
247+
}

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)