@camunda/ccma-shared-types 5.0.0 → 6.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/dist/_tests/approvals/dns-subdomain.spec.d.ts +1 -0
- package/dist/_tests/approvals/expected-value.spec.d.ts +1 -0
- package/dist/_tests/charts/chart-query.spec.d.ts +1 -0
- package/dist/_tests/dashboards/definition.spec.d.ts +1 -0
- package/dist/_tests/utils/elasticsearchPlanPath.utils.spec.d.ts +1 -0
- package/dist/approvals/dns-subdomain.d.ts +12 -0
- package/dist/approvals/dns-subdomain.js +16 -0
- package/dist/approvals/dns-subdomain.js.map +7 -0
- package/dist/approvals/expected-value.d.ts +33 -0
- package/dist/approvals/expected-value.js +26 -0
- package/dist/approvals/expected-value.js.map +7 -0
- package/dist/approvals/use-cases.const.d.ts +80 -0
- package/dist/approvals/use-cases.const.js +42 -2
- package/dist/approvals/use-cases.const.js.map +2 -2
- package/dist/charts/chart-metrics.d.ts +35 -0
- package/dist/charts/chart-metrics.js +188 -0
- package/dist/charts/chart-metrics.js.map +7 -0
- package/dist/charts/chart-query.d.ts +55 -0
- package/dist/charts/chart-query.js +376 -0
- package/dist/charts/chart-query.js.map +7 -0
- package/dist/charts/chart-spec.d.ts +103 -0
- package/dist/charts/chart-spec.js +79 -0
- package/dist/charts/chart-spec.js.map +7 -0
- package/dist/charts/index.d.ts +3 -0
- package/dist/charts/index.js +59 -0
- package/dist/charts/index.js.map +7 -0
- package/dist/dashboards/definition.d.ts +86 -0
- package/dist/dashboards/definition.js +137 -0
- package/dist/dashboards/definition.js.map +7 -0
- package/dist/dtos/Annotation.dto.d.ts +1 -1
- package/dist/dtos/Annotation.dto.js +10 -2
- package/dist/dtos/Annotation.dto.js.map +2 -2
- package/dist/dtos/ControlV2.dto.d.ts +17 -0
- package/dist/dtos/Dashboard.dto.d.ts +336 -0
- package/dist/dtos/Dashboard.dto.js +70 -0
- package/dist/dtos/Dashboard.dto.js.map +7 -0
- package/dist/dtos/FeatureFlags.dto.d.ts +88 -54
- package/dist/dtos/Hdp.dto.d.ts +5 -88
- package/dist/dtos/Organization.dto.d.ts +10 -0
- package/dist/dtos/Organization.dto.js.map +1 -1
- package/dist/enums/SystemAuditEntity.enum.d.ts +4 -2
- package/dist/enums/SystemAuditEntity.enum.js +2 -0
- package/dist/enums/SystemAuditEntity.enum.js.map +2 -2
- package/dist/index.d.ts +12 -3
- package/dist/index.js +150 -1
- package/dist/index.js.map +2 -2
- package/dist/permissions/accounts.permissions.d.ts +12 -0
- package/dist/permissions/accounts.permissions.js +11 -0
- package/dist/permissions/accounts.permissions.js.map +2 -2
- package/dist/permissions/combo.js +5 -0
- package/dist/permissions/combo.js.map +2 -2
- package/dist/search/compiler.d.ts +20 -0
- package/dist/search/compiler.js +48 -11
- package/dist/search/compiler.js.map +2 -2
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js.map +2 -2
- package/dist/search/parser.d.ts +3 -0
- package/dist/search/parser.js +3 -1
- package/dist/search/parser.js.map +2 -2
- package/dist/types/ClusterPlanFields.type.js +74 -74
- package/dist/types/ClusterPlanFields.type.js.map +2 -2
- package/dist/utils/elasticsearchPlanPath.utils.d.ts +18 -0
- package/dist/utils/elasticsearchPlanPath.utils.js +23 -0
- package/dist/utils/elasticsearchPlanPath.utils.js.map +7 -0
- package/dist/utils/qa-user.utils.d.ts +14 -0
- package/dist/utils/qa-user.utils.js +14 -0
- package/dist/utils/qa-user.utils.js.map +7 -0
- package/dist/utils/qa-user.utils.spec.d.ts +1 -0
- package/package.json +1 -1
package/dist/search/compiler.js
CHANGED
|
@@ -4,6 +4,12 @@ import {
|
|
|
4
4
|
class Compiler {
|
|
5
5
|
constructor(ctx) {
|
|
6
6
|
this.ctx = ctx;
|
|
7
|
+
this.dialect = ctx.dialect ?? "postgres";
|
|
8
|
+
if (this.dialect === "bigquery" && !BIGQUERY_PARAMETER_PREFIX.test(ctx.paramPrefix)) {
|
|
9
|
+
throw new Error(
|
|
10
|
+
`Search misconfigured: "${ctx.paramPrefix}" is not a valid BigQuery parameter prefix.`
|
|
11
|
+
);
|
|
12
|
+
}
|
|
7
13
|
this.lookup = new Map(
|
|
8
14
|
Object.entries(ctx.bindings).map(([field, binding]) => [
|
|
9
15
|
field.toLowerCase(),
|
|
@@ -16,6 +22,7 @@ class Compiler {
|
|
|
16
22
|
counter = 0;
|
|
17
23
|
/** Lowercased field name → { canonical, binding }, for case-insensitive lookup. */
|
|
18
24
|
lookup;
|
|
25
|
+
dialect;
|
|
19
26
|
compile(node) {
|
|
20
27
|
return { sql: this.node(node), params: this.params };
|
|
21
28
|
}
|
|
@@ -26,7 +33,7 @@ class Compiler {
|
|
|
26
33
|
case "or":
|
|
27
34
|
return `(${this.node(node.left)} OR ${this.node(node.right)})`;
|
|
28
35
|
case "not":
|
|
29
|
-
return `(NOT ${this.node(node.child)})`;
|
|
36
|
+
return this.dialect === "bigquery" ? `(NOT IFNULL(${this.node(node.child)}, FALSE))` : `(NOT ${this.node(node.child)})`;
|
|
30
37
|
case "term":
|
|
31
38
|
return this.term(node);
|
|
32
39
|
case "range":
|
|
@@ -65,8 +72,10 @@ class Compiler {
|
|
|
65
72
|
}
|
|
66
73
|
const lowerOp = node.lowerInclusive ? ">=" : ">";
|
|
67
74
|
const upperOp = node.upperInclusive ? "<=" : "<";
|
|
68
|
-
const
|
|
69
|
-
const
|
|
75
|
+
const lowerDate = this.parseDate(canonical, node.lower);
|
|
76
|
+
const upperDate = this.parseDate(canonical, node.upper);
|
|
77
|
+
const lower = this.bindDate(binding, lowerDate, lowerDate);
|
|
78
|
+
const upper = this.bindDate(binding, upperDate, upperDate);
|
|
70
79
|
return `(${binding.column} ${lowerOp} ${lower} AND ${binding.column} ${upperOp} ${upper})`;
|
|
71
80
|
}
|
|
72
81
|
// --- field kinds ----------------------------------------------------------
|
|
@@ -79,20 +88,37 @@ class Compiler {
|
|
|
79
88
|
);
|
|
80
89
|
}
|
|
81
90
|
const { column, cast } = entry.binding;
|
|
82
|
-
|
|
83
|
-
|
|
91
|
+
return this.like(
|
|
92
|
+
this.textTarget(column, cast),
|
|
93
|
+
likeArgument(value, "substring")
|
|
94
|
+
);
|
|
84
95
|
});
|
|
85
96
|
return clauses.length === 1 ? clauses[0] : `(${clauses.join(" OR ")})`;
|
|
86
97
|
}
|
|
87
98
|
stringMatch(binding, value) {
|
|
88
|
-
|
|
89
|
-
|
|
99
|
+
return this.like(
|
|
100
|
+
this.textTarget(binding.column, binding.cast),
|
|
101
|
+
likeArgument(value, binding.match)
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
textTarget(column, cast) {
|
|
105
|
+
if (!cast) {
|
|
106
|
+
return column;
|
|
107
|
+
}
|
|
108
|
+
return this.dialect === "bigquery" ? `CAST(${column} AS STRING)` : `CAST(${column} AS TEXT)`;
|
|
109
|
+
}
|
|
110
|
+
/** Case-insensitive LIKE. BigQuery has no ILIKE. */
|
|
111
|
+
like(target, argument) {
|
|
112
|
+
return this.dialect === "bigquery" ? `LOWER(${target}) LIKE LOWER(${this.bind(argument)})` : `${target} ILIKE ${this.bind(argument)}`;
|
|
90
113
|
}
|
|
91
114
|
computedMatch(binding, value) {
|
|
92
115
|
const ids = binding.resolve(value);
|
|
93
116
|
if (ids.length === 0) {
|
|
94
117
|
return "1 = 0";
|
|
95
118
|
}
|
|
119
|
+
if (this.dialect === "bigquery") {
|
|
120
|
+
return `${binding.idColumn} IN UNNEST(${this.bind([...ids])})`;
|
|
121
|
+
}
|
|
96
122
|
return idSetPredicate(binding.idColumn, this.bindName(joinIdSet(ids)));
|
|
97
123
|
}
|
|
98
124
|
dateTerm(binding, canonical, operator, value) {
|
|
@@ -106,10 +132,18 @@ class Compiler {
|
|
|
106
132
|
)
|
|
107
133
|
);
|
|
108
134
|
const nextDay = new Date(startDay.getTime() + 24 * 60 * 60 * 1e3);
|
|
109
|
-
return `(${binding.column} >= ${this.
|
|
135
|
+
return `(${binding.column} >= ${this.bindDate(binding, startDay, startDay.toISOString())} AND ${binding.column} < ${this.bindDate(binding, nextDay, nextDay.toISOString())})`;
|
|
136
|
+
}
|
|
137
|
+
const bound = this.parseDate(canonical, value);
|
|
138
|
+
return `${binding.column} ${operator} ${this.bindDate(binding, bound, bound.toISOString())}`;
|
|
139
|
+
}
|
|
140
|
+
/** `postgresValue` is the parameter that the `postgres` dialect binds. */
|
|
141
|
+
bindDate(binding, value, postgresValue) {
|
|
142
|
+
if (this.dialect !== "bigquery") {
|
|
143
|
+
return this.bind(postgresValue);
|
|
110
144
|
}
|
|
111
|
-
const
|
|
112
|
-
return
|
|
145
|
+
const iso = value.toISOString();
|
|
146
|
+
return binding.columnType === "date" ? `CAST(${this.bind(iso.slice(0, 10))} AS DATE)` : `CAST(${this.bind(iso)} AS TIMESTAMP)`;
|
|
113
147
|
}
|
|
114
148
|
// --- validation helpers ---------------------------------------------------
|
|
115
149
|
resolveField(field) {
|
|
@@ -165,9 +199,11 @@ class Compiler {
|
|
|
165
199
|
return name;
|
|
166
200
|
}
|
|
167
201
|
bind(value) {
|
|
168
|
-
|
|
202
|
+
const marker = this.dialect === "bigquery" ? "@" : ":";
|
|
203
|
+
return `${marker}${this.bindName(value)}`;
|
|
169
204
|
}
|
|
170
205
|
}
|
|
206
|
+
const BIGQUERY_PARAMETER_PREFIX = /^[A-Za-z_]\w*$/;
|
|
171
207
|
const compileQuery = (node, ctx) => new Compiler(ctx).compile(node);
|
|
172
208
|
const ID_SET_SEPARATOR = ",";
|
|
173
209
|
const idSetPredicate = (column, parameter) => `${column} = ANY(string_to_array(:${parameter}, '${ID_SET_SEPARATOR}'))`;
|
|
@@ -218,6 +254,7 @@ const editDistance = (left, right) => {
|
|
|
218
254
|
return dist[rows - 1][cols - 1];
|
|
219
255
|
};
|
|
220
256
|
export {
|
|
257
|
+
closestField,
|
|
221
258
|
compileQuery,
|
|
222
259
|
idSetPredicate,
|
|
223
260
|
joinIdSet,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/search/compiler.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 RangeNode,\n type SearchTermOperator,\n type TermNode,\n} from './ast.js'\n\n// ============================================================================\n// AST \u2192 parameterized SQL compiler.\n//\n// Entity-agnostic: the backend supplies a per-field {@link FieldBinding} map (the\n// SQL columns / computed resolvers stay backend-side), and this turns a parsed\n// query into a single parameterized SQL string for `qb.andWhere(sql, params)`.\n// Field names are whitelisted against the bindings; every value is a bound\n// parameter; wildcards are escaped \u2014 so a query can never inject SQL.\n// ============================================================================\n\n/** SQL binding for one searchable field (values live backend-side, not in the FE). */\nexport type FieldBinding =\n | {\n kind: 'string'\n /** SQL column expression, e.g. `c.name` or `planType.name`. */\n column: string\n /** Cast the column to text before matching (uuid columns). */\n cast?: boolean\n /** `substring` \u2192 ILIKE %v%; `exact` \u2192 case-insensitive equality. */\n match: 'substring' | 'exact'\n }\n | { kind: 'boolean'; column: string }\n /** Presence/absence of a nullable column (e.g. active-modification FK). */\n | { kind: 'nullable'; column: string }\n | { kind: 'date'; column: string }\n | {\n kind: 'computed'\n /** Column the resolved id set is matched against (e.g. `c.uuid`). */\n idColumn: string\n /** Resolve an equality value to matching ids (in-memory / config). */\n resolve: (value: string) => string[]\n }\n\nexport interface CompileContext {\n /** Field name \u2192 SQL binding. Keys are the whitelisted, canonical field names. */\n bindings: Record<string, FieldBinding>\n /** Fields a bare term ORs over (must be `string` bindings); always substring. */\n defaultFields: readonly string[]\n /** Unique-per-call-site parameter prefix (avoids clashing with other params). */\n paramPrefix: string\n}\n\nexport interface CompiledQuery {\n sql: string\n params: Record<string, unknown>\n}\n\nclass Compiler {\n private readonly params: Record<string, unknown> = {}\n private counter = 0\n /** Lowercased field name \u2192 { canonical, binding }, for case-insensitive lookup. */\n private readonly lookup: Map<\n string,\n { canonical: string; binding: FieldBinding }\n >\n\n constructor(private readonly ctx: CompileContext) {\n this.lookup = new Map(\n Object.entries(ctx.bindings).map(([field, binding]) => [\n field.toLowerCase(),\n { canonical: field, binding },\n ]),\n )\n }\n\n compile(node: QueryNode): CompiledQuery {\n return { sql: this.node(node), params: this.params }\n }\n\n private node(node: QueryNode): string {\n switch (node.kind) {\n case 'and':\n return `(${this.node(node.left)} AND ${this.node(node.right)})`\n case 'or':\n return `(${this.node(node.left)} OR ${this.node(node.right)})`\n case 'not':\n return `(NOT ${this.node(node.child)})`\n case 'term':\n return this.term(node)\n case 'range':\n return this.range(node)\n }\n }\n\n private term(node: TermNode): string {\n if (node.field === undefined) {\n return this.bareTerm(node.value)\n }\n const { canonical, binding } = this.resolveField(node.field)\n switch (binding.kind) {\n case 'string':\n this.requireMatchOperator(canonical, node.operator)\n return this.stringMatch(binding, node.value)\n case 'boolean':\n this.requireMatchOperator(canonical, node.operator)\n return `${binding.column} = ${this.bind(this.parseBoolean(canonical, node.value))}`\n case 'nullable':\n this.requireMatchOperator(canonical, node.operator)\n return this.parseBoolean(canonical, node.value)\n ? `${binding.column} IS NOT NULL`\n : `${binding.column} IS NULL`\n case 'computed':\n this.requireMatchOperator(canonical, node.operator)\n return this.computedMatch(binding, node.value)\n case 'date':\n return this.dateTerm(binding, canonical, node.operator, node.value)\n }\n }\n\n private range(node: RangeNode): string {\n const { canonical, binding } = this.resolveField(node.field)\n if (binding.kind !== 'date') {\n throw new LuceneQueryError(\n `Field \"${canonical}\" does not support ranges.`,\n 'bad-operator',\n )\n }\n const lowerOp = node.lowerInclusive ? '>=' : '>'\n const upperOp = node.upperInclusive ? '<=' : '<'\n const lower = this.bind(this.parseDate(canonical, node.lower))\n const upper = this.bind(this.parseDate(canonical, node.upper))\n return `(${binding.column} ${lowerOp} ${lower} AND ${binding.column} ${upperOp} ${upper})`\n }\n\n // --- field kinds ----------------------------------------------------------\n\n private bareTerm(value: string): string {\n const clauses = this.ctx.defaultFields.map((field) => {\n const entry = this.lookup.get(field.toLowerCase())\n if (!entry || entry.binding.kind !== 'string') {\n // A server-side registry/binding bug, not a bad query \u2014 throw a plain\n // Error so the routers surface it as a 5xx, never a 400.\n throw new Error(\n `Search misconfigured: default field \"${field}\" must be a string binding.`,\n )\n }\n const { column, cast } = entry.binding\n const target = cast ? `CAST(${column} AS TEXT)` : column\n return `${target} ILIKE ${this.bind(likeArgument(value, 'substring'))}`\n })\n return clauses.length === 1 ? clauses[0] : `(${clauses.join(' OR ')})`\n }\n\n private stringMatch(\n binding: Extract<FieldBinding, { kind: 'string' }>,\n value: string,\n ): string {\n const target = binding.cast\n ? `CAST(${binding.column} AS TEXT)`\n : binding.column\n return `${target} ILIKE ${this.bind(likeArgument(value, binding.match))}`\n }\n\n private computedMatch(\n binding: Extract<FieldBinding, { kind: 'computed' }>,\n value: string,\n ): string {\n const ids = binding.resolve(value)\n if (ids.length === 0) {\n // No entity matches this value \u2014 a guaranteed-false predicate (a\n // surrounding NOT correctly flips it to all-rows).\n return '1 = 0'\n }\n // One parameter, not one for each id. `status:healthy` resolves to the\n // whole healthy fleet, thus the set follows the size of the fleet.\n return idSetPredicate(binding.idColumn, this.bindName(joinIdSet(ids)))\n }\n\n private dateTerm(\n binding: Extract<FieldBinding, { kind: 'date' }>,\n canonical: string,\n operator: SearchTermOperator,\n value: string,\n ): string {\n if (operator === ':') {\n // Equality on a date means \"that calendar day\" (UTC): [day, nextDay).\n const start = this.parseDate(canonical, value)\n const startDay = new Date(\n Date.UTC(\n start.getUTCFullYear(),\n start.getUTCMonth(),\n start.getUTCDate(),\n ),\n )\n const nextDay = new Date(startDay.getTime() + 24 * 60 * 60 * 1000)\n return `(${binding.column} >= ${this.bind(startDay.toISOString())} AND ${binding.column} < ${this.bind(nextDay.toISOString())})`\n }\n const bound = this.parseDate(canonical, value).toISOString()\n return `${binding.column} ${operator} ${this.bind(bound)}`\n }\n\n // --- validation helpers ---------------------------------------------------\n\n private resolveField(field: string): {\n canonical: string\n binding: FieldBinding\n } {\n const entry = this.lookup.get(field.toLowerCase())\n if (!entry) {\n const suggestion = closestField(\n field,\n [...this.lookup.values()].map((value) => value.canonical),\n )\n throw new LuceneQueryError(\n `Unknown field \"${field}\".${suggestion ? ` Did you mean \"${suggestion}\"?` : ''}`,\n 'unknown-field',\n suggestion,\n )\n }\n return entry\n }\n\n private requireMatchOperator(\n field: string,\n operator: SearchTermOperator,\n ): void {\n if (operator !== ':') {\n throw new LuceneQueryError(\n `Field \"${field}\" supports only \"field:value\", not \"${operator}\".`,\n 'bad-operator',\n )\n }\n }\n\n private parseBoolean(field: string, value: string): boolean {\n const normalized = value.trim().toLowerCase()\n if (normalized === 'true') {\n return true\n }\n if (normalized === 'false') {\n return false\n }\n throw new LuceneQueryError(\n `Field \"${field}\" expects true or false, got \"${value}\".`,\n 'bad-value',\n )\n }\n\n private parseDate(field: string, value: string): Date {\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) {\n throw new LuceneQueryError(\n `Field \"${field}\" expects a date (e.g. 2024-01-31), got \"${value}\".`,\n 'bad-value',\n )\n }\n return parsed\n }\n\n /** Register a value and give back its parameter name, with no marker. */\n private bindName(value: unknown): string {\n const name = `${this.ctx.paramPrefix}_${this.counter++}`\n this.params[name] = value\n return name\n }\n\n private bind(value: unknown): string {\n return `:${this.bindName(value)}`\n }\n}\n\n/** Compile a parsed query into a single parameterized SQL fragment. */\nexport const compileQuery = (\n node: QueryNode,\n ctx: CompileContext,\n): CompiledQuery => new Compiler(ctx).compile(node)\n\nconst ID_SET_SEPARATOR = ','\n\n/**\n * Match `column` against an id set that arrives as ONE parameter.\n *\n * `IN (:...ids)` expands to one bind parameter, and one literal in the\n * statement, for each id. A dimension that does not live in SQL resolves to an\n * id set, and such a set follows the size of the fleet: the prod cluster fleet\n * is more than 1000. Both the statement text and the parameter count would\n * grow with it, and Postgres would plan the statement again at every size.\n *\n * Postgres splits the string here, thus the statement text is constant.\n * `= ANY(...)` can still use an index on the column.\n */\nexport const idSetPredicate = (column: string, parameter: string): string =>\n `${column} = ANY(string_to_array(:${parameter}, '${ID_SET_SEPARATOR}'))`\n\n/**\n * The id set as one separated string, for {@link idSetPredicate}.\n *\n * The value is a bind parameter, thus it cannot change the statement. But an\n * id that holds the separator would become two ids, thus such an id is\n * dropped. The id columns are text columns, so this stays a guard and not an\n * assumption.\n *\n * An empty string gives an empty array in Postgres. Thus the match finds\n * nothing, which is correct for an empty set.\n */\nexport const joinIdSet = (ids: readonly string[]): string =>\n ids.filter((id) => !id.includes(ID_SET_SEPARATOR)).join(ID_SET_SEPARATOR)\n\n/**\n * Turn a user value into an ILIKE argument: SQL wildcards (`%`, `_`, `\\`) are\n * escaped to literals, then the user's `*`/`?` become `%`/`_`; a plain\n * substring value is wrapped in `%\u2026%`.\n */\nexport const likeArgument = (\n value: string,\n match: 'substring' | 'exact',\n): string => {\n const escaped = value.replace(/([\\\\%_])/g, '\\\\$1')\n if (/[*?]/.test(value)) {\n return escaped.replace(/\\*/g, '%').replace(/\\?/g, '_')\n }\n return match === 'substring' ? `%${escaped}%` : escaped\n}\n\n/** Closest field name within edit distance 3, for \"did you mean\u2026\" hints. */\nconst closestField = (\n input: string,\n candidates: readonly string[],\n): string | undefined => {\n const needle = input.toLowerCase()\n let best: string | undefined\n let bestDistance = Number.POSITIVE_INFINITY\n for (const candidate of candidates) {\n const distance = editDistance(needle, candidate.toLowerCase())\n if (distance < bestDistance) {\n bestDistance = distance\n best = candidate\n }\n }\n return bestDistance <= 3 ? best : undefined\n}\n\n/** Standard Levenshtein distance. */\nconst editDistance = (left: string, right: string): number => {\n const rows = left.length + 1\n const cols = right.length + 1\n const dist = Array.from({ length: rows }, () =>\n new Array<number>(cols).fill(0),\n )\n for (let row = 0; row < rows; row += 1) {\n dist[row][0] = row\n }\n for (let col = 0; col < cols; col += 1) {\n dist[0][col] = col\n }\n for (let row = 1; row < rows; row += 1) {\n for (let col = 1; col < cols; col += 1) {\n const cost = left[row - 1] === right[col - 1] ? 0 : 1\n dist[row][col] = Math.min(\n dist[row - 1][col] + 1,\n dist[row][col - 1] + 1,\n dist[row - 1][col - 1] + cost,\n )\n }\n }\n return dist[rows - 1][cols - 1]\n}\n"],
|
|
5
|
-
"mappings": "AAMA;AAAA,EACE;AAAA,OAKK;
|
|
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 RangeNode,\n type SearchTermOperator,\n type TermNode,\n} from './ast.js'\n\n// ============================================================================\n// AST \u2192 parameterized SQL compiler.\n//\n// Entity-agnostic: the backend supplies a per-field {@link FieldBinding} map (the\n// SQL columns / computed resolvers stay backend-side), and this turns a parsed\n// query into a single parameterized SQL string for `qb.andWhere(sql, params)`.\n// Field names are whitelisted against the bindings; every value is a bound\n// parameter; wildcards are escaped \u2014 so a query can never inject SQL.\n//\n// The `bigquery` dialect writes the same predicates for BigQuery (HDP), with\n// `@name` parameters for `createQueryJob({ params })`.\n// ============================================================================\n\n/**\n * The SQL that the compiler writes.\n *\n * - `postgres`: `ILIKE`, `:name` parameters (TypeORM), and an id set as one\n * separated string.\n * - `bigquery`: `LOWER(x) LIKE LOWER(@name)`, `@name` parameters, and an id set\n * as one array parameter.\n */\nexport type SqlDialect = 'postgres' | 'bigquery'\n\n/** SQL binding for one searchable field (values live backend-side, not in the FE). */\nexport type FieldBinding =\n | {\n kind: 'string'\n /** SQL column expression, e.g. `c.name` or `planType.name`. */\n column: string\n /** Cast the column to text before matching (uuid columns). */\n cast?: boolean\n /** `substring` \u2192 ILIKE %v%; `exact` \u2192 case-insensitive equality. */\n match: 'substring' | 'exact'\n }\n | { kind: 'boolean'; column: string }\n /** Presence/absence of a nullable column (e.g. active-modification FK). */\n | { kind: 'nullable'; column: string }\n | {\n kind: 'date'\n column: string\n /**\n * The type of the column, for the `bigquery` dialect only. BigQuery does\n * not compare a DATE with a TIMESTAMP, so the compiler casts the\n * parameter to this type. A `date` column compares by UTC day. The\n * default is `timestamp`. Postgres does not read this field.\n */\n columnType?: 'timestamp' | 'date'\n }\n | {\n kind: 'computed'\n /** Column the resolved id set is matched against (e.g. `c.uuid`). */\n idColumn: string\n /** Resolve an equality value to matching ids (in-memory / config). */\n resolve: (value: string) => string[]\n }\n\nexport interface CompileContext {\n /** Field name \u2192 SQL binding. Keys are the whitelisted, canonical field names. */\n bindings: Record<string, FieldBinding>\n /** Fields a bare term ORs over (must be `string` bindings); always substring. */\n defaultFields: readonly string[]\n /** Unique-per-call-site parameter prefix (avoids clashing with other params). */\n paramPrefix: string\n /** The default is `postgres`. */\n dialect?: SqlDialect\n}\n\nexport interface CompiledQuery {\n sql: string\n params: Record<string, unknown>\n}\n\nclass Compiler {\n private readonly params: Record<string, unknown> = {}\n private counter = 0\n /** Lowercased field name \u2192 { canonical, binding }, for case-insensitive lookup. */\n private readonly lookup: Map<\n string,\n { canonical: string; binding: FieldBinding }\n >\n\n private readonly dialect: SqlDialect\n\n constructor(private readonly ctx: CompileContext) {\n this.dialect = ctx.dialect ?? 'postgres'\n if (\n this.dialect === 'bigquery' &&\n !BIGQUERY_PARAMETER_PREFIX.test(ctx.paramPrefix)\n ) {\n // A server-side bug, not a bad query: BigQuery refuses the name.\n throw new Error(\n `Search misconfigured: \"${ctx.paramPrefix}\" is not a valid BigQuery parameter prefix.`,\n )\n }\n this.lookup = new Map(\n Object.entries(ctx.bindings).map(([field, binding]) => [\n field.toLowerCase(),\n { canonical: field, binding },\n ]),\n )\n }\n\n compile(node: QueryNode): CompiledQuery {\n return { sql: this.node(node), params: this.params }\n }\n\n private node(node: QueryNode): string {\n switch (node.kind) {\n case 'and':\n return `(${this.node(node.left)} AND ${this.node(node.right)})`\n case 'or':\n return `(${this.node(node.left)} OR ${this.node(node.right)})`\n case 'not':\n // In BigQuery, a predicate on a NULL column is NULL, and `NOT NULL`\n // drops the row. `NOT env:production` must keep a row with no env.\n return this.dialect === 'bigquery'\n ? `(NOT IFNULL(${this.node(node.child)}, FALSE))`\n : `(NOT ${this.node(node.child)})`\n case 'term':\n return this.term(node)\n case 'range':\n return this.range(node)\n }\n }\n\n private term(node: TermNode): string {\n if (node.field === undefined) {\n return this.bareTerm(node.value)\n }\n const { canonical, binding } = this.resolveField(node.field)\n switch (binding.kind) {\n case 'string':\n this.requireMatchOperator(canonical, node.operator)\n return this.stringMatch(binding, node.value)\n case 'boolean':\n this.requireMatchOperator(canonical, node.operator)\n return `${binding.column} = ${this.bind(this.parseBoolean(canonical, node.value))}`\n case 'nullable':\n this.requireMatchOperator(canonical, node.operator)\n return this.parseBoolean(canonical, node.value)\n ? `${binding.column} IS NOT NULL`\n : `${binding.column} IS NULL`\n case 'computed':\n this.requireMatchOperator(canonical, node.operator)\n return this.computedMatch(binding, node.value)\n case 'date':\n return this.dateTerm(binding, canonical, node.operator, node.value)\n }\n }\n\n private range(node: RangeNode): string {\n const { canonical, binding } = this.resolveField(node.field)\n if (binding.kind !== 'date') {\n throw new LuceneQueryError(\n `Field \"${canonical}\" does not support ranges.`,\n 'bad-operator',\n )\n }\n const lowerOp = node.lowerInclusive ? '>=' : '>'\n const upperOp = node.upperInclusive ? '<=' : '<'\n const lowerDate = this.parseDate(canonical, node.lower)\n const upperDate = this.parseDate(canonical, node.upper)\n // Postgres gets the Date object here, and an ISO string on a term.\n const lower = this.bindDate(binding, lowerDate, lowerDate)\n const upper = this.bindDate(binding, upperDate, upperDate)\n return `(${binding.column} ${lowerOp} ${lower} AND ${binding.column} ${upperOp} ${upper})`\n }\n\n // --- field kinds ----------------------------------------------------------\n\n private bareTerm(value: string): string {\n const clauses = this.ctx.defaultFields.map((field) => {\n const entry = this.lookup.get(field.toLowerCase())\n if (!entry || entry.binding.kind !== 'string') {\n // A server-side registry/binding bug, not a bad query \u2014 throw a plain\n // Error so the routers surface it as a 5xx, never a 400.\n throw new Error(\n `Search misconfigured: default field \"${field}\" must be a string binding.`,\n )\n }\n const { column, cast } = entry.binding\n return this.like(\n this.textTarget(column, cast),\n likeArgument(value, 'substring'),\n )\n })\n return clauses.length === 1 ? clauses[0] : `(${clauses.join(' OR ')})`\n }\n\n private stringMatch(\n binding: Extract<FieldBinding, { kind: 'string' }>,\n value: string,\n ): string {\n return this.like(\n this.textTarget(binding.column, binding.cast),\n likeArgument(value, binding.match),\n )\n }\n\n private textTarget(column: string, cast: boolean | undefined): string {\n if (!cast) {\n return column\n }\n return this.dialect === 'bigquery'\n ? `CAST(${column} AS STRING)`\n : `CAST(${column} AS TEXT)`\n }\n\n /** Case-insensitive LIKE. BigQuery has no ILIKE. */\n private like(target: string, argument: string): string {\n return this.dialect === 'bigquery'\n ? `LOWER(${target}) LIKE LOWER(${this.bind(argument)})`\n : `${target} ILIKE ${this.bind(argument)}`\n }\n\n private computedMatch(\n binding: Extract<FieldBinding, { kind: 'computed' }>,\n value: string,\n ): string {\n const ids = binding.resolve(value)\n if (ids.length === 0) {\n // No entity matches this value \u2014 a guaranteed-false predicate (a\n // surrounding NOT correctly flips it to all-rows).\n return '1 = 0'\n }\n // One parameter, not one for each id. `status:healthy` resolves to the\n // whole healthy fleet, thus the set follows the size of the fleet.\n if (this.dialect === 'bigquery') {\n return `${binding.idColumn} IN UNNEST(${this.bind([...ids])})`\n }\n return idSetPredicate(binding.idColumn, this.bindName(joinIdSet(ids)))\n }\n\n private dateTerm(\n binding: Extract<FieldBinding, { kind: 'date' }>,\n canonical: string,\n operator: SearchTermOperator,\n value: string,\n ): string {\n if (operator === ':') {\n // Equality on a date means \"that calendar day\" (UTC): [day, nextDay).\n const start = this.parseDate(canonical, value)\n const startDay = new Date(\n Date.UTC(\n start.getUTCFullYear(),\n start.getUTCMonth(),\n start.getUTCDate(),\n ),\n )\n const nextDay = new Date(startDay.getTime() + 24 * 60 * 60 * 1000)\n return `(${binding.column} >= ${this.bindDate(binding, startDay, startDay.toISOString())} AND ${binding.column} < ${this.bindDate(binding, nextDay, nextDay.toISOString())})`\n }\n const bound = this.parseDate(canonical, value)\n return `${binding.column} ${operator} ${this.bindDate(binding, bound, bound.toISOString())}`\n }\n\n /** `postgresValue` is the parameter that the `postgres` dialect binds. */\n private bindDate(\n binding: Extract<FieldBinding, { kind: 'date' }>,\n value: Date,\n postgresValue: Date | string,\n ): string {\n if (this.dialect !== 'bigquery') {\n return this.bind(postgresValue)\n }\n const iso = value.toISOString()\n // A string parameter, cast in the statement: the client would send a JS\n // Date as a TIMESTAMP, which a DATE column cannot compare with.\n return binding.columnType === 'date'\n ? `CAST(${this.bind(iso.slice(0, 10))} AS DATE)`\n : `CAST(${this.bind(iso)} AS TIMESTAMP)`\n }\n\n // --- validation helpers ---------------------------------------------------\n\n private resolveField(field: string): {\n canonical: string\n binding: FieldBinding\n } {\n const entry = this.lookup.get(field.toLowerCase())\n if (!entry) {\n const suggestion = closestField(\n field,\n [...this.lookup.values()].map((value) => value.canonical),\n )\n throw new LuceneQueryError(\n `Unknown field \"${field}\".${suggestion ? ` Did you mean \"${suggestion}\"?` : ''}`,\n 'unknown-field',\n suggestion,\n )\n }\n return entry\n }\n\n private requireMatchOperator(\n field: string,\n operator: SearchTermOperator,\n ): void {\n if (operator !== ':') {\n throw new LuceneQueryError(\n `Field \"${field}\" supports only \"field:value\", not \"${operator}\".`,\n 'bad-operator',\n )\n }\n }\n\n private parseBoolean(field: string, value: string): boolean {\n const normalized = value.trim().toLowerCase()\n if (normalized === 'true') {\n return true\n }\n if (normalized === 'false') {\n return false\n }\n throw new LuceneQueryError(\n `Field \"${field}\" expects true or false, got \"${value}\".`,\n 'bad-value',\n )\n }\n\n private parseDate(field: string, value: string): Date {\n const parsed = new Date(value)\n if (Number.isNaN(parsed.getTime())) {\n throw new LuceneQueryError(\n `Field \"${field}\" expects a date (e.g. 2024-01-31), got \"${value}\".`,\n 'bad-value',\n )\n }\n return parsed\n }\n\n /** Register a value and give back its parameter name, with no marker. */\n private bindName(value: unknown): string {\n const name = `${this.ctx.paramPrefix}_${this.counter++}`\n this.params[name] = value\n return name\n }\n\n private bind(value: unknown): string {\n const marker = this.dialect === 'bigquery' ? '@' : ':'\n return `${marker}${this.bindName(value)}`\n }\n}\n\n/** BigQuery takes letters, digits and underscores, and no digit first. */\nconst BIGQUERY_PARAMETER_PREFIX = /^[A-Za-z_]\\w*$/\n\n/** Compile a parsed query into a single parameterized SQL fragment. */\nexport const compileQuery = (\n node: QueryNode,\n ctx: CompileContext,\n): CompiledQuery => new Compiler(ctx).compile(node)\n\nconst ID_SET_SEPARATOR = ','\n\n/**\n * Match `column` against an id set that arrives as ONE parameter.\n *\n * `IN (:...ids)` expands to one bind parameter, and one literal in the\n * statement, for each id. A dimension that does not live in SQL resolves to an\n * id set, and such a set follows the size of the fleet: the prod cluster fleet\n * is more than 1000. Both the statement text and the parameter count would\n * grow with it, and Postgres would plan the statement again at every size.\n *\n * Postgres splits the string here, thus the statement text is constant.\n * `= ANY(...)` can still use an index on the column.\n */\nexport const idSetPredicate = (column: string, parameter: string): string =>\n `${column} = ANY(string_to_array(:${parameter}, '${ID_SET_SEPARATOR}'))`\n\n/**\n * The id set as one separated string, for {@link idSetPredicate}.\n *\n * The value is a bind parameter, thus it cannot change the statement. But an\n * id that holds the separator would become two ids, thus such an id is\n * dropped. The id columns are text columns, so this stays a guard and not an\n * assumption.\n *\n * An empty string gives an empty array in Postgres. Thus the match finds\n * nothing, which is correct for an empty set.\n */\nexport const joinIdSet = (ids: readonly string[]): string =>\n ids.filter((id) => !id.includes(ID_SET_SEPARATOR)).join(ID_SET_SEPARATOR)\n\n/**\n * Turn a user value into an ILIKE argument: SQL wildcards (`%`, `_`, `\\`) are\n * escaped to literals, then the user's `*`/`?` become `%`/`_`; a plain\n * substring value is wrapped in `%\u2026%`.\n */\nexport const likeArgument = (\n value: string,\n match: 'substring' | 'exact',\n): string => {\n const escaped = value.replace(/([\\\\%_])/g, '\\\\$1')\n if (/[*?]/.test(value)) {\n return escaped.replace(/\\*/g, '%').replace(/\\?/g, '_')\n }\n return match === 'substring' ? `%${escaped}%` : escaped\n}\n\n/** Closest field name within edit distance 3, for \"did you mean\u2026\" hints. */\nexport const closestField = (\n input: string,\n candidates: readonly string[],\n): string | undefined => {\n const needle = input.toLowerCase()\n let best: string | undefined\n let bestDistance = Number.POSITIVE_INFINITY\n for (const candidate of candidates) {\n const distance = editDistance(needle, candidate.toLowerCase())\n if (distance < bestDistance) {\n bestDistance = distance\n best = candidate\n }\n }\n return bestDistance <= 3 ? best : undefined\n}\n\n/** Standard Levenshtein distance. */\nconst editDistance = (left: string, right: string): number => {\n const rows = left.length + 1\n const cols = right.length + 1\n const dist = Array.from({ length: rows }, () =>\n new Array<number>(cols).fill(0),\n )\n for (let row = 0; row < rows; row += 1) {\n dist[row][0] = row\n }\n for (let col = 0; col < cols; col += 1) {\n dist[0][col] = col\n }\n for (let row = 1; row < rows; row += 1) {\n for (let col = 1; col < cols; col += 1) {\n const cost = left[row - 1] === right[col - 1] ? 0 : 1\n dist[row][col] = Math.min(\n dist[row - 1][col] + 1,\n dist[row][col - 1] + 1,\n dist[row - 1][col - 1] + cost,\n )\n }\n }\n return dist[rows - 1][cols - 1]\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/search/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { type AndNode, isLuceneQueryError, LuceneQueryError, type NotNode, type OrNode, type QueryNode, type RangeNode, type SearchTermOperator, type TermNode, } from './ast.js';
|
|
2
2
|
export { CLUSTER_DEFAULT_SEARCH_FIELDS, CLUSTER_SEARCH_FIELDS, } from './cluster-search-fields.js';
|
|
3
|
-
export { type CompileContext, type CompiledQuery, compileQuery, type FieldBinding, idSetPredicate, joinIdSet, likeArgument, } from './compiler.js';
|
|
3
|
+
export { type CompileContext, type CompiledQuery, compileQuery, type FieldBinding, idSetPredicate, joinIdSet, likeArgument, type SqlDialect, } from './compiler.js';
|
|
4
4
|
export { findSearchField, type SearchFieldCapability, type SearchFieldSpec, type SearchFieldType, } from './fields.js';
|
|
5
5
|
export { ORG_DEFAULT_SEARCH_FIELDS, ORGANIZATION_SEARCH_FIELDS, } from './organization-search-fields.js';
|
|
6
6
|
export { parseLuceneQuery } from './parser.js';
|
package/dist/search/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/search/index.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\nexport {\n type AndNode,\n isLuceneQueryError,\n LuceneQueryError,\n type NotNode,\n type OrNode,\n type QueryNode,\n type RangeNode,\n type SearchTermOperator,\n type TermNode,\n} from './ast.js'\nexport {\n CLUSTER_DEFAULT_SEARCH_FIELDS,\n CLUSTER_SEARCH_FIELDS,\n} from './cluster-search-fields.js'\nexport {\n type CompileContext,\n type CompiledQuery,\n compileQuery,\n type FieldBinding,\n idSetPredicate,\n joinIdSet,\n likeArgument,\n} from './compiler.js'\nexport {\n findSearchField,\n type SearchFieldCapability,\n type SearchFieldSpec,\n type SearchFieldType,\n} from './fields.js'\nexport {\n ORG_DEFAULT_SEARCH_FIELDS,\n ORGANIZATION_SEARCH_FIELDS,\n} from './organization-search-fields.js'\nexport { parseLuceneQuery } from './parser.js'\n"],
|
|
5
|
-
"mappings": "AAMA;AAAA,EAEE;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EAGE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,
|
|
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\nexport {\n type AndNode,\n isLuceneQueryError,\n LuceneQueryError,\n type NotNode,\n type OrNode,\n type QueryNode,\n type RangeNode,\n type SearchTermOperator,\n type TermNode,\n} from './ast.js'\nexport {\n CLUSTER_DEFAULT_SEARCH_FIELDS,\n CLUSTER_SEARCH_FIELDS,\n} from './cluster-search-fields.js'\nexport {\n type CompileContext,\n type CompiledQuery,\n compileQuery,\n type FieldBinding,\n idSetPredicate,\n joinIdSet,\n likeArgument,\n type SqlDialect,\n} from './compiler.js'\nexport {\n findSearchField,\n type SearchFieldCapability,\n type SearchFieldSpec,\n type SearchFieldType,\n} from './fields.js'\nexport {\n ORG_DEFAULT_SEARCH_FIELDS,\n ORGANIZATION_SEARCH_FIELDS,\n} from './organization-search-fields.js'\nexport { parseLuceneQuery } from './parser.js'\n"],
|
|
5
|
+
"mappings": "AAMA;AAAA,EAEE;AAAA,EACA;AAAA,OAOK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP;AAAA,EAGE;AAAA,EAEA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AACP;AAAA,EACE;AAAA,OAIK;AACP;AAAA,EACE;AAAA,EACA;AAAA,OACK;AACP,SAAS,wBAAwB;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/search/parser.d.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import { type QueryNode } from './ast.js';
|
|
2
|
+
/** Hard caps so a pathological query can't blow up the parser (spec: safety). */
|
|
3
|
+
/** The longest query that the parser takes. Exported for the chart filter. */
|
|
4
|
+
export declare const LUCENE_QUERY_MAX_LENGTH = 512;
|
|
2
5
|
/**
|
|
3
6
|
* Parse a Lucene-subset query into an AST. Returns `undefined` for an empty or
|
|
4
7
|
* whitespace-only query (a no-op filter). Throws {@link LuceneQueryError} on any
|
package/dist/search/parser.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
LuceneQueryError
|
|
3
3
|
} from "./ast.js";
|
|
4
|
-
const
|
|
4
|
+
const LUCENE_QUERY_MAX_LENGTH = 512;
|
|
5
|
+
const MAX_INPUT_LENGTH = LUCENE_QUERY_MAX_LENGTH;
|
|
5
6
|
const MAX_NODES = 80;
|
|
6
7
|
const MAX_DEPTH = 25;
|
|
7
8
|
const WORD_BOUNDARY = /* @__PURE__ */ new Set(["(", ")"]);
|
|
@@ -319,6 +320,7 @@ const parseLuceneQuery = (input) => {
|
|
|
319
320
|
return new Parser(input).parse();
|
|
320
321
|
};
|
|
321
322
|
export {
|
|
323
|
+
LUCENE_QUERY_MAX_LENGTH,
|
|
322
324
|
parseLuceneQuery
|
|
323
325
|
};
|
|
324
326
|
//# sourceMappingURL=parser.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
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": "AAMA;AAAA,EACE;AAAA,OAGK;
|
|
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). */\n/** The longest query that the parser takes. Exported for the chart filter. */\nexport const LUCENE_QUERY_MAX_LENGTH = 512\nconst MAX_INPUT_LENGTH = LUCENE_QUERY_MAX_LENGTH\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": "AAMA;AAAA,EACE;AAAA,OAGK;AAkBA,MAAM,0BAA0B;AACvC,MAAM,mBAAmB;AACzB,MAAM,YAAY;AAClB,MAAM,YAAY;AAGlB,MAAM,gBAAgB,oBAAI,IAAI,CAAC,KAAK,GAAG,CAAC;AAExC,MAAM,OAAO;AAAA,EACM;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EAEhB,YAAY,OAAe;AACzB,SAAK,QAAQ;AAAA,EACf;AAAA,EAEA,QAA+B;AAC7B,SAAK,OAAO;AACZ,QAAI,KAAK,MAAM,GAAG;AAChB,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK,QAAQ,CAAC;AAC3B,SAAK,OAAO;AACZ,QAAI,CAAC,KAAK,MAAM,GAAG;AACjB,YAAM,IAAI;AAAA,QACR,eAAe,KAAK,KAAK,EAAE,CAAC,iBAAiB,KAAK,GAAG;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAIQ,QAAQ,OAA0B;AACxC,SAAK,WAAW,KAAK;AACrB,QAAI,OAAO,KAAK,SAAS,KAAK;AAC9B,eAAS;AACP,WAAK,OAAO;AACZ,UAAI,CAAC,KAAK,aAAa,IAAI,KAAK,CAAC,KAAK,aAAa,IAAI,GAAG;AACxD;AAAA,MACF;AACA,WAAK,eAAe;AACpB,YAAM,QAAQ,KAAK,SAAS,KAAK;AACjC,aAAO,KAAK,MAAM,EAAE,MAAM,MAAM,MAAM,MAAM,MAAM,CAAC;AAAA,IACrD;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,OAA0B;AACzC,SAAK,WAAW,KAAK;AACrB,QAAI,OAAO,KAAK,SAAS,KAAK;AAC9B,eAAS;AACP,WAAK,OAAO;AAEZ,UACE,KAAK,MAAM,KACX,KAAK,KAAK,MAAM,OAChB,KAAK,aAAa,IAAI,KACtB,KAAK,aAAa,IAAI,GACtB;AACA;AAAA,MACF;AAEA,UAAI,KAAK,aAAa,KAAK,KAAK,KAAK,aAAa,IAAI,GAAG;AACvD,aAAK,eAAe;AAAA,MACtB;AACA,YAAM,QAAQ,KAAK,SAAS,KAAK;AACjC,aAAO,KAAK,MAAM,EAAE,MAAM,OAAO,MAAM,MAAM,MAAM,CAAC;AAAA,IACtD;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,SAAS,OAA0B;AACzC,SAAK,WAAW,KAAK;AACrB,SAAK,OAAO;AACZ,QAAI,KAAK,aAAa,KAAK,GAAG;AAC5B,WAAK,eAAe;AACpB,aAAO,KAAK,MAAM,EAAE,MAAM,OAAO,OAAO,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;AAAA,IACpE;AAGA,QAAI,KAAK,KAAK,MAAM,KAAK;AACvB,WAAK,OAAO;AACZ,aAAO,KAAK,MAAM,EAAE,MAAM,OAAO,OAAO,KAAK,SAAS,QAAQ,CAAC,EAAE,CAAC;AAAA,IACpE;AACA,WAAO,KAAK,aAAa,KAAK;AAAA,EAChC;AAAA,EAEQ,aAAa,OAA0B;AAC7C,SAAK,OAAO;AACZ,QAAI,KAAK,KAAK,MAAM,KAAK;AACvB,WAAK,OAAO;AACZ,YAAM,OAAO,KAAK,QAAQ,QAAQ,CAAC;AACnC,WAAK,OAAO;AACZ,UAAI,KAAK,KAAK,MAAM,KAAK;AACvB,cAAM,IAAI;AAAA,UACR,2BAA2B,KAAK,GAAG;AAAA,UACnC;AAAA,QACF;AAAA,MACF;AACA,WAAK,OAAO;AACZ,aAAO;AAAA,IACT;AACA,WAAO,KAAK,YAAY;AAAA,EAC1B;AAAA;AAAA,EAIQ,cAAyB;AAC/B,SAAK,OAAO;AACZ,QAAI,KAAK,MAAM,KAAK,KAAK,KAAK,MAAM,KAAK;AACvC,YAAM,IAAI;AAAA,QACR,sCAAsC,KAAK,GAAG;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AAGA,QAAI,KAAK,KAAK,MAAM,KAAK;AACvB,aAAO,KAAK,MAAM;AAAA,QAChB,MAAM;AAAA,QACN,UAAU;AAAA,QACV,OAAO,KAAK,WAAW;AAAA,MACzB,CAAC;AAAA,IACH;AAEA,UAAM,OAAO,KAAK,cAAc;AAChC,QAAI,KAAK,KAAK,MAAM,KAAK;AACvB,WAAK,OAAO;AACZ,aAAO,KAAK,gBAAgB,IAAI;AAAA,IAClC;AAEA,QAAI,SAAS,IAAI;AACf,YAAM,IAAI;AAAA,QACR,sCAAsC,KAAK,GAAG;AAAA,QAC9C;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,MAAM,EAAE,MAAM,QAAQ,UAAU,KAAK,OAAO,KAAK,CAAC;AAAA,EAChE;AAAA,EAEQ,gBAAgB,OAA0B;AAChD,QAAI,UAAU,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,6CAA6C,KAAK,GAAG;AAAA,QACrD;AAAA,MACF;AAAA,IACF;AAEA,SAAK,OAAO;AACZ,UAAM,SAAS,KAAK,KAAK;AACzB,QAAI,WAAW,OAAO,WAAW,KAAK;AACpC,aAAO,KAAK,WAAW,OAAO,MAAM;AAAA,IACtC;AACA,UAAM,WAAW,KAAK,uBAAuB;AAE7C,SAAK,OAAO;AACZ,UAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,KAAK,WAAW,IAAI,KAAK,cAAc;AAC3E,QAAI,UAAU,IAAI;AAChB,YAAM,IAAI;AAAA,QACR,sBAAsB,KAAK,iBAAiB,KAAK,GAAG;AAAA,QACpD;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,MAAM,EAAE,MAAM,QAAQ,OAAO,UAAU,MAAM,CAAC;AAAA,EAC5D;AAAA,EAEQ,WAAW,OAAe,QAA8B;AAC9D,SAAK,OAAO;AACZ,UAAM,iBAAiB,WAAW;AAClC,SAAK,OAAO;AACZ,UAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,KAAK,WAAW,IAAI,KAAK,cAAc;AAC3E,SAAK,OAAO;AACZ,QAAI,CAAC,KAAK,aAAa,IAAI,GAAG;AAC5B,YAAM,IAAI;AAAA,QACR,+BAA+B,KAAK,iBAAiB,KAAK,GAAG;AAAA,QAC7D;AAAA,MACF;AAAA,IACF;AACA,SAAK,eAAe;AACpB,SAAK,OAAO;AACZ,UAAM,QAAQ,KAAK,KAAK,MAAM,MAAM,KAAK,WAAW,IAAI,KAAK,cAAc;AAC3E,SAAK,OAAO;AACZ,UAAM,SAAS,KAAK,KAAK;AACzB,QAAI,WAAW,OAAO,WAAW,KAAK;AACpC,YAAM,IAAI;AAAA,QACR,0CAA0C,KAAK,iBAAiB,KAAK,GAAG;AAAA,QACxE;AAAA,MACF;AAAA,IACF;AACA,SAAK,OAAO;AACZ,QAAI,UAAU,MAAM,UAAU,IAAI;AAChC,YAAM,IAAI;AAAA,QACR,cAAc,KAAK;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AACA,WAAO,KAAK,MAAM;AAAA,MAChB,MAAM;AAAA,MACN;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,gBAAgB,WAAW;AAAA,IAC7B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA,EAKQ,gBAAwB;AAC9B,UAAM,QAAQ,KAAK;AACnB,WAAO,CAAC,KAAK,MAAM,GAAG;AACpB,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,SAAS,OAAO,KAAK,KAAK,IAAI,KAAK,cAAc,IAAI,IAAI,GAAG;AAC9D;AAAA,MACF;AACA,WAAK,OAAO;AAAA,IACd;AACA,WAAO,KAAK,MAAM,MAAM,OAAO,KAAK,GAAG;AAAA,EACzC;AAAA;AAAA,EAGQ,gBAAwB;AAC9B,UAAM,QAAQ,KAAK;AACnB,WAAO,CAAC,KAAK,MAAM,GAAG;AACpB,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,KAAK,KAAK,IAAI,KAAK,cAAc,IAAI,IAAI,GAAG;AAC9C;AAAA,MACF;AACA,WAAK,OAAO;AAAA,IACd;AACA,WAAO,KAAK,MAAM,MAAM,OAAO,KAAK,GAAG;AAAA,EACzC;AAAA;AAAA,EAGQ,gBAAwB;AAC9B,UAAM,QAAQ,KAAK;AACnB,WAAO,CAAC,KAAK,MAAM,GAAG;AACpB,YAAM,OAAO,KAAK,KAAK;AACvB,UACE,KAAK,KAAK,IAAI,KACd,cAAc,IAAI,IAAI,KACtB,SAAS,OACT,SAAS,KACT;AACA;AAAA,MACF;AACA,WAAK,OAAO;AAAA,IACd;AACA,WAAO,KAAK,MAAM,MAAM,OAAO,KAAK,GAAG;AAAA,EACzC;AAAA,EAEQ,aAAqB;AAC3B,SAAK,OAAO;AACZ,QAAI,QAAQ;AACZ,WAAO,CAAC,KAAK,MAAM,GAAG;AACpB,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,SAAS,QAAQ,KAAK,MAAM,IAAI,KAAK,MAAM,QAAQ;AACrD,iBAAS,KAAK,MAAM,KAAK,MAAM,CAAC;AAChC,aAAK,OAAO;AACZ;AAAA,MACF;AACA,UAAI,SAAS,KAAK;AAChB,aAAK,OAAO;AACZ,eAAO;AAAA,MACT;AACA,eAAS;AACT,WAAK,OAAO;AAAA,IACd;AACA,UAAM,IAAI,iBAAiB,+BAA+B,QAAQ;AAAA,EACpE;AAAA,EAEQ,yBAA6C;AACnD,UAAM,OAAO,KAAK,KAAK;AACvB,QAAI,SAAS,OAAO,SAAS,KAAK;AAChC,WAAK,OAAO;AACZ,UAAI,KAAK,KAAK,MAAM,KAAK;AACvB,aAAK,OAAO;AACZ,eAAO,SAAS,MAAM,OAAO;AAAA,MAC/B;AACA,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,aAAa,SAA0B;AAC7C,UAAM,MAAM,KAAK,MAAM,QAAQ;AAC/B,QAAI,MAAM,KAAK,MAAM,QAAQ;AAC3B,aAAO;AAAA,IACT;AACA,UAAM,QAAQ,KAAK,MAAM,MAAM,KAAK,KAAK,GAAG;AAC5C,QAAI,MAAM,YAAY,MAAM,QAAQ,YAAY,GAAG;AACjD,aAAO;AAAA,IACT;AACA,QAAI,QAAQ,KAAK,MAAM,QAAQ;AAC7B,aAAO;AAAA,IACT;AACA,UAAM,OAAO,KAAK,MAAM,GAAG;AAC3B,WAAO,KAAK,KAAK,IAAI,KAAK,SAAS,OAAO,SAAS;AAAA,EACrD;AAAA;AAAA,EAGQ,iBAAuB;AAC7B,WAAO,CAAC,KAAK,MAAM,GAAG;AACpB,YAAM,OAAO,KAAK,KAAK;AACvB,UAAI,KAAK,KAAK,IAAI,KAAK,SAAS,OAAO,SAAS,KAAK;AACnD;AAAA,MACF;AACA,WAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA;AAAA,EAIQ,MAA2B,MAAY;AAC7C,SAAK,SAAS;AACd,QAAI,KAAK,QAAQ,WAAW;AAC1B,YAAM,IAAI,iBAAiB,yBAAyB,QAAQ;AAAA,IAC9D;AACA,WAAO;AAAA,EACT;AAAA,EAEQ,WAAW,OAAqB;AACtC,QAAI,QAAQ,WAAW;AACrB,YAAM,IAAI,iBAAiB,+BAA+B,QAAQ;AAAA,IACpE;AAAA,EACF;AAAA,EAEQ,SAAe;AACrB,WAAO,CAAC,KAAK,MAAM,KAAK,KAAK,KAAK,KAAK,KAAK,CAAC,GAAG;AAC9C,WAAK,OAAO;AAAA,IACd;AAAA,EACF;AAAA,EAEQ,KAAK,MAAuB;AAClC,WAAO,SAAS,OAAO,SAAS,OAAQ,SAAS,QAAQ,SAAS;AAAA,EACpE;AAAA,EAEQ,OAAe;AACrB,WAAO,KAAK,MAAM,KAAK,GAAG,KAAK;AAAA,EACjC;AAAA,EAEQ,QAAiB;AACvB,WAAO,KAAK,OAAO,KAAK,MAAM;AAAA,EAChC;AAAA,EAEQ,KAAK,KAAqB;AAChC,WAAO,KAAK,MAAM,MAAM,KAAK,KAAK,KAAK,MAAM,GAAG;AAAA,EAClD;AACF;AAOO,MAAM,mBAAmB,CAAC,UAAyC;AACxE,MAAI,MAAM,SAAS,kBAAkB;AACnC,UAAM,IAAI;AAAA,MACR,0BAA0B,gBAAgB;AAAA,MAC1C;AAAA,IACF;AAAA,EACF;AACA,SAAO,IAAI,OAAO,KAAK,EAAE,MAAM;AACjC;",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
|
@@ -288,92 +288,92 @@ const CLUSTER_PLAN_FIELDS = {
|
|
|
288
288
|
validator: numberMin1Validator,
|
|
289
289
|
description: "the replication for operate"
|
|
290
290
|
},
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
291
|
+
dataRetentionDays: {
|
|
292
|
+
notOnClusterPlan: true,
|
|
293
|
+
type: "number",
|
|
294
|
+
placeholder: "e.g. 7",
|
|
295
|
+
validator: numberMin0OrUndefinedValidator,
|
|
296
|
+
description: "number of days for operate retention"
|
|
297
|
+
}
|
|
298
|
+
},
|
|
299
|
+
elasticsearch: {
|
|
300
|
+
resources: {
|
|
301
|
+
limits: {
|
|
302
|
+
memory: {
|
|
303
|
+
type: "text",
|
|
304
|
+
placeholder: "e.g. 150Mi",
|
|
305
|
+
validator: undefinedOrQuantityValidator,
|
|
306
|
+
description: "Memory limit of an elastic node, e.g. 150Mi, 2Gi"
|
|
306
307
|
},
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
description: "Memory request of an elastic node, e.g. 150Mi, 2Gi"
|
|
313
|
-
},
|
|
314
|
-
cpu: {
|
|
315
|
-
type: "text",
|
|
316
|
-
placeholder: "e.g. 200m",
|
|
317
|
-
validator: undefinedOrQuantityValidator,
|
|
318
|
-
description: "CPU request of a an elastic node, e.g. 150m, 0.5, 1"
|
|
319
|
-
}
|
|
308
|
+
cpu: {
|
|
309
|
+
type: "text",
|
|
310
|
+
placeholder: "e.g. 200m",
|
|
311
|
+
validator: undefinedOrQuantityValidator,
|
|
312
|
+
description: "CPU limit of an elastic node, e.g. 150m, 0.5, 1"
|
|
320
313
|
}
|
|
321
314
|
},
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
type: "
|
|
325
|
-
placeholder: "e.g.
|
|
326
|
-
validator:
|
|
327
|
-
description: "
|
|
315
|
+
requests: {
|
|
316
|
+
memory: {
|
|
317
|
+
type: "text",
|
|
318
|
+
placeholder: "e.g. 150Mi",
|
|
319
|
+
validator: undefinedOrQuantityValidator,
|
|
320
|
+
description: "Memory request of an elastic node, e.g. 150Mi, 2Gi"
|
|
328
321
|
},
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
322
|
+
cpu: {
|
|
323
|
+
type: "text",
|
|
324
|
+
placeholder: "e.g. 200m",
|
|
325
|
+
validator: undefinedOrQuantityValidator,
|
|
326
|
+
description: "CPU request of a an elastic node, e.g. 150m, 0.5, 1"
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
config: {
|
|
331
|
+
nodesCount: {
|
|
332
|
+
type: "number",
|
|
333
|
+
placeholder: "e.g. 1",
|
|
334
|
+
validator: numberMin1Validator,
|
|
335
|
+
description: "The count of elasticsearch nodes - e.g. how many pods it runs, if its value is more than 1 it is deployed through the elastic-operator"
|
|
336
|
+
},
|
|
337
|
+
storage: {
|
|
338
|
+
storageClassName: {
|
|
339
|
+
type: "text",
|
|
340
|
+
placeholder: "e.g. fast-v2",
|
|
341
|
+
validator: notEmptyStringValidator,
|
|
342
|
+
description: "This needs to be set to a storageclass present in the k8s system. Can not be undefined if one value in storage: is set"
|
|
343
|
+
},
|
|
344
|
+
resources: {
|
|
345
|
+
requests: {
|
|
346
|
+
storage: {
|
|
347
|
+
type: "text",
|
|
348
|
+
placeholder: "e.g. 1GiB",
|
|
349
|
+
validator: quantityValidator,
|
|
350
|
+
description: "The initial storage size of the statefulset"
|
|
352
351
|
}
|
|
353
352
|
},
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
type: "text",
|
|
357
|
-
placeholder: "e.g. 10%",
|
|
358
|
-
validator: thresholdValidator,
|
|
359
|
-
description: "The free space percentage threshold where pvc autoscaling kicks in. (example 10%)"
|
|
360
|
-
},
|
|
361
|
-
increase: {
|
|
353
|
+
limits: {
|
|
354
|
+
storage: {
|
|
362
355
|
type: "text",
|
|
363
356
|
placeholder: "e.g. 1GiB",
|
|
364
|
-
validator:
|
|
365
|
-
description: "The
|
|
357
|
+
validator: undefinedOrQuantityValidator,
|
|
358
|
+
description: "The upper limit the pvc autoscaler can scale up to"
|
|
366
359
|
}
|
|
367
360
|
}
|
|
361
|
+
},
|
|
362
|
+
autoResizing: {
|
|
363
|
+
threshold: {
|
|
364
|
+
type: "text",
|
|
365
|
+
placeholder: "e.g. 10%",
|
|
366
|
+
validator: thresholdValidator,
|
|
367
|
+
description: "The free space percentage threshold where pvc autoscaling kicks in. (example 10%)"
|
|
368
|
+
},
|
|
369
|
+
increase: {
|
|
370
|
+
type: "text",
|
|
371
|
+
placeholder: "e.g. 1GiB",
|
|
372
|
+
validator: quantityValidator,
|
|
373
|
+
description: "The amount of disk space which is added as soon as autoscaling kicks in. (example 2Gi)"
|
|
374
|
+
}
|
|
368
375
|
}
|
|
369
376
|
}
|
|
370
|
-
},
|
|
371
|
-
dataRetentionDays: {
|
|
372
|
-
notOnClusterPlan: true,
|
|
373
|
-
type: "number",
|
|
374
|
-
placeholder: "e.g. 7",
|
|
375
|
-
validator: numberMin0OrUndefinedValidator,
|
|
376
|
-
description: "number of days for operate retention"
|
|
377
377
|
}
|
|
378
378
|
},
|
|
379
379
|
tasklist: {
|