@osqd/jql 0.1.1
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/CHANGELOG.md +220 -0
- package/LICENSE +102 -0
- package/README.md +84 -0
- package/bin/jql.mjs +5 -0
- package/conformance/cases.json +290 -0
- package/dist/array.d.ts +11 -0
- package/dist/async.d.ts +44 -0
- package/dist/canonical.d.ts +18 -0
- package/dist/cjs/array.d.ts +11 -0
- package/dist/cjs/async.d.ts +44 -0
- package/dist/cjs/canonical.d.ts +18 -0
- package/dist/cjs/cli.d.ts +15 -0
- package/dist/cjs/collections.d.ts +34 -0
- package/dist/cjs/core.d.ts +117 -0
- package/dist/cjs/errors.d.ts +17 -0
- package/dist/cjs/explain.d.ts +30 -0
- package/dist/cjs/global.d.ts +90 -0
- package/dist/cjs/group.d.ts +47 -0
- package/dist/cjs/index.d.ts +25 -0
- package/dist/cjs/internal/closest.d.ts +9 -0
- package/dist/cjs/internal/duration.d.ts +15 -0
- package/dist/cjs/internal/equal.d.ts +34 -0
- package/dist/cjs/internal/glob.d.ts +30 -0
- package/dist/cjs/internal/order.d.ts +24 -0
- package/dist/cjs/internal/path.d.ts +109 -0
- package/dist/cjs/internal/record.d.ts +18 -0
- package/dist/cjs/internal/values.d.ts +41 -0
- package/dist/cjs/limits.d.ts +64 -0
- package/dist/cjs/operators.d.ts +81 -0
- package/dist/cjs/package.json +3 -0
- package/dist/cjs/plan.d.ts +79 -0
- package/dist/cjs/search.d.ts +42 -0
- package/dist/cjs/targets/mongo.d.ts +55 -0
- package/dist/cjs/text/index.d.ts +12 -0
- package/dist/cjs/text/parse.d.ts +91 -0
- package/dist/cjs/text/suggest.d.ts +16 -0
- package/dist/cjs/text/write.d.ts +34 -0
- package/dist/cjs/types.d.ts +236 -0
- package/dist/cjs/vocabulary.d.ts +106 -0
- package/dist/cli.d.ts +15 -0
- package/dist/cli.js +2729 -0
- package/dist/cli.js.map +1 -0
- package/dist/collections.d.ts +34 -0
- package/dist/core.d.ts +117 -0
- package/dist/errors.d.ts +17 -0
- package/dist/explain.d.ts +30 -0
- package/dist/global.cjs +1953 -0
- package/dist/global.cjs.map +1 -0
- package/dist/global.d.ts +90 -0
- package/dist/global.js +1950 -0
- package/dist/global.js.map +1 -0
- package/dist/group.d.ts +47 -0
- package/dist/index.cjs +2529 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +2495 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/closest.d.ts +9 -0
- package/dist/internal/duration.d.ts +15 -0
- package/dist/internal/equal.d.ts +34 -0
- package/dist/internal/glob.d.ts +30 -0
- package/dist/internal/order.d.ts +24 -0
- package/dist/internal/path.d.ts +109 -0
- package/dist/internal/record.d.ts +18 -0
- package/dist/internal/values.d.ts +41 -0
- package/dist/limits.d.ts +64 -0
- package/dist/mongo.cjs +357 -0
- package/dist/mongo.cjs.map +1 -0
- package/dist/mongo.js +354 -0
- package/dist/mongo.js.map +1 -0
- package/dist/operators.d.ts +81 -0
- package/dist/plan.d.ts +79 -0
- package/dist/search.d.ts +42 -0
- package/dist/targets/mongo.d.ts +55 -0
- package/dist/text/index.d.ts +12 -0
- package/dist/text/parse.d.ts +91 -0
- package/dist/text/suggest.d.ts +16 -0
- package/dist/text/write.d.ts +34 -0
- package/dist/text.cjs +674 -0
- package/dist/text.cjs.map +1 -0
- package/dist/text.js +667 -0
- package/dist/text.js.map +1 -0
- package/dist/types.d.ts +236 -0
- package/dist/vocabulary.d.ts +106 -0
- package/docs/course/01-first-query.md +217 -0
- package/docs/course/02-operators.md +285 -0
- package/docs/course/03-arrays-and-paths.md +239 -0
- package/docs/course/04-combining.md +221 -0
- package/docs/course/05-dates.md +214 -0
- package/docs/course/06-typed-queries.md +240 -0
- package/docs/course/07-requests.md +261 -0
- package/docs/course/08-grouping.md +210 -0
- package/docs/course/09-explaining.md +171 -0
- package/docs/course/10-vocabulary.md +276 -0
- package/docs/course/11-the-search-box.md +349 -0
- package/docs/course/12-untrusted.md +257 -0
- package/docs/course/13-saved-filters.md +199 -0
- package/docs/course/14-streams-and-cli.md +276 -0
- package/docs/course/15-pushdown.md +240 -0
- package/docs/course/16-extending.md +199 -0
- package/docs/course/index.md +185 -0
- package/docs/design/decisions.md +198 -0
- package/docs/design/performance.md +102 -0
- package/docs/guides/adopting.md +81 -0
- package/docs/guides/pushdown.md +147 -0
- package/docs/guides/typescript.md +115 -0
- package/docs/guides/untrusted-input.md +86 -0
- package/docs/index.md +102 -0
- package/docs/reference/api.md +266 -0
- package/docs/reference/cli.md +103 -0
- package/docs/reference/index.md +12 -0
- package/docs/reference/specification.md +549 -0
- package/docs/reference/text-syntax.md +152 -0
- package/docs/start/quick-start.md +84 -0
- package/package.json +136 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The shapes a query is made of.
|
|
3
|
+
*
|
|
4
|
+
* A JQL query is a JSON document, so everything here describes JSON: no functions, no
|
|
5
|
+
* `RegExp` objects, no `Date` instances. That is the whole point of the language — a
|
|
6
|
+
* query that finds something can be written into a URL, a saved filter, a config file or
|
|
7
|
+
* a request body, sent to somebody else, and find the same thing there. A query that
|
|
8
|
+
* carried a function could do none of that.
|
|
9
|
+
*
|
|
10
|
+
* The types come in two strengths. `Query<T>` for a known `T` only accepts field paths
|
|
11
|
+
* that exist on `T`, with values of the right type, so a typo in a field name is a compile
|
|
12
|
+
* error rather than a filter that quietly matches nothing. `Query<unknown>` (and
|
|
13
|
+
* `Query<any>`) accepts any field, for data whose shape is not known until it arrives;
|
|
14
|
+
* the engine still refuses anything that is not JQL when the query is compiled.
|
|
15
|
+
*/
|
|
16
|
+
/** A JSON value, as a query document may contain one. */
|
|
17
|
+
export type JsonValue = string | number | boolean | null | readonly JsonValue[] | {
|
|
18
|
+
readonly [key: string]: JsonValue;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* An instant relative to now, so a saved filter goes on meaning "the last hour".
|
|
22
|
+
*
|
|
23
|
+
* The duration is a count and a unit — `"90s"`, `"1h30m"`, `"7d"` — from `ms`, `s`, `m`,
|
|
24
|
+
* `h`, `d` and `w`. Months and years are left out because neither has a fixed length.
|
|
25
|
+
*/
|
|
26
|
+
export type RelativeDate = {
|
|
27
|
+
readonly $ago: string;
|
|
28
|
+
} | {
|
|
29
|
+
readonly $ahead: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* A date, written the only way JSON can write one.
|
|
33
|
+
*
|
|
34
|
+
* An ISO 8601 string, epoch milliseconds, `"now"`, or a `RelativeDate`. When a query
|
|
35
|
+
* compares against one, the value in the document is read as a date too: a `Date`, an ISO
|
|
36
|
+
* string or epoch milliseconds. The literal is what says "this is a date", so no value is
|
|
37
|
+
* ever read as a date unless the query asked for that.
|
|
38
|
+
*/
|
|
39
|
+
export interface DateLiteral {
|
|
40
|
+
readonly $date: string | number | RelativeDate;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* A comparison with another field of the same item, rather than with a value.
|
|
44
|
+
*
|
|
45
|
+
* `{ "bytesOut": { "$gt": { "$field": "bytesIn" } } }`. It is still data — a path, not an
|
|
46
|
+
* expression — so a query holding one stores, travels and validates like any other.
|
|
47
|
+
*/
|
|
48
|
+
export interface FieldReference {
|
|
49
|
+
readonly $field: string;
|
|
50
|
+
}
|
|
51
|
+
/** The names `$type` accepts. `integer` is a `number` with no fractional part. */
|
|
52
|
+
export type TypeName = "string" | "number" | "integer" | "bigint" | "boolean" | "null" | "array" | "object" | "date";
|
|
53
|
+
/**
|
|
54
|
+
* A free-text search across a whole document, or across named fields.
|
|
55
|
+
*
|
|
56
|
+
* The string form searches every string (and, when the phrase has a digit in it, every
|
|
57
|
+
* number) anywhere in the document, case-insensitively. The object form narrows the
|
|
58
|
+
* fields and can turn case sensitivity on.
|
|
59
|
+
*/
|
|
60
|
+
export type TextSearch = string | {
|
|
61
|
+
readonly $search: string;
|
|
62
|
+
/** The fields to search. Absent means the whole document, or the vocabulary's text fields when it names some. */
|
|
63
|
+
readonly $fields?: readonly string[] | undefined;
|
|
64
|
+
/** Default `false`: a search box that cared about case would find less than people expect. */
|
|
65
|
+
readonly $caseSensitive?: boolean | undefined;
|
|
66
|
+
};
|
|
67
|
+
type Leaf = string | number | boolean | bigint | symbol | null | undefined | Date | RegExp | ((...args: never[]) => unknown);
|
|
68
|
+
/** How deep path completion goes. Past this a path is still valid at run time; the types simply stop enumerating it. */
|
|
69
|
+
type Depth = [never, 0, 1, 2, 3, 4, 5];
|
|
70
|
+
/** The element type of an array, or the type itself. */
|
|
71
|
+
export type ElementOf<T> = T extends readonly (infer E)[] ? E : T;
|
|
72
|
+
/**
|
|
73
|
+
* Every dot path into `T`, seeing through arrays the way the engine does.
|
|
74
|
+
*
|
|
75
|
+
* `{ items: { id: number }[] }` yields `"items"`, `"items.id"` (any element's id),
|
|
76
|
+
* `"items.0"` and `"items.0.id"` (one element by position).
|
|
77
|
+
*/
|
|
78
|
+
export type Path<T, D extends number = 5> = [D] extends [never] ? never : T extends Leaf ? never : T extends readonly (infer E)[] ? `${number}` | ArrayPaths<E, D> | Path<E, D> : T extends ReadonlyMap<string, infer V> ? string | `${string}.${Path<V, Depth[D]>}` : {
|
|
79
|
+
[K in keyof T & string]: K | SubPath<K, NonNullable<T[K]>, D>;
|
|
80
|
+
}[keyof T & string];
|
|
81
|
+
type ArrayPaths<E, D extends number> = Path<E, Depth[D]> extends infer P extends string ? `${number}.${P}` : never;
|
|
82
|
+
type SubPath<K extends string, V, D extends number> = Path<V, Depth[D]> extends infer P extends string ? `${K}.${P}` : never;
|
|
83
|
+
/** The type found at a path, with arrays seen through. `unknown` when the path does not resolve. */
|
|
84
|
+
export type PathValue<T, P extends string> = T extends readonly (infer E)[] ? P extends `${number}` ? E : P extends `${number}.${infer R}` ? PathValue<NonNullable<E>, R> : PathValue<NonNullable<E>, P> : T extends ReadonlyMap<string, infer V> ? P extends `${string}.${infer R}` ? PathValue<NonNullable<V>, R> : V : P extends keyof T ? T[P] : P extends `${infer K}.${infer R}` ? K extends keyof T ? PathValue<NonNullable<T[K]>, R> : unknown : unknown;
|
|
85
|
+
type Scalar<V> = ElementOf<NonNullable<V>>;
|
|
86
|
+
/** What a field may be compared to for equality. `null` also matches a missing field. */
|
|
87
|
+
export type Literal<V> = unknown extends V ? JsonValue | DateLiteral : LiteralOf<NonNullable<V>> | null;
|
|
88
|
+
type LiteralOf<V> = V extends Date ? DateLiteral : V extends readonly (infer E)[] ? readonly LiteralOf<E>[] | LiteralOf<E> : V extends string | number | boolean ? V : V extends bigint ? number : V extends object ? {
|
|
89
|
+
readonly [K in keyof V]: LiteralOf<V[K]>;
|
|
90
|
+
} : V;
|
|
91
|
+
/**
|
|
92
|
+
* What an ordering operator accepts for a value of type `V`. A `$date` is accepted for
|
|
93
|
+
* strings (ISO timestamps) and numbers (epoch milliseconds) as well as for `Date`s, because
|
|
94
|
+
* that is how dates are stored when they have been through JSON.
|
|
95
|
+
*/
|
|
96
|
+
type Bound<V> = unknown extends V ? number | string | DateLiteral : [Extract<Scalar<V>, number | bigint>] extends [never] ? [Extract<Scalar<V>, string>] extends [never] ? [Extract<Scalar<V>, Date>] extends [never] ? never : DateLiteral : string | DateLiteral : number | DateLiteral;
|
|
97
|
+
/** Available only when the field can hold a string. */
|
|
98
|
+
type StringOperand<V> = unknown extends V ? string | readonly string[] : [Extract<Scalar<V>, string>] extends [never] ? never : string | readonly string[];
|
|
99
|
+
/** Available only when the field can hold an array. */
|
|
100
|
+
type ArrayOnly<V, X> = unknown extends V ? X : [Extract<NonNullable<V>, readonly unknown[]>] extends [never] ? never : X;
|
|
101
|
+
/** Available when the field can hold something with a length: an array or a string. */
|
|
102
|
+
type HasLength<V, X> = unknown extends V ? X : [Extract<NonNullable<V>, readonly unknown[]>] extends [never] ? [Extract<Scalar<V>, string>] extends [never] ? never : X : X;
|
|
103
|
+
/**
|
|
104
|
+
* An operator expression on one field.
|
|
105
|
+
*
|
|
106
|
+
* Several operators in one object must all hold. On an array field each operator is
|
|
107
|
+
* tested against the array and against each element separately, so `{ $gt: 5, $lt: 10 }`
|
|
108
|
+
* holds for `[1, 20]`. `$elemMatch` is for "one element satisfies all of these".
|
|
109
|
+
*/
|
|
110
|
+
export interface Condition<V = unknown> {
|
|
111
|
+
readonly $eq?: Literal<V> | FieldReference | undefined;
|
|
112
|
+
/** Also matches a document without the field. */
|
|
113
|
+
readonly $ne?: Literal<V> | FieldReference | undefined;
|
|
114
|
+
readonly $gt?: Bound<V> | FieldReference | undefined;
|
|
115
|
+
readonly $gte?: Bound<V> | FieldReference | undefined;
|
|
116
|
+
readonly $lt?: Bound<V> | FieldReference | undefined;
|
|
117
|
+
readonly $lte?: Bound<V> | FieldReference | undefined;
|
|
118
|
+
/** An empty list matches nothing. */
|
|
119
|
+
readonly $in?: readonly Literal<V>[] | undefined;
|
|
120
|
+
/** Also matches a document without the field. An empty list matches everything. */
|
|
121
|
+
readonly $nin?: readonly Literal<V>[] | undefined;
|
|
122
|
+
readonly $exists?: boolean | undefined;
|
|
123
|
+
readonly $type?: TypeName | readonly TypeName[] | undefined;
|
|
124
|
+
/** Any of the given substrings. */
|
|
125
|
+
readonly $contains?: StringOperand<V> | undefined;
|
|
126
|
+
readonly $startsWith?: StringOperand<V> | undefined;
|
|
127
|
+
readonly $endsWith?: StringOperand<V> | undefined;
|
|
128
|
+
/** A whole component: the value starts and ends on a boundary between runs of letters and digits. */
|
|
129
|
+
readonly $word?: StringOperand<V> | undefined;
|
|
130
|
+
/** A glob: `*` for any run, `?` for one character, `\\` to escape either. */
|
|
131
|
+
readonly $glob?: StringOperand<V> | undefined;
|
|
132
|
+
/** An ECMAScript pattern, as a string. */
|
|
133
|
+
readonly $regex?: ([Extract<Scalar<V>, string>] extends [never] ? (unknown extends V ? string : never) : string) | undefined;
|
|
134
|
+
/** `i` makes every string comparison in this object case-insensitive; `m`, `s`, `u` apply to `$regex`. */
|
|
135
|
+
readonly $options?: string | undefined;
|
|
136
|
+
/** `[divisor, remainder]`. Offered on a big integer too, which is a number everywhere else in the language. */
|
|
137
|
+
readonly $mod?: ([Extract<Scalar<V>, number | bigint>] extends [never] ? (unknown extends V ? readonly [number, number] : never) : readonly [number, number]) | undefined;
|
|
138
|
+
readonly $size?: ArrayOnly<V, number | Condition<number>> | undefined;
|
|
139
|
+
/** The length of the value itself: elements of an array, UTF-16 code units of a string. */
|
|
140
|
+
readonly $length?: HasLength<V, number | Condition<number>> | undefined;
|
|
141
|
+
readonly $all?: ArrayOnly<V, readonly Literal<Scalar<V>>[]> | undefined;
|
|
142
|
+
readonly $elemMatch?: ArrayOnly<V, Query<Scalar<V>>> | undefined;
|
|
143
|
+
readonly $not?: Condition<V> | undefined;
|
|
144
|
+
}
|
|
145
|
+
/** A literal to compare with, another field to compare with, or an operator expression. */
|
|
146
|
+
export type FieldQuery<V> = Literal<V> | FieldReference | Condition<V>;
|
|
147
|
+
/** The operators that combine whole queries. Valid at the top of any query. */
|
|
148
|
+
export interface LogicalOperators<Q> {
|
|
149
|
+
/** Every one. An empty list matches everything. */
|
|
150
|
+
readonly $and?: readonly Q[] | undefined;
|
|
151
|
+
/** At least one. An empty list matches nothing. */
|
|
152
|
+
readonly $or?: readonly Q[] | undefined;
|
|
153
|
+
/** None of them. An empty list matches everything. */
|
|
154
|
+
readonly $nor?: readonly Q[] | undefined;
|
|
155
|
+
readonly $not?: Q | undefined;
|
|
156
|
+
readonly $text?: TextSearch | undefined;
|
|
157
|
+
/** Carried along and ignored, so a stored query can say why it exists. */
|
|
158
|
+
readonly $comment?: string | undefined;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* A query over values of type `T`.
|
|
162
|
+
*
|
|
163
|
+
* For an object type, the keys are field paths. For a primitive element type — an array
|
|
164
|
+
* of numbers, say — the query is a condition on the element itself: `{ $gt: 3 }`.
|
|
165
|
+
* `Extra` adds the fields a vocabulary computes, which are not on `T` itself.
|
|
166
|
+
*/
|
|
167
|
+
export type Query<T = unknown, Extra extends object = {}> = unknown extends T ? LooseQuery : NonNullable<T> extends Leaf ? Condition<T> & LogicalOperators<Query<T, Extra>> : TypedQuery<NonNullable<T>, Extra>;
|
|
168
|
+
type TypedQuery<T, Extra extends object> = {
|
|
169
|
+
readonly [P in Path<T>]?: FieldQuery<PathValue<T, P>> | undefined;
|
|
170
|
+
} & {
|
|
171
|
+
readonly [K in keyof Extra & string]?: FieldQuery<Extra[K]> | undefined;
|
|
172
|
+
} & LogicalOperators<Query<T, Extra>>;
|
|
173
|
+
/** A query whose field names are not known in advance. The engine validates it when it compiles. */
|
|
174
|
+
export type LooseQuery = LogicalOperators<LooseQuery> & {
|
|
175
|
+
readonly [path: string]: unknown;
|
|
176
|
+
};
|
|
177
|
+
declare const untypedBrand: unique symbol;
|
|
178
|
+
/**
|
|
179
|
+
* A query that came from outside the type system: typed into a search box, read from a
|
|
180
|
+
* URL, received in a request body.
|
|
181
|
+
*
|
|
182
|
+
* Accepted wherever a typed query is, so `rows.jqlFilter(parseText(input))` compiles. It is
|
|
183
|
+
* branded rather than being a plain `LooseQuery` because a plain one would also accept
|
|
184
|
+
* every object literal — including `{ nme: "Ada" }` — and the typed queries would stop
|
|
185
|
+
* catching typos. For the same reason it has no index signature: one in any member of a
|
|
186
|
+
* union switches off the excess-property check for the whole union, and
|
|
187
|
+
* `users.jqlSearch({ missing: 1 })` compiled until a type test caught it. Cast to
|
|
188
|
+
* `LooseQuery` to read its fields. The brand exists only in the types; the engine
|
|
189
|
+
* validates the query itself when it compiles it, which is the check that matters for
|
|
190
|
+
* input from outside.
|
|
191
|
+
*/
|
|
192
|
+
export type UntypedQuery = LogicalOperators<LooseQuery> & {
|
|
193
|
+
readonly [untypedBrand]: true;
|
|
194
|
+
};
|
|
195
|
+
/** A compiled query: a plain predicate. Any function of this shape is accepted wherever a query is. */
|
|
196
|
+
export type Matcher<T> = (value: T) => boolean;
|
|
197
|
+
/** A query document, one that came from outside the types, or one already compiled. */
|
|
198
|
+
export type QueryLike<T, Extra extends object = {}> = Query<T, Extra> | UntypedQuery | Matcher<T>;
|
|
199
|
+
/** `1` or `"asc"` for ascending, `-1` or `"desc"` for descending. */
|
|
200
|
+
export type SortDirection = 1 | -1 | "asc" | "desc";
|
|
201
|
+
/** Keys in order of precedence: the first key sorts, the second breaks its ties, and so on. */
|
|
202
|
+
export type Sort<T, Extra extends object = {}> = unknown extends T ? {
|
|
203
|
+
readonly [path: string]: SortDirection;
|
|
204
|
+
} : {
|
|
205
|
+
readonly [P in Path<NonNullable<T>> | (keyof Extra & string)]?: SortDirection;
|
|
206
|
+
};
|
|
207
|
+
/** A field a request may name: a path into `T`, or a vocabulary field. */
|
|
208
|
+
export type FieldName<T, Extra extends object = {}> = unknown extends T ? string : Path<NonNullable<T>> | (keyof Extra & string);
|
|
209
|
+
/**
|
|
210
|
+
* A whole question about a collection, as one JSON document.
|
|
211
|
+
*
|
|
212
|
+
* The envelope a service accepts and a dashboard puts in its URL: which items, in which
|
|
213
|
+
* order, which page, and which fields of each.
|
|
214
|
+
*/
|
|
215
|
+
export interface Request<T = unknown, Extra extends object = {}> {
|
|
216
|
+
/** A query, one already compiled, or any predicate — the same as every other place a query is taken. */
|
|
217
|
+
readonly where?: QueryLike<T, Extra> | undefined;
|
|
218
|
+
readonly sort?: Sort<T, Extra> | undefined;
|
|
219
|
+
/** How many matches to pass over first. Default 0. */
|
|
220
|
+
readonly skip?: number | undefined;
|
|
221
|
+
/** The most to return. Absent means all of them. */
|
|
222
|
+
readonly limit?: number | undefined;
|
|
223
|
+
/** Paths to keep in each result. Absent returns the items themselves. */
|
|
224
|
+
readonly fields?: readonly FieldName<T, Extra>[] | undefined;
|
|
225
|
+
/**
|
|
226
|
+
* Paths to drop from each result, applied after `fields`.
|
|
227
|
+
*
|
|
228
|
+
* What a server redacts with: the values never leave the process, and the request that
|
|
229
|
+
* says so is data like the rest of it. The items themselves are not touched — a result
|
|
230
|
+
* with something dropped is a copy.
|
|
231
|
+
*/
|
|
232
|
+
readonly omit?: readonly FieldName<T, Extra>[] | undefined;
|
|
233
|
+
}
|
|
234
|
+
/** Anything the collection helpers can read: arrays, array-likes, iterables, and maps (by value). */
|
|
235
|
+
export type Source<T> = Iterable<T> | ArrayLike<T>;
|
|
236
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import type { PathValue } from "./types.js";
|
|
2
|
+
/**
|
|
3
|
+
* The names a query may use for one kind of document.
|
|
4
|
+
*
|
|
5
|
+
* Without one, a field name is a path and nothing more. With one, a collection gets the
|
|
6
|
+
* names people actually type — `ip` for `actor`, `ua` for `request.headers.user-agent` —
|
|
7
|
+
* and fields that are computed rather than stored, like BotHandler's `outcome`, which is
|
|
8
|
+
* derived from the action and never kept anywhere.
|
|
9
|
+
*
|
|
10
|
+
* The same vocabulary serves the JSON engine and the text front end. That is deliberate:
|
|
11
|
+
* two places that each kept their own list of field names would be two dialects within a
|
|
12
|
+
* week, and a filter typed into a search box has to mean exactly what the JSON it becomes
|
|
13
|
+
* means.
|
|
14
|
+
*/
|
|
15
|
+
/**
|
|
16
|
+
* How the text front end reads a value typed after `field:`. The JSON engine ignores it:
|
|
17
|
+
* a JSON query says what it means with its operator.
|
|
18
|
+
*
|
|
19
|
+
* - `text` — contains the value, ignoring case. The default, and right for prose.
|
|
20
|
+
* - `word` — contains it as a whole component (`1.2.3.4` does not find `1.2.3.45`). For identifiers.
|
|
21
|
+
* - `exact` — equals it, ignoring case. For closed sets like a verdict or a method.
|
|
22
|
+
* - `number` — compares: `>70`, `<=5`, `10..20`, `42`.
|
|
23
|
+
* - `date` — compares instants the same way: `>2026-01-01`.
|
|
24
|
+
* - `boolean` — `true`/`false`, `yes`/`no`, `1`/`0`.
|
|
25
|
+
*/
|
|
26
|
+
export type FieldKind = "text" | "word" | "exact" | "number" | "date" | "boolean";
|
|
27
|
+
export interface FieldDefinition<T = unknown> {
|
|
28
|
+
/** Where the value lives. Default: the field's own name. */
|
|
29
|
+
readonly path?: string | undefined;
|
|
30
|
+
/** Computes the value instead. Called once per document the field is tested against; it must not throw. */
|
|
31
|
+
readonly get?: ((document: T) => unknown) | undefined;
|
|
32
|
+
/** Other names that mean this field. */
|
|
33
|
+
readonly aliases?: readonly string[] | undefined;
|
|
34
|
+
/** Default `"text"`. */
|
|
35
|
+
readonly kind?: FieldKind | undefined;
|
|
36
|
+
/** The values worth offering as completions, when the set is closed. Leave out for a field that takes anything. */
|
|
37
|
+
readonly values?: readonly string[] | undefined;
|
|
38
|
+
}
|
|
39
|
+
export interface VocabularyDefinition<T = unknown> {
|
|
40
|
+
readonly fields: Readonly<Record<string, FieldDefinition<T>>>;
|
|
41
|
+
/** The fields a bare search word looks in. Default: the whole document. */
|
|
42
|
+
readonly text?: readonly string[] | undefined;
|
|
43
|
+
/**
|
|
44
|
+
* Whether a name outside the vocabulary is refused. Default `false`, so any path still
|
|
45
|
+
* works alongside the named fields. Turn it on where the vocabulary is the whole
|
|
46
|
+
* contract — a public API whose documents may grow fields nobody promised to keep.
|
|
47
|
+
*/
|
|
48
|
+
readonly strict?: boolean | undefined;
|
|
49
|
+
}
|
|
50
|
+
/** One field, with every name that reaches it already resolved. */
|
|
51
|
+
export interface VocabularyField<T = unknown> {
|
|
52
|
+
readonly name: string;
|
|
53
|
+
readonly kind: FieldKind;
|
|
54
|
+
readonly path: string;
|
|
55
|
+
readonly get: ((document: T) => unknown) | undefined;
|
|
56
|
+
readonly aliases: readonly string[];
|
|
57
|
+
readonly values: readonly string[] | undefined;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* A vocabulary ready to use.
|
|
61
|
+
*
|
|
62
|
+
* `Extra` is the type of the fields it names, so a typed query can use them: build one
|
|
63
|
+
* with `defineVocabulary<Doc>()({ … })` and the computed names type-check.
|
|
64
|
+
*/
|
|
65
|
+
export interface Vocabulary<T = unknown, Extra extends object = {}> {
|
|
66
|
+
/** Every name, aliases included, lower-cased, to the field it means. */
|
|
67
|
+
readonly lookup: ReadonlyMap<string, VocabularyField<T>>;
|
|
68
|
+
/** The canonical fields, in the order they were defined. */
|
|
69
|
+
readonly fields: readonly VocabularyField<T>[];
|
|
70
|
+
/** Every name a query may use, aliases included, sorted. For completions. */
|
|
71
|
+
readonly names: readonly string[];
|
|
72
|
+
readonly text: readonly string[] | undefined;
|
|
73
|
+
readonly strict: boolean;
|
|
74
|
+
/** Carries the computed field types. Never present at run time. */
|
|
75
|
+
readonly __extra?: Extra | undefined;
|
|
76
|
+
}
|
|
77
|
+
type ExtraOf<T, F> = {
|
|
78
|
+
[K in keyof F]: F[K] extends {
|
|
79
|
+
get: (document: T) => infer R;
|
|
80
|
+
} ? R : F[K] extends {
|
|
81
|
+
path: infer P extends string;
|
|
82
|
+
} ? PathValue<T, P> : K extends string ? PathValue<T, K> : unknown;
|
|
83
|
+
};
|
|
84
|
+
/**
|
|
85
|
+
* Builds a vocabulary, refusing one that would quietly misbehave.
|
|
86
|
+
*
|
|
87
|
+
* Two names that collide, a text field that is not a field, a field with both a path and a
|
|
88
|
+
* getter: each is a vocabulary that looks defined and means something other than what it
|
|
89
|
+
* says, so each is an error here rather than a surprise at query time.
|
|
90
|
+
*
|
|
91
|
+
* Curried so the document type can be given while the field names are inferred:
|
|
92
|
+
* `defineVocabulary<Request>()({ fields: { … } })`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function defineVocabulary<T = unknown>(): <const D extends VocabularyDefinition<T>>(definition: D) => Vocabulary<T, ExtraOf<T, D["fields"]>>;
|
|
95
|
+
/**
|
|
96
|
+
* Refuses anything that is not a vocabulary, where one was expected.
|
|
97
|
+
*
|
|
98
|
+
* Two mistakes land here, and both used to arrive as `Cannot read properties of undefined
|
|
99
|
+
* (reading 'get')` from somewhere inside the engine. `defineVocabulary` is curried, so
|
|
100
|
+
* `defineVocabulary({ fields: … })` — the empty parentheses forgotten — returns a *function*;
|
|
101
|
+
* and a definition passed straight through is an object with no `lookup` on it. Neither is
|
|
102
|
+
* caught by the types from JavaScript, and a `TypeError` is not a `JqlError`, so `validate`
|
|
103
|
+
* re-threw it: the one call whose job is to answer 400 rather than 500 answered 500.
|
|
104
|
+
*/
|
|
105
|
+
export declare function checkVocabulary<T>(value: T, at?: string): T;
|
|
106
|
+
export {};
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface Streams {
|
|
2
|
+
readonly out: (text: string) => void;
|
|
3
|
+
readonly err: (text: string) => void;
|
|
4
|
+
/** Where a `-` argument, or no file at all, reads from. */
|
|
5
|
+
readonly stdin: () => AsyncIterable<string>;
|
|
6
|
+
}
|
|
7
|
+
/** Runs the command. Returns the exit code rather than calling `process.exit`, so it can be tested in-process. */
|
|
8
|
+
export declare function main(argv: readonly string[], io: Streams): Promise<number>;
|
|
9
|
+
/**
|
|
10
|
+
* The command as a process: reads stdin, writes the streams, sets the exit code.
|
|
11
|
+
*
|
|
12
|
+
* Kept apart from `main` so the tests can call `main` with writers of their own and assert
|
|
13
|
+
* which stream each line went to — part of the contract, not an implementation detail.
|
|
14
|
+
*/
|
|
15
|
+
export declare function run(argv?: readonly string[]): Promise<void>;
|