@rebasepro/server-postgres 0.21.2-canary.g1ea48be → 0.22.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/{backup-cli-CkjsJEcu.js → backup-cli-DqBakiMO.js} +2 -2
- package/dist/{backup-cli-CkjsJEcu.js.map → backup-cli-DqBakiMO.js.map} +1 -1
- package/dist/{backup-service-BNLwvxuy.js → backup-service-CaMOS76G.js} +2 -2
- package/dist/{backup-service-BNLwvxuy.js.map → backup-service-CaMOS76G.js.map} +1 -1
- package/dist/cli.js +9 -9
- package/dist/{doctor-Gzzd8wTq.js → doctor-CU9IogdL.js} +3 -3
- package/dist/{doctor-Gzzd8wTq.js.map → doctor-CU9IogdL.js.map} +1 -1
- package/dist/{ensure-collection-policies-B4IDFtQ-.js → ensure-collection-policies-Dzd-2S81.js} +4 -4
- package/dist/{ensure-collection-policies-B4IDFtQ-.js.map → ensure-collection-policies-Dzd-2S81.js.map} +1 -1
- package/dist/{ensure-collection-tables-Cr2ye5JC.js → ensure-collection-tables-CpAgy51F.js} +3 -3
- package/dist/{ensure-collection-tables-Cr2ye5JC.js.map → ensure-collection-tables-CpAgy51F.js.map} +1 -1
- package/dist/{ensure-tables-BnEvEJPr.js → ensure-tables-Cr5B4UmH.js} +2 -2
- package/dist/{ensure-tables-BnEvEJPr.js.map → ensure-tables-Cr5B4UmH.js.map} +1 -1
- package/dist/{generate-drizzle-schema-D2MDdJt-.js → generate-drizzle-schema-B537GIvz.js} +2 -2
- package/dist/{generate-drizzle-schema-D2MDdJt-.js.map → generate-drizzle-schema-B537GIvz.js.map} +1 -1
- package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js → generate-drizzle-schema-logic-BcMl7VSy.js} +2 -2
- package/dist/{generate-drizzle-schema-logic-DFa9qy9u.js.map → generate-drizzle-schema-logic-BcMl7VSy.js.map} +1 -1
- package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js → generate-postgres-ddl-logic-D7imhYV8.js} +2 -2
- package/dist/{generate-postgres-ddl-logic-CR2xcS7e.js.map → generate-postgres-ddl-logic-D7imhYV8.js.map} +1 -1
- package/dist/index.es.js +391 -54
- package/dist/index.es.js.map +1 -1
- package/dist/{plan-schema-Hgl62S-w.js → plan-schema-CboAIwLN.js} +13 -8
- package/dist/plan-schema-CboAIwLN.js.map +1 -0
- package/dist/{policy-drift-B0GDRh7_.js → policy-drift-xJfy9xG7.js} +2 -2
- package/dist/{policy-drift-B0GDRh7_.js.map → policy-drift-xJfy9xG7.js.map} +1 -1
- package/dist/{rls-bootstrap-sql-BHA6jLtj.js → rls-bootstrap-sql-H3rCFi3F.js} +5 -5
- package/dist/{rls-bootstrap-sql-BHA6jLtj.js.map → rls-bootstrap-sql-H3rCFi3F.js.map} +1 -1
- package/dist/schema/doctor-cli.js +2 -2
- package/dist/schema/generate-drizzle-schema.js +1 -1
- package/dist/schema/generate-postgres-ddl.js +1 -1
- package/dist/schema/search-column.d.ts +21 -1
- package/dist/services/FetchService.d.ts +50 -2
- package/dist/services/PersistService.d.ts +16 -1
- package/dist/services/RelationService.d.ts +31 -1
- package/dist/services/RelationWriteService.d.ts +16 -1
- package/dist/services/dataService.d.ts +16 -1
- package/dist/services/read-scope.d.ts +100 -0
- package/dist/utils/drizzle-conditions.d.ts +56 -6
- package/package.json +7 -7
- package/dist/plan-schema-Hgl62S-w.js.map +0 -1
|
@@ -4,8 +4,8 @@ __createRequire(import.meta.url);
|
|
|
4
4
|
import { i as diagnoseDbError } from "../cli-errors-Dka89exj.js";
|
|
5
5
|
import { o as validatePolicyPgRoles, s as warnOnAnonymousGrants } from "../rls-enforcement-C6Xk0lA6.js";
|
|
6
6
|
import { t as chalk } from "../source-Br7L7GOI.js";
|
|
7
|
-
import { n as loadCollections, r as runDoctor } from "../doctor-
|
|
8
|
-
import { n as formatPolicyDrift, r as hasDrift, t as checkPolicyDrift } from "../policy-drift-
|
|
7
|
+
import { n as loadCollections, r as runDoctor } from "../doctor-CU9IogdL.js";
|
|
8
|
+
import { n as formatPolicyDrift, r as hasDrift, t as checkPolicyDrift } from "../policy-drift-xJfy9xG7.js";
|
|
9
9
|
import path from "path";
|
|
10
10
|
//#region src/schema/doctor-policy-checks.ts
|
|
11
11
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { t as main } from "../generate-drizzle-schema-
|
|
3
|
+
import { t as main } from "../generate-drizzle-schema-B537GIvz.js";
|
|
4
4
|
//#region src/schema/bin/generate-drizzle-schema.ts
|
|
5
5
|
/**
|
|
6
6
|
* The built entry point for `generate-drizzle-schema`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from "module";
|
|
2
2
|
__createRequire(import.meta.url);
|
|
3
|
-
import { a as generatePostgresVectorDdl, i as generatePostgresTriggersDdl, n as generatePostgresPoliciesDdl, r as generatePostgresSearchDdl, t as generatePostgresDdl } from "../generate-postgres-ddl-logic-
|
|
3
|
+
import { a as generatePostgresVectorDdl, i as generatePostgresTriggersDdl, n as generatePostgresPoliciesDdl, r as generatePostgresSearchDdl, t as generatePostgresDdl } from "../generate-postgres-ddl-logic-D7imhYV8.js";
|
|
4
4
|
import { n as outError, t as out } from "../cli-output-CNdMql-L.js";
|
|
5
5
|
import { loadCollectionsFromDirectory } from "@rebasepro/server";
|
|
6
6
|
import { declaredDatabaseExtensions } from "@rebasepro/types";
|
|
@@ -43,7 +43,7 @@
|
|
|
43
43
|
* `auditor` and `auditoria` to `auditori` — *different lexemes* — so a query
|
|
44
44
|
* typed without accents misses every row that carries them.
|
|
45
45
|
*/
|
|
46
|
-
import { CollectionConfig, SearchConfig, SearchWeight } from "@rebasepro/types";
|
|
46
|
+
import { CollectionConfig, SearchConfig, SearchMode, SearchWeight } from "@rebasepro/types";
|
|
47
47
|
/**
|
|
48
48
|
* Names of the helper functions. Frozen: they are recorded in the stored
|
|
49
49
|
* generation expression of every search column ever created, so renaming one
|
|
@@ -67,6 +67,20 @@ export interface ResolvedSearchField {
|
|
|
67
67
|
sql: string;
|
|
68
68
|
/** The plain-text term this field contributes, for the fuzzy column. */
|
|
69
69
|
textSql: string;
|
|
70
|
+
/**
|
|
71
|
+
* The same text with accents folded **unconditionally**, for the substring
|
|
72
|
+
* half of {@link SearchMode} `"hybrid"`.
|
|
73
|
+
*
|
|
74
|
+
* Separate from {@link textSql} rather than replacing it, and that
|
|
75
|
+
* separation is the whole migration story for `mode`. `textSql` feeds the
|
|
76
|
+
* *stored* generated columns, so changing it changes their generation
|
|
77
|
+
* expression, which changes the fingerprint, which makes the next boot
|
|
78
|
+
* refuse (see `searchStampGuards`). This one is only ever interpolated into
|
|
79
|
+
* a WHERE clause, so a collection can switch to `"hybrid"` — and gain
|
|
80
|
+
* accent folding on the substring half — without rebuilding a column or
|
|
81
|
+
* taking an ACCESS EXCLUSIVE lock.
|
|
82
|
+
*/
|
|
83
|
+
foldedTextSql: string;
|
|
70
84
|
}
|
|
71
85
|
/** Everything the generators need to render one collection's search column. */
|
|
72
86
|
export interface SearchColumnSpec {
|
|
@@ -76,6 +90,12 @@ export interface SearchColumnSpec {
|
|
|
76
90
|
column: string;
|
|
77
91
|
language: string;
|
|
78
92
|
unaccent: boolean;
|
|
93
|
+
/**
|
|
94
|
+
* How the query side matches. Deliberately absent from
|
|
95
|
+
* {@link SearchColumnSpec.expression} and from every fingerprint: it
|
|
96
|
+
* describes the WHERE clause, not the column.
|
|
97
|
+
*/
|
|
98
|
+
mode: SearchMode;
|
|
79
99
|
fields: ResolvedSearchField[];
|
|
80
100
|
/** Body of `GENERATED ALWAYS AS ( … ) STORED` for the tsvector column. */
|
|
81
101
|
expression: string;
|
|
@@ -7,6 +7,7 @@ import { DrizzleClient } from "../interfaces.js";
|
|
|
7
7
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
|
|
8
8
|
import { type NestedPathHop } from "./nested-path.js";
|
|
9
9
|
import { type WithDeleted } from "./soft-delete.js";
|
|
10
|
+
import { type ReadCallContextProvider } from "./read-scope.js";
|
|
10
11
|
/**
|
|
11
12
|
* Service for handling all row read operations.
|
|
12
13
|
* Handles fetching, searching, counting, and filtering rows.
|
|
@@ -39,8 +40,22 @@ export declare function numericAggregateAliases(aggregates: {
|
|
|
39
40
|
export declare class FetchService {
|
|
40
41
|
private db;
|
|
41
42
|
private registry;
|
|
43
|
+
/**
|
|
44
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
45
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
46
|
+
* test, where a collection declaring the hook is refused rather than
|
|
47
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
48
|
+
*/
|
|
49
|
+
private callContext?;
|
|
42
50
|
private relationService;
|
|
43
|
-
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry
|
|
51
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry,
|
|
52
|
+
/**
|
|
53
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
54
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
55
|
+
* test, where a collection declaring the hook is refused rather than
|
|
56
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
57
|
+
*/
|
|
58
|
+
callContext?: ReadCallContextProvider | undefined);
|
|
44
59
|
/**
|
|
45
60
|
* Get the relational query builder for a given table name.
|
|
46
61
|
* Safely narrows the DrizzleClient union type to access db.query[tableName].
|
|
@@ -63,6 +78,31 @@ export declare class FetchService {
|
|
|
63
78
|
* compile stays unresolvable and so fails closed.
|
|
64
79
|
*/
|
|
65
80
|
private filterContext;
|
|
81
|
+
/**
|
|
82
|
+
* The conditions this collection's `beforeQuery` hooks ask to add, for a
|
|
83
|
+
* read that is about to be compiled.
|
|
84
|
+
*
|
|
85
|
+
* Resolved here, from the registry this service already holds, rather than
|
|
86
|
+
* handed in by a caller — so a read path that builds a WHERE cannot serve
|
|
87
|
+
* one of these collections without the hook. `[]` when nothing is declared,
|
|
88
|
+
* which leaves the compiled SQL byte-identical.
|
|
89
|
+
*
|
|
90
|
+
* Every caller pushes the result onto the same `allConditions` array its
|
|
91
|
+
* own filter goes into, and every one of those arrays is AND-ed. That is
|
|
92
|
+
* what keeps a hook additive: it is not trusted to narrow, it is only ever
|
|
93
|
+
* given a way to.
|
|
94
|
+
*/
|
|
95
|
+
private narrowRead;
|
|
96
|
+
/** {@link narrowRead}, pre-combined, for the reads that hold one `SQL`. */
|
|
97
|
+
private narrowReadCondition;
|
|
98
|
+
/**
|
|
99
|
+
* The read the hooks are shown, from the options a read path was given.
|
|
100
|
+
*
|
|
101
|
+
* Transport is left out on purpose — a `databaseId`, an `include` tree, a
|
|
102
|
+
* `withDeleted` flag are not part of "which rows". What is in is what a
|
|
103
|
+
* scope might reasonably branch on.
|
|
104
|
+
*/
|
|
105
|
+
private static describeRead;
|
|
66
106
|
/**
|
|
67
107
|
* The table column this collection's rows are keyed by, or `undefined`.
|
|
68
108
|
*
|
|
@@ -457,7 +497,15 @@ export declare class FetchService {
|
|
|
457
497
|
withDeleted?: WithDeleted;
|
|
458
498
|
}): Promise<Record<string, unknown>[]>;
|
|
459
499
|
/**
|
|
460
|
-
* Check if a field value is unique
|
|
500
|
+
* Check if a field value is unique.
|
|
501
|
+
*
|
|
502
|
+
* Deliberately **not** narrowed by `beforeQuery`, and it is the only read
|
|
503
|
+
* here that is not. The question is whether the value exists anywhere in
|
|
504
|
+
* the table, not whether it exists among the rows this caller can see:
|
|
505
|
+
* narrowed, it would answer "unique" for a value a hidden row already
|
|
506
|
+
* holds, and the insert would then be rejected by the unique constraint
|
|
507
|
+
* with a message about a row the caller has no way to find. Uniqueness is a
|
|
508
|
+
* property of the table.
|
|
461
509
|
*/
|
|
462
510
|
checkUniqueField(collectionPath: string, fieldName: string, value: unknown, excludeEntityId?: string, _databaseId?: string): Promise<boolean>;
|
|
463
511
|
/**
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { RelationService } from "./RelationService.js";
|
|
2
|
+
import type { ReadCallContextProvider } from "./read-scope.js";
|
|
2
3
|
import { RelationWriteService } from "./RelationWriteService.js";
|
|
3
4
|
import { FetchService } from "./FetchService.js";
|
|
4
5
|
import { DrizzleClient } from "../interfaces.js";
|
|
@@ -10,12 +11,26 @@ import { PostgresCollectionRegistry } from "../collections/PostgresCollectionReg
|
|
|
10
11
|
export declare class PersistService {
|
|
11
12
|
private db;
|
|
12
13
|
private registry;
|
|
14
|
+
/**
|
|
15
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
16
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
17
|
+
* test, where a collection declaring the hook is refused rather than
|
|
18
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
19
|
+
*/
|
|
20
|
+
private callContext?;
|
|
13
21
|
/** Reads: whether a row is under a parent, the key a link joins on. */
|
|
14
22
|
private relationService;
|
|
15
23
|
/** Writes: junction membership, foreign-key stamping, links. */
|
|
16
24
|
private relationWrites;
|
|
17
25
|
private fetchService;
|
|
18
|
-
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry
|
|
26
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry,
|
|
27
|
+
/**
|
|
28
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
29
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
30
|
+
* test, where a collection declaring the hook is refused rather than
|
|
31
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
32
|
+
*/
|
|
33
|
+
callContext?: ReadCallContextProvider | undefined);
|
|
19
34
|
/**
|
|
20
35
|
* Set the columns of one many-to-many link, without touching the membership.
|
|
21
36
|
*
|
|
@@ -4,6 +4,7 @@ import { DrizzleClient } from "../interfaces.js";
|
|
|
4
4
|
import { CollectionConfig, FilterValues, OrderByTuple, ResolvedRelation, ResolvedHasMany, ResolvedHasOne } from "@rebasepro/types";
|
|
5
5
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
|
|
6
6
|
import type { NestedPathHop } from "./nested-path.js";
|
|
7
|
+
import { type ReadCallContextProvider } from "./read-scope.js";
|
|
7
8
|
/**
|
|
8
9
|
* Typed wrapper for Drizzle dynamic query innerJoin.
|
|
9
10
|
* Drizzle's `$dynamic()` queries lose the `innerJoin` method from
|
|
@@ -44,7 +45,36 @@ export interface RelatedRow<M extends Record<string, unknown> = Record<string, u
|
|
|
44
45
|
export declare class RelationService {
|
|
45
46
|
private db;
|
|
46
47
|
private registry;
|
|
47
|
-
|
|
48
|
+
/**
|
|
49
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
50
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
51
|
+
* test, where a collection declaring the hook is refused rather than
|
|
52
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
53
|
+
*/
|
|
54
|
+
private callContext?;
|
|
55
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry,
|
|
56
|
+
/**
|
|
57
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
58
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
59
|
+
* test, where a collection declaring the hook is refused rather than
|
|
60
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
61
|
+
*/
|
|
62
|
+
callContext?: ReadCallContextProvider | undefined);
|
|
63
|
+
/**
|
|
64
|
+
* The **target** collection's `beforeQuery` narrowing, for rows reached
|
|
65
|
+
* through a relation.
|
|
66
|
+
*
|
|
67
|
+
* The target's, not the parent's, because these are the target's rows: a
|
|
68
|
+
* scope on `comments` has to hold whether they are listed at
|
|
69
|
+
* `/comments` or loaded as `post.comments`. Without this an `include` is a
|
|
70
|
+
* way around every row filter a collection declares — the exact hole
|
|
71
|
+
* `stripUnreadable` was once missing from the relation-ref branch.
|
|
72
|
+
*
|
|
73
|
+
* Folded into the `narrow` condition the loaders already thread through
|
|
74
|
+
* every relation kind, so a `via` join, a junction, a foreign key and the
|
|
75
|
+
* dynamic relation builder all apply it or none of them do.
|
|
76
|
+
*/
|
|
77
|
+
private narrowTargetRead;
|
|
48
78
|
/**
|
|
49
79
|
* One target row, as the {@link RelatedRow} everything here returns.
|
|
50
80
|
*
|
|
@@ -2,6 +2,7 @@ import { CollectionConfig, Properties, ResolvedManyToMany, ResolvedRelation, Res
|
|
|
2
2
|
import { DrizzleClient } from "../interfaces.js";
|
|
3
3
|
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
|
|
4
4
|
import type { NestedPathHop } from "./nested-path.js";
|
|
5
|
+
import type { ReadCallContextProvider } from "./read-scope.js";
|
|
5
6
|
/**
|
|
6
7
|
* A `_pivot` as the junction table's columns.
|
|
7
8
|
*
|
|
@@ -28,8 +29,22 @@ export declare function serializePivot(pivot: Record<string, unknown>, payload:
|
|
|
28
29
|
export declare class RelationWriteService {
|
|
29
30
|
private db;
|
|
30
31
|
private registry;
|
|
32
|
+
/**
|
|
33
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
34
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
35
|
+
* test, where a collection declaring the hook is refused rather than
|
|
36
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
37
|
+
*/
|
|
38
|
+
private callContext?;
|
|
31
39
|
private reads;
|
|
32
|
-
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry
|
|
40
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry,
|
|
41
|
+
/**
|
|
42
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
43
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
44
|
+
* test, where a collection declaring the hook is refused rather than
|
|
45
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
46
|
+
*/
|
|
47
|
+
callContext?: ReadCallContextProvider | undefined);
|
|
33
48
|
/**
|
|
34
49
|
* Remove the junction row linking a parent to `targetId`, leaving the target
|
|
35
50
|
* row itself alone.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { FilterValues, IncludeSpec, LogicalCondition, OrderByTuple } from "@rebasepro/types";
|
|
2
2
|
import type { VectorSearchParams } from "@rebasepro/types";
|
|
3
3
|
import { FetchService } from "./FetchService.js";
|
|
4
|
+
import type { ReadCallContextProvider } from "./read-scope.js";
|
|
4
5
|
import type { WithDeleted } from "./soft-delete.js";
|
|
5
6
|
import { PersistService } from "./PersistService.js";
|
|
6
7
|
import { RelationService } from "./RelationService.js";
|
|
@@ -25,9 +26,23 @@ export * from "../interfaces.js";
|
|
|
25
26
|
export declare class DataService implements DataRepository {
|
|
26
27
|
private db;
|
|
27
28
|
private registry;
|
|
29
|
+
/**
|
|
30
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
31
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
32
|
+
* test, where a collection declaring the hook is refused rather than
|
|
33
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
34
|
+
*/
|
|
35
|
+
private callContext?;
|
|
28
36
|
private fetchService;
|
|
29
37
|
private persistService;
|
|
30
|
-
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry
|
|
38
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry,
|
|
39
|
+
/**
|
|
40
|
+
* How this service's reads reach the identity their `beforeQuery` hooks
|
|
41
|
+
* run as. Passed by the driver that constructed it; absent only in a
|
|
42
|
+
* test, where a collection declaring the hook is refused rather than
|
|
43
|
+
* read unnarrowed. See `read-scope.ts`.
|
|
44
|
+
*/
|
|
45
|
+
callContext?: ReadCallContextProvider | undefined);
|
|
31
46
|
/**
|
|
32
47
|
* Fetch a single row by ID
|
|
33
48
|
*/
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `beforeQuery`: the one place a collection callback can change *which* rows a
|
|
3
|
+
* read asks for.
|
|
4
|
+
*
|
|
5
|
+
* `afterRead` runs over rows that have already been fetched, so until this
|
|
6
|
+
* existed an application developer who needed the read itself narrowed — a
|
|
7
|
+
* tenant scope, a visibility window, a per-role row filter — had two options:
|
|
8
|
+
* patch this driver, or `rebase eject`.
|
|
9
|
+
*
|
|
10
|
+
* Two properties are load-bearing, and both belong to the *shape* of the thing
|
|
11
|
+
* rather than to how carefully each call site was written.
|
|
12
|
+
*
|
|
13
|
+
* ## It can only narrow
|
|
14
|
+
*
|
|
15
|
+
* A hook returns a {@link QueryNarrowing} — a declarative filter, optionally a
|
|
16
|
+
* logical group — which is AND-ed into the query the caller sent. `AND(q, c)`
|
|
17
|
+
* is a subset of `q` for every `c`, so there is no `c` a hook can return that
|
|
18
|
+
* widens a read. The obvious alternative shape, `(query) => query`, can: a
|
|
19
|
+
* hook that drops a condition on the way through runs the query without it,
|
|
20
|
+
* and on an RLS data plane a dropped condition returns everything the policies
|
|
21
|
+
* happen to allow. That is the same reasoning behind
|
|
22
|
+
* `UnknownFilterFieldsMode` defaulting to `"error"`, and it is why that mode is
|
|
23
|
+
* **forced** to `"error"` for a hook's own filter here whatever the
|
|
24
|
+
* process-wide setting is: a scope condition naming a renamed column has to
|
|
25
|
+
* refuse the request, never be dropped from it.
|
|
26
|
+
*
|
|
27
|
+
* ## It cannot be skipped
|
|
28
|
+
*
|
|
29
|
+
* The hooks are resolved *by the read path itself*, out of the registry that
|
|
30
|
+
* read already holds — not handed to it by a caller who might forget. So a
|
|
31
|
+
* read that compiles a WHERE compiles this one too, or it throws.
|
|
32
|
+
*
|
|
33
|
+
* The one thing a read path cannot derive is the identity the hook runs as:
|
|
34
|
+
* `FetchService`, `RelationService` and `DataService` carry no user. That
|
|
35
|
+
* arrives as a {@link ReadCallContextProvider}, threaded from the driver that
|
|
36
|
+
* constructed them — the driver is the only object that knows both the caller
|
|
37
|
+
* and the connection, and it constructs a service per transaction anyway, so
|
|
38
|
+
* there is exactly one place per instance to pass it.
|
|
39
|
+
*
|
|
40
|
+
* Absence is the interesting case, and it fails closed: a service built with no
|
|
41
|
+
* provider refuses any read of a collection that declares `beforeQuery`, with
|
|
42
|
+
* a message naming the collection. So a future construction site that forgets
|
|
43
|
+
* the provider breaks loudly on the first scoped collection instead of quietly
|
|
44
|
+
* serving every row — which is the failure mode this whole module exists to
|
|
45
|
+
* rule out. A deployment with no `beforeQuery` anywhere never reaches any of
|
|
46
|
+
* this: the check is one map lookup and the compiled SQL is byte-identical.
|
|
47
|
+
*
|
|
48
|
+
* @module
|
|
49
|
+
*/
|
|
50
|
+
import { SQL } from "drizzle-orm";
|
|
51
|
+
import { PgTable } from "drizzle-orm/pg-core";
|
|
52
|
+
import type { CollectionConfig, ReadOperation, ReadQuery, RebaseCallContext } from "@rebasepro/types";
|
|
53
|
+
import type { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry.js";
|
|
54
|
+
import { type FilterCompilationOptions } from "../utils/drizzle-conditions.js";
|
|
55
|
+
/**
|
|
56
|
+
* How a read path reaches the context its `beforeQuery` hooks run with.
|
|
57
|
+
*
|
|
58
|
+
* A function rather than the context itself, because the driver's transaction
|
|
59
|
+
* handle and its `data` plane are established *after* it constructs the
|
|
60
|
+
* services that read through them — and because building a context costs an
|
|
61
|
+
* object that a collection with no hooks should never pay for.
|
|
62
|
+
*/
|
|
63
|
+
export type ReadCallContextProvider = () => RebaseCallContext;
|
|
64
|
+
/** Whether anything at all would run for this collection. */
|
|
65
|
+
export declare function hasBeforeQuery(registry: PostgresCollectionRegistry | undefined, collection: CollectionConfig | undefined): boolean;
|
|
66
|
+
/**
|
|
67
|
+
* What a read path is about to compile, in the form a hook is shown it.
|
|
68
|
+
*
|
|
69
|
+
* Built by the caller rather than derived here, because only the caller knows
|
|
70
|
+
* which of its options are part of the query and which are transport — a
|
|
71
|
+
* `databaseId`, an `include` tree, a `withDeleted` flag are not "which rows".
|
|
72
|
+
*/
|
|
73
|
+
export interface ReadQueryDescription {
|
|
74
|
+
operation: ReadOperation;
|
|
75
|
+
query: ReadQuery;
|
|
76
|
+
}
|
|
77
|
+
/** Everything `beforeQueryConditions` needs that is not about this one read. */
|
|
78
|
+
export interface BeforeQueryEnv {
|
|
79
|
+
registry: PostgresCollectionRegistry | undefined;
|
|
80
|
+
/**
|
|
81
|
+
* The context provider the driver handed down. `undefined` means no driver
|
|
82
|
+
* built this service, which is a refusal rather than a bypass — see the
|
|
83
|
+
* module comment.
|
|
84
|
+
*/
|
|
85
|
+
callContext: ReadCallContextProvider | undefined;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* The conditions every `beforeQuery` hook for this collection asks to add.
|
|
89
|
+
*
|
|
90
|
+
* Returned as a list rather than pre-combined so a caller pushes them onto the
|
|
91
|
+
* same `allConditions` array its own filter goes into. Every caller AND-es
|
|
92
|
+
* that array, which is what makes these additive — the hook is not trusted to
|
|
93
|
+
* narrow, it is only ever given a way to.
|
|
94
|
+
*/
|
|
95
|
+
export declare function beforeQueryConditions(env: BeforeQueryEnv, collection: CollectionConfig | undefined, path: string, table: PgTable<never>, description: ReadQueryDescription, filterContext: Omit<FilterCompilationOptions, "unknownFields">): Promise<SQL[]>;
|
|
96
|
+
/**
|
|
97
|
+
* {@link beforeQueryConditions}, pre-combined, for the read paths that hold a
|
|
98
|
+
* single `SQL` rather than a list of them — a single get, a relation loader.
|
|
99
|
+
*/
|
|
100
|
+
export declare function beforeQueryCondition(env: BeforeQueryEnv, collection: CollectionConfig | undefined, path: string, table: PgTable<never>, description: ReadQueryDescription, filterContext: Omit<FilterCompilationOptions, "unknownFields">): Promise<SQL | undefined>;
|
|
@@ -516,13 +516,20 @@ export declare class DrizzleConditionBuilder {
|
|
|
516
516
|
* - **Declared** — one `@@ websearch_to_tsquery` against the generated
|
|
517
517
|
* `tsvector` column. Stems, drops stopwords, AND-es the terms, reaches
|
|
518
518
|
* inside JSONB and arrays, and uses the GIN index.
|
|
519
|
-
* - **Not declared** —
|
|
520
|
-
* string properties, with
|
|
521
|
-
* so it is matched as the literal text
|
|
519
|
+
* - **Not declared** — `ILIKE '%term%'` per term, OR-ed across top-level
|
|
520
|
+
* string properties and AND-ed across the terms, with each term escaped
|
|
521
|
+
* (see {@link escapeLikePattern}) so it is matched as the literal text
|
|
522
|
+
* the user typed.
|
|
522
523
|
*
|
|
523
|
-
* The second is the default and stays the default
|
|
524
|
-
*
|
|
525
|
-
*
|
|
524
|
+
* The second is the default and stays the default, and it AND-es terms for
|
|
525
|
+
* the same reason `websearch_to_tsquery` does: a person typing two words
|
|
526
|
+
* into a search box means both, and a name lives in two columns. Matching
|
|
527
|
+
* the whole string per column instead — which is what this did — cannot
|
|
528
|
+
* find `sebastian melendez` on a row with `first_name`/`last_name`, and
|
|
529
|
+
* could not find `sebastian ` at all.
|
|
530
|
+
*
|
|
531
|
+
* Returns at most one condition. Every caller ORs what it gets back, which
|
|
532
|
+
* is right across columns and wrong across terms, so the AND is built here.
|
|
526
533
|
*
|
|
527
534
|
* `collection` is optional so that the callers which genuinely have no
|
|
528
535
|
* collection in hand — nested paths, derived views — keep working; without
|
|
@@ -544,6 +551,49 @@ export declare class DrizzleConditionBuilder {
|
|
|
544
551
|
* on user input, which `to_tsquery` does on so much as a stray parenthesis.
|
|
545
552
|
*/
|
|
546
553
|
static buildFullTextCondition(searchString: string, table: PgTable<any>, collection: CollectionConfig): SQL | undefined;
|
|
554
|
+
/**
|
|
555
|
+
* The substring half of {@link SearchMode} `"hybrid"`: every term found
|
|
556
|
+
* somewhere in the declared fields, with accents folded on both sides.
|
|
557
|
+
*
|
|
558
|
+
* Terms are AND-ed and fields OR-ed, exactly as the ILIKE default does and
|
|
559
|
+
* for the same reason — a person typing two words means both, and a name
|
|
560
|
+
* lives in two columns (`splitSearchTerms`). What it does *not* share with
|
|
561
|
+
* the default is its reach: these are the declared `search.fields`, so it
|
|
562
|
+
* folds accents, joins `text[]` and walks into JSONB through the same
|
|
563
|
+
* IMMUTABLE helpers the generated column uses.
|
|
564
|
+
*
|
|
565
|
+
* The needle is folded with the same function as the document, so
|
|
566
|
+
* `munoz` and `Muñoz` meet in the middle rather than one side being
|
|
567
|
+
* normalized and the other not — the subtle way to build a search that
|
|
568
|
+
* matches nothing.
|
|
569
|
+
*
|
|
570
|
+
* Interpolated as raw SQL because the field expressions are generated (they
|
|
571
|
+
* name columns and helper functions, which are identifiers, not values);
|
|
572
|
+
* the *term* is a bind parameter, escaped for LIKE first — see
|
|
573
|
+
* {@link escapeLikePattern} for what that escape is for.
|
|
574
|
+
*/
|
|
575
|
+
static buildFoldedSubstringCondition(searchString: string, spec: SearchColumnSpec): SQL | undefined;
|
|
576
|
+
/**
|
|
577
|
+
* Whether one text expression holds **any** term of the search string.
|
|
578
|
+
*
|
|
579
|
+
* The per-field question, where {@link buildFoldedSubstringCondition} asks
|
|
580
|
+
* the per-row one: `OR` across terms rather than `AND`, because a two-word
|
|
581
|
+
* query satisfied across two fields has each field carrying one term. Used
|
|
582
|
+
* by `_matches` to name the fields that caused a substring hit.
|
|
583
|
+
*/
|
|
584
|
+
static foldedSubstringOf(searchString: string, text: SQL): SQL;
|
|
585
|
+
/**
|
|
586
|
+
* One accent-folded `%term%` bind parameter per search term.
|
|
587
|
+
*
|
|
588
|
+
* The needle is folded with the same function as the document, so `munoz`
|
|
589
|
+
* and `Muñoz` meet in the middle instead of one side being normalized and
|
|
590
|
+
* the other not — the subtle way to build a search that matches nothing.
|
|
591
|
+
*
|
|
592
|
+
* A string with no terms at all is only whitespace, and stays the single
|
|
593
|
+
* literal pattern it is on the ILIKE path rather than becoming "no rows",
|
|
594
|
+
* so both paths answer a stray space the same way.
|
|
595
|
+
*/
|
|
596
|
+
private static foldedNeedles;
|
|
547
597
|
/**
|
|
548
598
|
* `websearch_to_tsquery(<config>, <normalized search string>)`.
|
|
549
599
|
*
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rebasepro/server-postgres",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "PostgreSQL data source backend implementation for Rebase with Drizzle ORM",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"cms",
|
|
@@ -50,11 +50,11 @@
|
|
|
50
50
|
"execa": "^9.6.1",
|
|
51
51
|
"pg": "^8.22.0",
|
|
52
52
|
"ws": "^8.21.1",
|
|
53
|
-
"@rebasepro/codegen": "0.
|
|
54
|
-
"@rebasepro/common": "0.
|
|
55
|
-
"@rebasepro/server": "0.
|
|
56
|
-
"@rebasepro/types": "0.
|
|
57
|
-
"@rebasepro/utils": "0.
|
|
53
|
+
"@rebasepro/codegen": "0.22.0",
|
|
54
|
+
"@rebasepro/common": "0.22.0",
|
|
55
|
+
"@rebasepro/server": "0.22.0",
|
|
56
|
+
"@rebasepro/types": "0.22.0",
|
|
57
|
+
"@rebasepro/utils": "0.22.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
60
|
"@electric-sql/pglite": "0.5.6",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"typescript": "^6.0.3",
|
|
71
71
|
"vite": "^8.1.5",
|
|
72
72
|
"vitest": "^4.1.10",
|
|
73
|
-
"@rebasepro/client": "0.
|
|
73
|
+
"@rebasepro/client": "0.22.0"
|
|
74
74
|
},
|
|
75
75
|
"gitHead": "d935eefa5aa8d1009a2398cfac2c1e4ee9aeb6b6",
|
|
76
76
|
"publishConfig": {
|