@travetto/model-query-language 8.0.0-alpha.8 → 8.0.0
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/README.md +1 -1
- package/__index__.ts +1 -1
- package/package.json +16 -16
- package/src/model-query.ts +8 -6
- package/src/parser.ts +7 -8
- package/src/tokenizer.ts +65 -24
- package/src/types.ts +11 -9
package/README.md
CHANGED
|
@@ -41,4 +41,4 @@ user.role in ['admin', 'root'] && (user.address.state == 'VA' || user.address.ci
|
|
|
41
41
|
```
|
|
42
42
|
|
|
43
43
|
### Regular Expression
|
|
44
|
-
When querying with regular expressions, patterns can be specified as `'strings'` or as `/patterns/`.
|
|
44
|
+
When querying with regular expressions, patterns can be specified as `'strings'` or as `/patterns/`. The latter allows for the case insensitive modifier: `/pattern/i`. Supporting the insensitive flag is up to the underlying model implementation.
|
package/__index__.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,38 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/model-query-language",
|
|
3
|
-
"version": "8.0.0
|
|
4
|
-
"type": "module",
|
|
3
|
+
"version": "8.0.0",
|
|
5
4
|
"description": "Datastore query language.",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"datastore",
|
|
7
|
+
"query-language",
|
|
8
8
|
"schema",
|
|
9
|
-
"typescript",
|
|
10
9
|
"travetto",
|
|
11
|
-
"
|
|
10
|
+
"typescript"
|
|
12
11
|
],
|
|
13
12
|
"homepage": "https://travetto.io",
|
|
14
13
|
"license": "MIT",
|
|
15
14
|
"author": {
|
|
16
|
-
"
|
|
17
|
-
"
|
|
15
|
+
"name": "Travetto Framework",
|
|
16
|
+
"email": "travetto.framework@gmail.com"
|
|
17
|
+
},
|
|
18
|
+
"repository": {
|
|
19
|
+
"url": "git+https://github.com/travetto/travetto.git",
|
|
20
|
+
"directory": "module/model-query"
|
|
18
21
|
},
|
|
19
22
|
"files": [
|
|
20
23
|
"__index__.ts",
|
|
21
24
|
"src",
|
|
22
25
|
"support"
|
|
23
26
|
],
|
|
27
|
+
"type": "module",
|
|
24
28
|
"main": "__index__.ts",
|
|
25
|
-
"
|
|
26
|
-
"
|
|
27
|
-
"directory": "module/model-query"
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
28
31
|
},
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@travetto/model": "^8.0.0
|
|
31
|
-
"@travetto/model-query": "^8.0.0
|
|
32
|
-
"@travetto/schema": "^8.0.0
|
|
33
|
+
"@travetto/model": "^8.0.0",
|
|
34
|
+
"@travetto/model-query": "^8.0.0",
|
|
35
|
+
"@travetto/schema": "^8.0.0"
|
|
33
36
|
},
|
|
34
37
|
"peerDependencies": {
|
|
35
|
-
"@travetto/test": "^8.0.0
|
|
38
|
+
"@travetto/test": "^8.0.0"
|
|
36
39
|
},
|
|
37
40
|
"peerDependenciesMeta": {
|
|
38
41
|
"@travetto/test": {
|
|
@@ -41,8 +44,5 @@
|
|
|
41
44
|
},
|
|
42
45
|
"travetto": {
|
|
43
46
|
"displayName": "Data Model Query Language"
|
|
44
|
-
},
|
|
45
|
-
"publishConfig": {
|
|
46
|
-
"access": "public"
|
|
47
47
|
}
|
|
48
48
|
}
|
package/src/model-query.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { Schema } from '@travetto/schema';
|
|
2
1
|
import type { PageableModelQuery } from '@travetto/model-query';
|
|
3
2
|
import { JSONUtil } from '@travetto/runtime';
|
|
3
|
+
import { Schema } from '@travetto/schema';
|
|
4
4
|
|
|
5
5
|
import { QueryLanguageParser } from './parser.ts';
|
|
6
6
|
|
|
7
|
-
const parse = <T>(key: string): T | undefined =>
|
|
7
|
+
const parse = <T>(key: string): T | undefined =>
|
|
8
|
+
!key || typeof key !== 'string' || !/^[{[]/.test(key) ? undefined : JSONUtil.fromUTF8(key);
|
|
8
9
|
|
|
9
10
|
@Schema()
|
|
10
11
|
export class QueryLanguageModelQuery {
|
|
@@ -13,10 +14,11 @@ export class QueryLanguageModelQuery {
|
|
|
13
14
|
...(self.limit ? { limit: self.limit } : {}),
|
|
14
15
|
...(self.offset ? { offset: self.offset } : {}),
|
|
15
16
|
...(self.sort ? { sort: parse(self.sort) } : {}),
|
|
16
|
-
...(self.where
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
...(self.where
|
|
18
|
+
? {
|
|
19
|
+
where: parse(self.where) ?? QueryLanguageParser.parseToQuery(self.where)
|
|
20
|
+
}
|
|
21
|
+
: {})
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
|
package/src/parser.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { castTo } from '@travetto/runtime';
|
|
2
1
|
import type { WhereClauseRaw } from '@travetto/model-query';
|
|
2
|
+
import { castTo } from '@travetto/runtime';
|
|
3
3
|
|
|
4
4
|
import { QueryLanguageTokenizer } from './tokenizer.ts';
|
|
5
|
-
import { type
|
|
5
|
+
import { type AllNode, type ArrayNode, type GroupNode, type Literal, OPERATOR_TRANSLATION, type Token } from './types.ts';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Determine if a token is boolean
|
|
@@ -15,7 +15,6 @@ function isBoolean(value: unknown): value is Token & { type: 'boolean' } {
|
|
|
15
15
|
* Language parser
|
|
16
16
|
*/
|
|
17
17
|
export class QueryLanguageParser {
|
|
18
|
-
|
|
19
18
|
/**
|
|
20
19
|
* Handle all clauses
|
|
21
20
|
*/
|
|
@@ -101,7 +100,6 @@ export class QueryLanguageParser {
|
|
|
101
100
|
* Parse all tokens
|
|
102
101
|
*/
|
|
103
102
|
static parse(tokens: Token[], position: number = 0): AllNode {
|
|
104
|
-
|
|
105
103
|
let top: (AllNode | Token)[] = [];
|
|
106
104
|
const stack: (typeof top)[] = [top];
|
|
107
105
|
let list: Literal[] | undefined;
|
|
@@ -111,7 +109,7 @@ export class QueryLanguageParser {
|
|
|
111
109
|
switch (token.type) {
|
|
112
110
|
case 'grouping':
|
|
113
111
|
if (token.value === 'start') {
|
|
114
|
-
stack.push(top = []);
|
|
112
|
+
stack.push((top = []));
|
|
115
113
|
} else {
|
|
116
114
|
const group = stack.pop()!;
|
|
117
115
|
top = stack.at(-1)!;
|
|
@@ -147,7 +145,7 @@ export class QueryLanguageParser {
|
|
|
147
145
|
default:
|
|
148
146
|
top.push(token);
|
|
149
147
|
}
|
|
150
|
-
token = tokens[
|
|
148
|
+
token = tokens[(position += 1)];
|
|
151
149
|
}
|
|
152
150
|
|
|
153
151
|
this.condense(top, 'or');
|
|
@@ -184,7 +182,8 @@ export class QueryLanguageParser {
|
|
|
184
182
|
}
|
|
185
183
|
return top;
|
|
186
184
|
}
|
|
187
|
-
default:
|
|
185
|
+
default:
|
|
186
|
+
throw new Error(`Unexpected node type: ${node.type}`);
|
|
188
187
|
}
|
|
189
188
|
}
|
|
190
189
|
|
|
@@ -196,4 +195,4 @@ export class QueryLanguageParser {
|
|
|
196
195
|
const node = this.parse(tokens);
|
|
197
196
|
return this.convert(node);
|
|
198
197
|
}
|
|
199
|
-
}
|
|
198
|
+
}
|
package/src/tokenizer.ts
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
import { TimeUtil } from '@travetto/runtime';
|
|
2
|
+
|
|
2
3
|
import type { Token, TokenizeState, TokenType } from './types.ts';
|
|
3
4
|
|
|
4
|
-
const OPEN_PARENS = 0x28,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const
|
|
10
|
-
|
|
5
|
+
const OPEN_PARENS = 0x28,
|
|
6
|
+
CLOSE_PARENS = 0x29,
|
|
7
|
+
OPEN_BRACKET = 0x5b,
|
|
8
|
+
CLOSE_BRACKET = 0x5d,
|
|
9
|
+
COMMA = 0x2c;
|
|
10
|
+
const GREATER_THAN = 0x3e,
|
|
11
|
+
LESS_THAN = 0x3c,
|
|
12
|
+
EQUAL = 0x3d,
|
|
13
|
+
NOT = 0x21,
|
|
14
|
+
MODULO = 0x25,
|
|
15
|
+
TILDE = 0x7e,
|
|
16
|
+
AND = 0x26,
|
|
17
|
+
OR = 0x7c;
|
|
18
|
+
const SPACE = 0x20,
|
|
19
|
+
TAB = 0x09;
|
|
20
|
+
const DBL_QUOTE = 0x22,
|
|
21
|
+
SGL_QUOTE = 0x27,
|
|
22
|
+
FORWARD_SLASH = 0x2f,
|
|
23
|
+
BACKSLASH = 0x5c;
|
|
24
|
+
const PERIOD = 0x2e,
|
|
25
|
+
UNDERSCORE = 0x54,
|
|
26
|
+
DOLLAR_SIGN = 0x24,
|
|
27
|
+
DASH = 0x2d;
|
|
28
|
+
const ZERO = 0x30,
|
|
29
|
+
NINE = 0x39,
|
|
30
|
+
UPPER_A = 0x41,
|
|
31
|
+
UPPER_Z = 0x5a,
|
|
32
|
+
LOWER_A = 0x61,
|
|
33
|
+
LOWER_Z = 0x7a;
|
|
34
|
+
const LOWER_I = 0x69,
|
|
35
|
+
LOWER_G = 0x67,
|
|
36
|
+
LOWER_M = 0x6d,
|
|
37
|
+
LOWER_S = 0x73;
|
|
11
38
|
|
|
12
39
|
const ESCAPE: Record<string, string> = {
|
|
13
40
|
'\\n': '\n',
|
|
@@ -26,7 +53,7 @@ const TOKEN_MAPPING: Record<string, Token> = {
|
|
|
26
53
|
or: { type: 'boolean', value: 'or' },
|
|
27
54
|
'||': { type: 'boolean', value: 'or' },
|
|
28
55
|
in: { type: 'operator', value: 'in' },
|
|
29
|
-
|
|
56
|
+
'not-in': { type: 'operator', value: 'not-in' },
|
|
30
57
|
not: { type: 'unary', value: 'not' },
|
|
31
58
|
'[': { type: 'array', value: 'start' },
|
|
32
59
|
']': { type: 'array', value: 'end' },
|
|
@@ -34,14 +61,13 @@ const TOKEN_MAPPING: Record<string, Token> = {
|
|
|
34
61
|
')': { type: 'grouping', value: 'end' },
|
|
35
62
|
null: { type: 'literal', value: null },
|
|
36
63
|
true: { type: 'literal', value: true },
|
|
37
|
-
false: { type: 'literal', value: false }
|
|
64
|
+
false: { type: 'literal', value: false }
|
|
38
65
|
};
|
|
39
66
|
|
|
40
67
|
/**
|
|
41
68
|
* Tokenizer for the query language
|
|
42
69
|
*/
|
|
43
70
|
export class QueryLanguageTokenizer {
|
|
44
|
-
|
|
45
71
|
/**
|
|
46
72
|
* Process the next token. Can specify expected type as needed
|
|
47
73
|
*/
|
|
@@ -51,8 +77,7 @@ export class QueryLanguageTokenizer {
|
|
|
51
77
|
let value: unknown = text;
|
|
52
78
|
if (!result && state.mode === 'literal') {
|
|
53
79
|
if (/^["']/.test(text)) {
|
|
54
|
-
value = text.substring(1, text.length - 1)
|
|
55
|
-
.replace(/\\[.]/g, (a, b) => ESCAPE[a] || b);
|
|
80
|
+
value = text.substring(1, text.length - 1).replace(/\\[.]/g, (a, b) => ESCAPE[a] || b);
|
|
56
81
|
} else if (/^\//.test(text)) {
|
|
57
82
|
const start = 1;
|
|
58
83
|
const end = text.lastIndexOf('/');
|
|
@@ -94,13 +119,15 @@ export class QueryLanguageTokenizer {
|
|
|
94
119
|
* Determine if valid token identifier
|
|
95
120
|
*/
|
|
96
121
|
static #isValidIdentToken(ch: number): boolean {
|
|
97
|
-
return (
|
|
122
|
+
return (
|
|
123
|
+
(ch >= ZERO && ch <= NINE) ||
|
|
98
124
|
(ch >= UPPER_A && ch <= UPPER_Z) ||
|
|
99
125
|
(ch >= LOWER_A && ch <= LOWER_Z) ||
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
126
|
+
ch === UNDERSCORE ||
|
|
127
|
+
ch === DASH ||
|
|
128
|
+
ch === DOLLAR_SIGN ||
|
|
129
|
+
ch === PERIOD
|
|
130
|
+
);
|
|
104
131
|
}
|
|
105
132
|
|
|
106
133
|
/**
|
|
@@ -142,25 +169,39 @@ export class QueryLanguageTokenizer {
|
|
|
142
169
|
const ch = text.charCodeAt(state.position);
|
|
143
170
|
switch (ch) {
|
|
144
171
|
// Handle punctuation
|
|
145
|
-
case OPEN_PARENS:
|
|
172
|
+
case OPEN_PARENS:
|
|
173
|
+
case CLOSE_PARENS:
|
|
174
|
+
case OPEN_BRACKET:
|
|
175
|
+
case CLOSE_BRACKET:
|
|
176
|
+
case COMMA:
|
|
146
177
|
this.#flush(state);
|
|
147
178
|
state.mode = 'punctuation';
|
|
148
179
|
break;
|
|
149
180
|
// Handle operator
|
|
150
|
-
case GREATER_THAN:
|
|
151
|
-
case
|
|
181
|
+
case GREATER_THAN:
|
|
182
|
+
case LESS_THAN:
|
|
183
|
+
case EQUAL:
|
|
184
|
+
case MODULO:
|
|
185
|
+
case NOT:
|
|
186
|
+
case TILDE:
|
|
187
|
+
case AND:
|
|
188
|
+
case OR:
|
|
152
189
|
this.#flush(state, 'operator');
|
|
153
190
|
break;
|
|
154
191
|
// Handle whitespace
|
|
155
|
-
case SPACE:
|
|
192
|
+
case SPACE:
|
|
193
|
+
case TAB:
|
|
156
194
|
this.#flush(state, 'whitespace');
|
|
157
195
|
break;
|
|
158
196
|
// Handle quotes and slashes
|
|
159
|
-
case DBL_QUOTE:
|
|
197
|
+
case DBL_QUOTE:
|
|
198
|
+
case SGL_QUOTE:
|
|
199
|
+
case FORWARD_SLASH:
|
|
160
200
|
this.#flush(state);
|
|
161
201
|
state.mode = 'literal';
|
|
162
202
|
state.position = this.readString(text, state.position) + 1;
|
|
163
|
-
if (ch === FORWARD_SLASH) {
|
|
203
|
+
if (ch === FORWARD_SLASH) {
|
|
204
|
+
// Read modifiers, not used by all, but useful in general
|
|
164
205
|
while (this.#isValidRegexFlag(text.charCodeAt(state.position))) {
|
|
165
206
|
state.position += 1;
|
|
166
207
|
}
|
|
@@ -182,4 +223,4 @@ export class QueryLanguageTokenizer {
|
|
|
182
223
|
|
|
183
224
|
return state.out;
|
|
184
225
|
}
|
|
185
|
-
}
|
|
226
|
+
}
|
package/src/types.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Supported token types
|
|
3
3
|
*/
|
|
4
|
-
export type TokenType =
|
|
5
|
-
'literal' | 'identifier' | 'boolean' |
|
|
6
|
-
'operator' | 'grouping' | 'array' |
|
|
7
|
-
'whitespace' | 'punctuation' | 'unary';
|
|
4
|
+
export type TokenType = 'literal' | 'identifier' | 'boolean' | 'operator' | 'grouping' | 'array' | 'whitespace' | 'punctuation' | 'unary';
|
|
8
5
|
|
|
9
6
|
/**
|
|
10
7
|
* Tokenization state
|
|
@@ -76,11 +73,16 @@ export type AllNode = ArrayNode | UnaryNode | GroupNode | ClauseNode;
|
|
|
76
73
|
* Translation of operators to model query keys
|
|
77
74
|
*/
|
|
78
75
|
export const OPERATOR_TRANSLATION: Record<string, string> = {
|
|
79
|
-
'<': '$lt',
|
|
80
|
-
'
|
|
81
|
-
'
|
|
82
|
-
'
|
|
83
|
-
|
|
76
|
+
'<': '$lt',
|
|
77
|
+
'<=': '$lte',
|
|
78
|
+
'>': '$gt',
|
|
79
|
+
'>=': '$gte',
|
|
80
|
+
'!=': '$ne',
|
|
81
|
+
'==': '$eq',
|
|
82
|
+
'~': '$regex',
|
|
83
|
+
'!': '$not',
|
|
84
|
+
in: '$in',
|
|
85
|
+
'not-in': '$nin'
|
|
84
86
|
};
|
|
85
87
|
|
|
86
88
|
export const VALID_OPERATORS = new Set(Object.keys(OPERATOR_TRANSLATION));
|