Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce resultTransformer.summary #1201

Merged
merged 4 commits into from
Jun 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions packages/core/src/result-transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import Result from './result'
import EagerResult from './result-eager'
import ResultSummary from './result-summary'
import { newError } from './error'
import { NumberOrInteger } from './graph-types'
import Integer from './integer'

type ResultTransformer<T> = (result: Result) => Promise<T>
/**
Expand Down Expand Up @@ -181,6 +183,24 @@ class ResultTransformers {
first<Entries extends RecordShape = RecordShape>(): ResultTransformer<Record<Entries> | undefined> {
return first
}

/**
* Creates a {@link ResultTransformer} which consumes the result and returns the {@link ResultSummary}.
*
* This result transformer is a shortcut to `(result) => result.summary()`.
*
* @example
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
* resultTransformer: neo4j.resultTransformers.summary()
* })
*
* @returns {ResultTransformer<ResultSummary<T>>} The result transformer
* @see {@link Driver#executeQuery}
* @experimental This is a preview feature
*/
summary <T extends NumberOrInteger = Integer> (): ResultTransformer<ResultSummary<T>> {
return summary
}
}

/**
Expand Down Expand Up @@ -221,3 +241,7 @@ async function first<Entries extends RecordShape> (result: Result): Promise<Reco
}
}
}

async function summary<T extends NumberOrInteger = Integer> (result: Result): Promise<ResultSummary<T>> {
return await result.summary()
}
13 changes: 9 additions & 4 deletions packages/core/src/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Query, PeekableAsyncIterator } from './types'
import { observer, util, connectionHolder } from './internal'
import { newError, PROTOCOL_ERROR } from './error'
import { NumberOrInteger } from './graph-types'
import Integer from './integer'

const { EMPTY_CONNECTION_HOLDER } = connectionHolder

Expand Down Expand Up @@ -182,12 +183,14 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
* *Should not be combined with {@link Result#subscribe} function.*
*
* @public
* @returns {Promise<ResultSummary>} - Result summary.
* @returns {Promise<ResultSummary<T>>} - Result summary.
*
*/
summary (): Promise<ResultSummary> {
summary<T extends NumberOrInteger = Integer> (): Promise<ResultSummary<T>> {
if (this._summary !== null) {
return Promise.resolve(this._summary)
// This type casting is needed since we are defining the number type of
// summary in Result template
return Promise.resolve(this._summary as unknown as ResultSummary<T>)
} else if (this._error !== null) {
return Promise.reject(this._error)
}
Expand All @@ -196,7 +199,9 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
.then(o => {
o.cancel()
o.subscribe(this._decorateObserver({
onCompleted: summary => resolve(summary),
// This type casting is needed since we are defining the number type of
// summary in Result template
onCompleted: summary => resolve(summary as unknown as ResultSummary<T>),
onError: err => reject(err)
}))
})
Expand Down
164 changes: 163 additions & 1 deletion packages/core/test/result-transformers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
* limitations under the License.
*/

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

Expand Down Expand Up @@ -268,6 +268,168 @@ describe('resultTransformers', () => {
})
})

