@rebasepro/server-postgres 0.13.0 → 0.13.1-canary.g06dbe5b
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/PostgresBackendDriver.d.ts +48 -1
- package/dist/auth/services.d.ts +19 -0
- package/dist/{src-DlPBctw_.js → auth-users-columns-CBEOeYqa.js} +835 -63
- package/dist/auth-users-columns-CBEOeYqa.js.map +1 -0
- package/dist/{backup-service-CD8o_1Sl.js → backup-service-Bww-Lg0s.js} +2 -2
- package/dist/{backup-service-CD8o_1Sl.js.map → backup-service-Bww-Lg0s.js.map} +1 -1
- package/dist/cli-helpers.d.ts +57 -1
- package/dist/data-transformer.d.ts +7 -2
- package/dist/data_driver-ULAyJEi9.js +193 -0
- package/dist/data_driver-ULAyJEi9.js.map +1 -0
- package/dist/{ensure-collection-policies-ViG8XiPn.js → ensure-collection-policies-B_JMGa5K.js} +2 -2
- package/dist/{ensure-collection-policies-ViG8XiPn.js.map → ensure-collection-policies-B_JMGa5K.js.map} +1 -1
- package/dist/{ensure-collection-tables-CBQdOETu.js → ensure-collection-tables-DzeTEvMv.js} +170 -20
- package/dist/ensure-collection-tables-DzeTEvMv.js.map +1 -0
- package/dist/index.es.js +1365 -338
- package/dist/index.es.js.map +1 -1
- package/dist/rls-bootstrap-sql-Bpv3nUZo.js +244 -0
- package/dist/rls-bootstrap-sql-Bpv3nUZo.js.map +1 -0
- package/dist/schema/auth-schema.d.ts +102 -0
- package/dist/schema/auth-users-columns.d.ts +97 -0
- package/dist/schema/doctor-policy-checks.d.ts +28 -0
- package/dist/schema/doctor.d.ts +23 -25
- package/dist/schema/ensure-collection-tables.d.ts +61 -7
- package/dist/schema/generate-drizzle-schema-logic.d.ts +10 -2
- package/dist/schema/generate-postgres-ddl-logic.d.ts +53 -5
- package/dist/schema/generated-schema-staleness.d.ts +39 -0
- package/dist/schema/rls-bootstrap-sql.d.ts +135 -0
- package/dist/schema/search-column.d.ts +248 -0
- package/dist/security/rls-enforcement.d.ts +61 -5
- package/dist/services/FetchService.d.ts +34 -7
- package/dist/services/RelationService.d.ts +30 -0
- package/dist/services/collection-helpers.d.ts +26 -0
- package/dist/services/dataService.d.ts +5 -0
- package/dist/services/realtimeService.d.ts +131 -21
- package/dist/{src-DoU9yPqq.js → src-C_wvdMnl.js} +91 -2
- package/dist/src-C_wvdMnl.js.map +1 -0
- package/dist/utils/drizzle-conditions.d.ts +124 -2
- package/dist/{websocket-B2LsrINK.js → websocket-D1qbmLZ2.js} +75 -18
- package/dist/websocket-D1qbmLZ2.js.map +1 -0
- package/package.json +9 -8
- package/src/PostgresBackendDriver.ts +172 -6
- package/src/PostgresBootstrapper.ts +55 -6
- package/src/auth/ensure-tables.ts +207 -86
- package/src/auth/services.ts +69 -5
- package/src/cli-helpers.ts +129 -10
- package/src/cli.ts +232 -30
- package/src/collections/validate-relations.ts +124 -17
- package/src/data-transformer.ts +120 -17
- package/src/history/ensure-history-table.ts +7 -0
- package/src/schema/auth-schema.ts +17 -1
- package/src/schema/auth-users-columns.ts +131 -0
- package/src/schema/doctor-cli.ts +12 -63
- package/src/schema/doctor-policy-checks.ts +105 -0
- package/src/schema/doctor.ts +135 -76
- package/src/schema/ensure-collection-tables.ts +374 -32
- package/src/schema/generate-drizzle-schema-logic.ts +132 -42
- package/src/schema/generate-postgres-ddl-logic.ts +294 -16
- package/src/schema/generate-postgres-ddl.ts +25 -2
- package/src/schema/generated-schema-staleness.ts +169 -0
- package/src/schema/introspect-db-logic.ts +66 -34
- package/src/schema/non-sql-collections.test.ts +131 -0
- package/src/schema/rls-bootstrap-sql.ts +288 -0
- package/src/schema/search-column.ts +643 -0
- package/src/security/anonymous-grants.test.ts +4 -2
- package/src/security/rls-enforcement.ts +149 -6
- package/src/services/BranchService.ts +5 -0
- package/src/services/FetchService.ts +175 -108
- package/src/services/PersistService.ts +38 -2
- package/src/services/RelationService.ts +110 -67
- package/src/services/channel-history.ts +14 -0
- package/src/services/channel-presence.ts +13 -0
- package/src/services/collection-helpers.ts +54 -1
- package/src/services/dataService.ts +5 -0
- package/src/services/realtimeService.ts +344 -79
- package/src/utils/drizzle-conditions.ts +365 -23
- package/src/utils/pg-error-utils.ts +8 -3
- package/src/websocket.ts +113 -16
- package/dist/ensure-collection-tables-CBQdOETu.js.map +0 -1
- package/dist/policy-CeA1JcxP.js +0 -105
- package/dist/policy-CeA1JcxP.js.map +0 -1
- package/dist/schema/auth-bootstrap-sql.d.ts +0 -24
- package/dist/src-DlPBctw_.js.map +0 -1
- package/dist/src-DoU9yPqq.js.map +0 -1
- package/dist/websocket-B2LsrINK.js.map +0 -1
- package/src/schema/auth-bootstrap-sql.ts +0 -47
|
@@ -54,9 +54,17 @@ export interface ExistingSchema {
|
|
|
54
54
|
* constraint that then fails harmlessly as a duplicate.
|
|
55
55
|
*/
|
|
56
56
|
constraints?: Set<string>;
|
|
57
|
+
/**
|
|
58
|
+
* `schema.table.column` → that column's comment, for the columns that have
|
|
59
|
+
* one. This is where a generated search column's fingerprint lives, so it
|
|
60
|
+
* is the only evidence that a `search` block has changed since the column
|
|
61
|
+
* was built. Absent is read as "no column is stamped", which plans a stamp
|
|
62
|
+
* and reports nothing as drifted.
|
|
63
|
+
*/
|
|
64
|
+
columnComments?: Map<string, string>;
|
|
57
65
|
}
|
|
58
66
|
export interface EnsureAction {
|
|
59
|
-
kind: "create-enum" | "create-table" | "add-column" | "add-constraint" | "rename-column";
|
|
67
|
+
kind: "create-enum" | "create-table" | "add-column" | "add-constraint" | "rename-column" | "create-extension" | "create-function" | "create-index" | "comment-column";
|
|
60
68
|
/** Qualified target, for logging: `public.posts` or `public.posts.title`. */
|
|
61
69
|
target: string;
|
|
62
70
|
sql: string;
|
|
@@ -76,6 +84,49 @@ export interface EnsurePlan {
|
|
|
76
84
|
* resolving to nothing — which is indistinguishable from having no data.
|
|
77
85
|
*/
|
|
78
86
|
legacyForeignKeys: LegacyForeignKey[];
|
|
87
|
+
/**
|
|
88
|
+
* Generated search columns whose `search` block has changed since they were
|
|
89
|
+
* built. Reported, never planned into `actions` — see
|
|
90
|
+
* {@link SearchColumnDrift} for why applying it is not this path's call.
|
|
91
|
+
*/
|
|
92
|
+
searchDrift: SearchColumnDrift[];
|
|
93
|
+
/**
|
|
94
|
+
* Generated search columns that exist but carry no fingerprint — created
|
|
95
|
+
* before this check existed, or by `search.sql` on an older CLI. The plan
|
|
96
|
+
* stamps them so the *next* change is detectable; whether they match the
|
|
97
|
+
* current block cannot be known, which is what the caller reports.
|
|
98
|
+
*/
|
|
99
|
+
searchAdopted: {
|
|
100
|
+
table: string;
|
|
101
|
+
column: string;
|
|
102
|
+
}[];
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* A generated search column built from a `search` block that has since changed.
|
|
106
|
+
*
|
|
107
|
+
* Reported instead of applied because the two ways to apply it are both worse
|
|
108
|
+
* than stopping. `ALTER COLUMN … SET EXPRESSION` exists only on PG17+ and
|
|
109
|
+
* rewrites the table either way; `DROP COLUMN` + `ADD COLUMN` rewrites it under
|
|
110
|
+
* an ACCESS EXCLUSIVE lock and rebuilds the GIN index. This module runs
|
|
111
|
+
* unattended against live customer data with nobody reading a diff — the same
|
|
112
|
+
* reason it withholds `SET NOT NULL` from an adopted table — so a multi-minute
|
|
113
|
+
* outage is not a decision it may take on its own.
|
|
114
|
+
*
|
|
115
|
+
* Not applying it silently is not an option either: that is the bug this
|
|
116
|
+
* detection exists for. A collection that added a field, flipped `unaccent` or
|
|
117
|
+
* raised a weight kept indexing the *old* set forever, and the only symptom was
|
|
118
|
+
* searches returning nothing for content plainly in the row.
|
|
119
|
+
*/
|
|
120
|
+
export interface SearchColumnDrift {
|
|
121
|
+
/** `schema.table`. */
|
|
122
|
+
table: string;
|
|
123
|
+
column: string;
|
|
124
|
+
/** The fingerprint recorded on the column. */
|
|
125
|
+
found: string;
|
|
126
|
+
/** The fingerprint the current `search` block computes. */
|
|
127
|
+
expected: string;
|
|
128
|
+
/** The statements that would rebuild the column, for the operator to run. */
|
|
129
|
+
rebuild: string[];
|
|
79
130
|
}
|
|
80
131
|
/** A relation column whose old and new spellings both plausibly apply. */
|
|
81
132
|
export interface LegacyForeignKey {
|
|
@@ -88,14 +139,17 @@ export interface LegacyForeignKey {
|
|
|
88
139
|
}
|
|
89
140
|
export interface EnsureOutcome extends EnsurePlan {
|
|
90
141
|
/**
|
|
91
|
-
*
|
|
142
|
+
* Actions that could not be applied and are non-fatal by nature.
|
|
92
143
|
*
|
|
93
|
-
* A foreign key can only fail on data that already
|
|
94
|
-
* column it would police exists either way, so the
|
|
95
|
-
*
|
|
96
|
-
* outage.
|
|
144
|
+
* Two kinds qualify. A foreign key can only fail on data that already
|
|
145
|
+
* violates it, and the column it would police exists either way, so the
|
|
146
|
+
* collection still serves; refusing to boot over one would turn a
|
|
147
|
+
* pre-existing data problem into an outage. A column comment is the search
|
|
148
|
+
* fingerprint, which needs table ownership — losing it costs drift
|
|
149
|
+
* detection on the next boot, not the deployment. Both are reported loudly.
|
|
97
150
|
*/
|
|
98
151
|
failures: {
|
|
152
|
+
kind: EnsureAction["kind"];
|
|
99
153
|
target: string;
|
|
100
154
|
error: string;
|
|
101
155
|
}[];
|
|
@@ -107,7 +161,7 @@ export interface EnsureOutcome extends EnsurePlan {
|
|
|
107
161
|
* that reference them, tables before the columns added to other tables (a new
|
|
108
162
|
* table may be the target of a relation), and nothing is emitted twice.
|
|
109
163
|
*/
|
|
110
|
-
export declare function planCollectionSchemaEnsure(
|
|
164
|
+
export declare function planCollectionSchemaEnsure(allCollections: CollectionConfig[], existing: ExistingSchema): EnsurePlan;
|
|
111
165
|
/** Read what the database has, for the schemas the collections live in. */
|
|
112
166
|
export declare function readExistingSchema(client: Queryable, schemas: string[]): Promise<ExistingSchema>;
|
|
113
167
|
/**
|
|
@@ -1,2 +1,10 @@
|
|
|
1
|
-
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
-
|
|
1
|
+
import { CollectionConfig, Property } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* The Drizzle column declaration a property compiles to, or `null` when the
|
|
4
|
+
* property puts no column on *this* table (an inverse relation, whose column
|
|
5
|
+
* lives on the target). Exported so it can be checked against its DDL twin
|
|
6
|
+
* `getSqlColumnType` directly — the two disagreeing is what left `geopoint`
|
|
7
|
+
* with a database column and no Drizzle key.
|
|
8
|
+
*/
|
|
9
|
+
export declare const getDrizzleColumn: (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]) => string | null;
|
|
10
|
+
export declare const generateSchema: (allCollections: CollectionConfig[], stripPolicies?: boolean) => Promise<string>;
|
|
@@ -29,8 +29,56 @@ export declare const generatePolicyStatements: (collection: CollectionConfig, ru
|
|
|
29
29
|
*/
|
|
30
30
|
export declare const quoteSqlLiteral: (value: string) => string;
|
|
31
31
|
export declare const getSqlColumnType: (propName: string, prop: Property, collection: CollectionConfig, collections: CollectionConfig[]) => string;
|
|
32
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Everything a `search` block needs, as a file Rebase applies itself.
|
|
34
|
+
*
|
|
35
|
+
* Search is the one part of the schema Atlas does not own. Two independent
|
|
36
|
+
* reasons, and either alone would be enough:
|
|
37
|
+
*
|
|
38
|
+
* 1. Its free tier refuses to *parse* a desired-state file that so much as
|
|
39
|
+
* contains a function — "functions and procedures are available to
|
|
40
|
+
* logged-in users only". A generated `tsvector` column cannot avoid one:
|
|
41
|
+
* `unaccent` is STABLE and jsonb flattening needs a set-returning function,
|
|
42
|
+
* so both have to be wrapped in an IMMUTABLE helper to be legal in a
|
|
43
|
+
* generated column at all.
|
|
44
|
+
* 2. Even with the file accepted, Atlas *wipes* the dev database it diffs
|
|
45
|
+
* against, so a helper seeded there beforehand is gone by the time the plan
|
|
46
|
+
* is analysed. There is no hook to reinstate it.
|
|
47
|
+
*
|
|
48
|
+
* So the column, its index and its helpers are excluded from Atlas's view
|
|
49
|
+
* (`searchExcludePatterns`) and applied from here — the same arrangement the
|
|
50
|
+
* RLS policies already use, and for the same underlying reason.
|
|
51
|
+
*
|
|
52
|
+
* Ordered as it must run: extensions, then helpers, then the column whose
|
|
53
|
+
* expression calls them, then the index over that column. Every statement is
|
|
54
|
+
* `IF NOT EXISTS` / `OR REPLACE`, because this is replayed on every push and
|
|
55
|
+
* appended to migrations that run against databases at any stage of their
|
|
56
|
+
* life. Empty when nothing opted in — the caller writes no file then.
|
|
57
|
+
*
|
|
58
|
+
* The leading half — extensions and helpers, without the table-shaped
|
|
59
|
+
* statements — is available on its own as {@link searchPrerequisiteStatements},
|
|
60
|
+
* for the dev database Atlas analyses plans against. That database has none of
|
|
61
|
+
* the project's tables, so it wants the functions and nothing else.
|
|
62
|
+
*/
|
|
63
|
+
export declare const searchPrerequisiteStatements: (allCollections: CollectionConfig[]) => string[];
|
|
64
|
+
export declare const generatePostgresSearchDdl: (allCollections: CollectionConfig[]) => string;
|
|
65
|
+
/**
|
|
66
|
+
* Glob patterns telling Atlas to leave the search column and its index alone.
|
|
67
|
+
*
|
|
68
|
+
* Without these, a desired state that omits search reads to Atlas as an
|
|
69
|
+
* instruction to drop the column — taking the index and the whole search
|
|
70
|
+
* feature with it on the next push.
|
|
71
|
+
*
|
|
72
|
+
* Fully qualified, `schema.table.object`, matching the include list. The
|
|
73
|
+
* two-part form is what Atlas wants when the connection URL scopes it to one
|
|
74
|
+
* schema and is *silently ignored* otherwise: it reads `posts.search_vector`
|
|
75
|
+
* as a table named `search_vector` in a schema named `posts`, matches nothing,
|
|
76
|
+
* and reports no error for the pattern that never fired.
|
|
77
|
+
*/
|
|
78
|
+
export declare const searchExcludePatterns: (allCollections: CollectionConfig[]) => string[];
|
|
79
|
+
export declare const generatePostgresDdl: (allCollections: CollectionConfig[], options?: {
|
|
33
80
|
includePolicies?: boolean;
|
|
81
|
+
includeSearch?: boolean;
|
|
34
82
|
}) => Promise<string>;
|
|
35
83
|
/** The RLS statements one declared collection's table needs, ready to run. */
|
|
36
84
|
/**
|
|
@@ -100,7 +148,7 @@ export interface JunctionTablePlan {
|
|
|
100
148
|
* is unknown yields the column without a constraint. Both mirror the generator
|
|
101
149
|
* exactly — a divergence here is a schema fork between boot and `db push`.
|
|
102
150
|
*/
|
|
103
|
-
export declare const planRelationalColumns: (
|
|
151
|
+
export declare const planRelationalColumns: (allCollections: CollectionConfig[]) => RelationalColumnPlan[];
|
|
104
152
|
/**
|
|
105
153
|
* The junction tables a bundle's many-to-many relations imply.
|
|
106
154
|
*
|
|
@@ -109,7 +157,7 @@ export declare const planRelationalColumns: (collections: CollectionConfig[]) =>
|
|
|
109
157
|
* junction with row-level security left off is readable and writable by every
|
|
110
158
|
* signed-in user, which is why the two must ship together.
|
|
111
159
|
*/
|
|
112
|
-
export declare const planJunctionTables: (
|
|
160
|
+
export declare const planJunctionTables: (allCollections: CollectionConfig[]) => JunctionTablePlan[];
|
|
113
161
|
export interface CollectionPolicyPlan {
|
|
114
162
|
/** The table's schema (e.g. `public`, `rebase`). */
|
|
115
163
|
schema: string;
|
|
@@ -137,6 +185,6 @@ export interface CollectionPolicyPlan {
|
|
|
137
185
|
* writable by every signed-in user. A junction whose table is still absent is
|
|
138
186
|
* skipped by the applier, not planned away here.
|
|
139
187
|
*/
|
|
140
|
-
export declare const planCollectionPolicies: (
|
|
141
|
-
export declare const generatePostgresPoliciesDdl: (
|
|
188
|
+
export declare const planCollectionPolicies: (allCollections: CollectionConfig[]) => CollectionPolicyPlan[];
|
|
189
|
+
export declare const generatePostgresPoliciesDdl: (allCollections: CollectionConfig[]) => string;
|
|
142
190
|
export {};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { CollectionConfig } from "@rebasepro/types";
|
|
2
|
+
/**
|
|
3
|
+
* Notice a generated Drizzle schema that a *library upgrade* invalidated.
|
|
4
|
+
*
|
|
5
|
+
* `rebase dev` already watches `config/collections` and warns when a collection
|
|
6
|
+
* file changes. That covers drift the developer caused. It cannot cover this one,
|
|
7
|
+
* because nothing the developer owns changed: 0.13 derives `category_id` where
|
|
8
|
+
* 0.12 derived `categorie_id`, from the same unedited collection. The watcher
|
|
9
|
+
* never fires, and `backend/src/schema.generated.ts` quietly stops describing the
|
|
10
|
+
* schema the runtime expects.
|
|
11
|
+
*
|
|
12
|
+
* The consequence is not cosmetic. Boot-ensure renames the column in the
|
|
13
|
+
* database, then relation validation reads the stale module and refuses to
|
|
14
|
+
* start — on that boot and every boot after it, because the rename is already
|
|
15
|
+
* applied and will not be attempted again.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately narrow: this answers "does the generated schema name a foreign key
|
|
18
|
+
* the way the previous rule did", not "is this file what we would generate now".
|
|
19
|
+
* The wide question would report every whitespace change in the generator as a
|
|
20
|
+
* fatal staleness, and a check that cries wolf gets switched off.
|
|
21
|
+
*/
|
|
22
|
+
/** One column the generated schema names under the pre-0.13 rule. */
|
|
23
|
+
export interface LegacyForeignKeyName {
|
|
24
|
+
/** Table whose column declaration is stale. */
|
|
25
|
+
table: string;
|
|
26
|
+
/** The name the generated schema declares. */
|
|
27
|
+
legacy: string;
|
|
28
|
+
/** The name this release derives, and which the database now carries. */
|
|
29
|
+
current: string;
|
|
30
|
+
/** `<collection>.<relation>` that derives it, for the message. */
|
|
31
|
+
relation: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* @param generatedSource contents of `backend/src/schema.generated.ts`
|
|
35
|
+
* @param collections the project's collections, as this release reads them
|
|
36
|
+
*/
|
|
37
|
+
export declare function findLegacyForeignKeyNames(generatedSource: string, collections: CollectionConfig[]): LegacyForeignKeyName[];
|
|
38
|
+
/** One-line summary for a log or a CLI notice. */
|
|
39
|
+
export declare function describeLegacyForeignKeyNames(found: LegacyForeignKeyName[]): string;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical SQL bootstrap for the RLS helper functions.
|
|
3
|
+
*
|
|
4
|
+
* Generated RLS policies reference `rebase.uid()` / `rebase.roles()` /
|
|
5
|
+
* `rebase.jwt()`, so any SQL stream that can contain policies must be
|
|
6
|
+
* self-contained: it has to (re)create these helpers first. This matters for
|
|
7
|
+
* the migration directory in particular — Atlas replays migrations against a
|
|
8
|
+
* clean dev database where no out-of-band bootstrap has ever run, so a
|
|
9
|
+
* migration carrying policies without this preamble fails with
|
|
10
|
+
* "function rebase.uid() does not exist".
|
|
11
|
+
*
|
|
12
|
+
* Idempotent (`IF NOT EXISTS` / `OR REPLACE`) so it can be prepended to every
|
|
13
|
+
* policies block and re-applied freely. The runtime boot path
|
|
14
|
+
* (`auth/ensure-tables.ts`) creates the same functions under an advisory lock
|
|
15
|
+
* for HMR-safety; keep the definitions in sync.
|
|
16
|
+
*
|
|
17
|
+
* ## Creating the `rebase` schema here is now safe, and required
|
|
18
|
+
*
|
|
19
|
+
* It deliberately did not, once. These functions lived in a schema called
|
|
20
|
+
* `auth`, and the note here read: creating `rebase` would leak it into Atlas's
|
|
21
|
+
* replayed migration state, and — absent from the desired `schema.sql` — Atlas
|
|
22
|
+
* would then plan `DROP SCHEMA "rebase" CASCADE`, taking the auth tables with
|
|
23
|
+
* it. That reasoning still holds; what changed is the second half of it. The
|
|
24
|
+
* DDL generator now emits `CREATE SCHEMA IF NOT EXISTS "rebase"`
|
|
25
|
+
* unconditionally, so the schema is always in the desired state and the diff is
|
|
26
|
+
* empty. (It used to appear only when some collection happened to declare
|
|
27
|
+
* `schema: "rebase"` — true for the scaffold's users collection, and not a
|
|
28
|
+
* property anything guaranteed.) `db push` additionally excludes the whole
|
|
29
|
+
* schema from the declarative apply.
|
|
30
|
+
*
|
|
31
|
+
* See `@rebasepro/types`' `rls-functions` for why the functions moved out of
|
|
32
|
+
* `auth` at all: the short version is that the name was Supabase's, and
|
|
33
|
+
* `CREATE OR REPLACE FUNCTION auth.uid() RETURNS text` cannot be applied over
|
|
34
|
+
* Supabase's `RETURNS uuid` — Postgres refuses, and the refusal used to be
|
|
35
|
+
* swallowed.
|
|
36
|
+
*/
|
|
37
|
+
/**
|
|
38
|
+
* The bootstrap as individual statements.
|
|
39
|
+
*
|
|
40
|
+
* Kept as an array because the two consumers need different shapes and only one
|
|
41
|
+
* of them can take a multi-command string: the migration preamble is written to
|
|
42
|
+
* a file and replayed by Atlas, but the boot path runs through drizzle, whose
|
|
43
|
+
* node-postgres handle speaks the extended query protocol and rejects more than
|
|
44
|
+
* one command per call. Splitting a joined string back apart on `$$;` would be
|
|
45
|
+
* a parser for a problem that does not need one.
|
|
46
|
+
*/
|
|
47
|
+
export declare const RLS_BOOTSTRAP_STATEMENTS: readonly string[];
|
|
48
|
+
/** The same statements as one script, for migration files and raw clients. */
|
|
49
|
+
export declare const RLS_BOOTSTRAP_SQL: string;
|
|
50
|
+
/**
|
|
51
|
+
* Removes the pre-1.0 `auth` schema, but only when Rebase is what put it there.
|
|
52
|
+
*
|
|
53
|
+
* ## Why this is safe against a Supabase database
|
|
54
|
+
*
|
|
55
|
+
* Two independent guards, and both have to pass:
|
|
56
|
+
*
|
|
57
|
+
* 1. **Each function is identified before it is dropped.** Ours returns `text`
|
|
58
|
+
* and reads the `app.uid` GUC; Supabase's returns `uuid` and reads
|
|
59
|
+
* `request.jwt.claims`. Nothing is dropped on a signature we did not write,
|
|
60
|
+
* so a Supabase database — where our `CREATE OR REPLACE` could never have
|
|
61
|
+
* succeeded in the first place, Postgres refusing to change a return type —
|
|
62
|
+
* matches nothing and this is a no-op.
|
|
63
|
+
* 2. **`DROP SCHEMA … RESTRICT`**, never CASCADE. If anything else at all still
|
|
64
|
+
* lives in `auth` (Supabase's `users` table, its other helpers), the drop
|
|
65
|
+
* fails and the schema stays. CASCADE here would be unrecoverable.
|
|
66
|
+
*
|
|
67
|
+
* ## Why it cannot run too early
|
|
68
|
+
*
|
|
69
|
+
* Postgres records a dependency from every RLS policy to the functions its body
|
|
70
|
+
* calls, so `DROP FUNCTION auth.uid()` fails for as long as a single policy
|
|
71
|
+
* still references it. That is the interlock, and it is load-bearing: the drop
|
|
72
|
+
* can only succeed once every policy has been recompiled to \`rebase.uid()\`.
|
|
73
|
+
* Callers therefore run this *after* applying policies, and treat a failure as
|
|
74
|
+
* "not yet — try again next boot" rather than as an error.
|
|
75
|
+
*/
|
|
76
|
+
export declare const DROP_LEGACY_AUTH_SCHEMA_SQL = "\nDO $rebase_drop_legacy$\nDECLARE\n dropped_any boolean := false;\nBEGIN\n -- Each function is matched on its own result type and body, so a schema\n -- that merely shares the name keeps everything it has.\n IF EXISTS (\n SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'auth' AND p.proname = 'uid'\n AND pg_get_function_result(p.oid) = 'text'\n AND p.prosrc LIKE '%app.uid%'\n ) THEN\n DROP FUNCTION auth.uid();\n dropped_any := true;\n END IF;\n\n IF EXISTS (\n SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'auth' AND p.proname = 'jwt'\n AND pg_get_function_result(p.oid) = 'jsonb'\n AND p.prosrc LIKE '%app.jwt%'\n ) THEN\n DROP FUNCTION auth.jwt();\n dropped_any := true;\n END IF;\n\n IF EXISTS (\n SELECT 1 FROM pg_proc p JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname = 'auth' AND p.proname = 'roles'\n AND pg_get_function_result(p.oid) = 'text'\n AND p.prosrc LIKE '%app.user_roles%'\n ) THEN\n DROP FUNCTION auth.roles();\n dropped_any := true;\n END IF;\n\n -- RESTRICT: only an empty schema goes. Anything else in there \u2014 including a\n -- Supabase installation left untouched above \u2014 keeps it.\n IF dropped_any THEN\n BEGIN\n EXECUTE 'DROP SCHEMA auth RESTRICT';\n EXCEPTION WHEN OTHERS THEN\n NULL;\n END;\n END IF;\nEND\n$rebase_drop_legacy$;\n";
|
|
77
|
+
/** Somebody's policy that still calls a pre-1.0 helper. */
|
|
78
|
+
export interface LegacyRlsDependent {
|
|
79
|
+
schema: string;
|
|
80
|
+
table: string;
|
|
81
|
+
policy: string;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Policies whose body still calls `auth.uid()` / `auth.roles()` / `auth.jwt()`.
|
|
85
|
+
*
|
|
86
|
+
* Postgres will not drop a function a policy depends on, so this is exactly the
|
|
87
|
+
* set standing between a database and losing the legacy schema. Rebase's own
|
|
88
|
+
* policies leave the list on the next push or boot, when they are recompiled —
|
|
89
|
+
* anything still here afterwards is hand-written, will never be recompiled by
|
|
90
|
+
* anybody, and is the reason the drop keeps being skipped. Silence there would
|
|
91
|
+
* leave an operator staring at a schema the release notes said would go.
|
|
92
|
+
*/
|
|
93
|
+
export declare const LEGACY_RLS_DEPENDENTS_SQL = "\n SELECT n.nspname AS schema, c.relname AS \"table\", p.polname AS policy\n FROM pg_policy p\n JOIN pg_class c ON c.oid = p.polrelid\n JOIN pg_namespace n ON n.oid = c.relnamespace\n WHERE pg_get_expr(p.polqual, p.polrelid) ~* '\\mauth\\.(uid|jwt|roles)\\s*\\('\n OR pg_get_expr(p.polwithcheck, p.polrelid) ~* '\\mauth\\.(uid|jwt|roles)\\s*\\('\n ORDER BY 1, 2, 3\n";
|
|
94
|
+
/** Somebody's *function* that still calls a pre-1.0 helper from its own body. */
|
|
95
|
+
export interface LegacyRlsFunctionDependent {
|
|
96
|
+
schema: string;
|
|
97
|
+
function: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Functions whose body calls `auth.uid()` / `auth.roles()` / `auth.jwt()`.
|
|
101
|
+
*
|
|
102
|
+
* This is the half `DROP FUNCTION ... RESTRICT` cannot see, and the reason it
|
|
103
|
+
* needs its own query. Postgres records a dependency for a *policy* that calls a
|
|
104
|
+
* function, which is why the drop is safe against the policies above — but a
|
|
105
|
+
* `LANGUAGE sql` function whose body is a **string literal** is not parsed when
|
|
106
|
+
* it is created, so nothing is recorded and `RESTRICT` has nothing to refuse on.
|
|
107
|
+
* The drop succeeds and the caller is left pointing at a function that no longer
|
|
108
|
+
* exists, which fails at *query* time rather than at boot.
|
|
109
|
+
*
|
|
110
|
+
* A downstream project building on these helpers is not hypothetical: the Rebase
|
|
111
|
+
* control plane defines `auth.is_org_member(uuid)` and `auth.is_org_admin(uuid)`
|
|
112
|
+
* in this very schema, each calling `auth.uid()` in a string body, and eleven of
|
|
113
|
+
* its row-level-security policies go through them. Every one of those would have
|
|
114
|
+
* started failing the first time a recompile left no policy referencing
|
|
115
|
+
* `auth.uid()` directly — the drop's own precondition.
|
|
116
|
+
*
|
|
117
|
+
* Matching on the body text is the only option available, and it is deliberately
|
|
118
|
+
* broad: a false positive costs a schema that stays one release longer and says
|
|
119
|
+
* why, while a false negative costs somebody their policies.
|
|
120
|
+
*/
|
|
121
|
+
export declare const LEGACY_RLS_FUNCTION_DEPENDENTS_SQL = "\n SELECT n.nspname AS schema, p.proname AS function\n FROM pg_proc p\n JOIN pg_namespace n ON n.oid = p.pronamespace\n WHERE n.nspname NOT IN ('pg_catalog', 'information_schema')\n AND NOT (n.nspname = 'auth' AND p.proname IN ('uid', 'jwt', 'roles'))\n AND p.prosrc ~* '\\mauth\\.(uid|jwt|roles)\\s*\\('\n ORDER BY 1, 2\n";
|
|
122
|
+
/**
|
|
123
|
+
* Retire the pre-1.0 `auth` schema, reporting what is holding it back.
|
|
124
|
+
*
|
|
125
|
+
* The shared implementation behind the CLI's post-push step and the runtime's
|
|
126
|
+
* post-policy step. Both used to just fire {@link DROP_LEGACY_AUTH_SCHEMA_SQL}
|
|
127
|
+
* and swallow whatever came back, which is right for the ordinary case — a
|
|
128
|
+
* table not recompiled *yet* — and wrong for the one that never resolves: a
|
|
129
|
+
* hand-written policy nothing will ever rewrite. Then the schema stays forever
|
|
130
|
+
* and nothing ever says why.
|
|
131
|
+
*/
|
|
132
|
+
export declare function dropLegacyAuthSchema(run: (sql: string) => Promise<Record<string, unknown>[]>, report: {
|
|
133
|
+
info: (m: string) => void;
|
|
134
|
+
warn: (m: string) => void;
|
|
135
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one place a collection's `search` block becomes SQL.
|
|
3
|
+
*
|
|
4
|
+
* Four things describe a Postgres table in this codebase — the DDL generator,
|
|
5
|
+
* the Drizzle schema generator, the runtime table builder for BaaS mode, and
|
|
6
|
+
* the boot-time schema ensure — and each of them has, at some point, described
|
|
7
|
+
* a column differently from the others. The `varchar(255)` note in
|
|
8
|
+
* `generate-postgres-ddl-logic` is one such scar: the same property produced a
|
|
9
|
+
* capped column down one path and an uncapped one down the other, and nothing
|
|
10
|
+
* failed until a user hit the cap.
|
|
11
|
+
*
|
|
12
|
+
* So the search column is not implemented four times. It is computed once,
|
|
13
|
+
* here, and every generator renders the same {@link SearchColumnSpec}. There is
|
|
14
|
+
* a test asserting exactly that (`search-column-contract.test.ts`); the point of
|
|
15
|
+
* this module is that the test has something to assert *about*.
|
|
16
|
+
*
|
|
17
|
+
* ## Why the expressions look the way they do
|
|
18
|
+
*
|
|
19
|
+
* A `GENERATED ALWAYS AS … STORED` expression must be strictly IMMUTABLE, and
|
|
20
|
+
* Postgres is stricter here than intuition. Verified against PostgreSQL 18:
|
|
21
|
+
*
|
|
22
|
+
* | expression | immutable |
|
|
23
|
+
* |-----------------------------------------|-----------|
|
|
24
|
+
* | `to_tsvector('spanish', col)` | yes |
|
|
25
|
+
* | `to_tsvector(col)` (1-arg) | **no** — depends on `default_text_search_config` |
|
|
26
|
+
* | `array_to_string(col, ' ')` | **no** |
|
|
27
|
+
* | `col::text` on `text[]` | **no** |
|
|
28
|
+
* | `to_jsonb(col)` | **no** |
|
|
29
|
+
* | `unaccent(col)` | **no** — dictionary lookup is STABLE |
|
|
30
|
+
* | `jsonb_to_tsvector('spanish', j, '["string"]')` | yes |
|
|
31
|
+
* | `setweight(...) || setweight(...)` | yes |
|
|
32
|
+
*
|
|
33
|
+
* Three of the four things a real search column needs are therefore unavailable
|
|
34
|
+
* directly, which is why {@link searchHelperFunctions} exists: each wraps a
|
|
35
|
+
* stable built-in in an SQL function declared IMMUTABLE. That declaration is a
|
|
36
|
+
* promise, and it is a true one for these three — array joining, JSON string
|
|
37
|
+
* extraction and accent folding are all deterministic for a given input; the
|
|
38
|
+
* built-ins are marked stable only because they must account for element types
|
|
39
|
+
* and dictionaries in general.
|
|
40
|
+
*
|
|
41
|
+
* The alternative was to skip `unaccent` and text arrays entirely. That is not
|
|
42
|
+
* a real option in an accented language: Postgres stems `auditoría` to
|
|
43
|
+
* `auditor` and `auditoria` to `auditori` — *different lexemes* — so a query
|
|
44
|
+
* typed without accents misses every row that carries them.
|
|
45
|
+
*/
|
|
46
|
+
import { CollectionConfig, SearchConfig, SearchWeight } from "@rebasepro/types";
|
|
47
|
+
/**
|
|
48
|
+
* Names of the helper functions. Frozen: they are recorded in the stored
|
|
49
|
+
* generation expression of every search column ever created, so renaming one
|
|
50
|
+
* orphans every table that already has a search column.
|
|
51
|
+
*/
|
|
52
|
+
export declare const SEARCH_TEXT_FN = "public.rebase_search_text";
|
|
53
|
+
export declare const SEARCH_UNACCENT_FN = "public.rebase_search_unaccent";
|
|
54
|
+
/** How a declared path reaches text, which decides the SQL that extracts it. */
|
|
55
|
+
type FieldKind = "text" | "text_array" | "jsonb";
|
|
56
|
+
/** One resolved field: where it lives, how to read it, what it is worth. */
|
|
57
|
+
export interface ResolvedSearchField {
|
|
58
|
+
/** The path exactly as the author wrote it, for error messages. */
|
|
59
|
+
path: string;
|
|
60
|
+
/** The physical column the path starts at. */
|
|
61
|
+
column: string;
|
|
62
|
+
/** Dotted remainder addressed inside a JSONB column, if any. */
|
|
63
|
+
jsonPath: string[];
|
|
64
|
+
kind: FieldKind;
|
|
65
|
+
weight: SearchWeight;
|
|
66
|
+
/** The `setweight(to_tsvector(…), 'X')` term this field contributes. */
|
|
67
|
+
sql: string;
|
|
68
|
+
/** The plain-text term this field contributes, for the fuzzy column. */
|
|
69
|
+
textSql: string;
|
|
70
|
+
}
|
|
71
|
+
/** Everything the generators need to render one collection's search column. */
|
|
72
|
+
export interface SearchColumnSpec {
|
|
73
|
+
schema: string;
|
|
74
|
+
table: string;
|
|
75
|
+
/** The generated `tsvector` column. */
|
|
76
|
+
column: string;
|
|
77
|
+
language: string;
|
|
78
|
+
unaccent: boolean;
|
|
79
|
+
fields: ResolvedSearchField[];
|
|
80
|
+
/** Body of `GENERATED ALWAYS AS ( … ) STORED` for the tsvector column. */
|
|
81
|
+
expression: string;
|
|
82
|
+
indexName: string;
|
|
83
|
+
/** Extensions that must exist before the column can be created. */
|
|
84
|
+
extensions: string[];
|
|
85
|
+
fuzzy?: {
|
|
86
|
+
column: string;
|
|
87
|
+
expression: string;
|
|
88
|
+
indexName: string;
|
|
89
|
+
threshold: number;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Raised when a `search` block names something that cannot be searched. */
|
|
93
|
+
export declare class SearchConfigError extends Error {
|
|
94
|
+
constructor(message: string);
|
|
95
|
+
}
|
|
96
|
+
/** The `search` block of a collection, or undefined when it has none. */
|
|
97
|
+
export declare const getSearchConfig: (collection: CollectionConfig) => SearchConfig | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Refuse a `search` block on a collection this engine does not store.
|
|
100
|
+
*
|
|
101
|
+
* The type only permits one on a `PostgresCollectionConfig`, so TypeScript
|
|
102
|
+
* already stops the ordinary case. This catches the rest — a JS config, a cast,
|
|
103
|
+
* a collection whose `engine` was changed after the block was written — because
|
|
104
|
+
* the alternative is the exact failure the block exists to prevent: a developer
|
|
105
|
+
* who declared what to index, saw no error, and got the substring fallback.
|
|
106
|
+
*
|
|
107
|
+
* Called with *every* collection, before the Postgres ones are filtered out.
|
|
108
|
+
*/
|
|
109
|
+
export declare const assertSearchIsPostgresOnly: (collections: CollectionConfig[]) => void;
|
|
110
|
+
/**
|
|
111
|
+
* Build the full spec for a collection, or undefined when it has not opted in.
|
|
112
|
+
*
|
|
113
|
+
* Throws {@link SearchConfigError} on a config that cannot be honoured. Callers
|
|
114
|
+
* at boot surface that as a startup failure — a search block that half-works is
|
|
115
|
+
* worse than one that refuses.
|
|
116
|
+
*/
|
|
117
|
+
export declare const buildSearchColumnSpec: (collection: CollectionConfig) => SearchColumnSpec | undefined;
|
|
118
|
+
/**
|
|
119
|
+
* The IMMUTABLE wrappers the generated expressions call.
|
|
120
|
+
*
|
|
121
|
+
* `CREATE OR REPLACE` so a boot against an existing database is a no-op rather
|
|
122
|
+
* than an error, and idempotent for the same reason every other boot-time DDL
|
|
123
|
+
* statement here is.
|
|
124
|
+
*
|
|
125
|
+
* The bodies are stable built-ins wrapped in an immutable promise — see the
|
|
126
|
+
* module comment for why that promise is sound. `STRICT` matters: it makes NULL
|
|
127
|
+
* in mean NULL out without executing the body, which is what the `coalesce` at
|
|
128
|
+
* each call site then absorbs.
|
|
129
|
+
*/
|
|
130
|
+
export declare const searchHelperFunctions: (spec: SearchColumnSpec) => string[];
|
|
131
|
+
/**
|
|
132
|
+
* `CREATE EXTENSION` statements the spec's expressions depend on.
|
|
133
|
+
*
|
|
134
|
+
* `WITH SCHEMA public` is load-bearing, not tidiness. An unqualified
|
|
135
|
+
* `CREATE EXTENSION` installs into the first schema on `search_path`, which
|
|
136
|
+
* defaults to `"$user", public` — and the scaffold's database role is named
|
|
137
|
+
* `rebase`, the same as the schema the generator creates one statement earlier.
|
|
138
|
+
* So the moment that schema exists, `CREATE EXTENSION unaccent` puts the
|
|
139
|
+
* dictionary in `rebase`, and every reference to `public.unaccent` below fails
|
|
140
|
+
* with "text search dictionary does not exist". Observed, not theorised.
|
|
141
|
+
*/
|
|
142
|
+
export declare const searchExtensionStatements: (spec: SearchColumnSpec) => string[];
|
|
143
|
+
/** The column definition as it appears inside `CREATE TABLE`. */
|
|
144
|
+
export declare const searchColumnDefinition: (spec: SearchColumnSpec) => string;
|
|
145
|
+
/** The fuzzy column definition, when the spec asks for one. */
|
|
146
|
+
export declare const fuzzyColumnDefinition: (spec: SearchColumnSpec) => string | undefined;
|
|
147
|
+
/**
|
|
148
|
+
* Index statements for the spec.
|
|
149
|
+
*
|
|
150
|
+
* `CONCURRENTLY` is deliberately *not* used here. This form is emitted into a
|
|
151
|
+
* SQL file replayed as one unit — a migration, or `search.sql` — where a
|
|
152
|
+
* concurrent build is not allowed. The boot-time ensure path runs statement by
|
|
153
|
+
* statement against tables that are live and populated, and uses the
|
|
154
|
+
* concurrent form instead; see `ensureSearchColumns`.
|
|
155
|
+
*/
|
|
156
|
+
export declare const searchIndexStatements: (spec: SearchColumnSpec) => string[];
|
|
157
|
+
/**
|
|
158
|
+
* Marker on the comment of every generated search column this module creates.
|
|
159
|
+
*
|
|
160
|
+
* Versioned because the fingerprint below is only comparable against itself: a
|
|
161
|
+
* future change to how it is computed has to read as "not stamped by this
|
|
162
|
+
* version" rather than as drift on every existing column.
|
|
163
|
+
*/
|
|
164
|
+
export declare const SEARCH_STAMP_PREFIX = "rebase:search:v1:";
|
|
165
|
+
/**
|
|
166
|
+
* A stable fingerprint of one generated column's expression.
|
|
167
|
+
*
|
|
168
|
+
* Why a stamp rather than reading the expression back: Postgres stores a
|
|
169
|
+
* generated column's expression *parsed*, and hands it back deparsed — casts
|
|
170
|
+
* made explicit, identifiers requoted, schema qualifications added or dropped
|
|
171
|
+
* according to `search_path`. Comparing that text to the text we generated
|
|
172
|
+
* would report drift on wording, and this comparison decides whether a boot
|
|
173
|
+
* refuses, so a false positive is an outage. The stamp is written by the same
|
|
174
|
+
* code that writes the column, so equality means what it says.
|
|
175
|
+
*/
|
|
176
|
+
export declare const searchExpressionFingerprint: (expression: string) => string;
|
|
177
|
+
/** One generated column, with the fingerprint that identifies its expression. */
|
|
178
|
+
export interface SearchColumnStamp {
|
|
179
|
+
column: string;
|
|
180
|
+
/** The expression the column is generated from. */
|
|
181
|
+
expression: string;
|
|
182
|
+
fingerprint: string;
|
|
183
|
+
/** `COMMENT ON COLUMN …`, which is where the fingerprint is recorded. */
|
|
184
|
+
sql: string;
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* The stamps for a spec's generated columns — one per column, never shared.
|
|
188
|
+
*
|
|
189
|
+
* Per column on purpose: turning `fuzzy` on adds a second column and changes
|
|
190
|
+
* nothing about the first, and a spec-wide fingerprint would report the
|
|
191
|
+
* untouched `tsvector` column as drifted and refuse a boot over a change that
|
|
192
|
+
* is purely additive.
|
|
193
|
+
*/
|
|
194
|
+
export declare const searchColumnStamps: (spec: SearchColumnSpec) => SearchColumnStamp[];
|
|
195
|
+
/**
|
|
196
|
+
* The same drift check as the boot ensure, for the SQL file.
|
|
197
|
+
*
|
|
198
|
+
* Needed because {@link searchColumnStamps} would otherwise *launder* drift on
|
|
199
|
+
* the migration path: `ADD COLUMN IF NOT EXISTS` does nothing to a column that
|
|
200
|
+
* exists, so a re-generated `search.sql` would stamp a stale column with the
|
|
201
|
+
* new block's fingerprint and the next boot would find them in agreement.
|
|
202
|
+
* Guarding first means the file refuses instead — `rebase db push` is attended,
|
|
203
|
+
* and the operator reading the failure is the person who changed the block.
|
|
204
|
+
*/
|
|
205
|
+
export declare const searchStampGuards: (spec: SearchColumnSpec) => string[];
|
|
206
|
+
/**
|
|
207
|
+
* The index names the spec creates.
|
|
208
|
+
*
|
|
209
|
+
* Needed by name, not just by statement, so Atlas can be told to exclude them
|
|
210
|
+
* from its diff — see `searchExcludePatterns`.
|
|
211
|
+
*/
|
|
212
|
+
export declare const searchIndexNames: (spec: SearchColumnSpec) => string[];
|
|
213
|
+
/**
|
|
214
|
+
* The generated column names a collection's search block adds, if any.
|
|
215
|
+
*
|
|
216
|
+
* These are physical columns on the table, so `SELECT *` returns them. They are
|
|
217
|
+
* an index in column form — a list of lexeme positions, or a concatenation of
|
|
218
|
+
* every searchable field on the row — and nothing outside the query planner has
|
|
219
|
+
* any use for them. Left in, every list response carries a second, larger copy
|
|
220
|
+
* of the row's text.
|
|
221
|
+
*/
|
|
222
|
+
export declare const searchColumnNames: (collection: CollectionConfig) => string[];
|
|
223
|
+
/**
|
|
224
|
+
* True for a column whose type only ever holds a search index.
|
|
225
|
+
*
|
|
226
|
+
* Independent of any collection config on purpose: an introspected database
|
|
227
|
+
* (BaaS mode) can carry a `tsvector` column this framework never created —
|
|
228
|
+
* Pagila's `film.fulltext` is the canonical one — and it should not be returned
|
|
229
|
+
* to callers either. `isDerivedIndexColumn` already keeps such a column out of
|
|
230
|
+
* the *properties*; this keeps it out of the *rows*.
|
|
231
|
+
*/
|
|
232
|
+
export declare const isSearchIndexColumn: (column: {
|
|
233
|
+
getSQLType?: () => string;
|
|
234
|
+
}) => boolean;
|
|
235
|
+
/**
|
|
236
|
+
* A drizzle select projection over `table` with the search columns dropped.
|
|
237
|
+
*
|
|
238
|
+
* Returns undefined when nothing needs dropping, so the common case keeps using
|
|
239
|
+
* a plain `select()` and this stays invisible in the generated SQL.
|
|
240
|
+
*/
|
|
241
|
+
export declare const visibleColumnProjection: (tableColumns: Record<string, {
|
|
242
|
+
getSQLType?: () => string;
|
|
243
|
+
}> | undefined, collection?: CollectionConfig) => Record<string, unknown> | undefined;
|
|
244
|
+
/** The same exclusion as a drizzle `db.query` `columns` denylist. */
|
|
245
|
+
export declare const hiddenColumnsOption: (tableColumns: Record<string, {
|
|
246
|
+
getSQLType?: () => string;
|
|
247
|
+
}> | undefined, collection?: CollectionConfig) => Record<string, false> | undefined;
|
|
248
|
+
export {};
|