@rebasepro/server-postgresql 0.7.0 → 0.8.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.
Files changed (56) hide show
  1. package/build-errors.txt +37 -0
  2. package/dist/PostgresAdapter.d.ts +6 -0
  3. package/dist/PostgresBackendDriver.d.ts +118 -0
  4. package/dist/PostgresBootstrapper.d.ts +46 -0
  5. package/dist/auth/ensure-tables.d.ts +10 -0
  6. package/dist/auth/services.d.ts +251 -0
  7. package/dist/cli-errors.d.ts +29 -0
  8. package/dist/cli-helpers.d.ts +7 -0
  9. package/dist/cli.d.ts +1 -0
  10. package/dist/collections/PostgresCollectionRegistry.d.ts +47 -0
  11. package/dist/connection.d.ts +65 -0
  12. package/dist/data-transformer.d.ts +55 -0
  13. package/dist/databasePoolManager.d.ts +20 -0
  14. package/dist/history/HistoryService.d.ts +71 -0
  15. package/dist/history/ensure-history-table.d.ts +7 -0
  16. package/dist/index.d.ts +14 -0
  17. package/dist/index.es.js +13560 -0
  18. package/dist/index.es.js.map +1 -0
  19. package/dist/interfaces.d.ts +18 -0
  20. package/dist/schema/auth-default-policies.d.ts +12 -0
  21. package/dist/schema/auth-schema.d.ts +2376 -0
  22. package/dist/schema/doctor-cli.d.ts +2 -0
  23. package/dist/schema/doctor.d.ts +52 -0
  24. package/dist/schema/generate-drizzle-schema-logic.d.ts +2 -0
  25. package/dist/schema/generate-drizzle-schema.d.ts +1 -0
  26. package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -0
  27. package/dist/schema/generate-postgres-ddl.d.ts +1 -0
  28. package/dist/schema/introspect-db-inference.d.ts +5 -0
  29. package/dist/schema/introspect-db-logic.d.ts +118 -0
  30. package/dist/schema/introspect-db.d.ts +1 -0
  31. package/dist/schema/test-schema.d.ts +24 -0
  32. package/dist/services/BranchService.d.ts +47 -0
  33. package/dist/services/EntityFetchService.d.ts +214 -0
  34. package/dist/services/EntityPersistService.d.ts +40 -0
  35. package/dist/services/RelationService.d.ts +98 -0
  36. package/dist/services/entity-helpers.d.ts +38 -0
  37. package/dist/services/entityService.d.ts +110 -0
  38. package/dist/services/index.d.ts +4 -0
  39. package/dist/services/realtimeService.d.ts +220 -0
  40. package/dist/types.d.ts +3 -0
  41. package/dist/utils/drizzle-conditions.d.ts +138 -0
  42. package/dist/utils/pg-array-null-patch.d.ts +16 -0
  43. package/dist/utils/pg-error-utils.d.ts +65 -0
  44. package/dist/utils/table-classification.d.ts +8 -0
  45. package/dist/websocket.d.ts +11 -0
  46. package/package.json +17 -17
  47. package/src/PostgresBackendDriver.ts +135 -24
  48. package/src/cli.ts +73 -1
  49. package/src/collections/PostgresCollectionRegistry.ts +6 -6
  50. package/src/data-transformer.ts +2 -2
  51. package/src/schema/auth-default-policies.ts +13 -6
  52. package/src/schema/generate-drizzle-schema-logic.ts +16 -79
  53. package/src/schema/generate-postgres-ddl-logic.ts +14 -51
  54. package/src/services/realtimeService.ts +26 -2
  55. package/test/entity-callbacks-redaction.test.ts +86 -0
  56. package/test/postgresDataDriver.test.ts +2 -1