describe('.summary()', () => {
describe('with a valid result', () => {
it('should return a ResultSummary', async () => {
const resultStreamObserverMock = new ResultStreamObserverMock()
const query = 'Query'
const params = { a: 1 }
const meta = { db: 'adb' }
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
const keys = ['a', 'b']
const rawRecord1 = [1, 2]
const rawRecord2 = [3, 4]
resultStreamObserverMock.onKeys(keys)
resultStreamObserverMock.onNext(rawRecord1)
resultStreamObserverMock.onNext(rawRecord2)
resultStreamObserverMock.onCompleted(meta)

const summary: ResultSummary = await resultTransformers.summary()(result)

expect(summary).toEqual(
new ResultSummary(query, params, meta)
)
})

it('should cancel stream', async () => {
const meta = { db: 'adb' }
const resultStreamObserverMock = new ResultStreamObserverMock()
const cancelSpy = jest.spyOn(resultStreamObserverMock, 'cancel')
cancelSpy.mockImplementation(() => resultStreamObserverMock.onCompleted(meta))
const query = 'Query'
const params = { a: 1 }
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
const keys = ['a', 'b']
const rawRecord1 = [1, 2]
const rawRecord2 = [3, 4]
resultStreamObserverMock.onKeys(keys)
resultStreamObserverMock.onNext(rawRecord1)
resultStreamObserverMock.onNext(rawRecord2)

const summary: ResultSummary = await resultTransformers.summary()(result)

expect(cancelSpy).toHaveBeenCalledTimes(1)
expect(summary).toEqual(
new ResultSummary(query, params, meta)
)
})

it('should return a ResultSummary<number>', async () => {
const resultStreamObserverMock = new ResultStreamObserverMock()
const query = 'Query'
const params = { a: 1 }
const meta = { db: 'adb' }
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
const keys = ['model', 'year']
const rawRecord1 = ['Beautiful Sedan', 1987]
const rawRecord2 = ['Hot Hatch', 1995]

resultStreamObserverMock.onKeys(keys)
resultStreamObserverMock.onNext(rawRecord1)
resultStreamObserverMock.onNext(rawRecord2)
resultStreamObserverMock.onCompleted(meta)
const summary = await resultTransformers.summary<number>()(result)
bigmontz marked this conversation as resolved.
Show resolved Hide resolved

const typeAssertionNumber: ResultSummary<number> = summary
// @ts-expect-error
const typeAssertionInteger: ResultSummary<Integer> = summary
// @ts-expect-error
const typeAssertionBigInt: ResultSummary<bigint> = summary

expect(typeAssertionNumber).toEqual(
new ResultSummary<Integer>(query, params, meta)
)

expect(typeAssertionInteger).toEqual(
new ResultSummary<Integer>(query, params, meta)
)

expect(typeAssertionBigInt).toEqual(
new ResultSummary<Integer>(query, params, meta)
)
})

it('should return a ResultSummary<bigint>', async () => {
const resultStreamObserverMock = new ResultStreamObserverMock()
const query = 'Query'
const params = { a: 1 }
const meta = { db: 'adb' }
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
const keys = ['model', 'year']
const rawRecord1 = ['Beautiful Sedan', 1987]
const rawRecord2 = ['Hot Hatch', 1995]

resultStreamObserverMock.onKeys(keys)
resultStreamObserverMock.onNext(rawRecord1)
resultStreamObserverMock.onNext(rawRecord2)
resultStreamObserverMock.onCompleted(meta)
const summary = await resultTransformers.summary<bigint>()(result)

const typeAssertionBigInt: ResultSummary<bigint> = summary
// @ts-expect-error
const typeAssertionNumber: ResultSummary<number> = summary
// @ts-expect-error
const typeAssertionInteger: ResultSummary<Integer> = summary

expect(typeAssertionNumber).toEqual(
new ResultSummary<Integer>(query, params, meta)
)

expect(typeAssertionInteger).toEqual(
new ResultSummary<Integer>(query, params, meta)
)

expect(typeAssertionBigInt).toEqual(
new ResultSummary<Integer>(query, params, meta)
)
})

it('should return a ResultSummary<Integer>', async () => {
const resultStreamObserverMock = new ResultStreamObserverMock()
const query = 'Query'
const params = { a: 1 }
const meta = { db: 'adb' }
const result = new Result(Promise.resolve(resultStreamObserverMock), query, params)
const keys = ['model', 'year']
const rawRecord1 = ['Beautiful Sedan', 1987]
const rawRecord2 = ['Hot Hatch', 1995]

resultStreamObserverMock.onKeys(keys)
resultStreamObserverMock.onNext(rawRecord1)
resultStreamObserverMock.onNext(rawRecord2)
resultStreamObserverMock.onCompleted(meta)
const summary = await resultTransformers.summary<Integer>()(result)

const typeAssertionInteger: ResultSummary<Integer> = summary
// @ts-expect-error
const typeAssertionNumber: ResultSummary<number> = summary
// @ts-expect-error
const typeAssertionBigInt: ResultSummary<bigint> = summary

expect(typeAssertionInteger).toEqual(
new ResultSummary<Integer>(query, params, meta)
)

expect(typeAssertionNumber).toEqual(
new ResultSummary<Integer>(query, params, meta)
)

expect(typeAssertionBigInt).toEqual(
new ResultSummary<Integer>(query, params, meta)
)
})
})

describe('when results fail', () => {
it('should propagate the exception', async () => {
const expectedError = newError('expected error')
const result = new Result(Promise.reject(expectedError), 'query')

await expect(resultTransformers.summary()(result)).rejects.toThrow(expectedError)
})
})
})

