@camunda/ccma-shared-types 0.4.0 → 0.5.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/dist/_tests/constants/failoverRegionPairs.const.spec.d.ts +1 -0
- package/dist/_tests/search/compiler.spec.d.ts +1 -0
- package/dist/_tests/search/parser.spec.d.ts +1 -0
- package/dist/_tests/search/registry.spec.d.ts +1 -0
- package/dist/_tests/types/ClusterPlanFields.type.spec.d.ts +1 -0
- package/dist/constants/failoverRegionPairs.const.d.ts +8 -1
- package/dist/constants/failoverRegionPairs.const.js +9 -4
- package/dist/constants/failoverRegionPairs.const.js.map +2 -2
- package/dist/dtos/Backup.dto.d.ts +5 -0
- package/dist/dtos/ClusterPlan.dto.d.ts +5 -0
- package/dist/dtos/ClusterPlanType.dto.d.ts +6 -0
- package/dist/dtos/CommonCluster.dto.d.ts +7 -0
- package/dist/dtos/ControlV2.dto.d.ts +62 -14
- package/dist/dtos/K8sContext.dto.d.ts +6 -0
- package/dist/dtos/Organization.dto.d.ts +2 -0
- package/dist/dtos/Organization.dto.js.map +1 -1
- package/dist/dtos/ReleaseTrainSanity.dto.d.ts +51 -0
- package/dist/dtos/ReleaseTrainSanity.dto.js +1 -0
- package/dist/dtos/ReleaseTrainSanity.dto.js.map +7 -0
- package/dist/enums/OrganizationRole.enum.d.ts +1 -0
- package/dist/enums/OrganizationRole.enum.js +7 -0
- package/dist/enums/OrganizationRole.enum.js.map +2 -2
- package/dist/enums/SystemAuditEntity.enum.d.ts +7 -2
- package/dist/enums/SystemAuditEntity.enum.js +5 -0
- package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
- package/dist/index.d.ts +5 -1
- package/dist/index.js +30 -0
- package/dist/index.js.map +2 -2
- package/dist/permissions/combo.js +3 -0
- package/dist/permissions/combo.js.map +2 -2
- package/dist/permissions/console.permissions.d.ts +2 -0
- package/dist/permissions/console.permissions.js +7 -0
- package/dist/permissions/console.permissions.js.map +2 -2
- package/dist/search/ast.d.ts +52 -0
- package/dist/search/ast.js +18 -0
- package/dist/search/ast.js.map +7 -0
- package/dist/search/cluster-search-fields.d.ts +11 -0
- package/dist/search/cluster-search-fields.js +136 -0
- package/dist/search/cluster-search-fields.js.map +7 -0
- package/dist/search/compiler.d.ts +48 -0
- package/dist/search/compiler.js +217 -0
- package/dist/search/compiler.js.map +7 -0
- package/dist/search/fields.d.ts +24 -0
- package/dist/search/fields.js +5 -0
- package/dist/search/fields.js.map +7 -0
- package/dist/search/index.d.ts +6 -0
- package/dist/search/index.js +33 -0
- package/dist/search/index.js.map +7 -0
- package/dist/search/organization-search-fields.d.ts +9 -0
- package/dist/search/organization-search-fields.js +50 -0
- package/dist/search/organization-search-fields.js.map +7 -0
- package/dist/search/parser.d.ts +7 -0
- package/dist/search/parser.js +324 -0
- package/dist/search/parser.js.map +7 -0
- package/dist/types/ClusterModification.type.d.ts +1 -0
- package/dist/types/ClusterPlanFields.type.d.ts +24 -0
- package/dist/types/ClusterPlanFields.type.js +591 -0
- package/dist/types/ClusterPlanFields.type.js.map +7 -0
- package/dist/types/ClusterPlanModification.type.d.ts +1 -0
- package/dist/types/EnterpriseIncludingValueMetrics.type.d.ts +1 -0
- package/dist/types/HdpTenantUsage.type.d.ts +4 -0
- package/dist/types/HdpTenantUsage.type.js +1 -0
- package/dist/types/HdpTenantUsage.type.js.map +7 -0
- package/package.json +2 -2
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LuceneQueryError
|
|
3
|
+
} from "./ast.js";
|
|
4
|
+
const MAX_INPUT_LENGTH = 512;
|
|
5
|
+
const MAX_NODES = 80;
|
|
6
|
+
const MAX_DEPTH = 25;
|
|
7
|
+
const WORD_BOUNDARY = /* @__PURE__ */ new Set(["(", ")"]);
|
|
8
|
+
class Parser {
|
|
9
|
+
input;
|
|
10
|
+
pos = 0;
|
|
11
|
+
nodes = 0;
|
|
12
|
+
constructor(input) {
|
|
13
|
+
this.input = input;
|
|
14
|
+
}
|
|
15
|
+
parse() {
|
|
16
|
+
this.skipWs();
|
|
17
|
+
if (this.atEnd()) {
|
|
18
|
+
return void 0;
|
|
19
|
+
}
|
|
20
|
+
const node = this.parseOr(0);
|
|
21
|
+
this.skipWs();
|
|
22
|
+
if (!this.atEnd()) {
|
|
23
|
+
throw new LuceneQueryError(
|
|
24
|
+
`Unexpected "${this.rest(12)}" at position ${this.pos}.`,
|
|
25
|
+
"syntax"
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return node;
|
|
29
|
+
}
|
|
30
|
+
// --- expression levels ----------------------------------------------------
|
|
31
|
+
parseOr(depth) {
|
|
32
|
+
this.guardDepth(depth);
|
|
33
|
+
let node = this.parseAnd(depth);
|
|
34
|
+
for (; ; ) {
|
|
35
|
+
this.skipWs();
|
|
36
|
+
if (!this.matchKeyword("OR") && !this.matchKeyword("||")) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
this.consumeKeyword();
|
|
40
|
+
const right = this.parseAnd(depth);
|
|
41
|
+
node = this.track({ kind: "or", left: node, right });
|
|
42
|
+
}
|
|
43
|
+
return node;
|
|
44
|
+
}
|
|
45
|
+
parseAnd(depth) {
|
|
46
|
+
this.guardDepth(depth);
|
|
47
|
+
let node = this.parseNot(depth);
|
|
48
|
+
for (; ; ) {
|
|
49
|
+
this.skipWs();
|
|
50
|
+
if (this.atEnd() || this.peek() === ")" || this.matchKeyword("OR") || this.matchKeyword("||")) {
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
53
|
+
if (this.matchKeyword("AND") || this.matchKeyword("&&")) {
|
|
54
|
+
this.consumeKeyword();
|
|
55
|
+
}
|
|
56
|
+
const right = this.parseNot(depth);
|
|
57
|
+
node = this.track({ kind: "and", left: node, right });
|
|
58
|
+
}
|
|
59
|
+
return node;
|
|
60
|
+
}
|
|
61
|
+
parseNot(depth) {
|
|
62
|
+
this.guardDepth(depth);
|
|
63
|
+
this.skipWs();
|
|
64
|
+
if (this.matchKeyword("NOT")) {
|
|
65
|
+
this.consumeKeyword();
|
|
66
|
+
return this.track({ kind: "not", child: this.parseNot(depth + 1) });
|
|
67
|
+
}
|
|
68
|
+
if (this.peek() === "-") {
|
|
69
|
+
this.pos += 1;
|
|
70
|
+
return this.track({ kind: "not", child: this.parseNot(depth + 1) });
|
|
71
|
+
}
|
|
72
|
+
return this.parsePrimary(depth);
|
|
73
|
+
}
|
|
74
|
+
parsePrimary(depth) {
|
|
75
|
+
this.skipWs();
|
|
76
|
+
if (this.peek() === "(") {
|
|
77
|
+
this.pos += 1;
|
|
78
|
+
const node = this.parseOr(depth + 1);
|
|
79
|
+
this.skipWs();
|
|
80
|
+
if (this.peek() !== ")") {
|
|
81
|
+
throw new LuceneQueryError(
|
|
82
|
+
`Missing ")" at position ${this.pos}.`,
|
|
83
|
+
"syntax"
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
this.pos += 1;
|
|
87
|
+
return node;
|
|
88
|
+
}
|
|
89
|
+
return this.parseClause();
|
|
90
|
+
}
|
|
91
|
+
// --- clauses --------------------------------------------------------------
|
|
92
|
+
parseClause() {
|
|
93
|
+
this.skipWs();
|
|
94
|
+
if (this.atEnd() || this.peek() === ")") {
|
|
95
|
+
throw new LuceneQueryError(
|
|
96
|
+
`Expected a search term at position ${this.pos}.`,
|
|
97
|
+
"syntax"
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
if (this.peek() === '"') {
|
|
101
|
+
return this.track({
|
|
102
|
+
kind: "term",
|
|
103
|
+
operator: ":",
|
|
104
|
+
value: this.readQuoted()
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
const word = this.readFieldWord();
|
|
108
|
+
if (this.peek() === ":") {
|
|
109
|
+
this.pos += 1;
|
|
110
|
+
return this.parseFieldValue(word);
|
|
111
|
+
}
|
|
112
|
+
if (word === "") {
|
|
113
|
+
throw new LuceneQueryError(
|
|
114
|
+
`Expected a search term at position ${this.pos}.`,
|
|
115
|
+
"syntax"
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
return this.track({ kind: "term", operator: ":", value: word });
|
|
119
|
+
}
|
|
120
|
+
parseFieldValue(field) {
|
|
121
|
+
if (field === "") {
|
|
122
|
+
throw new LuceneQueryError(
|
|
123
|
+
`Missing field name before ":" at position ${this.pos}.`,
|
|
124
|
+
"syntax"
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
this.skipWs();
|
|
128
|
+
const opener = this.peek();
|
|
129
|
+
if (opener === "[" || opener === "{") {
|
|
130
|
+
return this.parseRange(field, opener);
|
|
131
|
+
}
|
|
132
|
+
const operator = this.readComparisonOperator();
|
|
133
|
+
this.skipWs();
|
|
134
|
+
const value = this.peek() === '"' ? this.readQuoted() : this.readValueWord();
|
|
135
|
+
if (value === "") {
|
|
136
|
+
throw new LuceneQueryError(
|
|
137
|
+
`Missing value for "${field}" at position ${this.pos}.`,
|
|
138
|
+
"syntax"
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return this.track({ kind: "term", field, operator, value });
|
|
142
|
+
}
|
|
143
|
+
parseRange(field, opener) {
|
|
144
|
+
this.pos += 1;
|
|
145
|
+
const lowerInclusive = opener === "[";
|
|
146
|
+
this.skipWs();
|
|
147
|
+
const lower = this.peek() === '"' ? this.readQuoted() : this.readRangeAtom();
|
|
148
|
+
this.skipWs();
|
|
149
|
+
if (!this.matchKeyword("TO")) {
|
|
150
|
+
throw new LuceneQueryError(
|
|
151
|
+
`Expected "TO" in range for "${field}" at position ${this.pos}.`,
|
|
152
|
+
"syntax"
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
this.consumeKeyword();
|
|
156
|
+
this.skipWs();
|
|
157
|
+
const upper = this.peek() === '"' ? this.readQuoted() : this.readRangeAtom();
|
|
158
|
+
this.skipWs();
|
|
159
|
+
const closer = this.peek();
|
|
160
|
+
if (closer !== "]" && closer !== "}") {
|
|
161
|
+
throw new LuceneQueryError(
|
|
162
|
+
`Missing "]" or "}" to close range for "${field}" at position ${this.pos}.`,
|
|
163
|
+
"syntax"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
this.pos += 1;
|
|
167
|
+
if (lower === "" || upper === "") {
|
|
168
|
+
throw new LuceneQueryError(
|
|
169
|
+
`Range for "${field}" needs both bounds.`,
|
|
170
|
+
"syntax"
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return this.track({
|
|
174
|
+
kind: "range",
|
|
175
|
+
field,
|
|
176
|
+
lower,
|
|
177
|
+
upper,
|
|
178
|
+
lowerInclusive,
|
|
179
|
+
upperInclusive: closer === "]"
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// --- token readers --------------------------------------------------------
|
|
183
|
+
/** A field name / bareword: stops at whitespace, `:`, or a boundary char. */
|
|
184
|
+
readFieldWord() {
|
|
185
|
+
const start = this.pos;
|
|
186
|
+
while (!this.atEnd()) {
|
|
187
|
+
const char = this.peek();
|
|
188
|
+
if (char === ":" || this.isWs(char) || WORD_BOUNDARY.has(char)) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
this.pos += 1;
|
|
192
|
+
}
|
|
193
|
+
return this.input.slice(start, this.pos);
|
|
194
|
+
}
|
|
195
|
+
/** A value word: keeps `:` (e.g. times) but stops at whitespace/parens. */
|
|
196
|
+
readValueWord() {
|
|
197
|
+
const start = this.pos;
|
|
198
|
+
while (!this.atEnd()) {
|
|
199
|
+
const char = this.peek();
|
|
200
|
+
if (this.isWs(char) || WORD_BOUNDARY.has(char)) {
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
203
|
+
this.pos += 1;
|
|
204
|
+
}
|
|
205
|
+
return this.input.slice(start, this.pos);
|
|
206
|
+
}
|
|
207
|
+
/** A range bound: like a value word but also stops at `]`/`}`. */
|
|
208
|
+
readRangeAtom() {
|
|
209
|
+
const start = this.pos;
|
|
210
|
+
while (!this.atEnd()) {
|
|
211
|
+
const char = this.peek();
|
|
212
|
+
if (this.isWs(char) || WORD_BOUNDARY.has(char) || char === "]" || char === "}") {
|
|
213
|
+
break;
|
|
214
|
+
}
|
|
215
|
+
this.pos += 1;
|
|
216
|
+
}
|
|
217
|
+
return this.input.slice(start, this.pos);
|
|
218
|
+
}
|
|
219
|
+
readQuoted() {
|
|
220
|
+
this.pos += 1;
|
|
221
|
+
let value = "";
|
|
222
|
+
while (!this.atEnd()) {
|
|
223
|
+
const char = this.peek();
|
|
224
|
+
if (char === "\\" && this.pos + 1 < this.input.length) {
|
|
225
|
+
value += this.input[this.pos + 1];
|
|
226
|
+
this.pos += 2;
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
if (char === '"') {
|
|
230
|
+
this.pos += 1;
|
|
231
|
+
return value;
|
|
232
|
+
}
|
|
233
|
+
value += char;
|
|
234
|
+
this.pos += 1;
|
|
235
|
+
}
|
|
236
|
+
throw new LuceneQueryError("Unterminated quoted string.", "syntax");
|
|
237
|
+
}
|
|
238
|
+
readComparisonOperator() {
|
|
239
|
+
const char = this.peek();
|
|
240
|
+
if (char === ">" || char === "<") {
|
|
241
|
+
this.pos += 1;
|
|
242
|
+
if (this.peek() === "=") {
|
|
243
|
+
this.pos += 1;
|
|
244
|
+
return char === ">" ? ">=" : "<=";
|
|
245
|
+
}
|
|
246
|
+
return char;
|
|
247
|
+
}
|
|
248
|
+
return ":";
|
|
249
|
+
}
|
|
250
|
+
// --- keyword helpers ------------------------------------------------------
|
|
251
|
+
/**
|
|
252
|
+
* Does the input at the cursor start with `keyword` as a standalone token?
|
|
253
|
+
* Case-insensitive; the char after must be whitespace, a paren, or the end so
|
|
254
|
+
* `ORganization` is not read as `OR`.
|
|
255
|
+
*/
|
|
256
|
+
matchKeyword(keyword) {
|
|
257
|
+
const end = this.pos + keyword.length;
|
|
258
|
+
if (end > this.input.length) {
|
|
259
|
+
return false;
|
|
260
|
+
}
|
|
261
|
+
const slice = this.input.slice(this.pos, end);
|
|
262
|
+
if (slice.toUpperCase() !== keyword.toUpperCase()) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
if (end === this.input.length) {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
const next = this.input[end];
|
|
269
|
+
return this.isWs(next) || next === "(" || next === ")";
|
|
270
|
+
}
|
|
271
|
+
/** Consume the standalone keyword the cursor is on (length via next boundary). */
|
|
272
|
+
consumeKeyword() {
|
|
273
|
+
while (!this.atEnd()) {
|
|
274
|
+
const char = this.peek();
|
|
275
|
+
if (this.isWs(char) || char === "(" || char === ")") {
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
this.pos += 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
// --- primitives -----------------------------------------------------------
|
|
282
|
+
track(node) {
|
|
283
|
+
this.nodes += 1;
|
|
284
|
+
if (this.nodes > MAX_NODES) {
|
|
285
|
+
throw new LuceneQueryError("Query is too complex.", "syntax");
|
|
286
|
+
}
|
|
287
|
+
return node;
|
|
288
|
+
}
|
|
289
|
+
guardDepth(depth) {
|
|
290
|
+
if (depth > MAX_DEPTH) {
|
|
291
|
+
throw new LuceneQueryError("Query is nested too deeply.", "syntax");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
skipWs() {
|
|
295
|
+
while (!this.atEnd() && this.isWs(this.peek())) {
|
|
296
|
+
this.pos += 1;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
isWs(char) {
|
|
300
|
+
return char === " " || char === " " || char === "\n" || char === "\r";
|
|
301
|
+
}
|
|
302
|
+
peek() {
|
|
303
|
+
return this.input[this.pos] ?? "";
|
|
304
|
+
}
|
|
305
|
+
atEnd() {
|
|
306
|
+
return this.pos >= this.input.length;
|
|
307
|
+
}
|
|
308
|
+
rest(max) {
|
|
309
|
+
return this.input.slice(this.pos, this.pos + max);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const parseLuceneQuery = (input) => {
|
|
313
|
+
if (input.length > MAX_INPUT_LENGTH) {
|
|
314
|
+
throw new LuceneQueryError(
|
|
315
|
+
`Query is too long (max ${MAX_INPUT_LENGTH} characters).`,
|
|
316
|
+
"syntax"
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return new Parser(input).parse();
|
|
320
|
+
};
|
|
321
|
+
export {
|
|
322
|
+
parseLuceneQuery
|
|
323
|
+
};
|
|
324
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/search/parser.ts"],
|
|
4
|
+
"sourcesContent": ["/*\n * Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH\n * under one or more contributor license agreements. Licensed under a commercial license.\n * You may not use this file except in compliance with the commercial license.\n */\n\nimport {\n LuceneQueryError,\n type QueryNode,\n type SearchTermOperator,\n} from './ast.js'\n\n// ============================================================================\n// Hand-rolled recursive-descent parser for the Lucene subset (see ast.ts).\n//\n// Grammar (operator precedence low \u2192 high):\n// orExpr := andExpr ( OR andExpr )*\n// andExpr := notExpr ( AND? notExpr )* // adjacency = implicit AND\n// notExpr := (NOT | '-') notExpr | primary\n// primary := '(' orExpr ')' | clause\n// clause := WORD ':' value | value // bare value = default-field term\n// value := range | comparison | atom\n// range := ('[' | '{') atom TO atom (']' | '}')\n// atom := '\"' \u2026 '\"' | word\n// ============================================================================\n\n/** Hard caps so a pathological query can't blow up the parser (spec: safety). */\nconst MAX_INPUT_LENGTH = 512\nconst MAX_NODES = 80\nconst MAX_DEPTH = 25\n\n/** Characters that terminate an unquoted word (besides whitespace). */\nconst WORD_BOUNDARY = new Set(['(', ')'])\n\nclass Parser {\n private readonly input: string\n private pos = 0\n private nodes = 0\n\n constructor(input: string) {\n this.input = input\n }\n\n parse(): QueryNode | undefined {\n this.skipWs()\n if (this.atEnd()) {\n return undefined\n }\n const node = this.parseOr(0)\n this.skipWs()\n if (!this.atEnd()) {\n throw new LuceneQueryError(\n `Unexpected \"${this.rest(12)}\" at position ${this.pos}.`,\n 'syntax',\n )\n }\n return node\n }\n\n // --- expression levels ----------------------------------------------------\n\n private parseOr(depth: number): QueryNode {\n this.guardDepth(depth)\n let node = this.parseAnd(depth)\n for (;;) {\n this.skipWs()\n if (!this.matchKeyword('OR') && !this.matchKeyword('||')) {\n break\n }\n this.consumeKeyword()\n const right = this.parseAnd(depth)\n node = this.track({ kind: 'or', left: node, right })\n }\n return node\n }\n\n private parseAnd(depth: number): QueryNode {\n this.guardDepth(depth)\n let node = this.parseNot(depth)\n for (;;) {\n this.skipWs()\n // Stop the implicit-AND run at the end, a closing paren, or an OR.\n if (\n this.atEnd() ||\n this.peek() === ')' ||\n this.matchKeyword('OR') ||\n this.matchKeyword('||')\n ) {\n break\n }\n // An explicit AND is optional \u2014 adjacency already means AND.\n if (this.matchKeyword('AND') || this.matchKeyword('&&')) {\n this.consumeKeyword()\n }\n const right = this.parseNot(depth)\n node = this.track({ kind: 'and', left: node, right })\n }\n return node\n }\n\n private parseNot(depth: number): QueryNode {\n this.guardDepth(depth)\n this.skipWs()\n if (this.matchKeyword('NOT')) {\n this.consumeKeyword()\n return this.track({ kind: 'not', child: this.parseNot(depth + 1) })\n }\n // A leading '-' at a term boundary is negation (mid-value dashes are kept\n // by the word reader, so `region:aws-1` is unaffected).\n if (this.peek() === '-') {\n this.pos += 1\n return this.track({ kind: 'not', child: this.parseNot(depth + 1) })\n }\n return this.parsePrimary(depth)\n }\n\n private parsePrimary(depth: number): QueryNode {\n this.skipWs()\n if (this.peek() === '(') {\n this.pos += 1\n const node = this.parseOr(depth + 1)\n this.skipWs()\n if (this.peek() !== ')') {\n throw new LuceneQueryError(\n `Missing \")\" at position ${this.pos}.`,\n 'syntax',\n )\n }\n this.pos += 1\n return node\n }\n return this.parseClause()\n }\n\n // --- clauses --------------------------------------------------------------\n\n private parseClause(): QueryNode {\n this.skipWs()\n if (this.atEnd() || this.peek() === ')') {\n throw new LuceneQueryError(\n `Expected a search term at position ${this.pos}.`,\n 'syntax',\n )\n }\n\n // A quoted value at clause start is always a bare term.\n if (this.peek() === '\"') {\n return this.track({\n kind: 'term',\n operator: ':',\n value: this.readQuoted(),\n })\n }\n\n const word = this.readFieldWord()\n if (this.peek() === ':') {\n this.pos += 1\n return this.parseFieldValue(word)\n }\n // No colon \u2192 the word is a bare (default-field) term.\n if (word === '') {\n throw new LuceneQueryError(\n `Expected a search term at position ${this.pos}.`,\n 'syntax',\n )\n }\n return this.track({ kind: 'term', operator: ':', value: word })\n }\n\n private parseFieldValue(field: string): QueryNode {\n if (field === '') {\n throw new LuceneQueryError(\n `Missing field name before \":\" at position ${this.pos}.`,\n 'syntax',\n )\n }\n // Be forgiving of a space after the colon (`name: acme`).\n this.skipWs()\n const opener = this.peek()\n if (opener === '[' || opener === '{') {\n return this.parseRange(field, opener)\n }\n const operator = this.readComparisonOperator()\n // \u2026and after a comparison operator (`created: >= 2024-01-01`).\n this.skipWs()\n const value = this.peek() === '\"' ? this.readQuoted() : this.readValueWord()\n if (value === '') {\n throw new LuceneQueryError(\n `Missing value for \"${field}\" at position ${this.pos}.`,\n 'syntax',\n )\n }\n return this.track({ kind: 'term', field, operator, value })\n }\n\n private parseRange(field: string, opener: '[' | '{'): QueryNode {\n this.pos += 1 // consume opener\n const lowerInclusive = opener === '['\n this.skipWs()\n const lower = this.peek() === '\"' ? this.readQuoted() : this.readRangeAtom()\n this.skipWs()\n if (!this.matchKeyword('TO')) {\n throw new LuceneQueryError(\n `Expected \"TO\" in range for \"${field}\" at position ${this.pos}.`,\n 'syntax',\n )\n }\n this.consumeKeyword()\n this.skipWs()\n const upper = this.peek() === '\"' ? this.readQuoted() : this.readRangeAtom()\n this.skipWs()\n const closer = this.peek()\n if (closer !== ']' && closer !== '}') {\n throw new LuceneQueryError(\n `Missing \"]\" or \"}\" to close range for \"${field}\" at position ${this.pos}.`,\n 'syntax',\n )\n }\n this.pos += 1\n if (lower === '' || upper === '') {\n throw new LuceneQueryError(\n `Range for \"${field}\" needs both bounds.`,\n 'syntax',\n )\n }\n return this.track({\n kind: 'range',\n field,\n lower,\n upper,\n lowerInclusive,\n upperInclusive: closer === ']',\n })\n }\n\n // --- token readers --------------------------------------------------------\n\n /** A field name / bareword: stops at whitespace, `:`, or a boundary char. */\n private readFieldWord(): string {\n const start = this.pos\n while (!this.atEnd()) {\n const char = this.peek()\n if (char === ':' || this.isWs(char) || WORD_BOUNDARY.has(char)) {\n break\n }\n this.pos += 1\n }\n return this.input.slice(start, this.pos)\n }\n\n /** A value word: keeps `:` (e.g. times) but stops at whitespace/parens. */\n private readValueWord(): string {\n const start = this.pos\n while (!this.atEnd()) {\n const char = this.peek()\n if (this.isWs(char) || WORD_BOUNDARY.has(char)) {\n break\n }\n this.pos += 1\n }\n return this.input.slice(start, this.pos)\n }\n\n /** A range bound: like a value word but also stops at `]`/`}`. */\n private readRangeAtom(): string {\n const start = this.pos\n while (!this.atEnd()) {\n const char = this.peek()\n if (\n this.isWs(char) ||\n WORD_BOUNDARY.has(char) ||\n char === ']' ||\n char === '}'\n ) {\n break\n }\n this.pos += 1\n }\n return this.input.slice(start, this.pos)\n }\n\n private readQuoted(): string {\n this.pos += 1 // opening quote\n let value = ''\n while (!this.atEnd()) {\n const char = this.peek()\n if (char === '\\\\' && this.pos + 1 < this.input.length) {\n value += this.input[this.pos + 1]\n this.pos += 2\n continue\n }\n if (char === '\"') {\n this.pos += 1\n return value\n }\n value += char\n this.pos += 1\n }\n throw new LuceneQueryError('Unterminated quoted string.', 'syntax')\n }\n\n private readComparisonOperator(): SearchTermOperator {\n const char = this.peek()\n if (char === '>' || char === '<') {\n this.pos += 1\n if (this.peek() === '=') {\n this.pos += 1\n return char === '>' ? '>=' : '<='\n }\n return char\n }\n return ':'\n }\n\n // --- keyword helpers ------------------------------------------------------\n\n /**\n * Does the input at the cursor start with `keyword` as a standalone token?\n * Case-insensitive; the char after must be whitespace, a paren, or the end so\n * `ORganization` is not read as `OR`.\n */\n private matchKeyword(keyword: string): boolean {\n const end = this.pos + keyword.length\n if (end > this.input.length) {\n return false\n }\n const slice = this.input.slice(this.pos, end)\n if (slice.toUpperCase() !== keyword.toUpperCase()) {\n return false\n }\n if (end === this.input.length) {\n return true\n }\n const next = this.input[end]\n return this.isWs(next) || next === '(' || next === ')'\n }\n\n /** Consume the standalone keyword the cursor is on (length via next boundary). */\n private consumeKeyword(): void {\n while (!this.atEnd()) {\n const char = this.peek()\n if (this.isWs(char) || char === '(' || char === ')') {\n break\n }\n this.pos += 1\n }\n }\n\n // --- primitives -----------------------------------------------------------\n\n private track<T extends QueryNode>(node: T): T {\n this.nodes += 1\n if (this.nodes > MAX_NODES) {\n throw new LuceneQueryError('Query is too complex.', 'syntax')\n }\n return node\n }\n\n private guardDepth(depth: number): void {\n if (depth > MAX_DEPTH) {\n throw new LuceneQueryError('Query is nested too deeply.', 'syntax')\n }\n }\n\n private skipWs(): void {\n while (!this.atEnd() && this.isWs(this.peek())) {\n this.pos += 1\n }\n }\n\n private isWs(char: string): boolean {\n return char === ' ' || char === '\\t' || char === '\\n' || char === '\\r'\n }\n\n private peek(): string {\n return this.input[this.pos] ?? ''\n }\n\n private atEnd(): boolean {\n return this.pos >= this.input.length\n }\n\n private rest(max: number): string {\n return this.input.slice(this.pos, this.pos + max)\n }\n}\n\n/**\n * Parse a Lucene-subset query into an AST. Returns `undefined` for an empty or\n * whitespace-only query (a no-op filter). Throws {@link LuceneQueryError} on any\n * syntax problem.\n */\nexport const parseLuceneQuery = (input: string): QueryNode | undefined => {\n if (input.length > MAX_INPUT_LENGTH) {\n throw new LuceneQueryError(\n `Query is too long (max ${MAX_INPUT_LENGTH} characters).`,\n 'syntax',\n )\n }\n return new Parser(input).parse()\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Field specification for a cluster plan config: one entry per configurable
|
|
3
|
+
* leaf, mirroring the shape of the `plan` object stored on a cluster plan.
|
|
4
|
+
*
|
|
5
|
+
* It drives the per-field create/edit forms in the admin frontends (accounts
|
|
6
|
+
* `Create Cluster Plan`, control `Create cluster plan`) so a plan can be filled
|
|
7
|
+
* in field by field instead of hand-writing JSON — the descriptions, input
|
|
8
|
+
* types and validators live here rather than in either app.
|
|
9
|
+
*/
|
|
10
|
+
export interface ClusterPlanFieldSpec {
|
|
11
|
+
type: 'text' | 'number' | 'textarea' | 'checkbox';
|
|
12
|
+
description: string;
|
|
13
|
+
placeholder?: string;
|
|
14
|
+
validator: (value: any) => boolean;
|
|
15
|
+
/** Only settable on a cluster (modification), not on a cluster plan. */
|
|
16
|
+
notOnClusterPlan?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/** A nested group of field specs, keyed like the config object itself. */
|
|
19
|
+
export interface ClusterPlanFieldTree {
|
|
20
|
+
[key: string]: ClusterPlanFieldSpec | ClusterPlanFieldTree;
|
|
21
|
+
}
|
|
22
|
+
/** Narrow a node of the tree to a leaf field spec. */
|
|
23
|
+
export declare const isClusterPlanFieldSpec: (node: ClusterPlanFieldSpec | ClusterPlanFieldTree) => node is ClusterPlanFieldSpec;
|
|
24
|
+
export declare const CLUSTER_PLAN_FIELDS: ClusterPlanFieldTree;
|