@reopt-ai/opt-ui 1.5.0 → 1.7.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/COMPONENT_CATALOG.md +178 -50
- package/README.md +28 -7
- package/dist/app.css +170 -0
- package/dist/core/index.cjs +164 -324
- package/dist/core/index.d.cts +3 -6
- package/dist/core/index.d.ts +3 -6
- package/dist/core/index.js +3 -323
- package/dist/docs/01-getting-started.md +25 -5
- package/dist/docs/02-components/01-core.md +36 -28
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +144 -23
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +4 -4
- package/dist/docs/03-recipes/03-layouts.md +16 -12
- package/dist/docs/05-migration/01-breaking-changes.md +46 -1
- package/dist/field-sidebar-BTZCBviA.d.cts +1031 -0
- package/dist/field-sidebar-BTZCBviA.d.cts.map +1 -0
- package/dist/field-sidebar-CB8vjvUM.d.ts +1031 -0
- package/dist/field-sidebar-CB8vjvUM.d.ts.map +1 -0
- package/dist/field-sidebar-Cs-J-5VE.js +4233 -0
- package/dist/field-sidebar-Cs-J-5VE.js.map +1 -0
- package/dist/field-sidebar-CzIQX70-.cjs +4561 -0
- package/dist/id-registry.cjs +1939 -1872
- package/dist/id-registry.d.cts +14 -11
- package/dist/id-registry.d.cts.map +1 -0
- package/dist/id-registry.d.ts +14 -11
- package/dist/id-registry.d.ts.map +1 -0
- package/dist/id-registry.js +1937 -1871
- package/dist/id-registry.js.map +1 -0
- package/dist/id-registry.json +113 -28
- package/dist/index.cjs +8380 -9790
- package/dist/index.d.cts +1555 -1442
- package/dist/index.d.cts.map +1 -0
- package/dist/index.d.ts +1555 -1442
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7996 -9778
- package/dist/index.js.map +1 -0
- package/dist/meta.cjs +7753 -6875
- package/dist/meta.d.cts.map +1 -0
- package/dist/meta.d.ts.map +1 -0
- package/dist/meta.js +7752 -6846
- package/dist/meta.js.map +1 -0
- package/dist/pagination-DWbvACuN.js +3850 -0
- package/dist/pagination-DWbvACuN.js.map +1 -0
- package/dist/pagination-DjfxBcjX.cjs +4739 -0
- package/dist/query.cjs +386 -0
- package/dist/query.d.cts +141 -0
- package/dist/query.d.cts.map +1 -0
- package/dist/query.d.ts +141 -0
- package/dist/query.d.ts.map +1 -0
- package/dist/query.js +371 -0
- package/dist/query.js.map +1 -0
- package/dist/shader-surface-BUPZ0P-n.js +3654 -0
- package/dist/shader-surface-BUPZ0P-n.js.map +1 -0
- package/dist/shader-surface-BfTP9c1Q.d.ts +1788 -0
- package/dist/shader-surface-BfTP9c1Q.d.ts.map +1 -0
- package/dist/shader-surface-DRrcLrWf.d.cts +1788 -0
- package/dist/shader-surface-DRrcLrWf.d.cts.map +1 -0
- package/dist/shader-surface-Fav5K9UC.cjs +4041 -0
- package/dist/shells/index.cjs +58 -65
- package/dist/shells/index.d.cts +4 -5
- package/dist/shells/index.d.ts +4 -5
- package/dist/shells/index.js +4 -64
- package/dist/tailwind.css +49 -0
- package/dist/theme/presets/default.css +2 -2
- package/dist/theme/presets/minimal.css +2 -2
- package/dist/theme/presets/mono-dark.css +2 -2
- package/dist/theme/presets/natural.css +1 -1
- package/dist/theme/presets/pro.css +2 -2
- package/dist/theme/server.cjs +257 -228
- package/dist/theme/server.d.cts +48 -60
- package/dist/theme/server.d.cts.map +1 -0
- package/dist/theme/server.d.ts +48 -60
- package/dist/theme/server.d.ts.map +1 -0
- package/dist/theme/server.js +234 -189
- package/dist/theme/server.js.map +1 -0
- package/dist/types-D0FlcYnM.d.cts +301 -0
- package/dist/types-D0FlcYnM.d.cts.map +1 -0
- package/dist/types-D0FlcYnM.d.ts +301 -0
- package/dist/types-D0FlcYnM.d.ts.map +1 -0
- package/dist/visuals/index.cjs +10 -4
- package/dist/visuals/index.d.cts +2 -1
- package/dist/visuals/index.d.ts +2 -1
- package/dist/visuals/index.js +1 -2
- package/package.json +27 -11
- package/dist/chunk-3GWWZKX7.js +0 -38
- package/dist/chunk-3QFYBBL6.cjs +0 -4759
- package/dist/chunk-3XD4HIFL.js +0 -3241
- package/dist/chunk-AFF2HPE5.cjs +0 -5008
- package/dist/chunk-NXZJIHEZ.cjs +0 -3241
- package/dist/chunk-ONE3C5RV.cjs +0 -38
- package/dist/chunk-RBM2RNC2.js +0 -5008
- package/dist/chunk-VBGPEW45.js +0 -4759
- package/dist/index-BZ_lBlO1.d.ts +0 -474
- package/dist/index-BuvxoWHf.d.cts +0 -474
- package/dist/index-DWyqDIkH.d.cts +0 -1689
- package/dist/index-Uyijm14M.d.ts +0 -1689
- package/dist/types-D4-0lwaE.d.cts +0 -298
- package/dist/types-D4-0lwaE.d.ts +0 -298
package/dist/query.js
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
//#region src/lib/query/query-ast.ts
|
|
2
|
+
const EMPTY_QUERY = { clauses: [] };
|
|
3
|
+
/** Whether a query constrains anything at all. */
|
|
4
|
+
function isEmptyQuery(query) {
|
|
5
|
+
return !query || query.clauses.length === 0;
|
|
6
|
+
}
|
|
7
|
+
function sameClause(a, b) {
|
|
8
|
+
if (a.type !== b.type) return false;
|
|
9
|
+
if (a.type === "term" && b.type === "term") return a.value === b.value && a.negated === b.negated;
|
|
10
|
+
const fieldA = a;
|
|
11
|
+
const fieldB = b;
|
|
12
|
+
return fieldA.field === fieldB.field && fieldA.operator === fieldB.operator && fieldA.negated === fieldB.negated && fieldA.values.length === fieldB.values.length && fieldA.values.every((value, index) => value === fieldB.values[index]);
|
|
13
|
+
}
|
|
14
|
+
/** Add a clause unless an identical one is already there. */
|
|
15
|
+
function addClause(query, clause) {
|
|
16
|
+
if (query.clauses.some((existing) => sameClause(existing, clause))) return query;
|
|
17
|
+
return { clauses: [...query.clauses, clause] };
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Constrain a field to a value — what a cell's "filter for this" action does.
|
|
21
|
+
*
|
|
22
|
+
* Applying the opposite of a constraint already present replaces it rather than
|
|
23
|
+
* adding it: `status:200` then "filter out 200" should read as one changed
|
|
24
|
+
* mind, not as a query that can never match.
|
|
25
|
+
*/
|
|
26
|
+
function withFieldValue(query, field, value, options = {}) {
|
|
27
|
+
var _options$negated, _options$operator;
|
|
28
|
+
const negated = (_options$negated = options.negated) !== null && _options$negated !== void 0 ? _options$negated : false;
|
|
29
|
+
const operator = (_options$operator = options.operator) !== null && _options$operator !== void 0 ? _options$operator : "eq";
|
|
30
|
+
return { clauses: [...query.clauses.filter((clause) => !(clause.type === "field" && clause.field === field && clause.operator === operator && clause.values.length === 1 && clause.values[0] === value)), {
|
|
31
|
+
type: "field",
|
|
32
|
+
field,
|
|
33
|
+
operator,
|
|
34
|
+
values: [value],
|
|
35
|
+
negated
|
|
36
|
+
}] };
|
|
37
|
+
}
|
|
38
|
+
/** Drop every clause naming `field`. */
|
|
39
|
+
function withoutField(query, field) {
|
|
40
|
+
return { clauses: query.clauses.filter((clause) => clause.type !== "field" || clause.field !== field) };
|
|
41
|
+
}
|
|
42
|
+
/** Drop the clause at `index`, for a removable chip. */
|
|
43
|
+
function removeClause(query, index) {
|
|
44
|
+
return { clauses: query.clauses.filter((_, at) => at !== index) };
|
|
45
|
+
}
|
|
46
|
+
/** Every field the query names, in the order they first appear. */
|
|
47
|
+
function queryFields(query) {
|
|
48
|
+
const seen = [];
|
|
49
|
+
for (const clause of query.clauses) if (clause.type === "field" && !seen.includes(clause.field)) seen.push(clause.field);
|
|
50
|
+
return seen;
|
|
51
|
+
}
|
|
52
|
+
/** The values a field is constrained to, if any — for showing a filter as active. */
|
|
53
|
+
function fieldValues(query, field) {
|
|
54
|
+
return query.clauses.flatMap((clause) => clause.type === "field" && clause.field === field && !clause.negated ? clause.values : []);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Fold a cell action into the query.
|
|
58
|
+
*
|
|
59
|
+
* The pair of buttons on a log cell is the fastest path from "I noticed this
|
|
60
|
+
* value" to a filtered view, and the reason it works is that the result is a
|
|
61
|
+
* query the reader can then see, edit and share — not a hidden filter stack
|
|
62
|
+
* with its own rules.
|
|
63
|
+
*/
|
|
64
|
+
function applyCellAction(query, action) {
|
|
65
|
+
return withFieldValue(query, action.field, action.value, { negated: action.intent === "exclude" });
|
|
66
|
+
}
|
|
67
|
+
//#endregion
|
|
68
|
+
//#region src/lib/query/query-parse.ts
|
|
69
|
+
const FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
|
|
70
|
+
/**
|
|
71
|
+
* Split on whitespace, but keep quoted runs and parenthesised groups whole.
|
|
72
|
+
*
|
|
73
|
+
* An unterminated quote or group swallows the rest of the line rather than
|
|
74
|
+
* erroring: the reader is mid-word, and the alternative is the query flickering
|
|
75
|
+
* between two different meanings as they type the closing character.
|
|
76
|
+
*/
|
|
77
|
+
function tokenize(input) {
|
|
78
|
+
const tokens = [];
|
|
79
|
+
let index = 0;
|
|
80
|
+
while (index < input.length) {
|
|
81
|
+
while (index < input.length && /\s/.test(input[index])) index += 1;
|
|
82
|
+
if (index >= input.length) break;
|
|
83
|
+
const start = index;
|
|
84
|
+
let text = "";
|
|
85
|
+
let quoted = false;
|
|
86
|
+
let atTokenStart = true;
|
|
87
|
+
let depth = 0;
|
|
88
|
+
while (index < input.length) {
|
|
89
|
+
const char = input[index];
|
|
90
|
+
if (char === "\"") {
|
|
91
|
+
if (atTokenStart) quoted = true;
|
|
92
|
+
atTokenStart = false;
|
|
93
|
+
index += 1;
|
|
94
|
+
while (index < input.length && input[index] !== "\"") {
|
|
95
|
+
text += input[index];
|
|
96
|
+
index += 1;
|
|
97
|
+
}
|
|
98
|
+
index += 1;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (char === "(") depth += 1;
|
|
102
|
+
if (char === ")") depth = Math.max(0, depth - 1);
|
|
103
|
+
if (/\s/.test(char) && depth === 0) break;
|
|
104
|
+
atTokenStart = false;
|
|
105
|
+
text += char;
|
|
106
|
+
index += 1;
|
|
107
|
+
}
|
|
108
|
+
if (text.length > 0 || quoted) tokens.push({
|
|
109
|
+
text,
|
|
110
|
+
at: start,
|
|
111
|
+
quoted
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return tokens;
|
|
115
|
+
}
|
|
116
|
+
/** `(a or b)` → `["a", "b"]`; anything else is a single value. */
|
|
117
|
+
function readValues(raw) {
|
|
118
|
+
const trimmed = raw.trim();
|
|
119
|
+
if (!trimmed.startsWith("(")) return [stripQuotes(trimmed)];
|
|
120
|
+
return trimmed.replace(/^\(/, "").replace(/\)$/, "").split(/\s+or\s+/i).map((part) => stripQuotes(part.trim())).filter((part) => part.length > 0);
|
|
121
|
+
}
|
|
122
|
+
function stripQuotes(value) {
|
|
123
|
+
if (value.length >= 2 && value.startsWith("\"") && value.endsWith("\"")) return value.slice(1, -1);
|
|
124
|
+
return value.replace(/^"/, "");
|
|
125
|
+
}
|
|
126
|
+
const OPERATORS = [
|
|
127
|
+
{
|
|
128
|
+
symbol: ">=",
|
|
129
|
+
operator: "gte"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
symbol: "<=",
|
|
133
|
+
operator: "lte"
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
symbol: ":",
|
|
137
|
+
operator: "eq"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
symbol: ">",
|
|
141
|
+
operator: "gt"
|
|
142
|
+
},
|
|
143
|
+
{
|
|
144
|
+
symbol: "<",
|
|
145
|
+
operator: "lt"
|
|
146
|
+
}
|
|
147
|
+
];
|
|
148
|
+
/** Where a field operator starts in the token, or `-1`. */
|
|
149
|
+
function findOperator(text) {
|
|
150
|
+
let best = null;
|
|
151
|
+
for (const { symbol, operator } of OPERATORS) {
|
|
152
|
+
const index = text.indexOf(symbol);
|
|
153
|
+
if (index <= 0) continue;
|
|
154
|
+
if (!best || index < best.index || index === best.index && symbol.length > best.symbol.length) best = {
|
|
155
|
+
index,
|
|
156
|
+
symbol,
|
|
157
|
+
operator
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
return best;
|
|
161
|
+
}
|
|
162
|
+
/** Parse, keeping whatever could not be understood as a plain term. */
|
|
163
|
+
function parseQueryWithDiagnostics(input) {
|
|
164
|
+
const diagnostics = [];
|
|
165
|
+
const clauses = [];
|
|
166
|
+
let trailing = null;
|
|
167
|
+
for (const token of tokenize(input)) {
|
|
168
|
+
let text = token.text;
|
|
169
|
+
let negated = false;
|
|
170
|
+
if (!token.quoted && text.startsWith("-") && text.length > 1) {
|
|
171
|
+
negated = true;
|
|
172
|
+
text = text.slice(1);
|
|
173
|
+
}
|
|
174
|
+
const operator = token.quoted ? null : findOperator(text);
|
|
175
|
+
if (!operator) {
|
|
176
|
+
if (text.length > 0) clauses.push({
|
|
177
|
+
type: "term",
|
|
178
|
+
value: text,
|
|
179
|
+
negated
|
|
180
|
+
});
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const field = text.slice(0, operator.index);
|
|
184
|
+
const rawValue = text.slice(operator.index + operator.symbol.length);
|
|
185
|
+
if (!FIELD_NAME.test(field)) {
|
|
186
|
+
clauses.push({
|
|
187
|
+
type: "term",
|
|
188
|
+
value: text,
|
|
189
|
+
negated
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
if (rawValue === "") {
|
|
194
|
+
trailing = {
|
|
195
|
+
field,
|
|
196
|
+
operator: operator.operator,
|
|
197
|
+
value: ""
|
|
198
|
+
};
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (rawValue === "*" && operator.operator === "eq") {
|
|
202
|
+
clauses.push({
|
|
203
|
+
type: "field",
|
|
204
|
+
field,
|
|
205
|
+
operator: "exists",
|
|
206
|
+
values: [],
|
|
207
|
+
negated
|
|
208
|
+
});
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (operator.operator === "eq" && rawValue.length > 2 && rawValue.startsWith("*") && rawValue.endsWith("*")) {
|
|
212
|
+
clauses.push({
|
|
213
|
+
type: "field",
|
|
214
|
+
field,
|
|
215
|
+
operator: "contains",
|
|
216
|
+
values: [stripQuotes(rawValue.slice(1, -1))],
|
|
217
|
+
negated
|
|
218
|
+
});
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const values = readValues(rawValue);
|
|
222
|
+
if (values.length === 0) {
|
|
223
|
+
diagnostics.push({
|
|
224
|
+
at: token.at,
|
|
225
|
+
text: token.text,
|
|
226
|
+
message: "Empty value group"
|
|
227
|
+
});
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
if (operator.operator !== "eq" && values.length > 1) diagnostics.push({
|
|
231
|
+
at: token.at,
|
|
232
|
+
text: token.text,
|
|
233
|
+
message: "A comparison takes a single value"
|
|
234
|
+
});
|
|
235
|
+
const clause = {
|
|
236
|
+
type: "field",
|
|
237
|
+
field,
|
|
238
|
+
operator: operator.operator,
|
|
239
|
+
values: operator.operator === "eq" ? values : values.slice(0, 1),
|
|
240
|
+
negated
|
|
241
|
+
};
|
|
242
|
+
clauses.push(clause);
|
|
243
|
+
if (!rawValue.startsWith("(")) {
|
|
244
|
+
var _values$;
|
|
245
|
+
trailing = {
|
|
246
|
+
field,
|
|
247
|
+
operator: operator.operator,
|
|
248
|
+
value: (_values$ = values[0]) !== null && _values$ !== void 0 ? _values$ : ""
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
return {
|
|
253
|
+
query: { clauses },
|
|
254
|
+
diagnostics,
|
|
255
|
+
trailing
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
/** Parse, discarding diagnostics. */
|
|
259
|
+
function parseQuery(input) {
|
|
260
|
+
return parseQueryWithDiagnostics(input).query;
|
|
261
|
+
}
|
|
262
|
+
//#endregion
|
|
263
|
+
//#region src/lib/query/query-format.ts
|
|
264
|
+
const OPERATOR_SYMBOL = {
|
|
265
|
+
eq: ":",
|
|
266
|
+
contains: ":",
|
|
267
|
+
gt: ">",
|
|
268
|
+
gte: ">=",
|
|
269
|
+
lt: "<",
|
|
270
|
+
lte: "<=",
|
|
271
|
+
exists: ":"
|
|
272
|
+
};
|
|
273
|
+
/** Quote a value that would otherwise re-parse as something else. */
|
|
274
|
+
function quoteValue(value) {
|
|
275
|
+
return /[\s"()]/.test(value) || value === "" ? `"${value.replace(/"/g, "")}"` : value;
|
|
276
|
+
}
|
|
277
|
+
/** One clause as text. */
|
|
278
|
+
function formatClause(clause) {
|
|
279
|
+
var _clause$values$2;
|
|
280
|
+
const prefix = clause.negated ? "-" : "";
|
|
281
|
+
if (clause.type === "term") return `${prefix}${quoteValue(clause.value)}`;
|
|
282
|
+
if (clause.operator === "exists") return `${prefix}${clause.field}:*`;
|
|
283
|
+
if (clause.operator === "contains") {
|
|
284
|
+
var _clause$values$;
|
|
285
|
+
return `${prefix}${clause.field}:*${(_clause$values$ = clause.values[0]) !== null && _clause$values$ !== void 0 ? _clause$values$ : ""}*`;
|
|
286
|
+
}
|
|
287
|
+
const symbol = OPERATOR_SYMBOL[clause.operator];
|
|
288
|
+
if (clause.values.length > 1) return `${prefix}${clause.field}${symbol}(${clause.values.map(quoteValue).join(" or ")})`;
|
|
289
|
+
return `${prefix}${clause.field}${symbol}${quoteValue((_clause$values$2 = clause.values[0]) !== null && _clause$values$2 !== void 0 ? _clause$values$2 : "")}`;
|
|
290
|
+
}
|
|
291
|
+
/** The whole query as text. */
|
|
292
|
+
function formatQuery(query) {
|
|
293
|
+
return query.clauses.map(formatClause).join(" ");
|
|
294
|
+
}
|
|
295
|
+
/** A short human label for a clause chip. */
|
|
296
|
+
function describeClause(clause) {
|
|
297
|
+
if (clause.type === "term") return clause.negated ? `not "${clause.value}"` : `"${clause.value}"`;
|
|
298
|
+
if (clause.operator === "exists") return clause.negated ? `${clause.field} missing` : `${clause.field} present`;
|
|
299
|
+
if (clause.operator === "contains") {
|
|
300
|
+
var _clause$values$3;
|
|
301
|
+
return `${clause.negated ? "not " : ""}${clause.field} ⊃ ${(_clause$values$3 = clause.values[0]) !== null && _clause$values$3 !== void 0 ? _clause$values$3 : ""}`;
|
|
302
|
+
}
|
|
303
|
+
const values = clause.values.join(", ");
|
|
304
|
+
const symbol = OPERATOR_SYMBOL[clause.operator];
|
|
305
|
+
return `${clause.negated ? "not " : ""}${clause.field} ${symbol === ":" ? "=" : symbol} ${values}`;
|
|
306
|
+
}
|
|
307
|
+
//#endregion
|
|
308
|
+
//#region src/lib/query/query-match.ts
|
|
309
|
+
function toText(value) {
|
|
310
|
+
if (value === null || value === void 0) return "";
|
|
311
|
+
if (typeof value === "string") return value;
|
|
312
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
313
|
+
return JSON.stringify(value);
|
|
314
|
+
}
|
|
315
|
+
function compare(left, right) {
|
|
316
|
+
const leftNumber = typeof left === "number" ? left : Number(toText(left));
|
|
317
|
+
const rightNumber = Number(right);
|
|
318
|
+
if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) return leftNumber - rightNumber;
|
|
319
|
+
return toText(left).localeCompare(right);
|
|
320
|
+
}
|
|
321
|
+
function matchesClause(clause, read, searchable) {
|
|
322
|
+
if (clause.type === "term") return searchable().toLowerCase().includes(clause.value.toLowerCase());
|
|
323
|
+
const value = read(clause.field);
|
|
324
|
+
switch (clause.operator) {
|
|
325
|
+
case "exists": return value !== void 0 && value !== null && value !== "";
|
|
326
|
+
case "eq": return clause.values.some((candidate) => toText(value).toLowerCase() === candidate.toLowerCase());
|
|
327
|
+
case "contains": return clause.values.some((candidate) => toText(value).toLowerCase().includes(candidate.toLowerCase()));
|
|
328
|
+
case "gt":
|
|
329
|
+
var _clause$values$;
|
|
330
|
+
return compare(value, (_clause$values$ = clause.values[0]) !== null && _clause$values$ !== void 0 ? _clause$values$ : "") > 0;
|
|
331
|
+
case "gte":
|
|
332
|
+
var _clause$values$2;
|
|
333
|
+
return compare(value, (_clause$values$2 = clause.values[0]) !== null && _clause$values$2 !== void 0 ? _clause$values$2 : "") >= 0;
|
|
334
|
+
case "lt":
|
|
335
|
+
var _clause$values$3;
|
|
336
|
+
return compare(value, (_clause$values$3 = clause.values[0]) !== null && _clause$values$3 !== void 0 ? _clause$values$3 : "") < 0;
|
|
337
|
+
case "lte":
|
|
338
|
+
var _clause$values$4;
|
|
339
|
+
return compare(value, (_clause$values$4 = clause.values[0]) !== null && _clause$values$4 !== void 0 ? _clause$values$4 : "") <= 0;
|
|
340
|
+
default: return true;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
/**
|
|
344
|
+
* Whether one row satisfies every clause.
|
|
345
|
+
*
|
|
346
|
+
* `searchable` is lazy because most queries are field constraints, and
|
|
347
|
+
* flattening a row to text for a term match nobody asked for is the expensive
|
|
348
|
+
* part of filtering a live tail.
|
|
349
|
+
*/
|
|
350
|
+
function matchesQuery(query, read, searchable) {
|
|
351
|
+
return query.clauses.every((clause) => {
|
|
352
|
+
const matched = matchesClause(clause, read, searchable);
|
|
353
|
+
return clause.negated ? !matched : matched;
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
/** Convenience for plain objects, including one level of nested properties. */
|
|
357
|
+
function matchesQueryObject(query, row, options = {}) {
|
|
358
|
+
const nested = options.nested ? row[options.nested] : void 0;
|
|
359
|
+
const read = (field) => field in row ? row[field] : nested === null || nested === void 0 ? void 0 : nested[field];
|
|
360
|
+
let cached = null;
|
|
361
|
+
const searchable = () => {
|
|
362
|
+
var _cached;
|
|
363
|
+
(_cached = cached) !== null && _cached !== void 0 || (cached = [...Object.values(row).filter((value) => typeof value !== "object"), ...Object.values(nested !== null && nested !== void 0 ? nested : {})].map(toText).join(" "));
|
|
364
|
+
return cached;
|
|
365
|
+
};
|
|
366
|
+
return matchesQuery(query, read, searchable);
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
export { EMPTY_QUERY, addClause, applyCellAction, describeClause, fieldValues, formatClause, formatQuery, isEmptyQuery, matchesQuery, matchesQueryObject, parseQuery, parseQueryWithDiagnostics, queryFields, quoteValue, removeClause, withFieldValue, withoutField };
|
|
370
|
+
|
|
371
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":[],"sources":["../src/lib/query/query-ast.ts","../src/lib/query/query-parse.ts","../src/lib/query/query-format.ts","../src/lib/query/query-match.ts"],"sourcesContent":["/**\n * A search query, as a value.\n *\n * Free-text search and a row of filter pickers answer different halves of the\n * same question, and neither can express the other: a picker cannot say \"not\n * this\", and a text box cannot say \"this field, exactly\". A parsed query is one\n * thing that does both, and — because it is a value rather than a widget's\n * internal state — it survives a URL, a saved view, and the trip to a server\n * that has to turn it into a database filter.\n *\n * The grammar follows the one analytics tools have converged on (Elastic's\n * `EuiSearchBar`, Kibana, GitHub's issue search), minus the parts that only\n * make sense against a Lucene index:\n *\n * error a term anywhere in the row\n * \"connection reset\" a phrase, kept together\n * -health a term that must not appear\n * path:/checkout a field equal to a value\n * path:(/a or /b) a field equal to any of several values\n * -status:200 a field that must not equal a value\n * status>=500 a comparison\n * path:*checkout* the field contains this somewhere\n * utm_source:* the field is present at all\n *\n * Clauses combine with AND; `or` only groups values inside one field. That is\n * a deliberate ceiling: arbitrary boolean nesting reads worse than it queries,\n * and every backend this targets can express the restricted form directly.\n */\n\n/** How a field clause compares. */\nexport type QueryOperator =\n | \"eq\"\n | \"contains\"\n | \"gt\"\n | \"gte\"\n | \"lt\"\n | \"lte\"\n | \"exists\";\n\n/** A bare or quoted word matched against the whole row. */\nexport interface QueryTermClause {\n type: \"term\";\n value: string;\n negated: boolean;\n}\n\n/** A constraint on one named field. Several values read as \"any of\". */\nexport interface QueryFieldClause {\n type: \"field\";\n field: string;\n operator: QueryOperator;\n values: string[];\n negated: boolean;\n}\n\nexport type QueryClause = QueryTermClause | QueryFieldClause;\n\n/** A parsed query: clauses combined with AND. */\nexport interface Query {\n clauses: QueryClause[];\n}\n\nexport const EMPTY_QUERY: Query = { clauses: [] };\n\n/** Whether a query constrains anything at all. */\nexport function isEmptyQuery(query: Query | null | undefined): boolean {\n return !query || query.clauses.length === 0;\n}\n\n/* ------------------------------------------------------------------ */\n/* Editing */\n/* ------------------------------------------------------------------ */\n\nfunction sameClause(a: QueryClause, b: QueryClause): boolean {\n if (a.type !== b.type) return false;\n if (a.type === \"term\" && b.type === \"term\") {\n return a.value === b.value && a.negated === b.negated;\n }\n const fieldA = a as QueryFieldClause;\n const fieldB = b as QueryFieldClause;\n return (\n fieldA.field === fieldB.field &&\n fieldA.operator === fieldB.operator &&\n fieldA.negated === fieldB.negated &&\n fieldA.values.length === fieldB.values.length &&\n fieldA.values.every((value, index) => value === fieldB.values[index])\n );\n}\n\n/** Add a clause unless an identical one is already there. */\nexport function addClause(query: Query, clause: QueryClause): Query {\n if (query.clauses.some((existing) => sameClause(existing, clause))) {\n return query;\n }\n return { clauses: [...query.clauses, clause] };\n}\n\n/**\n * Constrain a field to a value — what a cell's \"filter for this\" action does.\n *\n * Applying the opposite of a constraint already present replaces it rather than\n * adding it: `status:200` then \"filter out 200\" should read as one changed\n * mind, not as a query that can never match.\n */\nexport function withFieldValue(\n query: Query,\n field: string,\n value: string,\n options: { negated?: boolean; operator?: QueryOperator } = {},\n): Query {\n const negated = options.negated ?? false;\n const operator = options.operator ?? \"eq\";\n const clauses = query.clauses.filter(\n (clause) =>\n !(\n clause.type === \"field\" &&\n clause.field === field &&\n clause.operator === operator &&\n clause.values.length === 1 &&\n clause.values[0] === value\n ),\n );\n return {\n clauses: [\n ...clauses,\n { type: \"field\", field, operator, values: [value], negated },\n ],\n };\n}\n\n/** Drop every clause naming `field`. */\nexport function withoutField(query: Query, field: string): Query {\n return {\n clauses: query.clauses.filter(\n (clause) => clause.type !== \"field\" || clause.field !== field,\n ),\n };\n}\n\n/** Drop the clause at `index`, for a removable chip. */\nexport function removeClause(query: Query, index: number): Query {\n return { clauses: query.clauses.filter((_, at) => at !== index) };\n}\n\n/** Every field the query names, in the order they first appear. */\nexport function queryFields(query: Query): string[] {\n const seen: string[] = [];\n for (const clause of query.clauses) {\n if (clause.type === \"field\" && !seen.includes(clause.field)) {\n seen.push(clause.field);\n }\n }\n return seen;\n}\n\n/** The values a field is constrained to, if any — for showing a filter as active. */\nexport function fieldValues(query: Query, field: string): string[] {\n return query.clauses.flatMap((clause) =>\n clause.type === \"field\" && clause.field === field && !clause.negated\n ? clause.values\n : [],\n );\n}\n\n/* ------------------------------------------------------------------ */\n/* Cell actions */\n/* ------------------------------------------------------------------ */\n\n/** What a \"filter for / filter out\" control on a value asks for. */\nexport interface QueryCellAction {\n field: string;\n value: string;\n intent: \"include\" | \"exclude\";\n}\n\n/**\n * Fold a cell action into the query.\n *\n * The pair of buttons on a log cell is the fastest path from \"I noticed this\n * value\" to a filtered view, and the reason it works is that the result is a\n * query the reader can then see, edit and share — not a hidden filter stack\n * with its own rules.\n */\nexport function applyCellAction(query: Query, action: QueryCellAction): Query {\n return withFieldValue(query, action.field, action.value, {\n negated: action.intent === \"exclude\",\n });\n}\n","/**\n * Text in, {@link Query} out — and never an exception.\n *\n * A search box is edited one keystroke at a time, so most of the strings this\n * sees are half-written: `path:`, `status>`, `\"unclosed`. A parser that throws\n * on those turns every intermediate state into an error banner, which trains\n * people to type somewhere else and paste. Instead, anything that cannot be\n * read as a clause is kept as a plain term, and the caller is told separately\n * (via {@link parseQueryWithDiagnostics}) if it wants to warn.\n *\n * Written by hand rather than generated. EUI compiles a PEG grammar for the\n * same job, which buys arbitrary nesting we deliberately do not offer, at the\n * cost of a build-time plugin in every consumer.\n */\nimport type {\n Query,\n QueryClause,\n QueryFieldClause,\n QueryOperator,\n} from \"./query-ast\";\n\n/** A part of the input that could not be read as intended. */\nexport interface QueryDiagnostic {\n /** Character offset into the original string. */\n at: number;\n text: string;\n message: string;\n}\n\nexport interface ParsedQuery {\n query: Query;\n diagnostics: QueryDiagnostic[];\n /** The final clause is still being typed — the caller can complete it. */\n trailing: { field: string; operator: QueryOperator; value: string } | null;\n}\n\nconst FIELD_NAME = /^[A-Za-z_][A-Za-z0-9_.-]*$/;\n\ninterface Token {\n text: string;\n at: number;\n /** The token was written inside quotes, so it is a phrase, not syntax. */\n quoted: boolean;\n}\n\n/**\n * Split on whitespace, but keep quoted runs and parenthesised groups whole.\n *\n * An unterminated quote or group swallows the rest of the line rather than\n * erroring: the reader is mid-word, and the alternative is the query flickering\n * between two different meanings as they type the closing character.\n */\nfunction tokenize(input: string): Token[] {\n const tokens: Token[] = [];\n let index = 0;\n\n while (index < input.length) {\n while (index < input.length && /\\s/.test(input[index]!)) index += 1;\n if (index >= input.length) break;\n\n const start = index;\n let text = \"\";\n // Only a token that *opens* with a quote is a phrase. `name:\"a b\"` quotes\n // its value, not the constraint — reading the whole token as a phrase there\n // would turn a field filter into a search for the literal text.\n let quoted = false;\n let atTokenStart = true;\n let depth = 0;\n\n while (index < input.length) {\n const char = input[index]!;\n\n if (char === '\"') {\n if (atTokenStart) quoted = true;\n atTokenStart = false;\n index += 1;\n while (index < input.length && input[index] !== '\"') {\n text += input[index];\n index += 1;\n }\n index += 1; // closing quote, or the end of the input\n continue;\n }\n\n if (char === \"(\") depth += 1;\n if (char === \")\") depth = Math.max(0, depth - 1);\n if (/\\s/.test(char) && depth === 0) break;\n\n atTokenStart = false;\n text += char;\n index += 1;\n }\n\n if (text.length > 0 || quoted) tokens.push({ text, at: start, quoted });\n }\n\n return tokens;\n}\n\n/** `(a or b)` → `[\"a\", \"b\"]`; anything else is a single value. */\nfunction readValues(raw: string): string[] {\n const trimmed = raw.trim();\n if (!trimmed.startsWith(\"(\")) {\n return [stripQuotes(trimmed)];\n }\n const inner = trimmed.replace(/^\\(/, \"\").replace(/\\)$/, \"\");\n return inner\n .split(/\\s+or\\s+/i)\n .map((part) => stripQuotes(part.trim()))\n .filter((part) => part.length > 0);\n}\n\nfunction stripQuotes(value: string): string {\n if (value.length >= 2 && value.startsWith('\"') && value.endsWith('\"')) {\n return value.slice(1, -1);\n }\n return value.replace(/^\"/, \"\");\n}\n\nconst OPERATORS: { symbol: string; operator: QueryOperator }[] = [\n { symbol: \">=\", operator: \"gte\" },\n { symbol: \"<=\", operator: \"lte\" },\n { symbol: \":\", operator: \"eq\" },\n { symbol: \">\", operator: \"gt\" },\n { symbol: \"<\", operator: \"lt\" },\n];\n\n/** Where a field operator starts in the token, or `-1`. */\nfunction findOperator(\n text: string,\n): { index: number; symbol: string; operator: QueryOperator } | null {\n let best: { index: number; symbol: string; operator: QueryOperator } | null =\n null;\n for (const { symbol, operator } of OPERATORS) {\n const index = text.indexOf(symbol);\n if (index <= 0) continue;\n // `>=` must win over the `>` inside it.\n if (\n !best ||\n index < best.index ||\n (index === best.index && symbol.length > best.symbol.length)\n ) {\n best = { index, symbol, operator };\n }\n }\n return best;\n}\n\n/** Parse, keeping whatever could not be understood as a plain term. */\nexport function parseQueryWithDiagnostics(input: string): ParsedQuery {\n const diagnostics: QueryDiagnostic[] = [];\n const clauses: QueryClause[] = [];\n let trailing: ParsedQuery[\"trailing\"] = null;\n\n for (const token of tokenize(input)) {\n let text = token.text;\n let negated = false;\n\n if (!token.quoted && text.startsWith(\"-\") && text.length > 1) {\n negated = true;\n text = text.slice(1);\n }\n\n const operator = token.quoted ? null : findOperator(text);\n if (!operator) {\n if (text.length > 0) clauses.push({ type: \"term\", value: text, negated });\n continue;\n }\n\n const field = text.slice(0, operator.index);\n const rawValue = text.slice(operator.index + operator.symbol.length);\n\n if (!FIELD_NAME.test(field)) {\n // `12:30` is a time, not a field constraint.\n clauses.push({ type: \"term\", value: text, negated });\n continue;\n }\n\n if (rawValue === \"\") {\n // Mid-typing: remember it so the caller can offer completions, but do not\n // constrain anything yet — an empty value would match nothing.\n trailing = { field, operator: operator.operator, value: \"\" };\n continue;\n }\n\n if (rawValue === \"*\" && operator.operator === \"eq\") {\n clauses.push({\n type: \"field\",\n field,\n operator: \"exists\",\n values: [],\n negated,\n });\n continue;\n }\n\n // `path:*checkout*` — the wildcards are the syntax, the middle is the value.\n // Distinct from a bare `*`, which asks whether the field is there at all.\n if (\n operator.operator === \"eq\" &&\n rawValue.length > 2 &&\n rawValue.startsWith(\"*\") &&\n rawValue.endsWith(\"*\")\n ) {\n clauses.push({\n type: \"field\",\n field,\n operator: \"contains\",\n values: [stripQuotes(rawValue.slice(1, -1))],\n negated,\n });\n continue;\n }\n\n const values = readValues(rawValue);\n if (values.length === 0) {\n diagnostics.push({\n at: token.at,\n text: token.text,\n message: \"Empty value group\",\n });\n continue;\n }\n\n if (operator.operator !== \"eq\" && values.length > 1) {\n diagnostics.push({\n at: token.at,\n text: token.text,\n message: \"A comparison takes a single value\",\n });\n }\n\n const clause: QueryFieldClause = {\n type: \"field\",\n field,\n operator: operator.operator,\n values: operator.operator === \"eq\" ? values : values.slice(0, 1),\n negated,\n };\n clauses.push(clause);\n if (!rawValue.startsWith(\"(\")) {\n trailing = { field, operator: operator.operator, value: values[0] ?? \"\" };\n }\n }\n\n return { query: { clauses }, diagnostics, trailing };\n}\n\n/** Parse, discarding diagnostics. */\nexport function parseQuery(input: string): Query {\n return parseQueryWithDiagnostics(input).query;\n}\n","/**\n * {@link Query} back to text, so the box shows what the chips mean.\n *\n * Round-tripping matters more than prettiness: whatever a cell action or a\n * sidebar click adds to the query has to appear in the input as something the\n * reader could have typed themselves, and could edit by hand from there.\n */\nimport type { Query, QueryClause, QueryOperator } from \"./query-ast\";\n\nconst OPERATOR_SYMBOL: Record<QueryOperator, string> = {\n eq: \":\",\n contains: \":\",\n gt: \">\",\n gte: \">=\",\n lt: \"<\",\n lte: \"<=\",\n exists: \":\",\n};\n\n/** Quote a value that would otherwise re-parse as something else. */\nexport function quoteValue(value: string): string {\n return /[\\s\"()]/.test(value) || value === \"\"\n ? `\"${value.replace(/\"/g, \"\")}\"`\n : value;\n}\n\n/** One clause as text. */\nexport function formatClause(clause: QueryClause): string {\n const prefix = clause.negated ? \"-\" : \"\";\n if (clause.type === \"term\") return `${prefix}${quoteValue(clause.value)}`;\n if (clause.operator === \"exists\") return `${prefix}${clause.field}:*`;\n if (clause.operator === \"contains\") {\n return `${prefix}${clause.field}:*${clause.values[0] ?? \"\"}*`;\n }\n\n const symbol = OPERATOR_SYMBOL[clause.operator];\n if (clause.values.length > 1) {\n return `${prefix}${clause.field}${symbol}(${clause.values.map(quoteValue).join(\" or \")})`;\n }\n return `${prefix}${clause.field}${symbol}${quoteValue(clause.values[0] ?? \"\")}`;\n}\n\n/** The whole query as text. */\nexport function formatQuery(query: Query): string {\n return query.clauses.map(formatClause).join(\" \");\n}\n\n/** A short human label for a clause chip. */\nexport function describeClause(clause: QueryClause): string {\n if (clause.type === \"term\") {\n return clause.negated ? `not \"${clause.value}\"` : `\"${clause.value}\"`;\n }\n if (clause.operator === \"exists\") {\n return clause.negated\n ? `${clause.field} missing`\n : `${clause.field} present`;\n }\n if (clause.operator === \"contains\") {\n return `${clause.negated ? \"not \" : \"\"}${clause.field} ⊃ ${clause.values[0] ?? \"\"}`;\n }\n const values = clause.values.join(\", \");\n const symbol = OPERATOR_SYMBOL[clause.operator];\n return `${clause.negated ? \"not \" : \"\"}${clause.field} ${symbol === \":\" ? \"=\" : symbol} ${values}`;\n}\n","/**\n * Run a {@link Query} against rows already in the browser.\n *\n * The server does the real filtering; this exists for the cases where asking it\n * would be worse than answering locally — live rows arriving over a socket that\n * must be held to the same filter as the page they land on, and a preview of\n * what a clause will do before it is committed.\n *\n * Keeping both readings in one module is the point: a query that means one\n * thing in the list and another in the live tail is a bug nobody reports,\n * because each half looks right on its own.\n */\nimport type { Query, QueryClause } from \"./query-ast\";\n\n/** How a row exposes its values. Missing fields return `undefined`. */\nexport type FieldReader = (field: string) => unknown;\n\nfunction toText(value: unknown): string {\n if (value === null || value === undefined) return \"\";\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\" || typeof value === \"boolean\")\n return String(value);\n return JSON.stringify(value);\n}\n\nfunction compare(left: unknown, right: string): number {\n const leftNumber = typeof left === \"number\" ? left : Number(toText(left));\n const rightNumber = Number(right);\n if (Number.isFinite(leftNumber) && Number.isFinite(rightNumber)) {\n return leftNumber - rightNumber;\n }\n return toText(left).localeCompare(right);\n}\n\nfunction matchesClause(\n clause: QueryClause,\n read: FieldReader,\n searchable: () => string,\n): boolean {\n if (clause.type === \"term\") {\n return searchable().toLowerCase().includes(clause.value.toLowerCase());\n }\n\n const value = read(clause.field);\n\n switch (clause.operator) {\n case \"exists\":\n return value !== undefined && value !== null && value !== \"\";\n case \"eq\":\n // Case-insensitive: a log reader typing `path:/Checkout` means the path.\n return clause.values.some(\n (candidate) => toText(value).toLowerCase() === candidate.toLowerCase(),\n );\n case \"contains\":\n return clause.values.some((candidate) =>\n toText(value).toLowerCase().includes(candidate.toLowerCase()),\n );\n case \"gt\":\n return compare(value, clause.values[0] ?? \"\") > 0;\n case \"gte\":\n return compare(value, clause.values[0] ?? \"\") >= 0;\n case \"lt\":\n return compare(value, clause.values[0] ?? \"\") < 0;\n case \"lte\":\n return compare(value, clause.values[0] ?? \"\") <= 0;\n default:\n return true;\n }\n}\n\n/**\n * Whether one row satisfies every clause.\n *\n * `searchable` is lazy because most queries are field constraints, and\n * flattening a row to text for a term match nobody asked for is the expensive\n * part of filtering a live tail.\n */\nexport function matchesQuery(\n query: Query,\n read: FieldReader,\n searchable: () => string,\n): boolean {\n return query.clauses.every((clause) => {\n const matched = matchesClause(clause, read, searchable);\n return clause.negated ? !matched : matched;\n });\n}\n\n/** Convenience for plain objects, including one level of nested properties. */\nexport function matchesQueryObject(\n query: Query,\n row: Record<string, unknown>,\n options: { nested?: string } = {},\n): boolean {\n const nested = options.nested\n ? (row[options.nested] as Record<string, unknown> | undefined)\n : undefined;\n const read: FieldReader = (field) =>\n field in row ? row[field] : nested?.[field];\n let cached: string | null = null;\n const searchable = () => {\n cached ??= [\n ...Object.values(row).filter((value) => typeof value !== \"object\"),\n ...Object.values(nested ?? {}),\n ]\n .map(toText)\n .join(\" \");\n return cached;\n };\n return matchesQuery(query, read, searchable);\n}\n"],"mappings":"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"}
|