@rebasepro/server-postgres 0.12.1-canary.gcfa333c → 0.12.1-canary.gddbd489
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 +1 -1
- package/dist/PostgresBootstrapper.d.ts +25 -1
- package/dist/auth/services.d.ts +6 -1
- package/dist/backup/backup-service.d.ts +10 -1
- package/dist/backup/pg-tools.d.ts +47 -0
- package/dist/{backup-service-DH9kPg-E.js → backup-service-CD8o_1Sl.js} +144 -11
- package/dist/backup-service-CD8o_1Sl.js.map +1 -0
- package/dist/cli-helpers.d.ts +39 -0
- package/dist/{connection-B5Wndbr1.js → connection-BuZ97wsr.js} +57 -3
- package/dist/connection-BuZ97wsr.js.map +1 -0
- package/dist/connection.d.ts +42 -0
- package/dist/{ensure-collection-policies-Vl3Cv1Q9.js → ensure-collection-policies-ViG8XiPn.js} +2 -2
- package/dist/{ensure-collection-policies-Vl3Cv1Q9.js.map → ensure-collection-policies-ViG8XiPn.js.map} +1 -1
- package/dist/{ensure-collection-tables-DsDsNl6o.js → ensure-collection-tables-CBQdOETu.js} +72 -12
- package/dist/ensure-collection-tables-CBQdOETu.js.map +1 -0
- package/dist/index.es.js +990 -677
- package/dist/index.es.js.map +1 -1
- package/dist/policy-CeA1JcxP.js +105 -0
- package/dist/policy-CeA1JcxP.js.map +1 -0
- package/dist/schema/ensure-collection-tables.d.ts +21 -1
- package/dist/schema/generate-postgres-ddl-logic.d.ts +19 -0
- package/dist/schema/introspect-db-constraints.d.ts +57 -0
- package/dist/schema/introspect-db-logic.d.ts +94 -5
- package/dist/schema/introspect-db-queries.d.ts +119 -0
- package/dist/schema/introspect-db-structure.d.ts +263 -0
- package/dist/schema/introspect-db-types.d.ts +11 -0
- package/dist/services/FetchService.d.ts +4 -1
- package/dist/services/RelationService.d.ts +24 -1
- package/dist/services/channel-bus/index.d.ts +1 -7
- package/dist/services/collection-helpers.d.ts +36 -2
- package/dist/services/dataService.d.ts +3 -1
- package/dist/services/row-pipeline.d.ts +1 -1
- package/dist/{src-DihrDFuP.js → src-DlPBctw_.js} +217 -101
- package/dist/src-DlPBctw_.js.map +1 -0
- package/dist/utils/connection-string.d.ts +29 -0
- package/dist/utils/drizzle-conditions.d.ts +5 -4
- package/dist/utils/pg-error-utils.d.ts +16 -0
- package/dist/{websocket-BKcGvILX.js → websocket-B2LsrINK.js} +8 -6
- package/dist/websocket-B2LsrINK.js.map +1 -0
- package/package.json +6 -6
- package/src/PostgresAdapter.ts +7 -2
- package/src/PostgresBackendDriver.ts +4 -0
- package/src/PostgresBootstrapper.ts +83 -22
- package/src/auth/services.ts +6 -3
- package/src/backup/backup-cli.ts +41 -2
- package/src/backup/backup-service.ts +38 -5
- package/src/backup/pg-tools.ts +96 -3
- package/src/cli-helpers.ts +70 -0
- package/src/cli.ts +44 -26
- package/src/collections/validate-relations.ts +15 -0
- package/src/connection.ts +73 -0
- package/src/data-transformer.ts +9 -3
- package/src/databasePoolManager.ts +5 -2
- package/src/schema/ensure-collection-tables.ts +81 -10
- package/src/schema/generate-drizzle-schema-logic.ts +35 -3
- package/src/schema/generate-postgres-ddl-logic.ts +50 -6
- package/src/schema/introspect-db-constraints.ts +385 -0
- package/src/schema/introspect-db-inference.ts +18 -8
- package/src/schema/introspect-db-logic.ts +385 -71
- package/src/schema/introspect-db-queries.ts +326 -0
- package/src/schema/introspect-db-structure.ts +670 -0
- package/src/schema/introspect-db-types.ts +56 -0
- package/src/schema/introspect-db.ts +37 -80
- package/src/security/policy-drift.test.ts +11 -3
- package/src/services/FetchService.ts +90 -14
- package/src/services/PersistService.ts +20 -6
- package/src/services/RelationService.ts +211 -45
- package/src/services/channel-bus/index.ts +0 -9
- package/src/services/collection-helpers.ts +69 -3
- package/src/services/dataService.ts +3 -1
- package/src/services/realtimeService.ts +3 -3
- package/src/services/row-pipeline.ts +1 -1
- package/src/utils/connection-string.ts +58 -0
- package/src/utils/drizzle-conditions.ts +31 -6
- package/src/utils/pg-error-utils.ts +19 -0
- package/src/websocket.ts +18 -9
- package/dist/backup-service-DH9kPg-E.js.map +0 -1
- package/dist/connection-B5Wndbr1.js.map +0 -1
- package/dist/ensure-collection-tables-DsDsNl6o.js.map +0 -1
- package/dist/src-DihrDFuP.js.map +0 -1
- package/dist/websocket-BKcGvILX.js.map +0 -1
|
@@ -175,10 +175,11 @@ export class DrizzleConditionBuilder {
|
|
|
175
175
|
static buildRelationScopeCondition(
|
|
176
176
|
relation: ResolvedRelation,
|
|
177
177
|
/**
|
|
178
|
-
* Lazy:
|
|
179
|
-
*
|
|
180
|
-
* the parent's *id* alone, and
|
|
181
|
-
* a child listing fail on a
|
|
178
|
+
* Lazy: `via`, `belongsTo`, and a foreign key that points at a
|
|
179
|
+
* `sourceKey` need the parent's own table. A junction and a plain
|
|
180
|
+
* foreign key are expressible from the parent's *id* alone, and
|
|
181
|
+
* requiring the table for them would make a child listing fail on a
|
|
182
|
+
* parent whose table isn't registered.
|
|
182
183
|
*/
|
|
183
184
|
parent: () => { table: PgTable<any>; idColumn: AnyPgColumn },
|
|
184
185
|
parentId: string | number,
|
|
@@ -239,7 +240,16 @@ export class DrizzleConditionBuilder {
|
|
|
239
240
|
`relation '${relation.relationName}'.`
|
|
240
241
|
);
|
|
241
242
|
}
|
|
242
|
-
return eq(fkColumn, parentId);
|
|
243
|
+
if (!relation.sourceKey) return eq(fkColumn, parentId);
|
|
244
|
+
|
|
245
|
+
// A link on a natural key: the foreign key holds a column of the
|
|
246
|
+
// parent row, not its id, so the parent has to be read. As a
|
|
247
|
+
// subquery rather than a prior SELECT, for the same reason
|
|
248
|
+
// `belongsTo` below is — one statement sees one snapshot, and a
|
|
249
|
+
// scope condition that read the key separately could be built
|
|
250
|
+
// from a value the very next statement no longer agrees with.
|
|
251
|
+
const { table, idColumn } = parent();
|
|
252
|
+
return sql`${fkColumn} = (SELECT ${sql.identifier(relation.sourceKey)} FROM ${table} WHERE ${idColumn} = ${parentId})`;
|
|
243
253
|
}
|
|
244
254
|
|
|
245
255
|
case "belongsTo": {
|
|
@@ -385,7 +395,22 @@ export class DrizzleConditionBuilder {
|
|
|
385
395
|
// inverse, so reversing it into a filter is a different problem
|
|
386
396
|
// from the two shapes below rather than a third case of them.
|
|
387
397
|
if (relation && (hasForeignKeyOnTarget(relation) || isManyToMany(relation)) && registry && sourceIdColumn) {
|
|
388
|
-
|
|
398
|
+
// The `EXISTS` correlates the target's foreign key with a column
|
|
399
|
+
// on *this* table, and that is the primary key only when the
|
|
400
|
+
// link joins on it. A `sourceKey` names a different one, and
|
|
401
|
+
// correlating on the id anyway silently matches nothing —
|
|
402
|
+
// "filter by this relation" would quietly return zero rows.
|
|
403
|
+
const correlationColumn = hasForeignKeyOnTarget(relation) && relation.sourceKey
|
|
404
|
+
? columnAt(relation.sourceKey)
|
|
405
|
+
: sourceIdColumn;
|
|
406
|
+
if (!correlationColumn) {
|
|
407
|
+
throw new Error(
|
|
408
|
+
`\`sourceKey: "${(relation as ResolvedForeignKeyOnTarget).sourceKey}"\` on relation ` +
|
|
409
|
+
`'${relation.relationName}' is not a column on '${collectionPath}', so a filter on ` +
|
|
410
|
+
"that relation has nothing to correlate against."
|
|
411
|
+
);
|
|
412
|
+
}
|
|
413
|
+
return { kind: "relation", relation, registry, sourceIdColumn: correlationColumn };
|
|
389
414
|
}
|
|
390
415
|
}
|
|
391
416
|
|
|
@@ -79,6 +79,25 @@ export function extractPgError(error: unknown): PostgresError | null {
|
|
|
79
79
|
return null;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
/**
|
|
83
|
+
* Whether the failure came back from Postgres rather than from building the
|
|
84
|
+
* query — which decides whether a fallback query is worth issuing.
|
|
85
|
+
*
|
|
86
|
+
* Reads here run inside a transaction (that is where `SET LOCAL ROLE` binds
|
|
87
|
+
* RLS). Once a statement raises, that transaction is aborted, and every later
|
|
88
|
+
* statement on it returns `25P02` — "current transaction is aborted, commands
|
|
89
|
+
* ignored until end of transaction block". So a retry after a database error
|
|
90
|
+
* cannot succeed, and it replaces a precise diagnosis ("invalid input syntax
|
|
91
|
+
* for type uuid") with a generic one. Rethrow instead.
|
|
92
|
+
*
|
|
93
|
+
* A query the driver could not even build — a missing reciprocal relation, say
|
|
94
|
+
* — never reached Postgres, leaves the transaction usable, and is exactly what
|
|
95
|
+
* the fallback paths exist for.
|
|
96
|
+
*/
|
|
97
|
+
export function reachedDatabase(error: unknown): boolean {
|
|
98
|
+
return extractPgError(error) !== null;
|
|
99
|
+
}
|
|
100
|
+
|
|
82
101
|
/**
|
|
83
102
|
* Walk the error cause chain and return the deepest meaningful message.
|
|
84
103
|
*/
|
package/src/websocket.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { RealtimeService } from "./services/realtimeService";
|
|
2
2
|
import { PostgresBackendDriver } from "./PostgresBackendDriver";
|
|
3
3
|
import type { DataDriver, DeleteProps, FetchCollectionProps, FetchOneProps, SaveProps, TableMetadata, BranchInfo, AuthAdapter } from "@rebasepro/types";
|
|
4
|
-
import { isSQLAdmin, isSchemaAdmin } from "@rebasepro/types";
|
|
4
|
+
import { ANONYMOUS_USER_ID, isSQLAdmin, isSchemaAdmin } from "@rebasepro/types";
|
|
5
5
|
import type { User } from "@rebasepro/types";
|
|
6
6
|
|
|
7
7
|
import { WebSocketServer, WebSocket } from "ws";
|
|
8
8
|
import { Server } from "http";
|
|
9
9
|
import { inspect } from "util";
|
|
10
|
-
import { extractUserFromToken, AccessTokenPayload, safeCompare } from "@rebasepro/server";
|
|
10
|
+
import { extractUserFromToken, AccessTokenPayload, safeCompare, resolveRequireAuth } from "@rebasepro/server";
|
|
11
11
|
import { logger } from "@rebasepro/server";
|
|
12
12
|
|
|
13
13
|
/** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */
|
|
@@ -116,11 +116,20 @@ export function createPostgresWebSocket(
|
|
|
116
116
|
logger.error("❌ [WebSocket Server] Error", { error: err });
|
|
117
117
|
});
|
|
118
118
|
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
119
|
+
// The same predicate the HTTP data routes use, from the same function —
|
|
120
|
+
// this socket is the other enforcement point for one product decision, and
|
|
121
|
+
// while it computed the answer itself it computed a different one. See
|
|
122
|
+
// `resolveRequireAuth` for what its local copy got wrong and why a `false`
|
|
123
|
+
// here grants access rather than skipping a check.
|
|
124
|
+
const requireAuth = !!authAdapter || resolveRequireAuth(authConfig as never);
|
|
125
|
+
|
|
126
|
+
if (requireAuth && !authAdapter && !authConfig?.jwtSecret && !authConfig?.serviceKey) {
|
|
127
|
+
logger.warn(
|
|
128
|
+
"🔐 [WebSocket Server] Authentication is required but no adapter, jwtSecret or " +
|
|
129
|
+
"serviceKey is configured — no client can complete AUTH, so every realtime " +
|
|
130
|
+
"message will be refused with UNAUTHORIZED."
|
|
131
|
+
);
|
|
132
|
+
}
|
|
124
133
|
|
|
125
134
|
wss.on("connection", (ws) => {
|
|
126
135
|
const clientId = `client_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
@@ -281,7 +290,7 @@ roles: verifiedUser.roles }
|
|
|
281
290
|
roles: session.user.roles ?? []
|
|
282
291
|
}
|
|
283
292
|
: {
|
|
284
|
-
uid:
|
|
293
|
+
uid: ANONYMOUS_USER_ID,
|
|
285
294
|
displayName: null,
|
|
286
295
|
email: null,
|
|
287
296
|
photoURL: null,
|
|
@@ -620,7 +629,7 @@ colors: true }));
|
|
|
620
629
|
const authContext = session?.user
|
|
621
630
|
? { uid: session.user.uid,
|
|
622
631
|
roles: session.user.roles ?? [] }
|
|
623
|
-
: { uid:
|
|
632
|
+
: { uid: ANONYMOUS_USER_ID,
|
|
624
633
|
roles: ["anon"] };
|
|
625
634
|
// Let RealtimeService handle these messages
|
|
626
635
|
await realtimeService.handleClientMessage(clientId, {
|