|
1 |
| -import { Query } from 'cypher-query-builder'; |
| 1 | +import { entries } from '@seedcompany/common'; |
| 2 | +import { Clause, Query } from 'cypher-query-builder'; |
| 3 | +import { Parameter } from 'cypher-query-builder/dist/typings/parameter-bag'; |
| 4 | +import { isExp, variable } from '../query'; |
| 5 | +import type { YieldTerms } from './yield'; |
2 | 6 |
|
3 | 7 | declare module 'cypher-query-builder/dist/typings/query' {
|
4 | 8 | interface Query {
|
5 | 9 | /**
|
6 |
| - * Apply custom query modifications while maintaining the fluent chain. |
| 10 | + * Call a procedure. |
7 | 11 | *
|
8 |
| - * @deprecated Use {@link apply}() instead. |
9 |
| - * |
10 |
| - * In the future this could be changed to utilize native neo4j call logic. |
| 12 | + * Args can be an array of positional args, or an object of named args. |
| 13 | + * Objects are still converted to positional args, so the order matters. |
| 14 | + * Objects allow the query parameters to be named for better readability. |
11 | 15 | */
|
12 |
| - call<A extends any[], R extends this | Query<any> | void>( |
13 |
| - fn: (query: this, ...args: A) => R, |
14 |
| - ...args: A |
15 |
| - ): R extends void ? this : R; |
| 16 | + call(procedure: ProcedureCall): this; |
| 17 | + call(procedureName: string, args?: ProcedureArgs): this; |
16 | 18 | }
|
17 | 19 | }
|
18 | 20 |
|
19 |
| -Query.prototype.call = function call< |
20 |
| - A extends any[], |
21 |
| - R extends Query<any> | void, |
22 |
| ->( |
| 21 | +Query.prototype.call = function call( |
23 | 22 | this: Query,
|
24 |
| - fn: (q: Query, ...args: A) => R, |
25 |
| - ...args: A |
26 |
| -): R extends void ? Query : R { |
27 |
| - return (fn(this, ...args) || this) as Exclude<R, void>; |
| 23 | + procedure: ProcedureCall | string, |
| 24 | + args?: ProcedureArgs, |
| 25 | +) { |
| 26 | + const call = |
| 27 | + typeof procedure === 'string' |
| 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; |
28 | 33 | };
|
| 34 | + |
| 35 | +interface ProcedureCall<Y extends string = string> { |
| 36 | + procedureName: string; |
| 37 | + args: ProcedureArgs; |
| 38 | + yieldTerms?: YieldTerms<Y>; |
| 39 | +} |
| 40 | +type ProcedureArgs = Record<string, any> | any[]; |
| 41 | + |
| 42 | +class Procedure extends Clause { |
| 43 | + private readonly params: Parameter[]; |
| 44 | + constructor(public name: string, public args: Record<string, any> | any[]) { |
| 45 | + super(); |
| 46 | + this.params = ( |
| 47 | + Array.isArray(args) |
| 48 | + ? args.map((value) => [undefined, value] as const) |
| 49 | + : entries(this.args as Record<string, any>) |
| 50 | + ).map(([key, value]) => |
| 51 | + isExp(value) ? variable(value) : this.addParam(value, key), |
| 52 | + ); |
| 53 | + } |
| 54 | + build() { |
| 55 | + return `CALL ${this.name}(${this.params.join(', ')})`; |
| 56 | + } |
| 57 | +} |
| 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 | + }); |
0 commit comments