@rebasepro/server-postgres 0.9.1-canary.fd3754b → 0.9.1-canary.ff338b5
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 +25 -2
- package/dist/PostgresBootstrapper.d.ts +10 -0
- package/dist/auth/services.d.ts +16 -0
- package/dist/collections/buildRegistry.d.ts +27 -0
- package/dist/connection.d.ts +21 -0
- package/dist/data-transformer.d.ts +9 -2
- package/dist/index.es.js +2005 -2583
- package/dist/index.es.js.map +1 -1
- package/dist/schema/doctor.d.ts +1 -1
- package/dist/security/policy-drift.d.ts +46 -5
- package/dist/security/rls-enforcement.d.ts +27 -2
- package/dist/services/FetchService.d.ts +4 -24
- package/dist/services/PersistService.d.ts +27 -1
- package/dist/services/RelationService.d.ts +34 -1
- package/dist/services/collection-helpers.d.ts +79 -14
- package/dist/services/dataService.d.ts +3 -1
- package/dist/services/index.d.ts +1 -1
- package/dist/services/realtimeService.d.ts +7 -0
- package/dist/services/row-pipeline.d.ts +63 -0
- package/package.json +16 -17
- package/src/PostgresBackendDriver.ts +127 -13
- package/src/PostgresBootstrapper.ts +68 -26
- package/src/auth/ensure-tables.ts +73 -11
- package/src/auth/services.ts +49 -19
- package/src/cli-helpers.ts +2 -20
- package/src/cli.ts +60 -0
- package/src/collections/buildRegistry.ts +59 -0
- package/src/connection.ts +61 -1
- package/src/data-transformer.ts +11 -9
- package/src/databasePoolManager.ts +2 -0
- package/src/schema/doctor-cli.ts +5 -1
- package/src/schema/doctor.ts +45 -20
- package/src/schema/generate-drizzle-schema-logic.ts +24 -29
- package/src/schema/generate-postgres-ddl-logic.ts +76 -28
- package/src/schema/introspect-db.ts +19 -2
- package/src/security/policy-drift.test.ts +153 -14
- package/src/security/policy-drift.ts +128 -10
- package/src/security/rls-enforcement.ts +64 -3
- package/src/services/BranchService.ts +42 -10
- package/src/services/FetchService.ts +65 -270
- package/src/services/PersistService.ts +130 -14
- package/src/services/RelationService.ts +153 -94
- package/src/services/collection-helpers.ts +164 -47
- package/src/services/dataService.ts +3 -2
- package/src/services/index.ts +1 -0
- package/src/services/realtimeService.ts +40 -19
- package/src/services/row-pipeline.ts +239 -0
- package/src/utils/drizzle-conditions.ts +13 -0
- package/src/websocket.ts +4 -1
- package/dist/chunk-DSJWtz9O.js +0 -40
- package/dist/schema/auth-default-policies.d.ts +0 -10
- package/dist/src-Eh-CZosp.js +0 -595
- package/dist/src-Eh-CZosp.js.map +0 -1
- package/src/schema/auth-default-policies.ts +0 -125
|
@@ -3,7 +3,7 @@ import { BranchService } from "./services/BranchService";
|
|
|
3
3
|
import { RealtimeService } from "./services/realtimeService";
|
|
4
4
|
import { DatabasePoolManager } from "./databasePoolManager";
|
|
5
5
|
import { DrizzleClient } from "./interfaces";
|
|
6
|
-
import { DatabaseAdmin, DataDriver, DeleteProps, CollectionConfig, FetchCollectionProps, FetchOneProps, ListenCollectionProps, ListenOneProps, RebaseClient, RebaseSdkData, RestFetchService, SaveProps, TableMetadata, User } from "@rebasepro/types";
|
|
6
|
+
import { DatabaseAdmin, DataDriver, DeleteProps, CollectionConfig, FetchCollectionProps, FetchOneProps, ListenCollectionProps, ListenOneProps, RebaseClient, RebaseSdkData, RestFetchService, SaveManyProps, SaveProps, TableMetadata, User } from "@rebasepro/types";
|
|
7
7
|
import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry";
|
|
8
8
|
import { HistoryService } from "./history/HistoryService";
|
|
9
9
|
export declare class PostgresBackendDriver implements DataDriver {
|
|
@@ -88,7 +88,20 @@ export declare class PostgresBackendDriver implements DataDriver {
|
|
|
88
88
|
listenCollection<M extends Record<string, unknown>>({ path, collection, filter, limit, offset, startAfter, orderBy, searchString, order, onUpdate, onError }: ListenCollectionProps<M>): () => void;
|
|
89
89
|
fetchOne<M extends Record<string, unknown>>({ path, id, databaseId, collection }: FetchOneProps<M>): Promise<Record<string, unknown> | undefined>;
|
|
90
90
|
listenOne<M extends Record<string, unknown>>({ path, id, collection, onUpdate, onError }: ListenOneProps<M>): () => void;
|
|
91
|
-
save<M extends Record<string, unknown>>({ path, id, values, collection, status }: SaveProps<M>): Promise<Record<string, unknown>>;
|
|
91
|
+
save<M extends Record<string, unknown>>({ path, id, values, collection, status, upsert }: SaveProps<M>): Promise<Record<string, unknown>>;
|
|
92
|
+
/**
|
|
93
|
+
* Write many rows through the same pipeline as {@link save}.
|
|
94
|
+
*
|
|
95
|
+
* The batch runs in one transaction of its own, so a failure part-way leaves
|
|
96
|
+
* nothing behind — the point of a batch is that a re-run starts from a known
|
|
97
|
+
* state. When this driver is already inside a transaction (the authenticated
|
|
98
|
+
* path, via `withTransaction`) the nested call becomes a savepoint, which is
|
|
99
|
+
* still atomic and still commits once.
|
|
100
|
+
*
|
|
101
|
+
* Rows are applied in order, so a batch that touches the same key twice ends
|
|
102
|
+
* with the last write winning, exactly as separate calls would.
|
|
103
|
+
*/
|
|
104
|
+
saveMany<M extends Record<string, unknown>>({ path, rows, collection, upsert }: SaveManyProps<M>): Promise<Record<string, unknown>[]>;
|
|
92
105
|
delete<M extends Record<string, unknown>>({ row, collection }: DeleteProps<M>): Promise<void>;
|
|
93
106
|
deleteAll(path: string): Promise<void>;
|
|
94
107
|
checkUniqueField(path: string, name: string, value: unknown, id?: string, collection?: CollectionConfig): Promise<boolean>;
|
|
@@ -143,6 +156,16 @@ export declare class AuthenticatedPostgresBackendDriver implements DataDriver {
|
|
|
143
156
|
fetchOne<M extends Record<string, unknown>>(props: FetchOneProps<M>): Promise<Record<string, unknown> | undefined>;
|
|
144
157
|
listenOne<M extends Record<string, unknown>>(props: ListenOneProps<M>): () => void;
|
|
145
158
|
save<M extends Record<string, unknown>>(props: SaveProps<M>): Promise<Record<string, unknown>>;
|
|
159
|
+
/**
|
|
160
|
+
* One transaction for the whole batch, rather than one per row.
|
|
161
|
+
*
|
|
162
|
+
* This is the point of the method: `save` opens a transaction per call, so
|
|
163
|
+
* importing 10k rows through it means 10k transactions (and, over HTTP, 10k
|
|
164
|
+
* round trips). Here the RLS context is established once and every row lands
|
|
165
|
+
* or none does. Realtime notifications are already deferred to commit by
|
|
166
|
+
* `withTransaction`, so a batch does not flood subscribers mid-flight.
|
|
167
|
+
*/
|
|
168
|
+
saveMany<M extends Record<string, unknown>>(props: SaveManyProps<M>): Promise<Record<string, unknown>[]>;
|
|
146
169
|
delete<M extends Record<string, unknown>>(props: DeleteProps<M>): Promise<void>;
|
|
147
170
|
deleteAll(path: string): Promise<void>;
|
|
148
171
|
checkUniqueField(path: string, name: string, value: unknown, id?: string, collection?: CollectionConfig): Promise<boolean>;
|
|
@@ -9,6 +9,7 @@ import { PostgresBackendDriver } from "./PostgresBackendDriver";
|
|
|
9
9
|
import { RealtimeService } from "./services/realtimeService";
|
|
10
10
|
import { DatabasePoolManager } from "./databasePoolManager";
|
|
11
11
|
import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry";
|
|
12
|
+
import { type CdcTableRef } from "./services/cdc/trigger-cdc";
|
|
12
13
|
export interface PostgresDriverConfig {
|
|
13
14
|
connectionString?: string;
|
|
14
15
|
adminConnectionString?: string;
|
|
@@ -36,6 +37,15 @@ export interface PostgresDriverInternals {
|
|
|
36
37
|
realtimeService: RealtimeService;
|
|
37
38
|
driver: PostgresBackendDriver;
|
|
38
39
|
poolManager?: DatabasePoolManager;
|
|
40
|
+
/**
|
|
41
|
+
* Attach CDC triggers to tables that did not exist when the driver
|
|
42
|
+
* bootstrapped. Only set when database-level capture is actually active.
|
|
43
|
+
*
|
|
44
|
+
* Auth owns its own tables and creates them later in boot, so at driver
|
|
45
|
+
* bootstrap they are legitimately missing and get skipped; without this
|
|
46
|
+
* they would stay uninstrumented until the next restart.
|
|
47
|
+
*/
|
|
48
|
+
provisionCdcForTables?: (tables: CdcTableRef[]) => Promise<void>;
|
|
39
49
|
}
|
|
40
50
|
/**
|
|
41
51
|
* Default PostgreSQL bootstrapper.
|
package/dist/auth/services.d.ts
CHANGED
|
@@ -19,6 +19,22 @@ export declare class UserService implements UserRepository {
|
|
|
19
19
|
private userIdentitiesTable;
|
|
20
20
|
constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
|
|
21
21
|
private getQualifiedUsersTableName;
|
|
22
|
+
/**
|
|
23
|
+
* Run a privileged auth write with an explicitly cleared RLS context.
|
|
24
|
+
*
|
|
25
|
+
* The auth services run on the base/owner connection, which by design
|
|
26
|
+
* carries a NULL `app.user_id` so the `auth.uid() IS NULL` server-escape
|
|
27
|
+
* in the default policies applies. That NULL is normally guaranteed by
|
|
28
|
+
* `set_config(..., is_local = true)` resetting at transaction end — but a
|
|
29
|
+
* GUC that survives on a pooled connection (or a connection role that
|
|
30
|
+
* doesn't bypass RLS: FORCE ROW LEVEL SECURITY, or a non-owner role)
|
|
31
|
+
* turns the trusted write into an RLS-scoped one and denies it with
|
|
32
|
+
* SQLSTATE 42501. Clearing the GUCs here, transaction-locally at the
|
|
33
|
+
* single chokepoint, makes the server context deterministic instead of
|
|
34
|
+
* trusting whatever state the pool hands us. `auth.uid()` reads '' as
|
|
35
|
+
* NULL via NULLIF, so '' is the server context.
|
|
36
|
+
*/
|
|
37
|
+
private withServerContext;
|
|
22
38
|
private mapRowToUser;
|
|
23
39
|
private mapPayload;
|
|
24
40
|
createUser(data: CreateUserData): Promise<UserData>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Relations } from "drizzle-orm";
|
|
2
|
+
import { PgEnum } from "drizzle-orm/pg-core";
|
|
3
|
+
import { CollectionConfig } from "@rebasepro/types";
|
|
4
|
+
import { PostgresCollectionRegistry } from "./PostgresCollectionRegistry";
|
|
5
|
+
/**
|
|
6
|
+
* Everything a registry is built from: the collections, and the drizzle schema
|
|
7
|
+
* they are backed by. In BaaS mode all of it is introspected from the live
|
|
8
|
+
* database; in CMS mode it comes from the config and the generated schema.
|
|
9
|
+
*/
|
|
10
|
+
export interface RegistrySchema {
|
|
11
|
+
collections?: CollectionConfig[];
|
|
12
|
+
tables?: Record<string, unknown>;
|
|
13
|
+
enums?: Record<string, PgEnum<[string, ...string[]]>>;
|
|
14
|
+
relations?: Record<string, Relations>;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Build the collection registry for a driver.
|
|
18
|
+
*
|
|
19
|
+
* The order matters and is the reason this is one function rather than a run of
|
|
20
|
+
* statements in the bootstrapper. Keys are resolved from the drizzle schema, so
|
|
21
|
+
* anything that inspects them has to run *after* the tables are registered —
|
|
22
|
+
* and `warnOnKeysTheAdminCannotResolve` fails open if it does not, because a
|
|
23
|
+
* collection whose table it cannot look up is one it has nothing to say about.
|
|
24
|
+
* Warned too early, it would skip every collection and report nothing, which
|
|
25
|
+
* reads exactly like having nothing to report.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildCollectionRegistry(schema: RegistrySchema): PostgresCollectionRegistry;
|
package/dist/connection.d.ts
CHANGED
|
@@ -19,6 +19,27 @@ export interface PostgresPoolConfig {
|
|
|
19
19
|
/** Enable TCP keep-alive (default: true) */
|
|
20
20
|
keepAlive?: boolean;
|
|
21
21
|
}
|
|
22
|
+
/**
|
|
23
|
+
* Destroy pool clients that are released while still inside a transaction.
|
|
24
|
+
*
|
|
25
|
+
* pg-pool returns a client to the idle list whenever `release()` is called
|
|
26
|
+
* without an error — even if the connection is still mid-transaction (status
|
|
27
|
+
* `T`/`E`). That happens in practice: drizzle's pool transaction releases in
|
|
28
|
+
* a `finally` after attempting ROLLBACK, and if the ROLLBACK itself fails
|
|
29
|
+
* (e.g. it was queued behind a statement that hit the client-side
|
|
30
|
+
* query_timeout), the client goes back dirty. The next checkout then runs
|
|
31
|
+
* its statements inside the zombie transaction — with the previous request's
|
|
32
|
+
* `app.*` RLS GUCs still applied, which turns unrelated queries into
|
|
33
|
+
* RLS-scoped ones (observed in production as registration failing with
|
|
34
|
+
* SQLSTATE 42501 under a leaked anonymous context).
|
|
35
|
+
*
|
|
36
|
+
* pg-pool emits `release` before it consults its private `_expired` set, so
|
|
37
|
+
* marking the client expired here makes `_release()` destroy it instead of
|
|
38
|
+
* pooling it. Both `client._txStatus` (pg ≥ 8.16) and `pool._expired` are
|
|
39
|
+
* private APIs — feature-detect and fall back to loud logging so an upstream
|
|
40
|
+
* change degrades to observability, never to silent corruption.
|
|
41
|
+
*/
|
|
42
|
+
export declare function guardPoolAgainstDirtyRelease(pool: Pool, label: string): void;
|
|
22
43
|
/**
|
|
23
44
|
* Create a Drizzle-backed Postgres connection with a production-grade
|
|
24
45
|
* connection pool.
|
|
@@ -12,12 +12,19 @@ import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegi
|
|
|
12
12
|
export interface SerializedEntityData {
|
|
13
13
|
/** Scalar column values ready for INSERT/UPDATE. */
|
|
14
14
|
scalarData: Record<string, unknown>;
|
|
15
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Inverse relation updates that must be applied to target tables.
|
|
17
|
+
*
|
|
18
|
+
* No address here: the row being written does not know its own. These are
|
|
19
|
+
* applied by `PersistService`, which addresses them with the id it holds —
|
|
20
|
+
* the one it was given for an update, or the one the INSERT returned — and
|
|
21
|
+
* that is the authority. This item used to carry a `currentId` derived from
|
|
22
|
+
* the *input* values, which nothing ever read.
|
|
23
|
+
*/
|
|
16
24
|
inverseRelationUpdates: Array<{
|
|
17
25
|
relationKey: string;
|
|
18
26
|
relation: Relation;
|
|
19
27
|
newValue: unknown;
|
|
20
|
-
currentId?: string | number;
|
|
21
28
|
}>;
|
|
22
29
|
/** JoinPath relation updates that require multi-hop writes. */
|
|
23
30
|
joinPathRelationUpdates: Array<{
|