Skip to content

Commit 91e38e3

Browse files
authored
Introduce resultTransformers.eager and resultTransformers.mapped (#1202)
**⚠️ This API is released as preview.** This method are aliases to `resultTransformers.eagerResultTransformer` and `resultTransformers.mappedResultTransformers`. **⚠️ This API is released as preview.**
1 parent 50aa6aa commit 91e38e3

File tree

3 files changed

+293
-81
lines changed

3 files changed

+293
-81
lines changed

packages/core/src/result-transformers.ts

+138-35
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,36 @@ class ResultTransformers {
5252
* const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
5353
*
5454
* @returns {ResultTransformer<EagerResult<Entries>>} The result transformer
55+
* @alias {@link ResultTransformers#eager}
5556
*/
5657
eagerResultTransformer<Entries extends RecordShape = RecordShape>(): ResultTransformer<EagerResult<Entries>> {
5758
return createEagerResultFromResult
5859
}
5960

61+
/**
62+
* Creates a {@link ResultTransformer} which transforms {@link Result} to {@link EagerResult}
63+
* by consuming the whole stream.
64+
*
65+
* This is the default implementation used in {@link Driver#executeQuery} and a alias to
66+
* {@link resultTransformers.eagerResultTransformer}
67+
*
68+
* @example
69+
* // This:
70+
* const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'}, {
71+
* resultTransformer: neo4j.resultTransformers.eager()
72+
* })
73+
* // is equivalent to:
74+
* const { keys, records, summary } = await driver.executeQuery('CREATE (p:Person{ name: $name }) RETURN p', { name: 'Person1'})
75+
*
76+
* @returns {ResultTransformer<EagerResult<Entries>>} The result transformer
77+
* @experimental this is a preview
78+
* @since 5.22.0
79+
* @alias {@link ResultTransformers#eagerResultTransformer}
80+
*/
81+
eager<Entries extends RecordShape = RecordShape>(): ResultTransformer<EagerResult<Entries>> {
82+
return createEagerResultFromResult
83+
}
84+
6085
/**
6186
* Creates a {@link ResultTransformer} which maps the {@link Record} in the result and collects it
6287
* along with the {@link ResultSummary} and {@link Result#keys}.
@@ -122,41 +147,81 @@ class ResultTransformers {
122147
mappedResultTransformer <
123148
R = Record, T = { records: R[], keys: string[], summary: ResultSummary }
124149
>(config: { map?: (rec: Record) => R | undefined, collect?: (records: R[], summary: ResultSummary, keys: string[]) => T }): ResultTransformer<T> {
125-
if (config == null || (config.collect == null && config.map == null)) {
126-
throw newError('Requires a map or/and a collect functions.')
127-
}
128-
return async (result: Result) => {
129-
return await new Promise((resolve, reject) => {
130-
const state: { keys: string[], records: R[] } = { records: [], keys: [] }
131-
132-
result.subscribe({
133-
onKeys (keys: string[]) {
134-
state.keys = keys
135-
},
136-
onNext (record: Record) {
137-
if (config.map != null) {
138-
const mappedRecord = config.map(record)
139-
if (mappedRecord !== undefined) {
140-
state.records.push(mappedRecord)
141-
}
142-
} else {
143-
state.records.push(record as unknown as R)
144-
}
145-
},
146-
onCompleted (summary: ResultSummary) {
147-
if (config.collect != null) {
148-
resolve(config.collect(state.records, summary, state.keys))
149-
} else {
150-
const obj = { records: state.records, summary, keys: state.keys }
151-
resolve(obj as unknown as T)
152-
}
153-
},
154-
onError (error: Error) {
155-
reject(error)
156-
}
157-
})
158-
})
159-
}
150+
return createMappedResultTransformer(config)
151+
}
152+
153+
/**
154+
* Creates a {@link ResultTransformer} which maps the {@link Record} in the result and collects it
155+
* along with the {@link ResultSummary} and {@link Result#keys}.
156+
*
157+
* NOTE: The config object requires map or/and collect to be valid.
158+
*
159+
* This method is a alias to {@link ResultTransformers#mappedResultTransformer}
160+
*
161+
*
162+
* @example
163+
* // Mapping the records
164+
* const { keys, records, summary } = await driver.executeQuery('MATCH (p:Person{ age: $age }) RETURN p.name as name', { age: 25 }, {
165+
* resultTransformer: neo4j.resultTransformers.mapped({
166+
* map(record) {
167+
* return record.get('name')
168+
* }
169+
* })
170+
* })
171+
*
172+
* records.forEach(name => console.log(`${name} has 25`))
173+
*
174+
* @example
175+
* // Mapping records and collect result
176+
* const names = await driver.executeQuery('MATCH (p:Person{ age: $age }) RETURN p.name as name', { age: 25 }, {
177+
* resultTransformer: neo4j.resultTransformers.mapped({
178+
* map(record) {
179+
* return record.get('name')
180+
* },
181+
* collect(records, summary, keys) {
182+
* return records
183+
* }
184+
* })
185+
* })
186+
*
187+
* names.forEach(name => console.log(`${name} has 25`))
188+
*
189+
* @example
190+
* // The transformer can be defined one and used everywhere
191+
* const getRecordsAsObjects = neo4j.resultTransformers.mapped({
192+
* map(record) {
193+
* return record.toObject()
194+
* },
195+
* collect(objects) {
196+
* return objects
197+
* }
198+
* })
199+
*
200+
* // The usage in a driver.executeQuery
201+
* const objects = await driver.executeQuery('MATCH (p:Person{ age: $age }) RETURN p.name as name', { age: 25 }, {
202+
* resultTransformer: getRecordsAsObjects
203+
* })
204+
* objects.forEach(object => console.log(`${object.name} has 25`))
205+
*
206+
*
207+
* // The usage in session.executeRead
208+
* const objects = await session.executeRead(tx => getRecordsAsObjects(tx.run('MATCH (p:Person{ age: $age }) RETURN p.name as name')))
209+
* objects.forEach(object => console.log(`${object.name} has 25`))
210+
*
211+
* @param {object} config The result transformer configuration
212+
* @param {function(record:Record):R} [config.map=function(record) { return record }] Method called for mapping each record
213+
* @param {function(records:R[], summary:ResultSummary, keys:string[]):T} [config.collect=function(records, summary, keys) { return { records, summary, keys }}] Method called for mapping
214+
* the result data to the transformer output.
215+
* @returns {ResultTransformer<T>} The result transformer
216+
* @experimental This is a preview feature
217+
* @alias {@link ResultTransformers#mappedResultTransformer}
218+
* @since 5.22.0
219+
* @see {@link Driver#executeQuery}
220+
*/
221+
mapped <
222+
R = Record, T = { records: R[], keys: string[], summary: ResultSummary }
223+
>(config: { map?: (rec: Record) => R | undefined, collect?: (records: R[], summary: ResultSummary, keys: string[]) => T }): ResultTransformer<T> {
224+
return createMappedResultTransformer(config)
160225
}
161226

162227
/**
@@ -222,6 +287,44 @@ async function createEagerResultFromResult<Entries extends RecordShape> (result:
222287
return new EagerResult<Entries>(keys, records, summary)
223288
}
224289

290+
function createMappedResultTransformer<R = Record, T = { records: R[], keys: string[], summary: ResultSummary }> (config: { map?: (rec: Record) => R | undefined, collect?: (records: R[], summary: ResultSummary, keys: string[]) => T }): ResultTransformer<T> {
291+
if (config == null || (config.collect == null && config.map == null)) {
292+
throw newError('Requires a map or/and a collect functions.')
293+
}
294+
return async (result: Result) => {
295+
return await new Promise((resolve, reject) => {
296+
const state: { keys: string[], records: R[] } = { records: [], keys: [] }
297+
298+
result.subscribe({
299+
onKeys (keys: string[]) {
300+
state.keys = keys
301+
},
302+
onNext (record: Record) {
303+
if (config.map != null) {
304+
const mappedRecord = config.map(record)
305+
if (mappedRecord !== undefined) {
306+
state.records.push(mappedRecord)
307+
}
308+
} else {
309+
state.records.push(record as unknown as R)
310+
}
311+
},
312+
onCompleted (summary: ResultSummary) {
313+
if (config.collect != null) {
314+
resolve(config.collect(state.records, summary, state.keys))
315+
} else {
316+
const obj = { records: state.records, summary, keys: state.keys }
317+
resolve(obj as unknown as T)
318+
}
319+
},
320+
onError (error: Error) {
321+
reject(error)
322+
}
323+
})
324+
})
325+
}
326+
}
327+
225328
async function first<Entries extends RecordShape> (result: Result): Promise<Record<Entries> | undefined> {
226329
// The async iterator is not used in the for await fashion
227330
// because the transpiler is generating a code which

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

+17-11
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@ import resultTransformers from '../src/result-transformers'
2020
import ResultStreamObserverMock from './utils/result-stream-observer.mock'
2121

2222
describe('resultTransformers', () => {
23-
describe('.eagerResultTransformer()', () => {
23+
describe.each([
24+
['.eagerResultTransformer()', resultTransformers.eagerResultTransformer],
25+
['.eager()', resultTransformers.eager]
26+
])('%s', (_, transformerFactory) => {
2427
describe('with a valid result', () => {
2528
it('it should return an EagerResult', async () => {
2629
const resultStreamObserverMock = new ResultStreamObserverMock()
@@ -36,7 +39,7 @@ describe('resultTransformers', () => {
3639
resultStreamObserverMock.onNext(rawRecord2)
3740
resultStreamObserverMock.onCompleted(meta)
3841

39-
const eagerResult: EagerResult = await resultTransformers.eagerResultTransformer()(result)
42+
const eagerResult: EagerResult = await transformerFactory()(result)
4043

4144
expect(eagerResult.keys).toEqual(keys)
4245
expect(eagerResult.records).toEqual([
@@ -66,7 +69,7 @@ describe('resultTransformers', () => {
6669
resultStreamObserverMock.onNext(rawRecord1)
6770
resultStreamObserverMock.onNext(rawRecord2)
6871
resultStreamObserverMock.onCompleted(meta)
69-
const eagerResult: EagerResult<Car> = await resultTransformers.eagerResultTransformer<Car>()(result)
72+
const eagerResult: EagerResult<Car> = await transformerFactory<Car>()(result)
7073

7174
expect(eagerResult.keys).toEqual(keys)
7275
expect(eagerResult.records).toEqual([
@@ -92,12 +95,15 @@ describe('resultTransformers', () => {
9295
const expectedError = newError('expected error')
9396
const result = new Result(Promise.reject(expectedError), 'query')
9497

95-
await expect(resultTransformers.eagerResultTransformer()(result)).rejects.toThrow(expectedError)
98+
await expect(transformerFactory()(result)).rejects.toThrow(expectedError)
9699
})
97100
})
98101
})
99102

100-
describe('.mappedResultTransformer', () => {
103+
describe.each([
104+
['.mappedResultTransformer', resultTransformers.mappedResultTransformer],
105+
['.mapped', resultTransformers.mapped]
106+
])('%s', (_, transformerFactory) => {
101107
describe('with a valid result', () => {
102108
it('should map and collect the result', async () => {
103109
const {
@@ -116,7 +122,7 @@ describe('resultTransformers', () => {
116122
ks: keys
117123
}))
118124

119-
const transform = resultTransformers.mappedResultTransformer({ map, collect })
125+
const transform = transformerFactory({ map, collect })
120126

121127
const { as, db, ks }: { as: number[], db: string | undefined | null, ks: string[] } = await transform(result)
122128

@@ -146,7 +152,7 @@ describe('resultTransformers', () => {
146152

147153
const map = jest.fn((record) => record.get('a') as number)
148154

149-
const transform = resultTransformers.mappedResultTransformer({ map })
155+
const transform = transformerFactory({ map })
150156

151157
const { records: as, summary, keys: receivedKeys }: { records: number[], summary: ResultSummary, keys: string[] } = await transform(result)
152158

@@ -177,7 +183,7 @@ describe('resultTransformers', () => {
177183
ks: keys
178184
}))
179185

180-
const transform = resultTransformers.mappedResultTransformer({ collect })
186+
const transform = transformerFactory({ collect })
181187

182188
const { recordsFetched, db, ks }: { recordsFetched: number, db: string | undefined | null, ks: string[] } = await transform(result)
183189

@@ -204,7 +210,7 @@ describe('resultTransformers', () => {
204210
return record.get('a') as number
205211
})
206212

207-
const transform = resultTransformers.mappedResultTransformer({ map })
213+
const transform = transformerFactory({ map })
208214

209215
const { records: as }: { records: number[] } = await transform(result)
210216

@@ -224,7 +230,7 @@ describe('resultTransformers', () => {
224230
{ Collect: () => {} }
225231
])('should throw if miss-configured [config=%o]', (config) => {
226232
// @ts-expect-error
227-
expect(() => resultTransformers.mappedResultTransformer(config))
233+
expect(() => transformerFactory(config))
228234
.toThrow(newError('Requires a map or/and a collect functions.'))
229235
})
230236

@@ -259,7 +265,7 @@ describe('resultTransformers', () => {
259265
it('should propagate the exception', async () => {
260266
const expectedError = newError('expected error')
261267
const result = new Result(Promise.reject(expectedError), 'query')
262-
const transformer = resultTransformers.mappedResultTransformer({
268+
const transformer = transformerFactory({
263269
collect: (records) => records
264270
})
265271

0 commit comments

Comments
 (0)