Skip to content

Commit 5f32321

Browse files
committed
feat(parser): adds possibility to pass additional field and document level context
1 parent 76d12c0 commit 5f32321

File tree

3 files changed

+54
-15
lines changed

3 files changed

+54
-15
lines changed

packages/core/spec/ObjectQueryParser.spec.ts

+25
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,31 @@ describe('ObjectQueryParser', () => {
4343
expect(conditions.every(c => c instanceof FieldCondition && c.operator === 'eq')).to.be.true
4444
})
4545

46+
it('passes additional field context details during processing field level operators', () => {
47+
const my = { type: 'field', parse: spy(defaultInstructionParsers.field) }
48+
const parser = new ObjectQueryParser({ my }, {
49+
fieldContext: { check: true }
50+
})
51+
parser.parse({ field: { my: 1 } })
52+
const context = spy.calls(my.parse)[0][2] as { check: boolean }
53+
54+
expect(context.check).to.be.true
55+
})
56+
57+
it('passes additional document context details during processing document level and compound operators', () => {
58+
const document = { type: 'document', parse: spy(defaultInstructionParsers.document) }
59+
const compound = { type: 'compound', parse: spy(defaultInstructionParsers.compound) }
60+
const parser = new ObjectQueryParser({ document, compound }, {
61+
documentContext: { check: true }
62+
})
63+
parser.parse({ document: true, compound: [] })
64+
const documentContext = spy.calls(document.parse)[0][2] as { check: boolean }
65+
const compoundContext = spy.calls(compound.parse)[0][2] as { check: boolean }
66+
67+
expect(documentContext.check).to.be.true
68+
expect(compoundContext.check).to.be.true
69+
})
70+
4671
describe('when "field" level parsing instruction is specified', () => {
4772
it('parses it to `FieldCondition`', () => {
4873
const my = { type: 'field' }

packages/core/src/parsers/ObjectQueryParser.ts

+27-15
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
} from '../types';
88
import { buildAnd as and } from '../builder';
99
import { parseInstruction } from './defaultInstructionParsers';
10-
import { identity, hasOperators } from '../utils';
10+
import { identity, hasOperators, object } from '../utils';
1111

1212
export type FieldQueryOperators<T extends {}> = {
1313
[K in keyof T]: T[K] extends {} ? T[K] : never
@@ -18,46 +18,57 @@ type ParsingInstructions = Record<string, NamedInstruction>;
1818
export interface QueryOptions {
1919
operatorToConditionName?(name: string): string
2020
defaultOperatorName?: string
21+
fieldContext?: Record<string, unknown>
22+
documentContext?: Record<string, unknown>
2123
}
2224

2325
export type ObjectQueryFieldParsingContext = ParsingContext<FieldParsingContext & {
24-
query: unknown,
26+
query: {},
2527
hasOperators<T>(value: unknown): value is T
2628
}>;
2729

2830
export class ObjectQueryParser<
2931
T extends Record<any, any>,
3032
U extends FieldQueryOperators<T> = FieldQueryOperators<T>
3133
> {
32-
protected readonly _instructions: ParsingInstructions;
33-
protected _fieldInstructionContext: ObjectQueryFieldParsingContext;
34-
protected readonly _options: Required<QueryOptions>;
35-
36-
constructor(instructions: Record<string, ParsingInstruction>, options?: QueryOptions) {
34+
private readonly _instructions: ParsingInstructions;
35+
private _fieldInstructionContext: ObjectQueryFieldParsingContext;
36+
private _documentInstructionContext: ParsingContext<{ query: {} }>;
37+
private readonly _options: Required<
38+
Pick<QueryOptions, 'operatorToConditionName' | 'defaultOperatorName'>
39+
>;
40+
41+
constructor(instructions: Record<string, ParsingInstruction>, options: QueryOptions = object()) {
3742
this.parse = this.parse.bind(this);
3843
this._options = {
39-
operatorToConditionName: identity,
40-
defaultOperatorName: 'eq',
41-
...options
44+
operatorToConditionName: options.operatorToConditionName || identity,
45+
defaultOperatorName: options.defaultOperatorName || 'eq',
4246
};
4347
this._instructions = Object.keys(instructions).reduce((all, name) => {
4448
all[name] = { name: this._options.operatorToConditionName(name), ...instructions[name] };
4549
return all;
4650
}, {} as ParsingInstructions);
4751
this._fieldInstructionContext = {
52+
...options.fieldContext,
4853
field: '',
4954
query: {},
5055
parse: this.parse,
5156
hasOperators: <T>(value: unknown): value is T => hasOperators(value, this._instructions),
5257
};
58+
this._documentInstructionContext = {
59+
...options.documentContext,
60+
parse: this.parse,
61+
query: {}
62+
};
5363
}
5464

5565
setParse(parse: this['parse']) {
5666
this.parse = parse;
5767
this._fieldInstructionContext.parse = parse;
68+
this._documentInstructionContext.parse = parse;
5869
}
5970

60-
protected parseField(field: string, operator: string, value: unknown, parentQuery: unknown) {
71+
protected parseField(field: string, operator: string, value: unknown, parentQuery: {}) {
6172
const instruction = this._instructions[operator];
6273

6374
if (!instruction) {
@@ -96,11 +107,12 @@ export class ObjectQueryParser<
96107
return conditions;
97108
}
98109

99-
parse<Q extends T, FQ extends U = U>(query: Q): Condition {
100-
const defaultContext = { query, parse: this.parse };
110+
parse<Q extends T>(query: Q): Condition {
101111
const conditions = [];
102112
const keys = Object.keys(query);
103113

114+
this._documentInstructionContext.query = query;
115+
104116
for (let i = 0, length = keys.length; i < length; i++) {
105117
const key = keys[i];
106118
const value = query[key];
@@ -111,8 +123,8 @@ export class ObjectQueryParser<
111123
throw new Error(`Cannot use parsing instruction for operator "${key}" in "document" context as it is supposed to be used in "${instruction.type}" context`);
112124
}
113125

114-
conditions.push(parseInstruction(instruction, value, defaultContext));
115-
} else if (hasOperators<FQ>(value, this._instructions)) {
126+
conditions.push(parseInstruction(instruction, value, this._documentInstructionContext));
127+
} else if (hasOperators<U>(value, this._instructions)) {
116128
conditions.push(...this.parseFieldOperators(key, value));
117129
} else {
118130
conditions.push(this.parseField(key, this._options.defaultOperatorName, value, query));

packages/core/src/utils.ts

+2
Original file line numberDiff line numberDiff line change
@@ -47,3 +47,5 @@ export function hasOperators<T>(value: any, instructions: Record<string, unknown
4747

4848
return false;
4949
}
50+
51+
export const object = () => Object.create(null);

0 commit comments

Comments
 (0)