@travetto/model-query-language 7.0.0-rc.0 → 7.0.0-rc.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/model-query-language",
3
- "version": "7.0.0-rc.0",
3
+ "version": "7.0.0-rc.2",
4
4
  "description": "Datastore query language.",
5
5
  "keywords": [
6
6
  "datastore",
@@ -26,12 +26,12 @@
26
26
  "directory": "module/model-query"
27
27
  },
28
28
  "dependencies": {
29
- "@travetto/model": "^7.0.0-rc.0",
30
- "@travetto/model-query": "^7.0.0-rc.0",
31
- "@travetto/schema": "^7.0.0-rc.0"
29
+ "@travetto/model": "^7.0.0-rc.2",
30
+ "@travetto/model-query": "^7.0.0-rc.2",
31
+ "@travetto/schema": "^7.0.0-rc.2"
32
32
  },
33
33
  "peerDependencies": {
34
- "@travetto/test": "^7.0.0-rc.0"
34
+ "@travetto/test": "^7.0.0-rc.2"
35
35
  },
36
36
  "peerDependenciesMeta": {
37
37
  "@travetto/test": {
@@ -3,7 +3,7 @@ import { PageableModelQuery } from '@travetto/model-query';
3
3
 
4
4
  import { QueryLanguageParser } from './parser.ts';
5
5
 
6
- const parse = <T>(k: string): T | undefined => !k || typeof k !== 'string' || !/^[\{\[]/.test(k) ? undefined : JSON.parse(k);
6
+ const parse = <T>(key: string): T | undefined => !key || typeof key !== 'string' || !/^[\{\[]/.test(key) ? undefined : JSON.parse(key);
7
7
 
8
8
  @Schema()
9
9
  export class QueryLanguageModelQuery {
package/src/parser.ts CHANGED
@@ -2,13 +2,13 @@ import { castTo } from '@travetto/runtime';
2
2
  import { WhereClauseRaw } from '@travetto/model-query';
3
3
 
4
4
  import { QueryLanguageTokenizer } from './tokenizer.ts';
5
- import { Token, Literal, GroupNode, OP_TRANSLATION, ArrayNode, AllNode } from './types.ts';
5
+ import { Token, Literal, GroupNode, OPERATOR_TRANSLATION, ArrayNode, AllNode } from './types.ts';
6
6
 
7
7
  /**
8
8
  * Determine if a token is boolean
9
9
  */
10
- function isBoolean(o: unknown): o is Token & { type: 'boolean' } {
11
- return !!o && typeof o === 'object' && 'type' in o && o.type === 'boolean';
10
+ function isBoolean(value: unknown): value is Token & { type: 'boolean' } {
11
+ return !!value && typeof value === 'object' && 'type' in value && value.type === 'boolean';
12
12
  }
13
13
 
14
14
  /**
@@ -20,32 +20,32 @@ export class QueryLanguageParser {
20
20
  * Handle all clauses
21
21
  */
22
22
  static handleClause(nodes: (AllNode | Token)[]): void {
23
- const val: Token | ArrayNode = castTo(nodes.pop());
24
- const op: Token & { value: string } = castTo(nodes.pop());
25
- const ident: Token & { value: string } = castTo(nodes.pop());
23
+ const value: Token | ArrayNode = castTo(nodes.pop());
24
+ const operator: Token & { value: string } = castTo(nodes.pop());
25
+ const identifier: Token & { value: string } = castTo(nodes.pop());
26
26
 
27
27
  // value isn't a literal or a list, bail
28
- if (val.type !== 'literal' && val.type !== 'list') {
29
- throw new Error(`Unexpected token: ${val.value}`);
28
+ if (value.type !== 'literal' && value.type !== 'list') {
29
+ throw new Error(`Unexpected token: ${value.value}`);
30
30
  }
31
31
 
32
32
  // If operator is not an operator, bail
33
- if (op.type !== 'operator') {
34
- throw new Error(`Unexpected token: ${op.value}`);
33
+ if (operator.type !== 'operator') {
34
+ throw new Error(`Unexpected token: ${operator.value}`);
35
35
  }
36
36
 
37
37
  // If operator is not known, bail
38
- const finalOp = OP_TRANSLATION[op.value];
38
+ const finalOperation = OPERATOR_TRANSLATION[operator.value];
39
39
 
40
- if (!finalOp) {
41
- throw new Error(`Unexpected operator: ${op.value}`);
40
+ if (!finalOperation) {
41
+ throw new Error(`Unexpected operator: ${operator.value}`);
42
42
  }
43
43
 
44
44
  nodes.push({
45
45
  type: 'clause',
46
- field: ident.value,
47
- op: finalOp,
48
- value: val.value
46
+ field: identifier.value,
47
+ operator: finalOperation,
48
+ value: value.value
49
49
  });
50
50
 
51
51
  // Handle unary support
@@ -58,21 +58,21 @@ export class QueryLanguageParser {
58
58
  * Condense nodes to remove unnecessary groupings
59
59
  * (a AND (b AND (c AND d))) => (a AND b AND c)
60
60
  */
61
- static condense(nodes: (AllNode | Token)[], op: 'and' | 'or'): void {
61
+ static condense(nodes: (AllNode | Token)[], operator: 'and' | 'or'): void {
62
62
  let second = nodes[nodes.length - 2];
63
63
 
64
- while (isBoolean(second) && second.value === op) {
64
+ while (isBoolean(second) && second.value === operator) {
65
65
  const right: AllNode = castTo(nodes.pop());
66
66
  nodes.pop()!;
67
67
  const left: AllNode = castTo(nodes.pop());
68
- const rg: GroupNode = castTo(right);
69
- if (rg.type === 'group' && rg.op === op) {
70
- rg.value.unshift(left);
71
- nodes.push(rg);
68
+ const rightGroup: GroupNode = castTo(right);
69
+ if (rightGroup.type === 'group' && rightGroup.operator === operator) {
70
+ rightGroup.value.unshift(left);
71
+ nodes.push(rightGroup);
72
72
  } else {
73
73
  nodes.push({
74
74
  type: 'group',
75
- op,
75
+ operator,
76
76
  value: [left, right]
77
77
  });
78
78
  }
@@ -88,11 +88,11 @@ export class QueryLanguageParser {
88
88
  const second = nodes[nodes.length - 2];
89
89
  if (second && second.type === 'unary' && second.value === 'not') {
90
90
  const node = nodes.pop();
91
- nodes.pop();
91
+ nodes.pop(); // This is second
92
92
  nodes.push({
93
93
  type: 'unary',
94
- op: 'not',
95
- value: castTo(node)
94
+ operator: 'not',
95
+ value: castTo<AllNode>(node)
96
96
  });
97
97
  }
98
98
  }
@@ -100,13 +100,13 @@ export class QueryLanguageParser {
100
100
  /**
101
101
  * Parse all tokens
102
102
  */
103
- static parse(tokens: Token[], pos: number = 0): AllNode {
103
+ static parse(tokens: Token[], position: number = 0): AllNode {
104
104
 
105
105
  let top: (AllNode | Token)[] = [];
106
106
  const stack: (typeof top)[] = [top];
107
- let arr: Literal[] | undefined;
107
+ let list: Literal[] | undefined;
108
108
 
109
- let token = tokens[pos];
109
+ let token = tokens[position];
110
110
  while (token) {
111
111
  switch (token.type) {
112
112
  case 'grouping':
@@ -123,31 +123,31 @@ export class QueryLanguageParser {
123
123
  break;
124
124
  case 'array':
125
125
  if (token.value === 'start') {
126
- arr = [];
126
+ list = [];
127
127
  } else {
128
- const arrNode: ArrayNode = { type: 'list', value: arr! };
128
+ const arrNode: ArrayNode = { type: 'list', value: list! };
129
129
  top.push(arrNode);
130
- arr = undefined;
130
+ list = undefined;
131
131
  this.handleClause(top);
132
132
  }
133
133
  break;
134
134
  case 'literal':
135
- if (arr !== undefined) {
136
- arr.push(token.value);
135
+ if (list !== undefined) {
136
+ list.push(token.value);
137
137
  } else {
138
138
  top.push(token);
139
139
  this.handleClause(top);
140
140
  }
141
141
  break;
142
142
  case 'punctuation':
143
- if (!arr) {
143
+ if (!list) {
144
144
  throw new Error(`Invalid token: ${token.value}`);
145
145
  }
146
146
  break;
147
147
  default:
148
148
  top.push(token);
149
149
  }
150
- token = tokens[++pos];
150
+ token = tokens[++position];
151
151
  }
152
152
 
153
153
  this.condense(top, 'or');
@@ -161,26 +161,26 @@ export class QueryLanguageParser {
161
161
  static convert<T = unknown>(node: AllNode): WhereClauseRaw<T> {
162
162
  switch (node.type) {
163
163
  case 'unary': {
164
- return castTo({ [`$${node.op!}`]: this.convert(node.value) });
164
+ return castTo({ [`$${node.operator!}`]: this.convert(node.value) });
165
165
  }
166
166
  case 'group': {
167
- return castTo({ [`$${node.op!}`]: node.value.map(x => this.convert(x)) });
167
+ return castTo({ [`$${node.operator!}`]: node.value.map(value => this.convert(value)) });
168
168
  }
169
169
  case 'clause': {
170
170
  const parts = node.field!.split('.');
171
171
  const top: WhereClauseRaw<T> = {};
172
172
  let sub: Record<string, unknown> = top;
173
- for (const p of parts) {
174
- sub = sub[p] = {};
173
+ for (const part of parts) {
174
+ sub = sub[part] = {};
175
175
  }
176
- if (node.op === '$regex' && typeof node.value === 'string') {
177
- sub[node.op!] = new RegExp(`^${node.value}`);
178
- } else if ((node.op === '$eq' || node.op === '$ne') && node.value === null) {
179
- sub.$exists = node.op !== '$eq';
180
- } else if ((node.op === '$in' || node.op === '$nin') && !Array.isArray(node.value)) {
181
- throw new Error(`Expected array literal for ${node.op}`);
176
+ if (node.operator === '$regex' && typeof node.value === 'string') {
177
+ sub[node.operator!] = new RegExp(`^${node.value}`);
178
+ } else if ((node.operator === '$eq' || node.operator === '$ne') && node.value === null) {
179
+ sub.$exists = node.operator !== '$eq';
180
+ } else if ((node.operator === '$in' || node.operator === '$nin') && !Array.isArray(node.value)) {
181
+ throw new Error(`Expected array literal for ${node.operator}`);
182
182
  } else {
183
- sub[node.op!] = node.value;
183
+ sub[node.operator!] = node.value;
184
184
  }
185
185
  return top;
186
186
  }
package/src/tokenizer.ts CHANGED
@@ -46,10 +46,10 @@ export class QueryLanguageTokenizer {
46
46
  * Process the next token. Can specify expected type as needed
47
47
  */
48
48
  static #processToken(state: TokenizeState, mode?: TokenType): Token {
49
- const text = state.text.substring(state.start, state.pos);
50
- const res = TOKEN_MAPPING[text.toLowerCase()];
49
+ const text = state.text.substring(state.start, state.position);
50
+ const result = TOKEN_MAPPING[text.toLowerCase()];
51
51
  let value: unknown = text;
52
- if (!res && state.mode === 'literal') {
52
+ if (!result && state.mode === 'literal') {
53
53
  if (/^["']/.test(text)) {
54
54
  value = text.substring(1, text.length - 1)
55
55
  .replace(/\\[.]/g, (a, b) => ESCAPE[a] || b);
@@ -67,18 +67,18 @@ export class QueryLanguageTokenizer {
67
67
  state.mode = 'identifier';
68
68
  }
69
69
  }
70
- return res ?? { value, type: state.mode || mode };
70
+ return result ?? { value, type: state.mode || mode };
71
71
  }
72
72
 
73
73
  /**
74
74
  * Flush state to output
75
75
  */
76
76
  static #flush(state: TokenizeState, mode?: TokenType): void {
77
- if ((!mode || !state.mode || mode !== state.mode) && state.start !== state.pos) {
77
+ if ((!mode || !state.mode || mode !== state.mode) && state.start !== state.position) {
78
78
  if (state.mode !== 'whitespace') {
79
79
  state.out.push(this.#processToken(state, mode));
80
80
  }
81
- state.start = state.pos;
81
+ state.start = state.position;
82
82
  }
83
83
  state.mode = mode || state.mode;
84
84
  }
@@ -106,23 +106,22 @@ export class QueryLanguageTokenizer {
106
106
  /**
107
107
  * Read string until quote
108
108
  */
109
- static readString(text: string, pos: number): number {
110
- const len = text.length;
111
- const ch = text.charCodeAt(pos);
112
- const q = ch;
113
- pos += 1;
114
- while (pos < len) {
115
- if (text.charCodeAt(pos) === q) {
109
+ static readString(text: string, position: number): number {
110
+ const length = text.length;
111
+ const ch = text.charCodeAt(position);
112
+ position += 1;
113
+ while (position < length) {
114
+ if (text.charCodeAt(position) === ch) {
116
115
  break;
117
- } else if (text.charCodeAt(pos) === BACKSLASH) {
118
- pos += 1;
116
+ } else if (text.charCodeAt(position) === BACKSLASH) {
117
+ position += 1;
119
118
  }
120
- pos += 1;
119
+ position += 1;
121
120
  }
122
- if (pos === len && text.charCodeAt(pos) !== q) {
121
+ if (position === length && text.charCodeAt(position) !== ch) {
123
122
  throw new Error('Unterminated string literal');
124
123
  }
125
- return pos;
124
+ return position;
126
125
  }
127
126
 
128
127
  /**
@@ -131,16 +130,16 @@ export class QueryLanguageTokenizer {
131
130
  static tokenize(text: string): Token[] {
132
131
  const state: TokenizeState = {
133
132
  out: [],
134
- pos: 0,
133
+ position: 0,
135
134
  start: 0,
136
135
  text,
137
136
  mode: undefined!
138
137
  };
139
138
  const len = text.length;
140
139
  // Loop through each char
141
- while (state.pos < len) {
140
+ while (state.position < len) {
142
141
  // Read code as a number, more efficient
143
- const ch = text.charCodeAt(state.pos);
142
+ const ch = text.charCodeAt(state.position);
144
143
  switch (ch) {
145
144
  // Handle punctuation
146
145
  case OPEN_PARENS: case CLOSE_PARENS: case OPEN_BRACKET: case CLOSE_BRACKET: case COMMA:
@@ -160,10 +159,10 @@ export class QueryLanguageTokenizer {
160
159
  case DBL_QUOTE: case SGL_QUOTE: case FORWARD_SLASH:
161
160
  this.#flush(state);
162
161
  state.mode = 'literal';
163
- state.pos = this.readString(text, state.pos) + 1;
162
+ state.position = this.readString(text, state.position) + 1;
164
163
  if (ch === FORWARD_SLASH) { // Read modifiers, not used by all, but useful in general
165
- while (this.#isValidRegexFlag(text.charCodeAt(state.pos))) {
166
- state.pos += 1;
164
+ while (this.#isValidRegexFlag(text.charCodeAt(state.position))) {
165
+ state.position += 1;
167
166
  }
168
167
  }
169
168
  this.#flush(state);
@@ -173,10 +172,10 @@ export class QueryLanguageTokenizer {
173
172
  if (this.#isValidIdentToken(ch)) {
174
173
  this.#flush(state, 'literal');
175
174
  } else {
176
- throw new Error(`Invalid character: ${text.substring(Math.max(0, state.pos - 10), state.pos + 1)}`);
175
+ throw new Error(`Invalid character: ${text.substring(Math.max(0, state.position - 10), state.position + 1)}`);
177
176
  }
178
177
  }
179
- state.pos += 1;
178
+ state.position += 1;
180
179
  }
181
180
 
182
181
  this.#flush(state);
package/src/types.ts CHANGED
@@ -11,7 +11,7 @@ export type TokenType =
11
11
  */
12
12
  export interface TokenizeState {
13
13
  out: Token[];
14
- pos: number;
14
+ position: number;
15
15
  start: number;
16
16
  text: string;
17
17
  mode: TokenType;
@@ -42,7 +42,7 @@ export interface Node<T extends string = string> {
42
42
  */
43
43
  export interface ClauseNode extends Node<'clause'> {
44
44
  field?: string;
45
- op?: string;
45
+ operator?: string;
46
46
  value?: Literal | Literal[];
47
47
  }
48
48
 
@@ -50,7 +50,7 @@ export interface ClauseNode extends Node<'clause'> {
50
50
  * Grouping
51
51
  */
52
52
  export interface GroupNode extends Node<'group'> {
53
- op?: 'and' | 'or';
53
+ operator?: 'and' | 'or';
54
54
  value: AllNode[];
55
55
  }
56
56
 
@@ -58,7 +58,7 @@ export interface GroupNode extends Node<'group'> {
58
58
  * Unary node
59
59
  */
60
60
  export interface UnaryNode extends Node<'unary'> {
61
- op?: 'not';
61
+ operator?: 'not';
62
62
  value: AllNode;
63
63
  }
64
64
 
@@ -66,7 +66,7 @@ export interface UnaryNode extends Node<'unary'> {
66
66
  * Array node
67
67
  */
68
68
  export interface ArrayNode extends Node<'list'> {
69
- op?: 'not';
69
+ operator?: 'not';
70
70
  value: Literal[];
71
71
  }
72
72
 
@@ -75,7 +75,7 @@ export type AllNode = ArrayNode | UnaryNode | GroupNode | ClauseNode;
75
75
  /**
76
76
  * Translation of operators to model query keys
77
77
  */
78
- export const OP_TRANSLATION: Record<string, string> = {
78
+ export const OPERATOR_TRANSLATION: Record<string, string> = {
79
79
  '<': '$lt', '<=': '$lte',
80
80
  '>': '$gt', '>=': '$gte',
81
81
  '!=': '$ne', '==': '$eq',
@@ -83,4 +83,4 @@ export const OP_TRANSLATION: Record<string, string> = {
83
83
  in: '$in', 'not-in': '$nin'
84
84
  };
85
85
 
86
- export const VALID_OPS = new Set(Object.keys(OP_TRANSLATION));
86
+ export const VALID_OPERATORS = new Set(Object.keys(OPERATOR_TRANSLATION));