@rebasepro/common 0.13.1-canary.gf57a27e → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/data/filter-conditions.d.ts +34 -0
- package/dist/data/filter-dialect.d.ts +55 -3
- package/dist/data/query_builder.d.ts +2 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.es.js +512 -80
- package/dist/index.es.js.map +1 -1
- package/dist/util/auth-default-policies.d.ts +0 -8
- package/dist/util/collections.d.ts +17 -0
- package/dist/util/permissions.d.ts +30 -0
- package/dist/util/relations.d.ts +29 -0
- package/package.json +5 -4
- package/src/data/buildRebaseData.ts +15 -5
- package/src/data/filter-conditions.ts +46 -0
- package/src/data/filter-dialect.ts +354 -51
- package/src/data/query_builder.ts +2 -2
- package/src/index.ts +1 -0
- package/src/util/auth-default-policies.ts +56 -15
- package/src/util/collections.ts +17 -1
- package/src/util/permissions.test.ts +23 -2
- package/src/util/permissions.ts +43 -6
- package/src/util/pg-column-to-property.ts +26 -4
- package/src/util/policy/evaluatePolicy.ts +24 -2
- package/src/util/policy/sqlToPolicy.ts +141 -14
- package/src/util/relations.ts +44 -1
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The `FilterValues` grammar, one level below the wire codec.
|
|
3
|
+
*
|
|
4
|
+
* A field's filter is either one `[op, value]` tuple or an **array** of them —
|
|
5
|
+
* `{ age: [[">=", 18], ["<", 65]] }` — which is what the fluent builder produces
|
|
6
|
+
* from two `.where()` calls on the same column. Reading that shape is grammar,
|
|
7
|
+
* not a driver detail, so every compiler reads it through here.
|
|
8
|
+
*
|
|
9
|
+
* It lived only inside the Postgres compiler, and the Mongo one destructured
|
|
10
|
+
* `const [op, value] = filterParam` regardless: given the array-of-tuples form
|
|
11
|
+
* `op` bound to `[">=", 18]`, no operator matched, and the condition was
|
|
12
|
+
* dropped. Both of them. A read asking for adults under 65 returned every row
|
|
13
|
+
* of the collection with a 200.
|
|
14
|
+
*
|
|
15
|
+
* @module
|
|
16
|
+
*/
|
|
17
|
+
import type { WhereFilterOp } from "@rebasepro/types";
|
|
18
|
+
/** One `[operator, value]` condition. */
|
|
19
|
+
export type FilterTuple = [WhereFilterOp, unknown];
|
|
20
|
+
/**
|
|
21
|
+
* Read one field's filter as the list of conditions it stands for.
|
|
22
|
+
*
|
|
23
|
+
* Accepts both declared shapes and normalises them to a list:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* toFilterTuples(["==", "active"]) // [["==", "active"]]
|
|
27
|
+
* toFilterTuples([[">=", 18], ["<", 65]]) // [[">=", 18], ["<", 65]]
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* A falsy, non-array or empty param has no conditions in it — the empty list,
|
|
31
|
+
* so a caller iterating adds nothing rather than compiling a tuple of
|
|
32
|
+
* `undefined`s and logging about an operator nobody sent.
|
|
33
|
+
*/
|
|
34
|
+
export declare function toFilterTuples(filterParam: unknown): FilterTuple[];
|
|
@@ -9,12 +9,61 @@
|
|
|
9
9
|
* metadata, so type coercion is the responsibility of the server-side data
|
|
10
10
|
* driver which has access to the collection schema.
|
|
11
11
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
12
|
+
* Structural characters inside a value are backslash-escaped: `,` → `\,`,
|
|
13
|
+
* `(` → `\(`, `)` → `\)`, and a literal backslash as `\\`. Decoding is
|
|
14
|
+
* deliberately conservative — only those four sequences are decoded, so a
|
|
15
|
+
* backslash that arrives unescaped from an older client survives intact.
|
|
14
16
|
*
|
|
15
17
|
* @module
|
|
16
18
|
*/
|
|
17
|
-
import { FilterValues, LogicalCondition, FilterCondition } from "@rebasepro/types";
|
|
19
|
+
import { WhereFilterOp, FilterValues, LogicalCondition, FilterCondition } from "@rebasepro/types";
|
|
20
|
+
/**
|
|
21
|
+
* A filter condition named an operator this dialect does not have.
|
|
22
|
+
*
|
|
23
|
+
* ## Why this throws, rather than returning a typed rejection
|
|
24
|
+
*
|
|
25
|
+
* `deserializeFilter` is the *shared* codec: the REST ingress
|
|
26
|
+
* (`packages/server/src/api/rest/query-parser.ts`), the browser SDK and the
|
|
27
|
+
* admin panel (`buildRebaseData.ts`) all decode through it. Two constraints
|
|
28
|
+
* follow.
|
|
29
|
+
*
|
|
30
|
+
* - It cannot throw the server's `ApiError`. `@rebasepro/common` does not
|
|
31
|
+
* depend on `@rebasepro/server` (the dependency runs the other way), and a
|
|
32
|
+
* browser client has no error handler to render an `ApiError` with. So the
|
|
33
|
+
* rejection is this plain `Error` subclass, whose `message` reads correctly
|
|
34
|
+
* wherever it surfaces — a rejected promise in an app, a 400 body over HTTP.
|
|
35
|
+
* - It cannot be a returned rejection *value*. Every caller assigns the result
|
|
36
|
+
* straight into a query it is about to run; a sentinel that none of them
|
|
37
|
+
* check would be ignored, which is exactly the silently-wrong-filter failure
|
|
38
|
+
* this exists to stop. Throwing is also what this file already does for the
|
|
39
|
+
* sibling cases — `serializeTuple` on an unknown canonical operator,
|
|
40
|
+
* `deserializeLogicalCondition` past the nesting bound — and the REST parser
|
|
41
|
+
* already converts the latter into a 400.
|
|
42
|
+
*
|
|
43
|
+
* `statusCode`, `code` and `details` are carried as fields because the server's
|
|
44
|
+
* Hono error handler duck-types those off any thrown error: a decode path that
|
|
45
|
+
* forgets to convert still answers 400 with the canonical envelope instead of a
|
|
46
|
+
* 500 that says "An unexpected error occurred". `query-parser.ts` converts
|
|
47
|
+
* explicitly all the same — that is the path the contract is stated on, and an
|
|
48
|
+
* incidental 400 is not a contract.
|
|
49
|
+
*/
|
|
50
|
+
export declare class UnknownFilterOperatorError extends Error {
|
|
51
|
+
/** The field the condition was written against. */
|
|
52
|
+
readonly field: string;
|
|
53
|
+
/** The operator string as it arrived, verbatim. */
|
|
54
|
+
readonly operator: string;
|
|
55
|
+
/** Every operator this dialect accepts, in canonical spelling. */
|
|
56
|
+
readonly validOperators: readonly WhereFilterOp[];
|
|
57
|
+
/** See the class docblock: read by the server's error handler. */
|
|
58
|
+
readonly statusCode = 400;
|
|
59
|
+
readonly code = "UNKNOWN_FILTER_OPERATOR";
|
|
60
|
+
readonly details: {
|
|
61
|
+
field: string;
|
|
62
|
+
operator: string;
|
|
63
|
+
validOperators: readonly WhereFilterOp[];
|
|
64
|
+
};
|
|
65
|
+
constructor(field: string, operator: string);
|
|
66
|
+
}
|
|
18
67
|
/**
|
|
19
68
|
* Convert `FilterValues` (or `WireFilterValues`) to a PostgREST-style
|
|
20
69
|
* querystring record.
|
|
@@ -48,6 +97,9 @@ export declare function serializeFilter(filter: FilterValues<string> | Record<st
|
|
|
48
97
|
*
|
|
49
98
|
* deserializeFilter({ age: ["gte.18", "lt.65"] })
|
|
50
99
|
* // → { age: [[">=", "18"], ["<", "65"]] }
|
|
100
|
+
*
|
|
101
|
+
* @throws {UnknownFilterOperatorError} when a condition names an operator this
|
|
102
|
+
* dialect does not have. See that class for why a rejection here is a throw.
|
|
51
103
|
*/
|
|
52
104
|
export declare function deserializeFilter(query: Record<string, unknown>): FilterValues<string>;
|
|
53
105
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CollectionAccessor, FilterCondition, FindResponse, LogicalCondition, QueryBuilderInterface, WhereFilterOp,
|
|
1
|
+
import { CollectionAccessor, FilterCondition, FindResponse, LogicalCondition, QueryBuilderInterface, WhereFilterOp, WhereValueFor, type ComputedSortField } from "@rebasepro/types";
|
|
2
2
|
export declare function or(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition;
|
|
3
3
|
export declare function and(...conditions: (FilterCondition | LogicalCondition)[]): LogicalCondition;
|
|
4
4
|
export declare function cond(column: string, operator: WhereFilterOp, value: unknown): FilterCondition;
|
|
@@ -11,7 +11,7 @@ export declare class QueryBuilder<M extends Record<string, unknown> = Record<str
|
|
|
11
11
|
* @example
|
|
12
12
|
* client.collection('users').where('age', '>=', 18).find()
|
|
13
13
|
*/
|
|
14
|
-
where<K extends keyof M & string>(column: K, operator:
|
|
14
|
+
where<K extends keyof M & string, Op extends WhereFilterOp>(column: K, operator: Op, value: WhereValueFor<Op, M[K]>): this;
|
|
15
15
|
where(logicalCondition: LogicalCondition): this;
|
|
16
16
|
/**
|
|
17
17
|
* Order the results by a specific column.
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,7 @@ export * from "./data/buildRoutedRebaseData";
|
|
|
5
5
|
export * from "./data/resolveDataSource";
|
|
6
6
|
export * from "./data/query_builder";
|
|
7
7
|
export * from "./data/paginate";
|
|
8
|
+
export * from "./data/filter-conditions";
|
|
8
9
|
export * from "./data/filter-dialect";
|
|
9
10
|
export * from "./data/sort-dialect";
|
|
10
11
|
export * from "./table-classification";
|