@reopt-ai/opt-ui 1.6.0 → 1.8.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 +96 -13
- package/dist/app.css +24 -0
- package/dist/core/index.cjs +3 -3
- package/dist/core/index.d.cts +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +2 -2
- package/dist/docs/02-components/01-core.md +1 -1
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +105 -12
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +4 -4
- package/dist/docs/05-migration/01-breaking-changes.md +18 -1
- package/dist/field-sidebar-5KYVlpSs.d.ts +1041 -0
- package/dist/field-sidebar-5KYVlpSs.d.ts.map +1 -0
- package/dist/field-sidebar-BP0iWkoV.d.cts +1041 -0
- package/dist/field-sidebar-BP0iWkoV.d.cts.map +1 -0
- package/dist/{workflow-canvas-BGBAcjf-.js → field-sidebar-BYD_UIjG.js} +1909 -52
- package/dist/field-sidebar-BYD_UIjG.js.map +1 -0
- package/dist/{workflow-canvas-3Jeevkgy.cjs → field-sidebar-Bqfp6dgg.cjs} +2037 -48
- package/dist/id-registry.cjs +41 -1
- package/dist/id-registry.d.cts +1 -1
- package/dist/id-registry.d.cts.map +1 -1
- package/dist/id-registry.d.ts +1 -1
- package/dist/id-registry.d.ts.map +1 -1
- package/dist/id-registry.js +41 -1
- package/dist/id-registry.js.map +1 -1
- package/dist/id-registry.json +85 -0
- package/dist/index.cjs +142 -56
- package/dist/index.d.cts +149 -80
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +149 -80
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -11
- package/dist/index.js.map +1 -1
- package/dist/meta.cjs +292 -0
- package/dist/meta.d.cts.map +1 -1
- package/dist/meta.d.ts.map +1 -1
- package/dist/meta.js +292 -0
- package/dist/meta.js.map +1 -1
- package/dist/{pagination-By7Ozv3U.js → pagination-DWbvACuN.js} +93 -31
- package/dist/pagination-DWbvACuN.js.map +1 -0
- package/dist/{pagination-Uv3CnGJz.cjs → pagination-DjfxBcjX.cjs} +98 -36
- 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-BWhO2xkk.js → shader-surface-BUPZ0P-n.js} +22 -83
- package/dist/shader-surface-BUPZ0P-n.js.map +1 -0
- package/dist/{shader-surface-DIVWtpFl.d.ts → shader-surface-BfTP9c1Q.d.ts} +139 -139
- package/dist/shader-surface-BfTP9c1Q.d.ts.map +1 -0
- package/dist/{shader-surface-BK1GMt5_.d.cts → shader-surface-DRrcLrWf.d.cts} +139 -139
- package/dist/shader-surface-DRrcLrWf.d.cts.map +1 -0
- package/dist/{shader-surface-DxLLfrE6.cjs → shader-surface-Fav5K9UC.cjs} +20 -87
- package/dist/shells/index.cjs +31 -24
- package/dist/shells/index.d.cts +2 -2
- package/dist/shells/index.d.ts +2 -2
- package/dist/shells/index.js +3 -2
- 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/package.json +17 -6
- package/dist/pagination-By7Ozv3U.js.map +0 -1
- package/dist/shader-surface-BK1GMt5_.d.cts.map +0 -1
- package/dist/shader-surface-BWhO2xkk.js.map +0 -1
- package/dist/shader-surface-DIVWtpFl.d.ts.map +0 -1
- package/dist/workflow-canvas-BGBAcjf-.js.map +0 -1
- package/dist/workflow-canvas-Bar_O2SQ.d.ts +0 -494
- package/dist/workflow-canvas-Bar_O2SQ.d.ts.map +0 -1
- package/dist/workflow-canvas-DghDRtHn.d.cts +0 -494
- package/dist/workflow-canvas-DghDRtHn.d.cts.map +0 -1
package/dist/query.d.cts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//#region src/lib/query/query-ast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A search query, as a value.
|
|
4
|
+
*
|
|
5
|
+
* Free-text search and a row of filter pickers answer different halves of the
|
|
6
|
+
* same question, and neither can express the other: a picker cannot say "not
|
|
7
|
+
* this", and a text box cannot say "this field, exactly". A parsed query is one
|
|
8
|
+
* thing that does both, and — because it is a value rather than a widget's
|
|
9
|
+
* internal state — it survives a URL, a saved view, and the trip to a server
|
|
10
|
+
* that has to turn it into a database filter.
|
|
11
|
+
*
|
|
12
|
+
* The grammar follows the one analytics tools have converged on (Elastic's
|
|
13
|
+
* `EuiSearchBar`, Kibana, GitHub's issue search), minus the parts that only
|
|
14
|
+
* make sense against a Lucene index:
|
|
15
|
+
*
|
|
16
|
+
* error a term anywhere in the row
|
|
17
|
+
* "connection reset" a phrase, kept together
|
|
18
|
+
* -health a term that must not appear
|
|
19
|
+
* path:/checkout a field equal to a value
|
|
20
|
+
* path:(/a or /b) a field equal to any of several values
|
|
21
|
+
* -status:200 a field that must not equal a value
|
|
22
|
+
* status>=500 a comparison
|
|
23
|
+
* path:*checkout* the field contains this somewhere
|
|
24
|
+
* utm_source:* the field is present at all
|
|
25
|
+
*
|
|
26
|
+
* Clauses combine with AND; `or` only groups values inside one field. That is
|
|
27
|
+
* a deliberate ceiling: arbitrary boolean nesting reads worse than it queries,
|
|
28
|
+
* and every backend this targets can express the restricted form directly.
|
|
29
|
+
*/
|
|
30
|
+
/** How a field clause compares. */
|
|
31
|
+
type QueryOperator = "eq" | "contains" | "gt" | "gte" | "lt" | "lte" | "exists";
|
|
32
|
+
/** A bare or quoted word matched against the whole row. */
|
|
33
|
+
interface QueryTermClause {
|
|
34
|
+
type: "term";
|
|
35
|
+
value: string;
|
|
36
|
+
negated: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A constraint on one named field. Several values read as "any of". */
|
|
39
|
+
interface QueryFieldClause {
|
|
40
|
+
type: "field";
|
|
41
|
+
field: string;
|
|
42
|
+
operator: QueryOperator;
|
|
43
|
+
values: string[];
|
|
44
|
+
negated: boolean;
|
|
45
|
+
}
|
|
46
|
+
type QueryClause = QueryTermClause | QueryFieldClause;
|
|
47
|
+
/** A parsed query: clauses combined with AND. */
|
|
48
|
+
interface Query {
|
|
49
|
+
clauses: QueryClause[];
|
|
50
|
+
}
|
|
51
|
+
declare const EMPTY_QUERY: Query;
|
|
52
|
+
/** Whether a query constrains anything at all. */
|
|
53
|
+
declare function isEmptyQuery(query: Query | null | undefined): boolean;
|
|
54
|
+
/** Add a clause unless an identical one is already there. */
|
|
55
|
+
declare function addClause(query: Query, clause: QueryClause): Query;
|
|
56
|
+
/**
|
|
57
|
+
* Constrain a field to a value — what a cell's "filter for this" action does.
|
|
58
|
+
*
|
|
59
|
+
* Applying the opposite of a constraint already present replaces it rather than
|
|
60
|
+
* adding it: `status:200` then "filter out 200" should read as one changed
|
|
61
|
+
* mind, not as a query that can never match.
|
|
62
|
+
*/
|
|
63
|
+
declare function withFieldValue(query: Query, field: string, value: string, options?: {
|
|
64
|
+
negated?: boolean;
|
|
65
|
+
operator?: QueryOperator;
|
|
66
|
+
}): Query;
|
|
67
|
+
/** Drop every clause naming `field`. */
|
|
68
|
+
declare function withoutField(query: Query, field: string): Query;
|
|
69
|
+
/** Drop the clause at `index`, for a removable chip. */
|
|
70
|
+
declare function removeClause(query: Query, index: number): Query;
|
|
71
|
+
/** Every field the query names, in the order they first appear. */
|
|
72
|
+
declare function queryFields(query: Query): string[];
|
|
73
|
+
/** The values a field is constrained to, if any — for showing a filter as active. */
|
|
74
|
+
declare function fieldValues(query: Query, field: string): string[];
|
|
75
|
+
/** What a "filter for / filter out" control on a value asks for. */
|
|
76
|
+
interface QueryCellAction {
|
|
77
|
+
field: string;
|
|
78
|
+
value: string;
|
|
79
|
+
intent: "include" | "exclude";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Fold a cell action into the query.
|
|
83
|
+
*
|
|
84
|
+
* The pair of buttons on a log cell is the fastest path from "I noticed this
|
|
85
|
+
* value" to a filtered view, and the reason it works is that the result is a
|
|
86
|
+
* query the reader can then see, edit and share — not a hidden filter stack
|
|
87
|
+
* with its own rules.
|
|
88
|
+
*/
|
|
89
|
+
declare function applyCellAction(query: Query, action: QueryCellAction): Query;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/lib/query/query-parse.d.ts
|
|
92
|
+
/** A part of the input that could not be read as intended. */
|
|
93
|
+
interface QueryDiagnostic {
|
|
94
|
+
/** Character offset into the original string. */
|
|
95
|
+
at: number;
|
|
96
|
+
text: string;
|
|
97
|
+
message: string;
|
|
98
|
+
}
|
|
99
|
+
interface ParsedQuery {
|
|
100
|
+
query: Query;
|
|
101
|
+
diagnostics: QueryDiagnostic[];
|
|
102
|
+
/** The final clause is still being typed — the caller can complete it. */
|
|
103
|
+
trailing: {
|
|
104
|
+
field: string;
|
|
105
|
+
operator: QueryOperator;
|
|
106
|
+
value: string;
|
|
107
|
+
} | null;
|
|
108
|
+
}
|
|
109
|
+
/** Parse, keeping whatever could not be understood as a plain term. */
|
|
110
|
+
declare function parseQueryWithDiagnostics(input: string): ParsedQuery;
|
|
111
|
+
/** Parse, discarding diagnostics. */
|
|
112
|
+
declare function parseQuery(input: string): Query;
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/lib/query/query-format.d.ts
|
|
115
|
+
/** Quote a value that would otherwise re-parse as something else. */
|
|
116
|
+
declare function quoteValue(value: string): string;
|
|
117
|
+
/** One clause as text. */
|
|
118
|
+
declare function formatClause(clause: QueryClause): string;
|
|
119
|
+
/** The whole query as text. */
|
|
120
|
+
declare function formatQuery(query: Query): string;
|
|
121
|
+
/** A short human label for a clause chip. */
|
|
122
|
+
declare function describeClause(clause: QueryClause): string;
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/lib/query/query-match.d.ts
|
|
125
|
+
/** How a row exposes its values. Missing fields return `undefined`. */
|
|
126
|
+
type FieldReader = (field: string) => unknown;
|
|
127
|
+
/**
|
|
128
|
+
* Whether one row satisfies every clause.
|
|
129
|
+
*
|
|
130
|
+
* `searchable` is lazy because most queries are field constraints, and
|
|
131
|
+
* flattening a row to text for a term match nobody asked for is the expensive
|
|
132
|
+
* part of filtering a live tail.
|
|
133
|
+
*/
|
|
134
|
+
declare function matchesQuery(query: Query, read: FieldReader, searchable: () => string): boolean;
|
|
135
|
+
/** Convenience for plain objects, including one level of nested properties. */
|
|
136
|
+
declare function matchesQueryObject(query: Query, row: Record<string, unknown>, options?: {
|
|
137
|
+
nested?: string;
|
|
138
|
+
}): boolean;
|
|
139
|
+
//#endregion
|
|
140
|
+
export { EMPTY_QUERY, type FieldReader, type ParsedQuery, type Query, type QueryCellAction, type QueryClause, type QueryDiagnostic, type QueryFieldClause, type QueryOperator, type QueryTermClause, addClause, applyCellAction, describeClause, fieldValues, formatClause, formatQuery, isEmptyQuery, matchesQuery, matchesQueryObject, parseQuery, parseQueryWithDiagnostics, queryFields, quoteValue, removeClause, withFieldValue, withoutField };
|
|
141
|
+
//# sourceMappingURL=query.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.cts","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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8BY;;UAUK;EACf;EACA;EACA;;;UAIe;EACf;EACA;EACA,UAAU;EACV;EACA;;KAGU,cAAc,kBAAkB;;UAG3B;EACf,SAAS;;cAGE,aAAa;;iBAGV,aAAa,OAAO;;iBAyBpB,UAAU,OAAO,OAAO,QAAQ,cAAc;;;;;;;;iBAc9C,eACd,OAAO,OACP,eACA,eACA;EAAW;EAAmB,WAAW;IACxC;;iBAsBa,aAAa,OAAO,OAAO,gBAAgB;;iBAS3C,aAAa,OAAO,OAAO,gBAAgB;;iBAK3C,YAAY,OAAO;;iBAWnB,YAAY,OAAO,OAAO;;UAazB;EACf;EACA;EACA;;;;;;;;;;iBAWc,gBAAgB,OAAO,OAAO,QAAQ,kBAAkB;;;;UCjKvD;;EAEf;EACA;EACA;;UAGe;EACf,OAAO;EACP,aAAa;;EAEb;IAAY;IAAe,UAAU;IAAe;;;;iBAoHtC,0BAA0B,gBAAgB;;iBAoG1C,WAAW,gBAAgB;;;;iBCrO3B,WAAW;;iBAOX,aAAa,QAAQ;;iBAgBrB,YAAY,OAAO;;iBAKnB,eAAe,QAAQ;;;;KCjC3B,eAAe;;;;;;;;iBA8DX,aACd,OAAO,OACP,MAAM,aACN;;iBASc,mBACd,OAAO,OACP,KAAK,yBACL;EAAW"}
|
package/dist/query.d.ts
ADDED
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
//#region src/lib/query/query-ast.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* A search query, as a value.
|
|
4
|
+
*
|
|
5
|
+
* Free-text search and a row of filter pickers answer different halves of the
|
|
6
|
+
* same question, and neither can express the other: a picker cannot say "not
|
|
7
|
+
* this", and a text box cannot say "this field, exactly". A parsed query is one
|
|
8
|
+
* thing that does both, and — because it is a value rather than a widget's
|
|
9
|
+
* internal state — it survives a URL, a saved view, and the trip to a server
|
|
10
|
+
* that has to turn it into a database filter.
|
|
11
|
+
*
|
|
12
|
+
* The grammar follows the one analytics tools have converged on (Elastic's
|
|
13
|
+
* `EuiSearchBar`, Kibana, GitHub's issue search), minus the parts that only
|
|
14
|
+
* make sense against a Lucene index:
|
|
15
|
+
*
|
|
16
|
+
* error a term anywhere in the row
|
|
17
|
+
* "connection reset" a phrase, kept together
|
|
18
|
+
* -health a term that must not appear
|
|
19
|
+
* path:/checkout a field equal to a value
|
|
20
|
+
* path:(/a or /b) a field equal to any of several values
|
|
21
|
+
* -status:200 a field that must not equal a value
|
|
22
|
+
* status>=500 a comparison
|
|
23
|
+
* path:*checkout* the field contains this somewhere
|
|
24
|
+
* utm_source:* the field is present at all
|
|
25
|
+
*
|
|
26
|
+
* Clauses combine with AND; `or` only groups values inside one field. That is
|
|
27
|
+
* a deliberate ceiling: arbitrary boolean nesting reads worse than it queries,
|
|
28
|
+
* and every backend this targets can express the restricted form directly.
|
|
29
|
+
*/
|
|
30
|
+
/** How a field clause compares. */
|
|
31
|
+
type QueryOperator = "eq" | "contains" | "gt" | "gte" | "lt" | "lte" | "exists";
|
|
32
|
+
/** A bare or quoted word matched against the whole row. */
|
|
33
|
+
interface QueryTermClause {
|
|
34
|
+
type: "term";
|
|
35
|
+
value: string;
|
|
36
|
+
negated: boolean;
|
|
37
|
+
}
|
|
38
|
+
/** A constraint on one named field. Several values read as "any of". */
|
|
39
|
+
interface QueryFieldClause {
|
|
40
|
+
type: "field";
|
|
41
|
+
field: string;
|
|
42
|
+
operator: QueryOperator;
|
|
43
|
+
values: string[];
|
|
44
|
+
negated: boolean;
|
|
45
|
+
}
|
|
46
|
+
type QueryClause = QueryTermClause | QueryFieldClause;
|
|
47
|
+
/** A parsed query: clauses combined with AND. */
|
|
48
|
+
interface Query {
|
|
49
|
+
clauses: QueryClause[];
|
|
50
|
+
}
|
|
51
|
+
declare const EMPTY_QUERY: Query;
|
|
52
|
+
/** Whether a query constrains anything at all. */
|
|
53
|
+
declare function isEmptyQuery(query: Query | null | undefined): boolean;
|
|
54
|
+
/** Add a clause unless an identical one is already there. */
|
|
55
|
+
declare function addClause(query: Query, clause: QueryClause): Query;
|
|
56
|
+
/**
|
|
57
|
+
* Constrain a field to a value — what a cell's "filter for this" action does.
|
|
58
|
+
*
|
|
59
|
+
* Applying the opposite of a constraint already present replaces it rather than
|
|
60
|
+
* adding it: `status:200` then "filter out 200" should read as one changed
|
|
61
|
+
* mind, not as a query that can never match.
|
|
62
|
+
*/
|
|
63
|
+
declare function withFieldValue(query: Query, field: string, value: string, options?: {
|
|
64
|
+
negated?: boolean;
|
|
65
|
+
operator?: QueryOperator;
|
|
66
|
+
}): Query;
|
|
67
|
+
/** Drop every clause naming `field`. */
|
|
68
|
+
declare function withoutField(query: Query, field: string): Query;
|
|
69
|
+
/** Drop the clause at `index`, for a removable chip. */
|
|
70
|
+
declare function removeClause(query: Query, index: number): Query;
|
|
71
|
+
/** Every field the query names, in the order they first appear. */
|
|
72
|
+
declare function queryFields(query: Query): string[];
|
|
73
|
+
/** The values a field is constrained to, if any — for showing a filter as active. */
|
|
74
|
+
declare function fieldValues(query: Query, field: string): string[];
|
|
75
|
+
/** What a "filter for / filter out" control on a value asks for. */
|
|
76
|
+
interface QueryCellAction {
|
|
77
|
+
field: string;
|
|
78
|
+
value: string;
|
|
79
|
+
intent: "include" | "exclude";
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Fold a cell action into the query.
|
|
83
|
+
*
|
|
84
|
+
* The pair of buttons on a log cell is the fastest path from "I noticed this
|
|
85
|
+
* value" to a filtered view, and the reason it works is that the result is a
|
|
86
|
+
* query the reader can then see, edit and share — not a hidden filter stack
|
|
87
|
+
* with its own rules.
|
|
88
|
+
*/
|
|
89
|
+
declare function applyCellAction(query: Query, action: QueryCellAction): Query;
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/lib/query/query-parse.d.ts
|
|
92
|
+
/** A part of the input that could not be read as intended. */
|
|
93
|
+
interface QueryDiagnostic {
|
|
94
|
+
/** Character offset into the original string. */
|
|
95
|
+
at: number;
|
|
96
|
+
text: string;
|
|
97
|
+
message: string;
|
|
98
|
+
}
|
|
99
|
+
interface ParsedQuery {
|
|
100
|
+
query: Query;
|
|
101
|
+
diagnostics: QueryDiagnostic[];
|
|
102
|
+
/** The final clause is still being typed — the caller can complete it. */
|
|
103
|
+
trailing: {
|
|
104
|
+
field: string;
|
|
105
|
+
operator: QueryOperator;
|
|
106
|
+
value: string;
|
|
107
|
+
} | null;
|
|
108
|
+
}
|
|
109
|
+
/** Parse, keeping whatever could not be understood as a plain term. */
|
|
110
|
+
declare function parseQueryWithDiagnostics(input: string): ParsedQuery;
|
|
111
|
+
/** Parse, discarding diagnostics. */
|
|
112
|
+
declare function parseQuery(input: string): Query;
|
|
113
|
+
//#endregion
|
|
114
|
+
//#region src/lib/query/query-format.d.ts
|
|
115
|
+
/** Quote a value that would otherwise re-parse as something else. */
|
|
116
|
+
declare function quoteValue(value: string): string;
|
|
117
|
+
/** One clause as text. */
|
|
118
|
+
declare function formatClause(clause: QueryClause): string;
|
|
119
|
+
/** The whole query as text. */
|
|
120
|
+
declare function formatQuery(query: Query): string;
|
|
121
|
+
/** A short human label for a clause chip. */
|
|
122
|
+
declare function describeClause(clause: QueryClause): string;
|
|
123
|
+
//#endregion
|
|
124
|
+
//#region src/lib/query/query-match.d.ts
|
|
125
|
+
/** How a row exposes its values. Missing fields return `undefined`. */
|
|
126
|
+
type FieldReader = (field: string) => unknown;
|
|
127
|
+
/**
|
|
128
|
+
* Whether one row satisfies every clause.
|
|
129
|
+
*
|
|
130
|
+
* `searchable` is lazy because most queries are field constraints, and
|
|
131
|
+
* flattening a row to text for a term match nobody asked for is the expensive
|
|
132
|
+
* part of filtering a live tail.
|
|
133
|
+
*/
|
|
134
|
+
declare function matchesQuery(query: Query, read: FieldReader, searchable: () => string): boolean;
|
|
135
|
+
/** Convenience for plain objects, including one level of nested properties. */
|
|
136
|
+
declare function matchesQueryObject(query: Query, row: Record<string, unknown>, options?: {
|
|
137
|
+
nested?: string;
|
|
138
|
+
}): boolean;
|
|
139
|
+
//#endregion
|
|
140
|
+
export { EMPTY_QUERY, type FieldReader, type ParsedQuery, type Query, type QueryCellAction, type QueryClause, type QueryDiagnostic, type QueryFieldClause, type QueryOperator, type QueryTermClause, addClause, applyCellAction, describeClause, fieldValues, formatClause, formatQuery, isEmptyQuery, matchesQuery, matchesQueryObject, parseQuery, parseQueryWithDiagnostics, queryFields, quoteValue, removeClause, withFieldValue, withoutField };
|
|
141
|
+
//# sourceMappingURL=query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.d.ts","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"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;KA8BY;;UAUK;EACf;EACA;EACA;;;UAIe;EACf;EACA;EACA,UAAU;EACV;EACA;;KAGU,cAAc,kBAAkB;;UAG3B;EACf,SAAS;;cAGE,aAAa;;iBAGV,aAAa,OAAO;;iBAyBpB,UAAU,OAAO,OAAO,QAAQ,cAAc;;;;;;;;iBAc9C,eACd,OAAO,OACP,eACA,eACA;EAAW;EAAmB,WAAW;IACxC;;iBAsBa,aAAa,OAAO,OAAO,gBAAgB;;iBAS3C,aAAa,OAAO,OAAO,gBAAgB;;iBAK3C,YAAY,OAAO;;iBAWnB,YAAY,OAAO,OAAO;;UAazB;EACf;EACA;EACA;;;;;;;;;;iBAWc,gBAAgB,OAAO,OAAO,QAAQ,kBAAkB;;;;UCjKvD;;EAEf;EACA;EACA;;UAGe;EACf,OAAO;EACP,aAAa;;EAEb;IAAY;IAAe,UAAU;IAAe;;;;iBAoHtC,0BAA0B,gBAAgB;;iBAoG1C,WAAW,gBAAgB;;;;iBCrO3B,WAAW;;iBAOX,aAAa,QAAQ;;iBAgBrB,YAAY,OAAO;;iBAKnB,eAAe,QAAQ;;;;KCjC3B,eAAe;;;;;;;;iBA8DX,aACd,OAAO,OACP,MAAM,aACN;;iBASc,mBACd,OAAO,OACP,KAAK,yBACL;EAAW"}
|
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
|