@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,117 @@
|
|
|
1
|
+
import { JqlError } from "./errors.js";
|
|
2
|
+
import { type Reach } from "./internal/path.js";
|
|
3
|
+
import { type Limits } from "./limits.js";
|
|
4
|
+
import type { Matcher, QueryLike, UntypedQuery } from "./types.js";
|
|
5
|
+
import { type Vocabulary } from "./vocabulary.js";
|
|
6
|
+
/**
|
|
7
|
+
* The engine: a query document in, a predicate out.
|
|
8
|
+
*
|
|
9
|
+
* **Compile once, test many.** Everything that depends only on the query — validating it,
|
|
10
|
+
* resolving field names, splitting paths, building sets for `$in`, lower-casing for `$options:
|
|
11
|
+
* "i"`, compiling patterns — happens here, once. What comes back is a tree of small closures
|
|
12
|
+
* that does only the part that depends on the document. Filtering a million items with one
|
|
13
|
+
* query parses that query once, not a million times.
|
|
14
|
+
*
|
|
15
|
+
* **No code generation.** Emitting JavaScript source and calling `new Function` would be
|
|
16
|
+
* faster still, and it would also be refused by every page with a Content-Security-Policy
|
|
17
|
+
* worth having — including the dashboards in this project, whose build fails on a
|
|
18
|
+
* `new Function`. Closures run everywhere.
|
|
19
|
+
*
|
|
20
|
+
* **Cheapest first.** The parts of an `$and` are reordered so an equality test runs before a
|
|
21
|
+
* pattern and a pattern before a free-text walk, because the first part to fail decides the
|
|
22
|
+
* answer and the rest never run. Every operator is a pure test, so the order changes the
|
|
23
|
+
* cost and never the result.
|
|
24
|
+
*
|
|
25
|
+
* **No implicit cache.** A cache keyed on the query object would hand back a stale predicate
|
|
26
|
+
* the moment somebody edited the object they had already queried with — a filter that looks
|
|
27
|
+
* updated and is not. Compiling a small query costs about as much as testing a handful of
|
|
28
|
+
* documents, so the helpers compile per call, and a hot loop calls `compile` once and keeps
|
|
29
|
+
* the result.
|
|
30
|
+
*/
|
|
31
|
+
export interface CompileOptions<Extra extends object = {}> {
|
|
32
|
+
/** The field names, aliases and computed fields a query may use. Its fields become names a typed query may use. */
|
|
33
|
+
readonly vocabulary?: Vocabulary<never, Extra> | undefined;
|
|
34
|
+
/** How much of a query to accept. Default `DEFAULT_LIMITS`; use `UNTRUSTED_LIMITS` for queries from outside. */
|
|
35
|
+
readonly limits?: Partial<Limits> | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Where "now" comes from, for the relative dates in `{ "$date": { "$ago": "1h" } }`.
|
|
38
|
+
* Default the system clock, read **once**, when the query compiles.
|
|
39
|
+
*
|
|
40
|
+
* Read once because a window that moved while a scan was in progress would judge two
|
|
41
|
+
* items a second apart against different hours. It also means a long-lived compiled
|
|
42
|
+
* query keeps the instant it was compiled at: recompile to move the window, which costs
|
|
43
|
+
* well under a microsecond.
|
|
44
|
+
*/
|
|
45
|
+
readonly now?: (() => number) | undefined;
|
|
46
|
+
/**
|
|
47
|
+
* Operators this project adds to the language, such as an address-in-network test that
|
|
48
|
+
* no amount of JSON could express.
|
|
49
|
+
*
|
|
50
|
+
* **A query using one is no longer portable JQL**, which is why their names must begin
|
|
51
|
+
* `$x`: somebody reading a stored filter can see at a glance that it needs more than a
|
|
52
|
+
* standard engine, and a future version of the language cannot collide with one.
|
|
53
|
+
*/
|
|
54
|
+
readonly operators?: readonly OperatorDefinition[] | undefined;
|
|
55
|
+
}
|
|
56
|
+
/** An operator a project adds to the language. Its name must match `$x` followed by a capital: `$xCidr`. */
|
|
57
|
+
export interface OperatorDefinition {
|
|
58
|
+
readonly name: string;
|
|
59
|
+
/**
|
|
60
|
+
* Builds the test, once, when the query compiles. Throw `JqlError` for an operand this
|
|
61
|
+
* operator cannot use; the message reaches whoever wrote the query.
|
|
62
|
+
*/
|
|
63
|
+
readonly compile: (operand: unknown, at: string) => (value: unknown) => boolean;
|
|
64
|
+
/** Roughly what it costs against the others, so an `$and` can run the cheap parts first. Default 4. */
|
|
65
|
+
readonly cost?: number | undefined;
|
|
66
|
+
/** Whether it is also tried against each element of an array value. Default true, like the built-in operators. */
|
|
67
|
+
readonly elementwise?: boolean | undefined;
|
|
68
|
+
}
|
|
69
|
+
/** The operators that combine queries. */
|
|
70
|
+
export declare const QUERY_OPERATORS: readonly string[];
|
|
71
|
+
/** The operators that test a field. At the top of a query they test the item itself. */
|
|
72
|
+
export declare const FIELD_OPERATORS: readonly string[];
|
|
73
|
+
/**
|
|
74
|
+
* Operators whose meaning `$options: "i"` changes.
|
|
75
|
+
*
|
|
76
|
+
* Not in the barrel — this is the language's own list (specification §5.5), shared with
|
|
77
|
+
* `explain`, which has to know which pieces of a condition the flag travels with.
|
|
78
|
+
*/
|
|
79
|
+
export declare const CASE_AWARE: Set<string>;
|
|
80
|
+
/**
|
|
81
|
+
* Compiles a query into a predicate.
|
|
82
|
+
*
|
|
83
|
+
* Throws `JqlError` for anything that is not a valid query, naming where in the query the
|
|
84
|
+
* problem is. A function passed in is returned as it is, so every helper that takes a
|
|
85
|
+
* query also takes a predicate somebody already has.
|
|
86
|
+
*/
|
|
87
|
+
export declare function compile<T = unknown, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Matcher<T>;
|
|
88
|
+
/** Whether one value matches a query. For a one-off; in a loop, `compile` once instead. */
|
|
89
|
+
export declare function matches<T, Extra extends object = {}>(value: T, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
90
|
+
/**
|
|
91
|
+
* Checks a query, and hands back the predicate when it is a good one.
|
|
92
|
+
*
|
|
93
|
+
* What a service calls on a query it has just received, so it can answer 400 with the
|
|
94
|
+
* sentence rather than 500 with a stack. The predicate comes with it because the caller
|
|
95
|
+
* needs one next, and the two ways of getting it separately are both worse: compiling
|
|
96
|
+
* again costs a second compile, and — the reason this returns it rather than only a
|
|
97
|
+
* verdict — it is compiled from a second set of options, which is how a query gets
|
|
98
|
+
* checked against the untrusted limits and then run without them.
|
|
99
|
+
*/
|
|
100
|
+
export declare function validate<T = unknown>(query: unknown, options?: CompileOptions<object>): {
|
|
101
|
+
valid: true;
|
|
102
|
+
test: Matcher<T>;
|
|
103
|
+
} | {
|
|
104
|
+
valid: false;
|
|
105
|
+
error: JqlError;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Marks a query from outside the type system — parsed JSON, a URL parameter — as one to be
|
|
109
|
+
* checked when it is compiled rather than by the compiler. Returns the same object.
|
|
110
|
+
*/
|
|
111
|
+
export declare function untyped(query: unknown): UntypedQuery;
|
|
112
|
+
/**
|
|
113
|
+
* How a field name is reached, by the same rules a query uses. For sorting and projection,
|
|
114
|
+
* which must name fields exactly as the query beside them does. Not part of the public
|
|
115
|
+
* surface.
|
|
116
|
+
*/
|
|
117
|
+
export declare function reachField(name: string, vocabulary: Vocabulary<unknown, object> | undefined, at: string): Reach;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one way a query is refused.
|
|
3
|
+
*
|
|
4
|
+
* A query that looks right and matches nothing is the failure this library exists to
|
|
5
|
+
* prevent: a misspelt operator, a field value that is `undefined` because a variable was,
|
|
6
|
+
* a regex that does not compile. Each of those used to be a filter that silently let
|
|
7
|
+
* everything through or nothing through, and nobody could tell which from the result. So
|
|
8
|
+
* `compile` refuses, with a sentence saying what was wrong and where in the query it was.
|
|
9
|
+
*/
|
|
10
|
+
export declare class JqlError extends Error {
|
|
11
|
+
/**
|
|
12
|
+
* Where in the query the problem is, written the way you would reach it in code:
|
|
13
|
+
* `$or[1].age.$gt`. Empty for the query as a whole.
|
|
14
|
+
*/
|
|
15
|
+
readonly at: string;
|
|
16
|
+
constructor(message: string, at?: string);
|
|
17
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { type CompileOptions } from "./core.js";
|
|
2
|
+
import type { QueryLike } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Why one item does or does not match a query.
|
|
5
|
+
*
|
|
6
|
+
* A filter that returns nothing tells you nothing about which part of it was wrong. This
|
|
7
|
+
* takes the query apart and answers for each piece separately, with the values it actually
|
|
8
|
+
* read, so an operator looking at a row and a saved filter that disagree can see the clause
|
|
9
|
+
* that decided it.
|
|
10
|
+
*
|
|
11
|
+
* **It runs the real engine on every piece.** Each clause is compiled on its own and tested,
|
|
12
|
+
* rather than interpreted here by a second reading of the language — a second reading would
|
|
13
|
+
* be a second set of semantics, and the one thing worse than a filter you cannot explain is
|
|
14
|
+
* an explanation that does not match what the filter did. The cost is one small compile per
|
|
15
|
+
* clause, which is why this is for one item at a time and `compile` is for the collection.
|
|
16
|
+
*/
|
|
17
|
+
export interface Explanation {
|
|
18
|
+
/** Whether this clause holds for the item. */
|
|
19
|
+
readonly matched: boolean;
|
|
20
|
+
/** Where the clause is in the query, written as it would be reached in code: `$or[1].age.$gt`. */
|
|
21
|
+
readonly at: string;
|
|
22
|
+
/** The clause itself, as the JSON fragment that was evaluated. */
|
|
23
|
+
readonly clause: unknown;
|
|
24
|
+
/** One sentence: what was read, or how the parts came out. */
|
|
25
|
+
readonly because: string;
|
|
26
|
+
/** The clauses this one is made of. */
|
|
27
|
+
readonly parts?: readonly Explanation[];
|
|
28
|
+
}
|
|
29
|
+
/** Explains one item against one query. */
|
|
30
|
+
export declare function explain<T, Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, item: T, options?: CompileOptions<Extra>): Explanation;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { CompileOptions } from "./core.js";
|
|
2
|
+
import type { FieldName, QueryLike, Request, Source } from "./types.js";
|
|
3
|
+
export { install, uninstall } from "./array.js";
|
|
4
|
+
declare global {
|
|
5
|
+
interface ReadonlyArray<T> {
|
|
6
|
+
/** The first item matching the query, or `undefined`. */
|
|
7
|
+
jqlSearch<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T | undefined;
|
|
8
|
+
/** Every item matching the query, as a new array. */
|
|
9
|
+
jqlFilter<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
|
|
10
|
+
/** How many items match. */
|
|
11
|
+
jqlCount<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
12
|
+
/** Whether any item matches. */
|
|
13
|
+
jqlSome<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
14
|
+
/** Whether every item matches. True when there are none. */
|
|
15
|
+
jqlEvery<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
16
|
+
/** The position of the first match, or -1. */
|
|
17
|
+
jqlFindIndex<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
18
|
+
/** The matches and the rest. */
|
|
19
|
+
jqlPartition<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): [T[], T[]];
|
|
20
|
+
/** A whole request: `where`, `sort`, `skip`, `limit`, `fields`, `omit`. */
|
|
21
|
+
jqlQuery<Extra extends object = {}>(request: Request<T, NoInfer<Extra>> & {
|
|
22
|
+
readonly fields: readonly FieldName<T, NoInfer<Extra>>[];
|
|
23
|
+
}, options?: CompileOptions<Extra>): Record<string, unknown>[];
|
|
24
|
+
/** With `omit`, each result is a copy without those paths — which is `Partial<T>`, not `T`. */
|
|
25
|
+
jqlQuery<Extra extends object = {}>(request: Request<T, NoInfer<Extra>> & {
|
|
26
|
+
readonly omit: readonly FieldName<T, NoInfer<Extra>>[];
|
|
27
|
+
}, options?: CompileOptions<Extra>): Partial<T>[];
|
|
28
|
+
jqlQuery<Extra extends object = {}>(request?: Request<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
|
|
29
|
+
}
|
|
30
|
+
interface Array<T> {
|
|
31
|
+
jqlSearch<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T | undefined;
|
|
32
|
+
jqlFilter<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
|
|
33
|
+
jqlCount<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
34
|
+
jqlSome<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
35
|
+
jqlEvery<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
36
|
+
jqlFindIndex<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
37
|
+
jqlPartition<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): [T[], T[]];
|
|
38
|
+
jqlQuery<Extra extends object = {}>(request: Request<T, NoInfer<Extra>> & {
|
|
39
|
+
readonly fields: readonly FieldName<T, NoInfer<Extra>>[];
|
|
40
|
+
}, options?: CompileOptions<Extra>): Record<string, unknown>[];
|
|
41
|
+
jqlQuery<Extra extends object = {}>(request: Request<T, NoInfer<Extra>> & {
|
|
42
|
+
readonly omit: readonly FieldName<T, NoInfer<Extra>>[];
|
|
43
|
+
}, options?: CompileOptions<Extra>): Partial<T>[];
|
|
44
|
+
jqlQuery<Extra extends object = {}>(request?: Request<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
|
|
45
|
+
}
|
|
46
|
+
interface ArrayConstructor {
|
|
47
|
+
/** The first item of any array, array-like, iterable or `Map` (by value) matching the query. */
|
|
48
|
+
jqlSearch<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): V | undefined;
|
|
49
|
+
jqlSearch<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T | undefined;
|
|
50
|
+
jqlFilter<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): V[];
|
|
51
|
+
jqlFilter<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
|
|
52
|
+
jqlCount<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
53
|
+
jqlCount<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
54
|
+
jqlSome<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
55
|
+
jqlSome<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
56
|
+
jqlEvery<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
57
|
+
jqlEvery<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): boolean;
|
|
58
|
+
jqlFindIndex<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
59
|
+
jqlFindIndex<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
60
|
+
jqlPartition<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): [V[], V[]];
|
|
61
|
+
jqlPartition<T, Extra extends object = {}>(source: Source<T>, query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): [T[], T[]];
|
|
62
|
+
jqlQuery<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, request: Request<V, NoInfer<Extra>> & {
|
|
63
|
+
readonly fields: readonly FieldName<V, NoInfer<Extra>>[];
|
|
64
|
+
}, options?: CompileOptions<Extra>): Record<string, unknown>[];
|
|
65
|
+
jqlQuery<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, request: Request<V, NoInfer<Extra>> & {
|
|
66
|
+
readonly omit: readonly FieldName<V, NoInfer<Extra>>[];
|
|
67
|
+
}, options?: CompileOptions<Extra>): Partial<V>[];
|
|
68
|
+
jqlQuery<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, request?: Request<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): V[];
|
|
69
|
+
jqlQuery<T, Extra extends object = {}>(source: Source<T>, request: Request<T, NoInfer<Extra>> & {
|
|
70
|
+
readonly fields: readonly FieldName<T, NoInfer<Extra>>[];
|
|
71
|
+
}, options?: CompileOptions<Extra>): Record<string, unknown>[];
|
|
72
|
+
jqlQuery<T, Extra extends object = {}>(source: Source<T>, request: Request<T, NoInfer<Extra>> & {
|
|
73
|
+
readonly omit: readonly FieldName<T, NoInfer<Extra>>[];
|
|
74
|
+
}, options?: CompileOptions<Extra>): Partial<T>[];
|
|
75
|
+
jqlQuery<T, Extra extends object = {}>(source: Source<T>, request?: Request<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T[];
|
|
76
|
+
}
|
|
77
|
+
interface Map<K, V> {
|
|
78
|
+
/** The first value matching the query. */
|
|
79
|
+
jqlSearch<Extra extends object = {}>(query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): V | undefined;
|
|
80
|
+
/** The entries whose value matches, as a new `Map`. */
|
|
81
|
+
jqlFilter<Extra extends object = {}>(query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): Map<K, V>;
|
|
82
|
+
jqlCount<Extra extends object = {}>(query: QueryLike<V, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
83
|
+
}
|
|
84
|
+
interface Set<T> {
|
|
85
|
+
jqlSearch<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): T | undefined;
|
|
86
|
+
/** The members that match, as a new `Set`. */
|
|
87
|
+
jqlFilter<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): Set<T>;
|
|
88
|
+
jqlCount<Extra extends object = {}>(query: QueryLike<T, NoInfer<Extra>>, options?: CompileOptions<Extra>): number;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { type CompileOptions } from "./core.js";
|
|
2
|
+
import type { QueryLike, Source } from "./types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Counting the matches by one field.
|
|
5
|
+
*
|
|
6
|
+
* Every dashboard in this family ends up doing this: a thousand requests become a dozen
|
|
7
|
+
* actors you can read down, a feed becomes counts per verdict, per rule, per path. Each
|
|
8
|
+
* one used to write the tally loop itself, which is where the disagreements crept in —
|
|
9
|
+
* whether an item with two tags counts twice, what happens to the items with no value at
|
|
10
|
+
* all, and what order the groups come back in. It is one answer now.
|
|
11
|
+
*
|
|
12
|
+
* One pass over the source, one `Map` of counts, and the items kept only when asked for.
|
|
13
|
+
*/
|
|
14
|
+
export interface Grouped<T> {
|
|
15
|
+
/** The value that names the group. `null` covers everything that cannot name one — see below. */
|
|
16
|
+
readonly key: unknown;
|
|
17
|
+
readonly count: number;
|
|
18
|
+
/** Present only when `items` asked for them. */
|
|
19
|
+
readonly items?: readonly T[] | undefined;
|
|
20
|
+
}
|
|
21
|
+
export interface GroupOptions<T, Extra extends object = {}> extends CompileOptions<Extra> {
|
|
22
|
+
/** Which items to count. Absent counts them all. */
|
|
23
|
+
readonly where?: QueryLike<T, NoInfer<Extra>> | undefined;
|
|
24
|
+
/** How many groups to return. Absent returns all of them. */
|
|
25
|
+
readonly limit?: number | undefined;
|
|
26
|
+
/** Keep the items too: `true` for all of them, a number for the first few. Default: counts only. */
|
|
27
|
+
readonly items?: boolean | number | undefined;
|
|
28
|
+
/** `"count"` (default) puts the largest group first; `"key"` orders by the value itself. */
|
|
29
|
+
readonly sort?: "count" | "key" | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Groups the matching items by the value at `by`, largest group first.
|
|
33
|
+
*
|
|
34
|
+
* Three rules worth knowing, each because leaving it unsaid is how two dashboards came to
|
|
35
|
+
* disagree about the same feed:
|
|
36
|
+
*
|
|
37
|
+
* - **An item counts once in each group it belongs to.** A path that reaches an array —
|
|
38
|
+
* tags, say — puts the item in a group per distinct tag, and a repeated value inside one
|
|
39
|
+
* item counts once.
|
|
40
|
+
* - **Everything that cannot name a group shares `null`**: a missing field, a `null`, a value
|
|
41
|
+
* that is an object, and an empty list. That is the "no rule matched" heading, and it is a
|
|
42
|
+
* real answer rather than a silently dropped row — which is the point, because it is what
|
|
43
|
+
* makes the counts add up to the number of rows there are.
|
|
44
|
+
* - **Ties break by the key**, so the same data always comes back in the same order.
|
|
45
|
+
*/
|
|
46
|
+
export declare function group<K, V, Extra extends object = {}>(source: ReadonlyMap<K, V>, by: string, options?: GroupOptions<V, Extra>): Grouped<V>[];
|
|
47
|
+
export declare function group<T, Extra extends object = {}>(source: Source<T>, by: string, options?: GroupOptions<T, Extra>): Grouped<T>[];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@osqd/jql` — the JSON Query Language and its engine.
|
|
3
|
+
*
|
|
4
|
+
* The query methods on `Array`, `Map` and `Set` are in `@osqd/jql/global`, and the
|
|
5
|
+
* search-box syntax is in `@osqd/jql/text`. Neither is loaded from here.
|
|
6
|
+
*/
|
|
7
|
+
export { compile, matches, untyped, validate, FIELD_OPERATORS, QUERY_OPERATORS } from "./core.js";
|
|
8
|
+
export { count, every, filter, filterMap, filterRecord, find, findEntry, findIndex, partition, some } from "./collections.js";
|
|
9
|
+
export { search } from "./search.js";
|
|
10
|
+
export { group } from "./group.js";
|
|
11
|
+
export { explain } from "./explain.js";
|
|
12
|
+
export { canonical, fingerprint } from "./canonical.js";
|
|
13
|
+
export { plan } from "./plan.js";
|
|
14
|
+
export { countAsync, everyAsync, filterAsync, filterStream, findAsync, searchAsync, someAsync } from "./async.js";
|
|
15
|
+
export { defineVocabulary } from "./vocabulary.js";
|
|
16
|
+
export { JqlError } from "./errors.js";
|
|
17
|
+
export { DEFAULT_LIMITS, UNTRUSTED_LIMITS } from "./limits.js";
|
|
18
|
+
export type { CompileOptions, OperatorDefinition } from "./core.js";
|
|
19
|
+
export type { AsyncSource } from "./async.js";
|
|
20
|
+
export type { Capabilities, Kept, Plan } from "./plan.js";
|
|
21
|
+
export type { Grouped, GroupOptions } from "./group.js";
|
|
22
|
+
export type { Explanation } from "./explain.js";
|
|
23
|
+
export type { Limits } from "./limits.js";
|
|
24
|
+
export type { FieldDefinition, FieldKind, Vocabulary, VocabularyDefinition, VocabularyField } from "./vocabulary.js";
|
|
25
|
+
export type { Condition, DateLiteral, ElementOf, FieldName, FieldReference, FieldQuery, JsonValue, Literal, LogicalOperators, LooseQuery, Matcher, Path, PathValue, Query, QueryLike, RelativeDate, Request, Sort, SortDirection, Source, TextSearch, TypeName, UntypedQuery, } from "./types.js";
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* "Did you mean" for names that almost exist.
|
|
3
|
+
*
|
|
4
|
+
* An unknown operator is refused either way; naming the one that was probably meant turns
|
|
5
|
+
* a lookup in the documentation into a one-character fix.
|
|
6
|
+
*/
|
|
7
|
+
export declare function closest(word: string, candidates: Iterable<string>): string | undefined;
|
|
8
|
+
/** The suffix of an error sentence that offers the likely name, or nothing. */
|
|
9
|
+
export declare function didYouMean(word: string, candidates: Iterable<string>): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durations, for the relative dates a saved filter is written with.
|
|
3
|
+
*
|
|
4
|
+
* `"1h"`, `"30m"`, `"1h30m"`, `"7d"`. Written as one or more counts with a unit, largest
|
|
5
|
+
* first by convention but in any order, because a filter is typed by a person and
|
|
6
|
+
* `"30m1h"` means what it says.
|
|
7
|
+
*
|
|
8
|
+
* Months and years are deliberately absent: neither has a fixed length, so `"1mo"` would
|
|
9
|
+
* mean something slightly different in February, and a filter whose window changes size
|
|
10
|
+
* with the calendar is one nobody can reason about. Use days or weeks.
|
|
11
|
+
*/
|
|
12
|
+
/** The unit names, for an error that says what is accepted. */
|
|
13
|
+
export declare const DURATION_UNITS: string[];
|
|
14
|
+
/** A duration in milliseconds, or `undefined` when the text is not one. */
|
|
15
|
+
export declare function parseDuration(text: string): number | undefined;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Equality between a document value and a query literal.
|
|
3
|
+
*
|
|
4
|
+
* Built once per literal rather than called generically, because the literal is known at
|
|
5
|
+
* compile time and the document value is the only thing that changes: a primitive
|
|
6
|
+
* literal becomes one `===`, and only an object or array literal pays for a walk.
|
|
7
|
+
*
|
|
8
|
+
* The rules, which the specification states and every implementation must share:
|
|
9
|
+
* objects are equal when they have the same keys with equal values, **in any order**
|
|
10
|
+
* (JSON does not promise key order, so a comparison that depended on it would depend on
|
|
11
|
+
* whoever serialised the query); a key whose value is `undefined` counts as absent;
|
|
12
|
+
* arrays are equal element by element, in order; a `$date` literal equals any value that
|
|
13
|
+
* reads as the same instant.
|
|
14
|
+
*/
|
|
15
|
+
export type Equals = (value: unknown) => boolean;
|
|
16
|
+
/**
|
|
17
|
+
* The same whole number as a big integer, when a literal is one and could be written either
|
|
18
|
+
* way.
|
|
19
|
+
*
|
|
20
|
+
* Settled once, when the query compiles, so the comparison stays a single `===` against a
|
|
21
|
+
* value prepared in advance. Only a whole number gets one: nothing is a big integer and a
|
|
22
|
+
* fraction, and a string or a boolean can never be one either.
|
|
23
|
+
*/
|
|
24
|
+
export declare function alsoBig(literal: unknown): bigint | undefined;
|
|
25
|
+
export declare function equalsLiteral(literal: unknown, now: number, ignoreCase?: boolean): Equals;
|
|
26
|
+
/**
|
|
27
|
+
* Equality between two values that both came out of a document, for `$field` references.
|
|
28
|
+
*
|
|
29
|
+
* The same rules as equality with a literal — same type, arrays in order, objects in any
|
|
30
|
+
* key order — but neither side is known when the query compiles, so nothing can be
|
|
31
|
+
* prepared. Missing is not equal to anything, including itself: a reference that reaches
|
|
32
|
+
* nothing matches nothing.
|
|
33
|
+
*/
|
|
34
|
+
export declare function valuesEqual(a: unknown, b: unknown): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob matching: `*`, `?`, and `\` to escape either of them.
|
|
3
|
+
*
|
|
4
|
+
* What people mean when they reach for a pattern in a search box — `/api/*`,
|
|
5
|
+
* `*.example.com` — and the reason it is not `$regex` with the sharp edges filed off: this
|
|
6
|
+
* runs without a regular-expression engine, so it is available when patterns are turned
|
|
7
|
+
* off for untrusted callers, and it cannot backtrack exponentially.
|
|
8
|
+
*
|
|
9
|
+
* No character classes. `[a-z]` is a literal `[a-z]`, because half a class syntax is worse
|
|
10
|
+
* than none: somebody would write `[!abc]` expecting negation and get a match against those
|
|
11
|
+
* four characters instead, with nothing to say so.
|
|
12
|
+
*
|
|
13
|
+
* **A pattern is taken apart once and becomes the narrowest matcher that fits it.** Nearly
|
|
14
|
+
* every real glob is a prefix, a suffix, something in the middle, or a run of literals
|
|
15
|
+
* separated by stars, and each of those is one or two native string calls per value. Only a
|
|
16
|
+
* pattern with `?` in it walks character by character, and only that walk pays for splitting
|
|
17
|
+
* the text into characters — which, measured against `startsWith`, was four times the cost of
|
|
18
|
+
* the whole comparison.
|
|
19
|
+
*/
|
|
20
|
+
export type GlobMatcher = (text: string) => boolean;
|
|
21
|
+
export declare function compileGlob(pattern: string): GlobMatcher;
|
|
22
|
+
/**
|
|
23
|
+
* The same glob as the body of a regular expression, for a target that speaks patterns
|
|
24
|
+
* rather than running the matcher — a store this query is being pushed down to.
|
|
25
|
+
*
|
|
26
|
+
* It shares the tokenizer with the matcher on purpose: an escape read one way here and
|
|
27
|
+
* another way there would mean a query that found different things depending on which half
|
|
28
|
+
* of the system answered it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function compileGlobPattern(pattern: string, quoteLiteral: (text: string) => string): string;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One total order over every value a document can hold.
|
|
3
|
+
*
|
|
4
|
+
* A sort has to put *something* first when one item's field is a number and another's is
|
|
5
|
+
* a string, or missing. The order is fixed and written down in the specification, so two
|
|
6
|
+
* implementations sort a shared result the same way:
|
|
7
|
+
*
|
|
8
|
+
* missing and null < numbers < strings < objects < booleans < dates
|
|
9
|
+
*
|
|
10
|
+
* Strings compare by UTF-16 code unit, not by locale. A locale-aware sort would put a
|
|
11
|
+
* shared link's rows in a different order for each reader, which is worse than an order
|
|
12
|
+
* that is merely unidiomatic for some of them. Objects compare equal to one another, so
|
|
13
|
+
* their order is the order they arrived in.
|
|
14
|
+
*/
|
|
15
|
+
export declare function compareValues(a: unknown, b: unknown): number;
|
|
16
|
+
/**
|
|
17
|
+
* The value an item sorts by, from everything its path reached.
|
|
18
|
+
*
|
|
19
|
+
* A path that reaches an array — or fans out over one — reaches several values. Ascending
|
|
20
|
+
* sorts by the smallest of them and descending by the largest: an order
|
|
21
|
+
* by "tags" puts first whatever has the tag that sorts first. Arrays nested past sixteen
|
|
22
|
+
* levels are passed over, so an array that contains itself ends the walk.
|
|
23
|
+
*/
|
|
24
|
+
export declare function sortValue(values: readonly unknown[], descending: boolean, depth?: number): unknown;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reaching a field inside a document.
|
|
3
|
+
*
|
|
4
|
+
* A path is dot-separated: `address.city`, `items.id`, `items.0.id`. Three rules:
|
|
5
|
+
*
|
|
6
|
+
* - **An array is seen through.** `items.id` reaches the `id` of every element, and a
|
|
7
|
+
* condition on it holds when it holds for any of them.
|
|
8
|
+
* - **A numeric segment on an array is a position.** `items.0.id` is the first element's.
|
|
9
|
+
* - **A `Map` is read by key**, so a document that keeps a lookup table in a `Map` can be
|
|
10
|
+
* queried the same way as one that keeps it in an object.
|
|
11
|
+
*
|
|
12
|
+
* Only own properties are read. `{ constructor: { $exists: true } }` would otherwise match
|
|
13
|
+
* every object ever made, and `__proto__` would reach somewhere a query has no business
|
|
14
|
+
* being. The check costs a `hasOwn` call, so it is paid only for the handful of names
|
|
15
|
+
* `Object.prototype` and `Map.prototype` actually have — every other name is read
|
|
16
|
+
* directly. `Map.prototype` is on the list because a `Map` document read directly would
|
|
17
|
+
* answer `size` or `get` with its own method rather than with the entry of that name.
|
|
18
|
+
*/
|
|
19
|
+
/**
|
|
20
|
+
* Calls `test` with each value the path reaches, stopping at the first `true`.
|
|
21
|
+
*
|
|
22
|
+
* A path that reaches nothing calls `test(undefined)` once, so "missing" is a value a
|
|
23
|
+
* condition can ask about: that is how `{ field: null }` and `$exists: false` see it.
|
|
24
|
+
*/
|
|
25
|
+
export type Reach = (document: unknown, test: (value: unknown) => boolean) => boolean;
|
|
26
|
+
/** A single-valued read, for paths that cannot fan out. Absent when the path can. */
|
|
27
|
+
export type Read = (document: unknown) => unknown;
|
|
28
|
+
/** What a probe returns when the path met an array before its last segment, and so may reach several values. */
|
|
29
|
+
export declare const FANOUT: unique symbol;
|
|
30
|
+
/**
|
|
31
|
+
* A straight read of a multi-segment path: the one value it reaches, or `FANOUT` if an
|
|
32
|
+
* array stood in the way and only `Reach` can answer.
|
|
33
|
+
*
|
|
34
|
+
* The point is the common case. `address.city` on a record with no arrays on the way has
|
|
35
|
+
* exactly one value, and reading it directly lets the engine test it the way a hand-written
|
|
36
|
+
* `p.address.city === "x"` would — one read, one comparison — instead of through a walk and
|
|
37
|
+
* a callback. A missing or primitive step reads as `undefined`, the same as `Reach` sees it.
|
|
38
|
+
*/
|
|
39
|
+
export type Probe = (document: unknown) => unknown;
|
|
40
|
+
/** Whether a name could be found on a plain object's prototype, and so must always be asked for carefully. */
|
|
41
|
+
export declare function needsGuard(key: string): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* What a plain object's prototype is, for the cheapest plainness test there is.
|
|
44
|
+
*
|
|
45
|
+
* `document.__proto__ === PLAIN` is a map check — about as fast as reading a field — where
|
|
46
|
+
* `Object.getPrototypeOf` is a call and `Object.hasOwn` is a second lookup. Reading it is
|
|
47
|
+
* safe in both directions: a document carrying its own `__proto__` field (which is what
|
|
48
|
+
* `JSON.parse` makes of one) simply fails the test and takes the careful path, and so does
|
|
49
|
+
* anything with a prototype of its own.
|
|
50
|
+
*/
|
|
51
|
+
export declare const PLAIN: Object;
|
|
52
|
+
/**
|
|
53
|
+
* One field of one value: own properties and `Map` entries, nothing else.
|
|
54
|
+
*
|
|
55
|
+
* Ownership is checked **before** the read, not after, and for every name rather than for a
|
|
56
|
+
* list of known-inherited ones. The list was only ever `Object.prototype` and
|
|
57
|
+
* `Map.prototype`, so a document that was a class instance answered `{ role: "admin" }` from
|
|
58
|
+
* its prototype — and `{ secret: … }` by *running* an inherited getter, which is a query
|
|
59
|
+
* executing code the document's author wrote. Asking first is also why no accessor outside
|
|
60
|
+
* the item can run at all.
|
|
61
|
+
*/
|
|
62
|
+
export declare function readOwn(value: object, key: string): unknown;
|
|
63
|
+
/**
|
|
64
|
+
* The value at a single-segment path.
|
|
65
|
+
*
|
|
66
|
+
* The overwhelmingly common case — `{ id: 2 }` — so it is written out: a direct property
|
|
67
|
+
* read, with the `Map` check paid only when the read found nothing, which a plain object
|
|
68
|
+
* with the field never does.
|
|
69
|
+
*/
|
|
70
|
+
export declare function readKey(key: string): Read;
|
|
71
|
+
/** The whole document, for queries that are conditions on the value itself. */
|
|
72
|
+
export declare const readSelf: Read;
|
|
73
|
+
/**
|
|
74
|
+
* Builds the walk for a multi-segment path.
|
|
75
|
+
*
|
|
76
|
+
* A loop while the path meets only objects, which is nearly always, and the recursive
|
|
77
|
+
* fan-out only from the first array it meets. `address.city` on a record with no arrays in
|
|
78
|
+
* it costs two property reads and one call.
|
|
79
|
+
*/
|
|
80
|
+
export declare function reachPath(keys: readonly string[]): Reach;
|
|
81
|
+
/** Builds the straight read for a multi-segment path. */
|
|
82
|
+
export declare function probePath(keys: readonly string[]): Probe;
|
|
83
|
+
/** The same, from a value some other accessor produced. */
|
|
84
|
+
export declare function probeFrom(read: Read, keys: readonly string[]): Probe;
|
|
85
|
+
/** Continues a walk from a value some other accessor produced. */
|
|
86
|
+
export declare function reachFrom(read: Read, keys: readonly string[]): Reach;
|
|
87
|
+
/**
|
|
88
|
+
* How many segments a path may have.
|
|
89
|
+
*
|
|
90
|
+
* The same number as the document depth above, and for the same reason: a path longer than
|
|
91
|
+
* the deepest document a walk will follow can reach nothing anyway, so refusing it costs a
|
|
92
|
+
* query nothing real. What it buys is two things. A field name is the one part of a query
|
|
93
|
+
* whose size the node count does not bound — `{"a.a.a…": 1}` is a single node however long
|
|
94
|
+
* it is — and each segment becomes an object and a step in a walk, so a field name pasted
|
|
95
|
+
* from somewhere could turn a small query into a large one.
|
|
96
|
+
*
|
|
97
|
+
* And it makes the same path mean the same thing everywhere. Projection walked its own tree
|
|
98
|
+
* of segments and stopped at sixty-four, silently, so a query matched on a path that
|
|
99
|
+
* `fields` then dropped from the result: the filter and the projection disagreed about a
|
|
100
|
+
* path they were both given.
|
|
101
|
+
*/
|
|
102
|
+
export declare const MAX_PATH_SEGMENTS = 512;
|
|
103
|
+
/**
|
|
104
|
+
* Splits a field name into segments, refusing the ones that can reach nothing.
|
|
105
|
+
*
|
|
106
|
+
* Returns the reason as a sentence for the caller to raise, so this module stays free of
|
|
107
|
+
* the error type and its location bookkeeping.
|
|
108
|
+
*/
|
|
109
|
+
export declare function splitPath(path: string): string[] | string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Writing a field onto a result object.
|
|
3
|
+
*
|
|
4
|
+
* `target[key] = value` looks harmless until the key is `__proto__`. Assignment goes
|
|
5
|
+
* through the setter inherited from `Object.prototype`, which *replaces the object's
|
|
6
|
+
* prototype* instead of adding a property — so the field the caller asked for is missing
|
|
7
|
+
* and the object answers to names nobody put on it.
|
|
8
|
+
*
|
|
9
|
+
* It is reachable from data rather than from code: `JSON.parse('{"__proto__": …}')` makes
|
|
10
|
+
* an own `__proto__` property, so any document read from a file, a request body or a store
|
|
11
|
+
* can carry one. Projecting such a document used to lose that field and hand back a result
|
|
12
|
+
* whose prototype was whatever the document said, which the projection's own test could not
|
|
13
|
+
* see because the property it was looking for was genuinely there — on the prototype.
|
|
14
|
+
*
|
|
15
|
+
* Defining the property puts it where it was meant to go. Every other key takes the plain
|
|
16
|
+
* assignment, so the guard costs one comparison on the path that builds results.
|
|
17
|
+
*/
|
|
18
|
+
export declare function setField(target: Record<string, unknown>, key: string, value: unknown): void;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { DateLiteral, FieldReference, TypeName } from "../types.js";
|
|
2
|
+
/** An object literal or a JSON-parsed object: what a query document is made of. */
|
|
3
|
+
export declare function isPlainObject(value: unknown): value is Record<string, unknown>;
|
|
4
|
+
/** `{ $date: … }` and nothing else. */
|
|
5
|
+
export declare function isDateLiteral(value: unknown): value is DateLiteral;
|
|
6
|
+
/** `{ $field: "path" }` and nothing else: a comparison with another field of the same item. */
|
|
7
|
+
export declare function isFieldReference(value: unknown): value is FieldReference;
|
|
8
|
+
/**
|
|
9
|
+
* The instant a `$date` literal stands for, in epoch milliseconds, or `NaN`.
|
|
10
|
+
*
|
|
11
|
+
* `now` is passed in rather than read here, because a query holding `{ "$ago": "1h" }` has
|
|
12
|
+
* to resolve against one instant for the whole run: reading the clock per comparison would
|
|
13
|
+
* let the window move while a scan is in progress, so two items a second apart could be
|
|
14
|
+
* judged against different hours. It is also what makes a relative filter testable without
|
|
15
|
+
* waiting.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveDate(value: unknown, now: number): number;
|
|
18
|
+
/**
|
|
19
|
+
* An instant from a string, read the same way on every machine.
|
|
20
|
+
*
|
|
21
|
+
* JavaScript reads a date-only string as midnight **UTC** and a timestamp with no offset as
|
|
22
|
+
* **local time** — so `"2026-01-01T12:00:00"` was a different instant in Tokyo and in London,
|
|
23
|
+
* and a query and a document that both held one answered differently depending on where the
|
|
24
|
+
* process happened to be running. That is the failure the language already rules out for
|
|
25
|
+
* `"12/31/2020"`: the same query meaning two things. A missing offset is UTC here, which is
|
|
26
|
+
* the rule the date-only form already follows.
|
|
27
|
+
*/
|
|
28
|
+
export declare function instant(text: string): number;
|
|
29
|
+
export declare function toTime(value: unknown): number;
|
|
30
|
+
/** The type names `$type` knows, in one list, so the error for a wrong one can offer the right one. */
|
|
31
|
+
export declare const TYPE_NAMES: readonly TypeName[];
|
|
32
|
+
/** Whether a document value is of the named type. A missing value is of no type. */
|
|
33
|
+
export declare function isOfType(value: unknown, type: TypeName): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* A readable name for a value in an error sentence.
|
|
36
|
+
*
|
|
37
|
+
* Specific about what it was given, because "a query is an object, not an object" — which
|
|
38
|
+
* is what a `Map`, a `Set` and a class instance all used to produce — tells the reader
|
|
39
|
+
* nothing at all about the thing they passed.
|
|
40
|
+
*/
|
|
41
|
+
export declare function describe(value: unknown): string;
|