describe('.first', () => {
describe('with a valid result', () => {
it('should return an single Record', async () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/core/test/result.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,7 +240,7 @@ describe('Result', () => {
await expect(result.summary()).rejects.toThrow(expectedError)
})

it('should resolve summary pushe afterwards', done => {
it('should resolve summary push afterwards', done => {
const metadata = {
resultConsumedAfter: 20,
resultAvailableAfter: 124,
Expand Down
24 changes: 24 additions & 0 deletions packages/neo4j-driver-deno/lib/core/result-transformers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import Result from './result.ts'
import EagerResult from './result-eager.ts'
import ResultSummary from './result-summary.ts'
import { newError } from './error.ts'
import { NumberOrInteger } from './graph-types.ts'
import Integer from './integer.ts'

type ResultTransformer<T> = (result: Result) => Promise<T>
/**
Expand Down Expand Up @@ -181,6 +183,24 @@ class ResultTransformers {
first<Entries extends RecordShape = RecordShape>(): ResultTransformer<Record<Entries> | undefined> {
return first
}

/**
* Creates a {@link ResultTransformer} which consumes the result and returns the {@link ResultSummary}.
*
* This result transformer is a shortcut to `(result) => result.summary()`.
*
* @example
* const summary = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
* resultTransformer: neo4j.resultTransformers.summary()
* })
*
* @returns {ResultTransformer<ResultSummary<T>>} The result transformer
* @see {@link Driver#executeQuery}
* @experimental This is a preview feature
*/
summary <T extends NumberOrInteger = Integer> (): ResultTransformer<ResultSummary<T>> {
return summary
}
}

/**
Expand Down Expand Up @@ -221,3 +241,7 @@ async function first<Entries extends RecordShape> (result: Result): Promise<Reco
}
}
}

async function summary<T extends NumberOrInteger = Integer> (result: Result): Promise<ResultSummary<T>> {
return await result.summary()
}
13 changes: 9 additions & 4 deletions packages/neo4j-driver-deno/lib/core/result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { Query, PeekableAsyncIterator } from './types.ts'
import { observer, util, connectionHolder } from './internal/index.ts'
import { newError, PROTOCOL_ERROR } from './error.ts'
import { NumberOrInteger } from './graph-types.ts'
import Integer from './integer.ts'

const { EMPTY_CONNECTION_HOLDER } = connectionHolder

Expand Down Expand Up @@ -182,12 +183,14 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
* *Should not be combined with {@link Result#subscribe} function.*
*
* @public
* @returns {Promise<ResultSummary>} - Result summary.
* @returns {Promise<ResultSummary<T>>} - Result summary.
*
*/
summary (): Promise<ResultSummary> {
summary<T extends NumberOrInteger = Integer> (): Promise<ResultSummary<T>> {
if (this._summary !== null) {
return Promise.resolve(this._summary)
// This type casting is needed since we are defining the number type of
// summary in Result template
return Promise.resolve(this._summary as unknown as ResultSummary<T>)
} else if (this._error !== null) {
return Promise.reject(this._error)
}
Expand All @@ -196,7 +199,9 @@ class Result<R extends RecordShape = RecordShape> implements Promise<QueryResult
.then(o => {
o.cancel()
o.subscribe(this._decorateObserver({
onCompleted: summary => resolve(summary),
// This type casting is needed since we are defining the number type of
// summary in Result template
onCompleted: summary => resolve(summary as unknown as ResultSummary<T>),
onError: err => reject(err)
}))
})
Expand Down