@@ -0,0 +1,37 @@
1
+
2
+ > @rebasepro/server-postgresql@0.0.1 build /Users/francesco/rebase/packages/server-postgresql
3
+ > vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json
4
+
5
+ vite v5.4.21 building for production...
6
+ transforming...
7
+ ✓ 328 modules transformed.
8
+ rendering chunks...
9
+ computing gzip size...
10
+ dist/index.es.js 434.54 kB │ gzip: 83.11 kB │ map: 1,031.37 kB
11
+ dist/index.umd.js 459.59 kB │ gzip: 84.58 kB │ map: 1,035.30 kB
12
+ ✓ built in 1.23s
13
+ src/auth/services.ts(119,15): error TS2322: Type '{ id: unknown; email: unknown; passwordHash: {} | null; displayName: {} | null; photoUrl: {} | null; provider: unknown; googleId: {} | null; emailVerified: {}; emailVerificationToken: {} | null; emailVerificationSentAt: {} | null; createdAt: unknown; updatedAt: unknown; }[]' is not assignable to type '{ email: string; id: string; createdAt: Date; passwordHash: string | null; displayName: string | null; photoUrl: string | null; provider: string; googleId: string | null; emailVerified: boolean; emailVerificationToken: string | null; emailVerificationSentAt: Date | null; updatedAt: Date; }[]'.
14
+ Type '{ id: unknown; email: unknown; passwordHash: {} | null; displayName: {} | null; photoUrl: {} | null; provider: unknown; googleId: {} | null; emailVerified: {}; emailVerificationToken: {} | null; emailVerificationSentAt: {} | null; createdAt: unknown; updatedAt: unknown; }' is not assignable to type '{ email: string; id: string; createdAt: Date; passwordHash: string | null; displayName: string | null; photoUrl: string | null; provider: string; googleId: string | null; emailVerified: boolean; emailVerificationToken: string | null; emailVerificationSentAt: Date | null; updatedAt: Date; }'.
15
+ Types of property 'email' are incompatible.
16
+ Type 'unknown' is not assignable to type 'string'.
17
+ src/collections/PostgresCollectionRegistry.ts(90,143): error TS2344: Type 'Record<string, unknown>' does not satisfy the constraint 'User'.
18
+ Type 'Record<string, unknown>' is missing the following properties from type 'User': uid, displayName, email, photoURL, and 2 more.
19
+ src/PostgresBootstrapper.ts(201,59): error TS2345: Argument of type 'number | undefined' is not assignable to parameter of type 'Partial<HistoryRetentionConfig> | undefined'.
20
+ Type 'number' has no properties in common with type 'Partial<HistoryRetentionConfig>'.
21
+ src/schema/generate-drizzle-schema-logic.ts(455,121): error TS2344: Type 'Record<string, unknown>' does not satisfy the constraint 'User'.
22
+ Type 'Record<string, unknown>' is missing the following properties from type 'User': uid, displayName, email, photoURL, and 2 more.
23
+ src/services/EntityFetchService.ts(37,72): error TS2344: Type 'Record<string, unknown>' does not satisfy the constraint 'TablesRelationalConfig'.
24
+ 'string' index signatures are incompatible.
25
+ Type 'unknown' is not assignable to type 'TableRelationalConfig'.
26
+ src/services/EntityFetchService.ts(39,61): error TS2344: Type 'Record<string, unknown>' does not satisfy the constraint 'TablesRelationalConfig'.
27
+ 'string' index signatures are incompatible.
28
+ Type 'unknown' is not assignable to type 'TableRelationalConfig'.
29
+ src/services/realtimeService.ts(621,21): error TS2322: Type 'Record<string, unknown> | undefined' is not assignable to type 'Partial<Record<string, [WhereFilterOp, unknown]>> | undefined'.
30
+ Type 'Record<string, unknown>' is not assignable to type 'Partial<Record<string, [WhereFilterOp, unknown]>>'.
31
+ 'string' index signatures are incompatible.
32
+ Type 'unknown' is not assignable to type '[WhereFilterOp, unknown] | undefined'.
33
+ src/services/realtimeService.ts(630,13): error TS2322: Type 'Record<string, unknown> | undefined' is not assignable to type 'Partial<Record<string, [WhereFilterOp, unknown]>> | undefined'.
34
+ Type 'Record<string, unknown>' is not assignable to type 'Partial<Record<string, [WhereFilterOp, unknown]>>'.
35
+ 'string' index signatures are incompatible.
36
+ Type 'unknown' is not assignable to type '[WhereFilterOp, unknown] | undefined'.
37
+  ELIFECYCLE  Command failed with exit code 2.
@@ -0,0 +1,6 @@
1
+ import { DatabaseAdapter } from "@rebasepro/types";
2
+ import type { PostgresDriverConfig } from "./PostgresBootstrapper";
3
+ /**
4
+ * Creates a Postgres database adapter for Rebase.
5
+ */
6
+ export declare function createPostgresAdapter(pgConfig: PostgresDriverConfig): DatabaseAdapter;
@@ -0,0 +1,118 @@
1
+ import { EntityService } from "./services/entityService";
2
+ import { BranchService } from "./services/BranchService";
3
+ import { RealtimeService } from "./services/realtimeService";
4
+ import { DatabasePoolManager } from "./databasePoolManager";
5
+ import { DrizzleClient } from "./interfaces";
6
+ import { DatabaseAdmin, DataDriver, DeleteEntityProps, Entity, EntityCollection, FetchCollectionProps, FetchEntityProps, ListenCollectionProps, ListenEntityProps, RebaseClient, RebaseData, RestFetchService, SaveEntityProps, TableMetadata, User } from "@rebasepro/types";
7
+ import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry";
8
+ import { HistoryService } from "./history/HistoryService";
9
+ export declare class PostgresBackendDriver implements DataDriver {
10
+ db: DrizzleClient;
11
+ readonly registry: PostgresCollectionRegistry;
12
+ poolManager?: DatabasePoolManager | undefined;
13
+ key: string;
14
+ initialised: boolean;
15
+ entityService: EntityService;
16
+ realtimeService: RealtimeService;
17
+ historyService?: HistoryService;
18
+ branchService?: BranchService;
19
+ user?: User;
20
+ data: RebaseData;
21
+ client?: RebaseClient;
22
+ /**
23
+ * Auto-set to `true` when a SET LOCAL ROLE fails with insufficient
24
+ * privileges, so subsequent queries skip the doomed attempt.
25
+ * Mirrors the static `DISABLE_DB_ROLE_SWITCHING` env var but is
26
+ * learned at runtime.
27
+ */
28
+ private _roleSwitchingDisabled;
29
+ /**
30
+ * When true, realtime notifications are deferred until after the
31
+ * wrapping transaction commits. Set by `withAuth` → `withTransaction`.
32
+ */
33
+ _deferNotifications: boolean;
34
+ _pendingNotifications: Array<{
35
+ path: string;
36
+ entityId: string;
37
+ entity: Entity | null;
38
+ databaseId?: string;
39
+ }>;
40
+ constructor(db: DrizzleClient, realtimeService: RealtimeService, registry: PostgresCollectionRegistry, user?: User, poolManager?: DatabasePoolManager | undefined, historyService?: HistoryService);
41
+ /**
42
+ * Typed admin capabilities (SQLAdmin + SchemaAdmin + BranchAdmin).
43
+ * Implemented as a getter so method references are resolved at call-time,
44
+ * allowing test spies applied after construction to take effect.
45
+ */
46
+ get admin(): DatabaseAdmin;
47
+ /**
48
+ * REST-optimised fetch service (include-aware eager-loading).
49
+ * Delegates to the underlying EntityFetchService which already
50
+ * implements the matching method signatures.
51
+ */
52
+ get restFetchService(): import("./services").EntityFetchService;
53
+ private buildCallContext;
54
+ private resolveCollectionCallbacks;
55
+ fetchCollection<M extends Record<string, unknown>>({ path, collection, filter, limit, offset, startAfter, orderBy, searchString, order, vectorSearch }: FetchCollectionProps<M>): Promise<Entity<M>[]>;
56
+ listenCollection<M extends Record<string, unknown>>({ path, collection, filter, limit, offset, startAfter, orderBy, searchString, order, onUpdate, onError }: ListenCollectionProps<M>): () => void;
57
+ fetchEntity<M extends Record<string, unknown>>({ path, entityId, databaseId, collection }: FetchEntityProps<M>): Promise<Entity<M> | undefined>;
58
+ listenEntity<M extends Record<string, unknown>>({ path, entityId, collection, onUpdate, onError }: ListenEntityProps<M>): () => void;
59
+ saveEntity<M extends Record<string, unknown>>({ path, entityId, values, collection, status }: SaveEntityProps<M>): Promise<Entity<M>>;
60
+ deleteEntity<M extends Record<string, unknown>>({ entity, collection }: DeleteEntityProps<M>): Promise<void>;
61
+ deleteAll(path: string): Promise<void>;
62
+ checkUniqueField(path: string, name: string, value: unknown, entityId?: string, collection?: EntityCollection): Promise<boolean>;
63
+ countEntities<M extends Record<string, unknown>>({ path, collection, filter, searchString }: FetchCollectionProps<M>): Promise<number>;
64
+ private getTargetDb;
65
+ executeSql(sqlText: string, options?: {
66
+ database?: string;
67
+ role?: string;
68
+ params?: unknown[];
69
+ }): Promise<Record<string, unknown>[]>;
70
+ fetchAvailableDatabases(): Promise<string[]>;
71
+ fetchAvailableRoles(): Promise<string[]>;
72
+ fetchCurrentDatabase(): Promise<string | undefined>;
73
+ /**
74
+ * Fetch public tables that are not yet mapped to a collection.
75
+ * Excludes internal tables (_rebase_*, _auth_*, auth tables, etc.)
76
+ * and junction/connection tables used for many-to-many relations.
77
+ */
78
+ fetchUnmappedTables(mappedPaths?: string[]): Promise<string[]>;
79
+ /**
80
+ * Fetch metadata for a given table from information_schema (columns, policies, constraints).
81
+ */
82
+ fetchTableMetadata(tableName: string): Promise<TableMetadata>;
83
+ private generateSubscriptionId;
84
+ /**
85
+ * Create a new delegate instance with authenticated context.
86
+ * Starts a transaction and sets the current_user_id and current_user_roles
87
+ * configuration parameters for PostgreSQL Row Level Security.
88
+ */
89
+ withAuth(user: User): Promise<DataDriver>;
90
+ }
91
+ export declare class AuthenticatedPostgresBackendDriver implements DataDriver {
92
+ delegate: PostgresBackendDriver;
93
+ key: string;
94
+ initialised: boolean;
95
+ user: User;
96
+ data: RebaseData;
97
+ constructor(delegate: PostgresBackendDriver, user: User);
98
+ /**
99
+ * Typed admin capabilities — delegates to the base driver.
100
+ */
101
+ admin: DatabaseAdmin;
102
+ get restFetchService(): RestFetchService | undefined;
103
+ private withTransaction;
104
+ fetchCollection<M extends Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<Entity<M>[]>;
105
+ /**
106
+ * Injects the authenticated user's context into the most recently
107
+ * registered realtime subscription so RLS-aware polling can apply.
108
+ */
109
+ private injectAuthContext;
110
+ listenCollection<M extends Record<string, unknown>>(props: ListenCollectionProps<M>): () => void;
111
+ fetchEntity<M extends Record<string, unknown>>(props: FetchEntityProps<M>): Promise<Entity<M> | undefined>;
112
+ listenEntity<M extends Record<string, unknown>>(props: ListenEntityProps<M>): () => void;
113
+ saveEntity<M extends Record<string, unknown>>(props: SaveEntityProps<M>): Promise<Entity<M>>;
114
+ deleteEntity<M extends Record<string, unknown>>(props: DeleteEntityProps<M>): Promise<void>;
115
+ deleteAll(path: string): Promise<void>;
116
+ checkUniqueField(path: string, name: string, value: unknown, entityId?: string, collection?: EntityCollection): Promise<boolean>;
117
+ countEntities<M extends Record<string, unknown>>(props: FetchCollectionProps<M>): Promise<number>;
118
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * PostgresBootstrapper
3
+ *
4
+ * Implements the `BackendBootstrapper` interface for PostgreSQL.
5
+ */
6
+ import { NodePgDatabase } from "drizzle-orm/node-postgres";
7
+ import { BackendBootstrapper } from "@rebasepro/types";
8
+ import { PostgresBackendDriver } from "./PostgresBackendDriver";
9
+ import { RealtimeService } from "./services/realtimeService";
10
+ import { DatabasePoolManager } from "./databasePoolManager";
11
+ import { PostgresCollectionRegistry } from "./collections/PostgresCollectionRegistry";
12
+ export interface PostgresDriverConfig {
13
+ connectionString?: string;
14
+ adminConnectionString?: string;
15
+ readConnectionString?: string;
16
+ connection?: unknown;
17
+ schema?: {
18
+ tables?: Record<string, unknown>;
19
+ enums?: Record<string, unknown>;
20
+ relations?: Record<string, unknown>;
21
+ };
22
+ }
23
+ /**
24
+ * Opaque internals bag that PostgresBootstrapper stores during `initializeDriver()`
25
+ * and re-uses in subsequent lifecycle hooks.
26
+ */
27
+ export interface PostgresDriverInternals {
28
+ db: NodePgDatabase<any>;
29
+ readDb?: NodePgDatabase<any>;
30
+ registry: PostgresCollectionRegistry;
31
+ realtimeService: RealtimeService;
32
+ driver: PostgresBackendDriver;
33
+ poolManager?: DatabasePoolManager;
34
+ }
35
+ /**
36
+ * Default PostgreSQL bootstrapper.
37
+ *
38
+ * Use it to register Postgres with `initializeRebaseBackend()`:
39
+ * ```typescript
40
+ * initializeRebaseBackend({
41
+ * ...config,
42
+ * bootstrappers: [postgresBootstrapper()]
43
+ * });
44
+ * ```
45
+ */
46
+ export declare function createPostgresBootstrapper(pgConfig: PostgresDriverConfig): BackendBootstrapper;
@@ -0,0 +1,10 @@
1
+ import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
+ import type { EntityCollection } from "@rebasepro/types";
3
+ /**
4
+ * Auto-create auth tables if they don't exist.
5
+ *
6
+ * @param db — Drizzle database instance
7
+ * @param collection — The collection that represents auth users.
8
+ * When omitted, a default `rebase.users` table is created.
9
+ */
10
+ export declare function ensureAuthTablesExist(db: NodePgDatabase, collection?: EntityCollection): Promise<void>;
@@ -0,0 +1,251 @@
1
+ import { NodePgDatabase } from "drizzle-orm/node-postgres";
2
+ import type { RebasePgTable } from "../types";
3
+ import { UserRepository, TokenRepository, MfaRepository, AuthRepository, UserData, CreateUserData, RoleData, CreateRoleData, RefreshTokenInfo, PasswordResetTokenInfo, MagicLinkTokenInfo, UserIdentityData, ListUsersOptions, PaginatedUsersResult, MfaFactor, MfaChallengeInfo, RoleData as Role } from "@rebasepro/server-core";
4
+ export type { Role };
5
+ export interface AuthSchemaTables {
6
+ users: RebasePgTable;
7
+ refreshTokens: RebasePgTable;
8
+ passwordResetTokens: RebasePgTable;
9
+ appConfig: RebasePgTable;
10
+ userIdentities: RebasePgTable;
11
+ }
12
+ /**
13
+ * PostgreSQL implementation of UserRepository.
14
+ * Handles all user-related database operations using Drizzle ORM.
15
+ */
16
+ export declare class UserService implements UserRepository {
17
+ private db;
18
+ private usersTable;
19
+ private userIdentitiesTable;
20
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
21
+ private getQualifiedUsersTableName;
22
+ private mapRowToUser;
23
+ private mapPayload;
24
+ createUser(data: CreateUserData): Promise<UserData>;
25
+ getUserById(id: string): Promise<UserData | null>;
26
+ getUserByEmail(email: string): Promise<UserData | null>;
27
+ getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
28
+ getUserIdentities(userId: string): Promise<UserIdentityData[]>;
29
+ linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
30
+ updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
31
+ deleteUser(id: string): Promise<void>;
32
+ listUsers(): Promise<UserData[]>;
33
+ listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
34
+ /**
35
+ * Update user's password hash
36
+ */
37
+ updatePassword(id: string, passwordHash: string): Promise<void>;
38
+ /**
39
+ * Set email verification status
40
+ */
41
+ setEmailVerified(id: string, verified: boolean): Promise<void>;
42
+ /**
43
+ * Set email verification token
44
+ */
45
+ setVerificationToken(id: string, token: string | null): Promise<void>;
46
+ /**
47
+ * Find user by email verification token
48
+ */
49
+ getUserByVerificationToken(token: string): Promise<UserData | null>;
50
+ /**
51
+ * Get roles for a user from database (inline TEXT[] column)
52
+ */
53
+ getUserRoles(userId: string): Promise<Role[]>;
54
+ /**
55
+ * Get role IDs for a user
56
+ */
57
+ getUserRoleIds(userId: string): Promise<string[]>;
58
+ /**
59
+ * Set roles for a user (replaces existing roles)
60
+ */
61
+ setUserRoles(userId: string, roleIds: string[]): Promise<void>;
62
+ /**
63
+ * Assign a specific role to new user (appends if not present)
64
+ */
65
+ assignDefaultRole(userId: string, roleId: string): Promise<void>;
66
+ /**
67
+ * Get user with their roles
68
+ */
69
+ getUserWithRoles(userId: string): Promise<{
70
+ user: UserData;
71
+ roles: Role[];
72
+ } | null>;
73
+ }
74
+ export declare class RefreshTokenService {
75
+ private db;
76
+ private refreshTokensTable;
77
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
78
+ private getQualifiedRefreshTokensTableName;
79
+ createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
80
+ findByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
81
+ deleteByHash(tokenHash: string): Promise<void>;
82
+ deleteAllForUser(userId: string): Promise<void>;
83
+ listForUser(userId: string): Promise<RefreshTokenInfo[]>;
84
+ deleteById(id: string, userId: string): Promise<void>;
85
+ }
86
+ /**
87
+ * Password reset token service
88
+ */
89
+ export declare class PasswordResetTokenService {
90
+ private db;
91
+ private passwordResetTokensTable;
92
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
93
+ private getQualifiedPasswordResetTokensTableName;
94
+ /**
95
+ * Create a password reset token
96
+ */
97
+ createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
98
+ /**
99
+ * Find a valid (not expired, not used) token by hash
100
+ */
101
+ findValidByHash(tokenHash: string): Promise<{
102
+ userId: string;
103
+ expiresAt: Date;
104
+ } | null>;
105
+ /**
106
+ * Mark token as used
107
+ */
108
+ markAsUsed(tokenHash: string): Promise<void>;
109
+ /**
110
+ * Delete all tokens for a user
111
+ */
112
+ deleteAllForUser(userId: string): Promise<void>;
113
+ /**
114
+ * Clean up expired tokens
115
+ */
116
+ deleteExpired(): Promise<void>;
117
+ }
118
+ /**
119
+ * Magic link token service.
120
+ * Handles magic link token storage for passwordless email login.
121
+ */
122
+ export declare class MagicLinkTokenService {
123
+ private db;
124
+ private magicLinkTokensTable;
125
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
126
+ private getQualifiedTableName;
127
+ createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
128
+ findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
129
+ markAsUsed(tokenHash: string): Promise<void>;
130
+ }
131
+ /**
132
+ * PostgreSQL implementation of TokenRepository.
133
+ * Combines refresh token and password reset token operations.
134
+ */
135
+ export declare class PostgresTokenRepository implements TokenRepository {
136
+ private db;
137
+ private refreshTokenService;
138
+ private passwordResetTokenService;
139
+ private magicLinkTokenService;
140
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
141
+ createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
142
+ findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
143
+ deleteRefreshToken(tokenHash: string): Promise<void>;
144
+ deleteAllRefreshTokensForUser(userId: string): Promise<void>;
145
+ listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]>;
146
+ deleteRefreshTokenById(id: string, userId: string): Promise<void>;
147
+ createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
148
+ findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
149
+ markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
150
+ deleteAllPasswordResetTokensForUser(userId: string): Promise<void>;
151
+ deleteExpiredTokens(): Promise<void>;
152
+ createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
153
+ findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
154
+ markMagicLinkTokenUsed(tokenHash: string): Promise<void>;
155
+ }
156
+ /**
157
+ * PostgreSQL implementation of AuthRepository.
158
+ * Combines user, role, and token repository operations.
159
+ * This provides a convenient single-class interface for all auth operations.
160
+ */
161
+ export declare class PostgresAuthRepository implements AuthRepository {
162
+ private db;
163
+ private userService;
164
+ private tokenRepository;
165
+ constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
166
+ createUser(data: CreateUserData): Promise<UserData>;
167
+ getUserById(id: string): Promise<UserData | null>;
168
+ getUserByEmail(email: string): Promise<UserData | null>;
169
+ getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
170
+ getUserIdentities(userId: string): Promise<UserIdentityData[]>;
171
+ linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
172
+ updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
173
+ deleteUser(id: string): Promise<void>;
174
+ listUsers(): Promise<UserData[]>;
175
+ listUsersPaginated(options?: ListUsersOptions): Promise<PaginatedUsersResult>;
176
+ updatePassword(id: string, passwordHash: string): Promise<void>;
177
+ setEmailVerified(id: string, verified: boolean): Promise<void>;
178
+ setVerificationToken(id: string, token: string | null): Promise<void>;
179
+ getUserByVerificationToken(token: string): Promise<UserData | null>;
180
+ getUserRoles(userId: string): Promise<RoleData[]>;
181
+ getUserRoleIds(userId: string): Promise<string[]>;
182
+ setUserRoles(userId: string, roleIds: string[]): Promise<void>;
183
+ assignDefaultRole(userId: string, roleId: string): Promise<void>;
184
+ getUserWithRoles(userId: string): Promise<{
185
+ user: UserData;
186
+ roles: RoleData[];
187
+ } | null>;
188
+ getRoleById(id: string): Promise<RoleData | null>;
189
+ listRoles(): Promise<RoleData[]>;
190
+ createRole(_data: CreateRoleData): Promise<RoleData>;
191
+ updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
192
+ deleteRole(_id: string): Promise<void>;
193
+ createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
194
+ findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
195
+ deleteRefreshToken(tokenHash: string): Promise<void>;
196
+ deleteAllRefreshTokensForUser(userId: string): Promise<void>;
197
+ listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]>;
198
+ deleteRefreshTokenById(id: string, userId: string): Promise<void>;
199
+ createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
200
+ findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
201
+ markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
202
+ deleteAllPasswordResetTokensForUser(userId: string): Promise<void>;
203
+ deleteExpiredTokens(): Promise<void>;
204
+ createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
205
+ findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
206
+ markMagicLinkTokenUsed(tokenHash: string): Promise<void>;
207
+ private _mfaService;
208
+ private getMfaService;
209
+ createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
210
+ getMfaFactors(userId: string): Promise<MfaFactor[]>;
211
+ getMfaFactorById(factorId: string): Promise<(MfaFactor & {
212
+ secretEncrypted: string;
213
+ }) | null>;
214
+ verifyMfaFactor(factorId: string): Promise<void>;
215
+ deleteMfaFactor(factorId: string, userId: string): Promise<void>;
216
+ createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
217
+ getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
218
+ verifyMfaChallenge(challengeId: string): Promise<void>;
219
+ createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void>;
220
+ useRecoveryCode(userId: string, codeHash: string): Promise<boolean>;
221
+ getUnusedRecoveryCodeCount(userId: string): Promise<number>;
222
+ deleteAllRecoveryCodes(userId: string): Promise<void>;
223
+ hasVerifiedMfaFactors(userId: string): Promise<boolean>;
224
+ }
225
+ /**
226
+ * PostgreSQL implementation of MfaRepository.
227
+ * Handles all MFA-related database operations.
228
+ */
229
+ export declare class MfaService implements MfaRepository {
230
+ private db;
231
+ private schemaName;
232
+ constructor(db: NodePgDatabase, schemaName?: string);
233
+ private qualify;
234
+ createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
235
+ getMfaFactors(userId: string): Promise<MfaFactor[]>;
236
+ getMfaFactorById(factorId: string): Promise<(MfaFactor & {
237
+ secretEncrypted: string;
238
+ }) | null>;
239
+ verifyMfaFactor(factorId: string): Promise<void>;
240
+ deleteMfaFactor(factorId: string, userId: string): Promise<void>;
241
+ createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
242
+ getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
243
+ verifyMfaChallenge(challengeId: string): Promise<void>;
244
+ createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void>;
245
+ useRecoveryCode(userId: string, codeHash: string): Promise<boolean>;
246
+ getUnusedRecoveryCodeCount(userId: string): Promise<number>;
247
+ deleteAllRecoveryCodes(userId: string): Promise<void>;
248
+ hasVerifiedMfaFactors(userId: string): Promise<boolean>;
249
+ }
250
+ /** PostgreSQL user repository implementation */
251
+ export type PostgresUserRepository = UserService;
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Detect whether an error (or AggregateError wrapping multiple attempts)
3
+ * represents an ECONNREFUSED — i.e. the database is simply not running.
4
+ *
5
+ * Handles:
6
+ * - Direct `{ code: "ECONNREFUSED" }` errors from Node `net`
7
+ * - `AggregateError` from dual-stack IPv4+IPv6 connection attempts
8
+ * - Drizzle's `cause`-wrapped pg errors
9
+ */
10
+ export declare function isEconnrefused(err: unknown): boolean;
11
+ /**
12
+ * Detect PostgreSQL authentication failures.
13
+ * PG error codes: 28P01 (invalid_password), 28000 (invalid_authorization_specification)
14
+ */
15
+ export declare function isAuthFailure(err: unknown): boolean;
16
+ /**
17
+ * Pre-flight check: verify that the database is reachable before running
18
+ * a heavy subprocess (Atlas, migrations, etc.).
19
+ *
20
+ * Exits with code 1 and a friendly banner on known failure modes.
21
+ * On unknown errors, logs a warning and allows the caller to proceed.
22
+ */
23
+ export declare function checkDatabaseConnectivity(databaseUrl: string): Promise<void>;
24
+ /**
25
+ * Post-hoc error diagnosis for direct database operations (e.g. applyPolicies).
26
+ * Returns a formatted diagnostic string if the error matches a known pattern,
27
+ * or null if unrecognized.
28
+ */
29
+ export declare function diagnoseDbError(err: unknown, databaseUrl?: string): string | null;
@@ -0,0 +1,7 @@
1
+ import type { EntityCollection } from "@rebasepro/types";
2
+ export declare function resolveLocalBin(binName: string): string | null;
3
+ export declare function getTableIncludesFromCollections(collections: EntityCollection[]): Promise<string[]>;
4
+ export declare function getTableIncludes(collectionsPath: string): Promise<string[]>;
5
+ export declare function getDevDatabaseUrl(databaseUrl: string): string;
6
+ export declare function ensureDevDatabaseExists(databaseUrl: string, devDatabaseUrl: string): Promise<void>;
7
+ export declare function getTableExcludes(databaseUrl: string, collectionsPath: string): Promise<string[]>;
package/dist/cli.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function runPluginCommand(args: string[]): Promise<void>;
@@ -0,0 +1,47 @@
1
+ import { CollectionRegistry } from "@rebasepro/common";
2
+ import { type EntityCollection } from "@rebasepro/types";
3
+ import { PgEnum, PgTable } from "drizzle-orm/pg-core";
4
+ import { Relations } from "drizzle-orm";
5
+ import { CollectionRegistryInterface } from "../interfaces";
6
+ /**
7
+ * PostgreSQL-specific collection registry.
8
+ * Extends the base CollectionRegistry with support for Drizzle ORM tables, enums, and relations.
9
+ *
10
+ * Satisfies CollectionRegistryInterface through inheritance from CollectionRegistry.
11
+ */
12
+ export declare class PostgresCollectionRegistry extends CollectionRegistry implements CollectionRegistryInterface {
13
+ private tables;
14
+ private enums;
15
+ private relations;
16
+ registerTable(table: PgTable, tableName: string): void;
17
+ getTable(tableName: string): PgTable | undefined;
18
+ /**
19
+ * Checks if a specific collection has a registered table
20
+ */
21
+ hasTableForCollection(tableName: string): boolean;
22
+ /**
23
+ * Returns all registered table names.
24
+ */
25
+ getTableNames(): string[];
26
+ /**
27
+ * Finds collections assigned to a specific data source that do not have a registered table.
28
+ */
29
+ getCollectionsWithoutTables(dataSourceKey?: string): EntityCollection[];
30
+ registerEnums(enums: Record<string, PgEnum<[string, ...string[]]>>): void;
31
+ registerRelations(relations: Record<string, Relations>): void;
32
+ getEnum(name: string): PgEnum<[string, ...string[]]> | undefined;
33
+ getRelation(name: string): Relations | undefined;
34
+ getAllEnums(): Record<string, PgEnum<[string, ...string[]]>>;
35
+ getAllRelations(): Record<string, Relations>;
36
+ /**
37
+ * Get the merged schema object (tables + relations) for use with Drizzle's
38
+ * relational query API (`db.query`).
39
+ */
40
+ getMergedSchema(): Record<string, unknown>;
41
+ /**
42
+ * Get the available Drizzle relation keys for a given collection path.
43
+ * Maps from the collection's relation property names to the Drizzle relation names
44
+ * defined in the schema.
45
+ */
46
+ getRelationKeysForCollection(collectionPath: string): string[];
47
+ }
@@ -0,0 +1,65 @@
1
+ import { Pool } from "pg";
2
+ /**
3
+ * Configuration for the Postgres connection pool.
4
+ *
5
+ * Sensible defaults are provided for production Cloud Run / single-instance
6
+ * deployments. Override via environment variables or explicit config.
7
+ */
8
+ export interface PostgresPoolConfig {
9
+ /** Maximum number of connections in the pool (default: 20) */
10
+ max?: number;
11
+ /** Close idle connections after this many ms (default: 30 000) */
12
+ idleTimeoutMillis?: number;
13
+ /** Abort connection attempts after this many ms (default: 10 000) */
14
+ connectionTimeoutMillis?: number;
15
+ /** Per-query timeout in ms (default: 30 000) */
16
+ queryTimeout?: number;
17
+ /** Per-statement timeout in ms (default: 30 000) */
18
+ statementTimeout?: number;
19
+ /** Enable TCP keep-alive (default: true) */
20
+ keepAlive?: boolean;
21
+ }
22
+ /**
23
+ * Create a Drizzle-backed Postgres connection with a production-grade
24
+ * connection pool.
25
+ *
26
+ * @param connectionString Postgres connection URL
27
+ * @param schema Optional Drizzle schema for the relational API
28
+ * @param poolConfig Optional pool tuning (merged over defaults)
29
+ *
30
+ * @returns `{ db, pool, connectionString }` — the `pool` is exposed so
31
+ * callers can register shutdown hooks (`pool.end()`) or monitor
32
+ * pool metrics.
33
+ */
34
+ export declare function createPostgresDatabaseConnection(connectionString: string, schema?: Record<string, unknown>, poolConfig?: PostgresPoolConfig): {
35
+ db: import("drizzle-orm/node-postgres").NodePgDatabase<Record<string, unknown>> & {
36
+ $client: Pool;
37
+ };
38
+ pool: Pool;
39
+ connectionString: string;
40
+ };
41
+ /**
42
+ * Create a direct (non-pooled) connection for operations that require
43
+ * session-level features incompatible with PgBouncer transaction mode,
44
+ * such as LISTEN/NOTIFY, prepared statements, or advisory locks.
45
+ *
46
+ * Uses a smaller pool since this is only for specific use cases.
47
+ */
48
+ export declare function createDirectDatabaseConnection(connectionString: string, schema?: Record<string, unknown>, poolConfig?: PostgresPoolConfig): {
49
+ db: import("drizzle-orm/node-postgres").NodePgDatabase<Record<string, unknown>> & {
50
+ $client: Pool;
51
+ };
52
+ pool: Pool;
53
+ connectionString: string;
54
+ };
55
+ /**
56
+ * Create a read-only connection for routing read queries to replicas.
57
+ * Uses a moderate pool size since reads are distributed across replicas.
58
+ */
59
+ export declare function createReadReplicaConnection(connectionString: string, schema?: Record<string, unknown>, poolConfig?: PostgresPoolConfig): {
60
+ db: import("drizzle-orm/node-postgres").NodePgDatabase<Record<string, unknown>> & {
61
+ $client: Pool;
62
+ };
63
+ pool: Pool;
64
+ connectionString: string;
65
+ };