@rebasepro/server-postgres 0.9.1-canary.a639738 → 0.9.1-canary.a8dbf1c

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/README.md CHANGED
@@ -76,6 +76,27 @@ process.on("SIGTERM", async () => {
76
76
  | `statementTimeout` | 30,000 |
77
77
  | `keepAlive` | true |
78
78
 
79
+ ## Testing
80
+
81
+ This package runs **two test runners**, split by directory. This is deliberate — check which half you are in before running anything.
82
+
83
+ | Tests | Runner | Config | Command |
84
+ |-------|--------|--------|---------|
85
+ | `test/*.ts` (unit) | jest | [`jest.config.cjs`](./jest.config.cjs) | `pnpm test` |
86
+ | `test/e2e/**` (integration) | vitest | [`vitest.e2e.config.ts`](./vitest.e2e.config.ts) | `pnpm test:e2e` |
87
+
88
+ The unit tests use jest's injected globals (`describe`/`it`/`expect`) and `jest.mock`. The e2e tests import explicitly from `vitest` and need Docker (testcontainers spins up a real Postgres).
89
+
90
+ **Running a single unit test:**
91
+
92
+ ```bash
93
+ npx jest test/auth-services.test.ts
94
+ ```
95
+
96
+ Pointing `vitest` at a unit test is the easy mistake here — it produces a bare `ReferenceError: jest is not defined` and a "no tests" result, which looks exactly like a dead or broken test file rather than the wrong runner. [`vitest.config.ts`](./vitest.config.ts) exists solely to intercept that and print the command you actually wanted.
97
+
98
+ `pnpm test` runs jest **without** `--passWithNoTests`: if the unit suite ever stops being collected, CI fails instead of going quietly green.
99
+
79
100
  ## Related Packages
80
101
 
81
102
  | Package | Role |
@@ -114,6 +114,24 @@ export declare class PostgresBackendDriver implements DataDriver {
114
114
  }): Promise<Record<string, unknown>[]>;
115
115
  fetchAvailableDatabases(): Promise<string[]>;
116
116
  fetchAvailableRoles(): Promise<string[]>;
117
+ /**
118
+ * Application-level roles actually in use in this project.
119
+ *
120
+ * Distinct from {@link fetchAvailableRoles}, which returns native
121
+ * PostgreSQL roles from `pg_roles` (`postgres`, `rebase_user`, …). Those
122
+ * are the roles the SQL editor can `SET ROLE` to. *These* are the strings
123
+ * held in the users table's `roles` column, injected per-transaction as
124
+ * `auth.roles()` and matched by `SecurityRule.roles`. Feeding the pg roles
125
+ * into a `SecurityRule.roles` field produces a condition no user can ever
126
+ * satisfy, so the two must not be conflated.
127
+ *
128
+ * Roles have no registry table — they were migrated out of
129
+ * `rebase.user_roles` onto an inline `roles TEXT[]` column — so the live
130
+ * set is derived from what is assigned. A role that is declared in a policy
131
+ * but held by nobody yet cannot be discovered here; callers that need it
132
+ * should union in the roles they already know about.
133
+ */
134
+ fetchApplicationRoles(): Promise<string[]>;
117
135
  fetchCurrentDatabase(): Promise<string | undefined>;
118
136
  /**
119
137
  * Fetch public tables that are not yet mapped to a collection.
@@ -4,7 +4,7 @@
4
4
  * Implements the `BackendBootstrapper` interface for PostgreSQL.
5
5
  */
6
6
  import { NodePgDatabase } from "drizzle-orm/node-postgres";
7
- import { BackendBootstrapper } from "@rebasepro/types";
7
+ import { BackendBootstrapper, type RealtimeChannelsConfig } from "@rebasepro/types";
8
8
  import { PostgresBackendDriver } from "./PostgresBackendDriver";
9
9
  import { RealtimeService } from "./services/realtimeService";
10
10
  import { DatabasePoolManager } from "./databasePoolManager";
@@ -25,6 +25,12 @@ export interface PostgresDriverConfig {
25
25
  * (BaaS mode). Defaults to `public`.
26
26
  */
27
27
  introspectionSchema?: string;
28
+ /**
29
+ * Realtime options. Currently only channel retention, which is opt-in:
30
+ * without rules here no channel keeps any history and broadcast stays
31
+ * fire-and-forget. See {@link ChannelRetentionRule}.
32
+ */
33
+ realtime?: RealtimeChannelsConfig;
28
34
  }
29
35
  /**
30
36
  * Opaque internals bag that PostgresBootstrapper stores during `initializeDriver()`
@@ -23,7 +23,7 @@ export declare class UserService implements UserRepository {
23
23
  * Run a privileged auth write with an explicitly cleared RLS context.
24
24
  *
25
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
26
+ * carries a NULL `app.uid` so the `auth.uid() IS NULL` server-escape
27
27
  * in the default policies applies. That NULL is normally guaranteed by
28
28
  * `set_config(..., is_local = true)` resetting at transaction end — but a
29
29
  * GUC that survives on a pooled connection (or a connection role that
@@ -41,8 +41,8 @@ export declare class UserService implements UserRepository {
41
41
  getUserById(id: string): Promise<UserData | null>;
42
42
  getUserByEmail(email: string): Promise<UserData | null>;
43
43
  getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
44
- getUserIdentities(userId: string): Promise<UserIdentityData[]>;
45
- linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
44
+ getUserIdentities(uid: string): Promise<UserIdentityData[]>;
45
+ linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
46
46
  updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
47
47
  deleteUser(id: string): Promise<void>;
48
48
  listUsers(): Promise<UserData[]>;
@@ -66,23 +66,23 @@ export declare class UserService implements UserRepository {
66
66
  /**
67
67
  * Get roles for a user from database (inline TEXT[] column)
68
68
  */
69
- getUserRoles(userId: string): Promise<Role[]>;
69
+ getUserRoles(uid: string): Promise<Role[]>;
70
70
  /**
71
71
  * Get role IDs for a user
72
72
  */
73
- getUserRoleIds(userId: string): Promise<string[]>;
73
+ getUserRoleIds(uid: string): Promise<string[]>;
74
74
  /**
75
75
  * Set roles for a user (replaces existing roles)
76
76
  */
77
- setUserRoles(userId: string, roleIds: string[]): Promise<void>;
77
+ setUserRoles(uid: string, roleIds: string[]): Promise<void>;
78
78
  /**
79
79
  * Assign a specific role to new user (appends if not present)
80
80
  */
81
- assignDefaultRole(userId: string, roleId: string): Promise<void>;
81
+ assignDefaultRole(uid: string, roleId: string): Promise<void>;
82
82
  /**
83
83
  * Get user with their roles
84
84
  */
85
- getUserWithRoles(userId: string): Promise<{
85
+ getUserWithRoles(uid: string): Promise<{
86
86
  user: UserData;
87
87
  roles: Role[];
88
88
  } | null>;
@@ -91,12 +91,12 @@ export declare class RefreshTokenService {
91
91
  private db;
92
92
  private refreshTokensTable;
93
93
  constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
94
- createToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
94
+ createToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
95
95
  findByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
96
96
  deleteByHash(tokenHash: string): Promise<void>;
97
- deleteAllForUser(userId: string): Promise<void>;
98
- listForUser(userId: string): Promise<RefreshTokenInfo[]>;
99
- deleteById(id: string, userId: string): Promise<void>;
97
+ deleteAllForUser(uid: string): Promise<void>;
98
+ listForUser(uid: string): Promise<RefreshTokenInfo[]>;
99
+ deleteById(id: string, uid: string): Promise<void>;
100
100
  }
101
101
  /**
102
102
  * Password reset token service
@@ -109,12 +109,12 @@ export declare class PasswordResetTokenService {
109
109
  /**
110
110
  * Create a password reset token
111
111
  */
112
- createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
112
+ createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
113
113
  /**
114
114
  * Find a valid (not expired, not used) token by hash
115
115
  */
116
116
  findValidByHash(tokenHash: string): Promise<{
117
- userId: string;
117
+ uid: string;
118
118
  expiresAt: Date;
119
119
  } | null>;
120
120
  /**
@@ -124,7 +124,7 @@ export declare class PasswordResetTokenService {
124
124
  /**
125
125
  * Delete all tokens for a user
126
126
  */
127
- deleteAllForUser(userId: string): Promise<void>;
127
+ deleteAllForUser(uid: string): Promise<void>;
128
128
  /**
129
129
  * Clean up expired tokens
130
130
  */
@@ -139,7 +139,7 @@ export declare class MagicLinkTokenService {
139
139
  private magicLinkTokensTable;
140
140
  constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
141
141
  private getQualifiedTableName;
142
- createToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
142
+ createToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
143
143
  findValidByHash(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
144
144
  markAsUsed(tokenHash: string): Promise<void>;
145
145
  }
@@ -153,18 +153,18 @@ export declare class PostgresTokenRepository implements TokenRepository {
153
153
  private passwordResetTokenService;
154
154
  private magicLinkTokenService;
155
155
  constructor(db: NodePgDatabase, tableOrTables?: RebasePgTable | Partial<AuthSchemaTables>);
156
- createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
156
+ createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
157
157
  findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
158
158
  deleteRefreshToken(tokenHash: string): Promise<void>;
159
- deleteAllRefreshTokensForUser(userId: string): Promise<void>;
160
- listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]>;
161
- deleteRefreshTokenById(id: string, userId: string): Promise<void>;
162
- createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
159
+ deleteAllRefreshTokensForUser(uid: string): Promise<void>;
160
+ listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]>;
161
+ deleteRefreshTokenById(id: string, uid: string): Promise<void>;
162
+ createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
163
163
  findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
164
164
  markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
165
- deleteAllPasswordResetTokensForUser(userId: string): Promise<void>;
165
+ deleteAllPasswordResetTokensForUser(uid: string): Promise<void>;
166
166
  deleteExpiredTokens(): Promise<void>;
167
- createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
167
+ createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
168
168
  findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
169
169
  markMagicLinkTokenUsed(tokenHash: string): Promise<void>;
170
170
  }
@@ -182,8 +182,8 @@ export declare class PostgresAuthRepository implements AuthRepository {
182
182
  getUserById(id: string): Promise<UserData | null>;
183
183
  getUserByEmail(email: string): Promise<UserData | null>;
184
184
  getUserByIdentity(provider: string, providerId: string): Promise<UserData | null>;
185
- getUserIdentities(userId: string): Promise<UserIdentityData[]>;
186
- linkUserIdentity(userId: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
185
+ getUserIdentities(uid: string): Promise<UserIdentityData[]>;
186
+ linkUserIdentity(uid: string, provider: string, providerId: string, profileData?: Record<string, unknown>): Promise<void>;
187
187
  updateUser(id: string, data: Partial<Omit<CreateUserData, "id">>): Promise<UserData | null>;
188
188
  deleteUser(id: string): Promise<void>;
189
189
  listUsers(): Promise<UserData[]>;
@@ -192,11 +192,11 @@ export declare class PostgresAuthRepository implements AuthRepository {
192
192
  setEmailVerified(id: string, verified: boolean): Promise<void>;
193
193
  setVerificationToken(id: string, token: string | null): Promise<void>;
194
194
  getUserByVerificationToken(token: string): Promise<UserData | null>;
195
- getUserRoles(userId: string): Promise<RoleData[]>;
196
- getUserRoleIds(userId: string): Promise<string[]>;
197
- setUserRoles(userId: string, roleIds: string[]): Promise<void>;
198
- assignDefaultRole(userId: string, roleId: string): Promise<void>;
199
- getUserWithRoles(userId: string): Promise<{
195
+ getUserRoles(uid: string): Promise<RoleData[]>;
196
+ getUserRoleIds(uid: string): Promise<string[]>;
197
+ setUserRoles(uid: string, roleIds: string[]): Promise<void>;
198
+ assignDefaultRole(uid: string, roleId: string): Promise<void>;
199
+ getUserWithRoles(uid: string): Promise<{
200
200
  user: UserData;
201
201
  roles: RoleData[];
202
202
  } | null>;
@@ -205,37 +205,37 @@ export declare class PostgresAuthRepository implements AuthRepository {
205
205
  createRole(_data: CreateRoleData): Promise<RoleData>;
206
206
  updateRole(id: string, data: Partial<Omit<RoleData, "id">>): Promise<RoleData | null>;
207
207
  deleteRole(_id: string): Promise<void>;
208
- createRefreshToken(userId: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
208
+ createRefreshToken(uid: string, tokenHash: string, expiresAt: Date, userAgent?: string, ipAddress?: string): Promise<void>;
209
209
  findRefreshTokenByHash(tokenHash: string): Promise<RefreshTokenInfo | null>;
210
210
  deleteRefreshToken(tokenHash: string): Promise<void>;
211
- deleteAllRefreshTokensForUser(userId: string): Promise<void>;
212
- listRefreshTokensForUser(userId: string): Promise<RefreshTokenInfo[]>;
213
- deleteRefreshTokenById(id: string, userId: string): Promise<void>;
214
- createPasswordResetToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
211
+ deleteAllRefreshTokensForUser(uid: string): Promise<void>;
212
+ listRefreshTokensForUser(uid: string): Promise<RefreshTokenInfo[]>;
213
+ deleteRefreshTokenById(id: string, uid: string): Promise<void>;
214
+ createPasswordResetToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
215
215
  findValidPasswordResetToken(tokenHash: string): Promise<PasswordResetTokenInfo | null>;
216
216
  markPasswordResetTokenUsed(tokenHash: string): Promise<void>;
217
- deleteAllPasswordResetTokensForUser(userId: string): Promise<void>;
217
+ deleteAllPasswordResetTokensForUser(uid: string): Promise<void>;
218
218
  deleteExpiredTokens(): Promise<void>;
219
- createMagicLinkToken(userId: string, tokenHash: string, expiresAt: Date): Promise<void>;
219
+ createMagicLinkToken(uid: string, tokenHash: string, expiresAt: Date): Promise<void>;
220
220
  findValidMagicLinkToken(tokenHash: string): Promise<MagicLinkTokenInfo | null>;
221
221
  markMagicLinkTokenUsed(tokenHash: string): Promise<void>;
222
222
  private _mfaService;
223
223
  private getMfaService;
224
- createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
225
- getMfaFactors(userId: string): Promise<MfaFactor[]>;
224
+ createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
225
+ getMfaFactors(uid: string): Promise<MfaFactor[]>;
226
226
  getMfaFactorById(factorId: string): Promise<(MfaFactor & {
227
227
  secretEncrypted: string;
228
228
  }) | null>;
229
229
  verifyMfaFactor(factorId: string): Promise<void>;
230
- deleteMfaFactor(factorId: string, userId: string): Promise<void>;
230
+ deleteMfaFactor(factorId: string, uid: string): Promise<void>;
231
231
  createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
232
232
  getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
233
233
  verifyMfaChallenge(challengeId: string): Promise<void>;
234
- createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void>;
235
- useRecoveryCode(userId: string, codeHash: string): Promise<boolean>;
236
- getUnusedRecoveryCodeCount(userId: string): Promise<number>;
237
- deleteAllRecoveryCodes(userId: string): Promise<void>;
238
- hasVerifiedMfaFactors(userId: string): Promise<boolean>;
234
+ createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void>;
235
+ useRecoveryCode(uid: string, codeHash: string): Promise<boolean>;
236
+ getUnusedRecoveryCodeCount(uid: string): Promise<number>;
237
+ deleteAllRecoveryCodes(uid: string): Promise<void>;
238
+ hasVerifiedMfaFactors(uid: string): Promise<boolean>;
239
239
  }
240
240
  /**
241
241
  * PostgreSQL implementation of MfaRepository.
@@ -246,21 +246,21 @@ export declare class MfaService implements MfaRepository {
246
246
  private schemaName;
247
247
  constructor(db: NodePgDatabase, schemaName?: string);
248
248
  private qualify;
249
- createMfaFactor(userId: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
250
- getMfaFactors(userId: string): Promise<MfaFactor[]>;
249
+ createMfaFactor(uid: string, factorType: "totp", secretEncrypted: string, friendlyName?: string): Promise<MfaFactor>;
250
+ getMfaFactors(uid: string): Promise<MfaFactor[]>;
251
251
  getMfaFactorById(factorId: string): Promise<(MfaFactor & {
252
252
  secretEncrypted: string;
253
253
  }) | null>;
254
254
  verifyMfaFactor(factorId: string): Promise<void>;
255
- deleteMfaFactor(factorId: string, userId: string): Promise<void>;
255
+ deleteMfaFactor(factorId: string, uid: string): Promise<void>;
256
256
  createMfaChallenge(factorId: string, ipAddress?: string): Promise<MfaChallengeInfo>;
257
257
  getMfaChallengeById(challengeId: string): Promise<MfaChallengeInfo | null>;
258
258
  verifyMfaChallenge(challengeId: string): Promise<void>;
259
- createRecoveryCodes(userId: string, codeHashes: string[]): Promise<void>;
260
- useRecoveryCode(userId: string, codeHash: string): Promise<boolean>;
261
- getUnusedRecoveryCodeCount(userId: string): Promise<number>;
262
- deleteAllRecoveryCodes(userId: string): Promise<void>;
263
- hasVerifiedMfaFactors(userId: string): Promise<boolean>;
259
+ createRecoveryCodes(uid: string, codeHashes: string[]): Promise<void>;
260
+ useRecoveryCode(uid: string, codeHash: string): Promise<boolean>;
261
+ getUnusedRecoveryCodeCount(uid: string): Promise<number>;
262
+ deleteAllRecoveryCodes(uid: string): Promise<void>;
263
+ hasVerifiedMfaFactors(uid: string): Promise<boolean>;
264
264
  }
265
265
  /** PostgreSQL user repository implementation */
266
266
  export type PostgresUserRepository = UserService;