Skip to content

Commit 4eb682a

Browse files
committed
Refactor call/yield to show their relationship
This allows strict types for the terms of what the procedure yields
1 parent 5f8a184 commit 4eb682a

File tree

7 files changed

+61
-32
lines changed

7 files changed

+61
-32
lines changed

src/components/file/media/media.repository.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,9 +114,11 @@ export class MediaRepository extends CommonRepository {
114114
// Update the labels if typename is given, and maybe changed.
115115
.apply((q) =>
116116
res
117-
? q
118-
.call(apoc.create.setLabels('node', res.dbLabels))
119-
.yield('node as labelsAdded')
117+
? q.call(
118+
apoc.create
119+
.setLabels('node', res.dbLabels)
120+
.yield({ node: 'labelsAdded' }),
121+
)
120122
: q,
121123
)
122124
// Grab the previous media node or null

src/components/product/product.repository.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,7 +552,9 @@ export class ProductRepository extends CommonRepository {
552552
.query()
553553
.apply((q) =>
554554
query
555-
? q.apply(ProductCompletionDescriptionIndex.search(query))
555+
? q.call(
556+
ProductCompletionDescriptionIndex.search(query).yield('node'),
557+
)
556558
: q.matchNode('node', 'ProductCompletionDescription'),
557559
)
558560
.apply((q) =>

src/components/search/search.repository.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ export class SearchRepository extends CommonRepository {
3434

3535
.union()
3636

37-
.apply(GlobalIndex.search(lucene, { yield: 'node as property' }))
37+
.call(GlobalIndex.search(lucene).yield({ node: 'property' }))
3838
.match([node('node'), relation('out', 'r', ACTIVE), node('property')])
3939
.return(['node', 'collect(type(r)) as matchedProps'])
4040
// The input.count is going to be applied once the results are 'filtered'

src/core/database/query-augmentation/call.ts

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { entries } from '@seedcompany/common';
22
import { Clause, Query } from 'cypher-query-builder';
33
import { Parameter } from 'cypher-query-builder/dist/typings/parameter-bag';
44
import { isExp, variable } from '../query';
5+
import type { YieldTerms } from './yield';
56

67
declare module 'cypher-query-builder/dist/typings/query' {
78
interface Query {
@@ -22,16 +23,19 @@ Query.prototype.call = function call(
2223
procedure: ProcedureCall | string,
2324
args?: ProcedureArgs,
2425
) {
25-
const clause =
26+
const call =
2627
typeof procedure === 'string'
27-
? new Procedure(procedure, args ?? [])
28-
: new Procedure(procedure.name, procedure.args);
29-
return this.continueChainClause(clause);
28+
? { procedureName: procedure, args: args ?? [] }
29+
: procedure;
30+
const clause = new Procedure(call.procedureName, call.args);
31+
const next = this.continueChainClause(clause);
32+
return call.yieldTerms ? next.yield(call.yieldTerms) : next;
3033
};
3134

32-
interface ProcedureCall {
33-
name: string;
35+
interface ProcedureCall<Y extends string = string> {
36+
procedureName: string;
3437
args: ProcedureArgs;
38+
yieldTerms?: YieldTerms<Y>;
3539
}
3640
type ProcedureArgs = Record<string, any> | any[];
3741

@@ -51,3 +55,23 @@ class Procedure extends Clause {
5155
return `CALL ${this.name}(${this.params.join(', ')})`;
5256
}
5357
}
58+
59+
export const procedure =
60+
<const Y extends string>(
61+
procedureName: string,
62+
// eslint-disable-next-line @seedcompany/no-unused-vars
63+
yieldDefs: readonly Y[],
64+
) =>
65+
(args: ProcedureArgs) => ({
66+
procedureName,
67+
args,
68+
yield: (yieldTerms: YieldTerms<Y>) =>
69+
Object.assign(
70+
(query: Query) => query.call(procedureName, args).yield(yieldTerms),
71+
{
72+
procedureName,
73+
args,
74+
yieldTerms,
75+
},
76+
),
77+
});

src/core/database/query-augmentation/yield.ts

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,23 @@
1-
import { Many } from '@seedcompany/common';
1+
import { isPlainObject } from '@nestjs/common/utils/shared.utils.js';
2+
import { isNotFalsy, many, Many, Nil } from '@seedcompany/common';
23
import { Clause, Query } from 'cypher-query-builder';
34

45
declare module 'cypher-query-builder/dist/typings/query' {
56
interface Query {
6-
yield(...terms: Array<Many<string>>): this;
7+
yield(terms: YieldTerms): this;
78
}
89
}
910

10-
Query.prototype.yield = function (this: Query, ...terms: Array<Many<string>>) {
11-
const flattened = terms.flat();
11+
export type YieldTerms<T extends string = string> =
12+
| Many<T | Nil>
13+
| Partial<Record<T, string | boolean | Nil>>;
14+
15+
Query.prototype.yield = function (this: Query, terms: YieldTerms) {
16+
const flattened = isPlainObject(terms)
17+
? Object.entries(terms).flatMap(([k, v]) =>
18+
v === false || v == null ? [] : v === true ? k : `${k} as ${v}`,
19+
)
20+
: many(terms).filter(isNotFalsy);
1221
if (flattened.length === 0) return this;
1322
return this.continueChainClause(new Yield(flattened));
1423
};

src/core/database/query/cypher-functions.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { procedure } from '../query-augmentation/call';
12
import { exp, ExpressionInput } from './cypher-expression';
23
import { IndexFullTextQueryNodes } from './full-text';
34

@@ -77,10 +78,8 @@ export const apoc = {
7778
toMap: fn1('apoc.convert.toMap'),
7879
},
7980
create: {
80-
setLabels: (node: ExpressionInput, labels: readonly string[]) => ({
81-
name: 'apoc.create.setLabels',
82-
args: { node: exp(node), labels },
83-
}),
81+
setLabels: (node: ExpressionInput, labels: readonly string[]) =>
82+
procedure('apoc.create.setLabels', ['node'])({ node: exp(node), labels }),
8483
},
8584
};
8685

src/core/database/query/full-text.ts

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { entries, isNotNil, many, Many, mapKeys } from '@seedcompany/common';
22
import { Query } from 'cypher-query-builder';
33
import { pickBy } from 'lodash';
44
import { LiteralUnion } from 'type-fest';
5+
import { procedure } from '../query-augmentation/call';
56
import { CypherExpression, exp, isExp } from './cypher-expression';
67
import { db } from './cypher-functions';
78

@@ -59,23 +60,17 @@ export const FullTextIndex = (config: {
5960
*/
6061
search: (
6162
query: string,
62-
config: {
63-
yield?: Many<string>;
63+
options: {
6464
skip?: number;
6565
limit?: number;
6666
analyzer?: Analyzer;
6767
} = {},
6868
) => {
69-
const { yield: yieldTerms = ['node'], ...options } = config;
70-
7169
// fallback to "" when no query is given, so that no results are
7270
// returned instead of the procedure failing
7371
query = query.trim() || '""';
7472

75-
return (q: Query) =>
76-
q
77-
.call(db.index.fulltext.queryNodes(indexName, query, options))
78-
.yield(yieldTerms);
73+
return db.index.fulltext.queryNodes(indexName, query, options);
7974
},
8075
};
8176
};
@@ -95,17 +90,15 @@ export const IndexFullTextQueryNodes = (
9590
analyzer?: string;
9691
}
9792
| CypherExpression,
98-
) => ({
99-
name: 'db.index.fulltext.queryNodes',
100-
args: {
93+
) =>
94+
procedure('db.index.fulltext.queryNodes', ['node', 'score'])({
10195
indexName,
10296
query,
10397
...(options &&
10498
(Object.values(options).filter(isNotNil).length > 0 || isExp(options))
10599
? { options }
106100
: undefined),
107-
},
108-
});
101+
});
109102

110103
type Analyzer = LiteralUnion<KnownAnalyzer, string>;
111104

0 commit comments

Comments
 (0)