@rebasepro/types 0.9.1-canary.ff338b5 → 0.10.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.
- package/dist/controllers/client.d.ts +4 -4
- package/dist/index.es.js +1 -1
- package/dist/index.es.js.map +1 -1
- package/dist/types/auth_adapter.d.ts +48 -4
- package/dist/types/backend.d.ts +48 -1
- package/dist/types/collections.d.ts +13 -8
- package/dist/types/policy.d.ts +1 -1
- package/dist/types/websockets.d.ts +43 -0
- package/package.json +1 -1
- package/src/controllers/client.ts +4 -4
- package/src/types/auth_adapter.ts +52 -4
- package/src/types/backend.ts +51 -1
- package/src/types/collections.ts +13 -8
- package/src/types/policy.ts +1 -1
- package/src/types/websockets.ts +45 -0
|
@@ -86,7 +86,7 @@ export interface AuthAdapterCapabilities {
|
|
|
86
86
|
*/
|
|
87
87
|
passwordReset: boolean;
|
|
88
88
|
/**
|
|
89
|
-
* Whether the adapter exposes `POST /admin/users/:
|
|
89
|
+
* Whether the adapter exposes `POST /admin/users/:uid/reset-password`,
|
|
90
90
|
* letting an admin reset another user's password.
|
|
91
91
|
*
|
|
92
92
|
* Independent of `passwordReset`: the built-in adapter supports this even
|
|
@@ -180,8 +180,8 @@ export interface UserManagementAdapter {
|
|
|
180
180
|
createUser(data: AuthCreateUserData): Promise<AuthUserData>;
|
|
181
181
|
updateUser(id: string, data: Partial<AuthCreateUserData>): Promise<AuthUserData | null>;
|
|
182
182
|
deleteUser(id: string): Promise<void>;
|
|
183
|
-
getUserRoles(
|
|
184
|
-
setUserRoles(
|
|
183
|
+
getUserRoles(uid: string): Promise<string[]>;
|
|
184
|
+
setUserRoles(uid: string, roleIds: string[]): Promise<void>;
|
|
185
185
|
}
|
|
186
186
|
/**
|
|
187
187
|
* Result of `AuthAdapter.prepareUserCreation()`.
|
|
@@ -201,6 +201,26 @@ export interface UserCreationPrepareResult {
|
|
|
201
201
|
/** Whether an invitation was sent (only relevant when hookHandledEmail is true). */
|
|
202
202
|
invitationSent: boolean;
|
|
203
203
|
}
|
|
204
|
+
/**
|
|
205
|
+
* What a create body for an auth collection may name beyond the collection's
|
|
206
|
+
* own declared fields — see `AuthAdapter.describeUserCreationContract()`.
|
|
207
|
+
*
|
|
208
|
+
* @group Auth
|
|
209
|
+
*/
|
|
210
|
+
export interface UserCreationWriteContract {
|
|
211
|
+
/**
|
|
212
|
+
* Whether to check the body for fields neither the collection nor
|
|
213
|
+
* {@link extraFields} declares. `false` skips the check entirely.
|
|
214
|
+
*/
|
|
215
|
+
validate: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* Credential and provider keys the adapter consumes itself, which the
|
|
218
|
+
* collection therefore does not declare as columns. `password` is the
|
|
219
|
+
* canonical one: `prepareUserCreation` hashes it into `passwordHash` and
|
|
220
|
+
* deletes it before the row is ever built.
|
|
221
|
+
*/
|
|
222
|
+
extraFields: string[];
|
|
223
|
+
}
|
|
204
224
|
/**
|
|
205
225
|
* Result of `AuthAdapter.finalizeUserCreation()`.
|
|
206
226
|
*
|
|
@@ -255,7 +275,7 @@ export interface AuthResponsePayload {
|
|
|
255
275
|
*/
|
|
256
276
|
export interface TransformAuthResponseContext {
|
|
257
277
|
/** The authenticated user's ID. */
|
|
258
|
-
|
|
278
|
+
uid: string;
|
|
259
279
|
/** The auth method that triggered this response. */
|
|
260
280
|
method: "login" | "register" | "oauth" | "refresh" | "anonymous" | "magic-link" | "mfa";
|
|
261
281
|
/** The raw HTTP request (for reading headers, IP, etc.). */
|
|
@@ -371,6 +391,30 @@ export interface AuthAdapter {
|
|
|
371
391
|
* @returns Processed values ready for `driver.save()`, plus metadata for the post-save step.
|
|
372
392
|
*/
|
|
373
393
|
prepareUserCreation?(values: Record<string, unknown>, collectionAuth?: unknown): Promise<UserCreationPrepareResult>;
|
|
394
|
+
/**
|
|
395
|
+
* Describe what a create body for this auth collection is allowed to name,
|
|
396
|
+
* so unknown-field validation can run on it.
|
|
397
|
+
*
|
|
398
|
+
* A signup body is not the collection's shape: it carries credential fields
|
|
399
|
+
* like `password` that the users table never declares as columns, and
|
|
400
|
+
* `prepareUserCreation` maps them onto real ones. Validating the raw body
|
|
401
|
+
* against the collection alone would reject every legitimate signup — which
|
|
402
|
+
* is why the check used to be skipped outright for auth collections. That
|
|
403
|
+
* skip was total, so an undeclared field was silently dropped and the write
|
|
404
|
+
* still returned 201, while the same typo on a normal collection was a 400.
|
|
405
|
+
*
|
|
406
|
+
* This narrows the exemption to the fields the adapter actually consumes.
|
|
407
|
+
*
|
|
408
|
+
* `validate: false` disables the check for this collection, and is the right
|
|
409
|
+
* answer when a custom `onCreateUser` hook is configured: the body is then
|
|
410
|
+
* the hook's contract, not the collection's, and this layer cannot know what
|
|
411
|
+
* the hook accepts.
|
|
412
|
+
*
|
|
413
|
+
* If not implemented, validation is skipped — the pre-existing behaviour.
|
|
414
|
+
*
|
|
415
|
+
* @param collectionAuth - The parsed `AuthCollectionConfig` from the collection (if `auth` is an object).
|
|
416
|
+
*/
|
|
417
|
+
describeUserCreationContract?(collectionAuth?: unknown): UserCreationWriteContract;
|
|
374
418
|
/**
|
|
375
419
|
* Finalize a user creation after the entity has been persisted.
|
|
376
420
|
*
|
package/dist/types/backend.d.ts
CHANGED
|
@@ -162,6 +162,40 @@ export interface SingleSubscriptionConfig {
|
|
|
162
162
|
path: string;
|
|
163
163
|
id: string | number;
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Opt-in retention for one set of broadcast channels.
|
|
167
|
+
*
|
|
168
|
+
* Retention is configured on the server and nowhere else. A channel is created
|
|
169
|
+
* by whoever names it, so letting a client ask for its own history depth would
|
|
170
|
+
* let any visitor commit the backend to unbounded storage; and presence-only or
|
|
171
|
+
* notification-only channels — the overwhelming majority — must not pay for a
|
|
172
|
+
* feature they never use. With no rules configured nothing is written, no table
|
|
173
|
+
* is created, and broadcast behaves exactly as it did before history existed.
|
|
174
|
+
*/
|
|
175
|
+
export interface ChannelRetentionRule {
|
|
176
|
+
/**
|
|
177
|
+
* Channel name to match. Either exact (`"doc:42"`) or a trailing-`*` prefix
|
|
178
|
+
* (`"doc:*"`). Deliberately not a full glob or RegExp: this decides what
|
|
179
|
+
* gets written to disk, and a rule whose blast radius is not obvious at a
|
|
180
|
+
* glance is the wrong shape for that.
|
|
181
|
+
*/
|
|
182
|
+
match: string;
|
|
183
|
+
/** Keep at most this many of the most recent messages per channel. */
|
|
184
|
+
limit?: number;
|
|
185
|
+
/**
|
|
186
|
+
* Keep messages for at most this long. Accepts a millisecond count or a
|
|
187
|
+
* short duration string (`"30s"`, `"15m"`, `"24h"`, `"7d"`).
|
|
188
|
+
*/
|
|
189
|
+
ttl?: number | string;
|
|
190
|
+
}
|
|
191
|
+
/** Server-side realtime options. */
|
|
192
|
+
export interface RealtimeChannelsConfig {
|
|
193
|
+
/**
|
|
194
|
+
* Retention rules, most specific first — the first match wins. Omitted or
|
|
195
|
+
* empty means no channel retains anything.
|
|
196
|
+
*/
|
|
197
|
+
channels?: ChannelRetentionRule[];
|
|
198
|
+
}
|
|
165
199
|
/**
|
|
166
200
|
* Abstract realtime provider interface.
|
|
167
201
|
* Handles real-time subscriptions and notifications for entity changes.
|
|
@@ -258,9 +292,22 @@ export interface SQLAdmin {
|
|
|
258
292
|
*/
|
|
259
293
|
fetchAvailableDatabases?(): Promise<string[]>;
|
|
260
294
|
/**
|
|
261
|
-
* Fetch the available database roles.
|
|
295
|
+
* Fetch the available *native PostgreSQL* database roles (from `pg_roles`).
|
|
296
|
+
*
|
|
297
|
+
* These are connection-level roles — what the SQL editor can `SET ROLE` to,
|
|
298
|
+
* and what `SecurityRule.pgRoles` targets. They are NOT application roles;
|
|
299
|
+
* for those use {@link fetchApplicationRoles}.
|
|
262
300
|
*/
|
|
263
301
|
fetchAvailableRoles?(): Promise<string[]>;
|
|
302
|
+
/**
|
|
303
|
+
* Fetch the *application-level* roles in use in this project.
|
|
304
|
+
*
|
|
305
|
+
* These are the strings stored on the users table's `roles` column and
|
|
306
|
+
* exposed to policies as `auth.roles()` — what `SecurityRule.roles`
|
|
307
|
+
* matches against. Distinct from {@link fetchAvailableRoles}; the two are
|
|
308
|
+
* not interchangeable.
|
|
309
|
+
*/
|
|
310
|
+
fetchApplicationRoles?(): Promise<string[]>;
|
|
264
311
|
/**
|
|
265
312
|
* Fetch the current database name.
|
|
266
313
|
*/
|
|
@@ -909,9 +909,14 @@ export interface SecurityRuleBase {
|
|
|
909
909
|
* **Shortcut.** Restrict this rule to users that have one of these
|
|
910
910
|
* application-level roles.
|
|
911
911
|
*
|
|
912
|
-
* **Important:** These are NOT native PostgreSQL database roles
|
|
913
|
-
*
|
|
914
|
-
*
|
|
912
|
+
* **Important:** These are NOT native PostgreSQL database roles — names
|
|
913
|
+
* like `public`, `anon` or `authenticated` belong to {@link pgRoles} and
|
|
914
|
+
* produce a condition no user can satisfy if used here. These are
|
|
915
|
+
* application roles managed by Rebase, stored as an inline `roles TEXT[]`
|
|
916
|
+
* column on the users table, and injected into each transaction as
|
|
917
|
+
* `app.user_roles` — which `auth.roles()` reads.
|
|
918
|
+
*
|
|
919
|
+
* There is no roles registry: a role exists once it is assigned to a user.
|
|
915
920
|
*
|
|
916
921
|
* Generates a safe array-overlap condition — the user passes if they hold
|
|
917
922
|
* *any* of the listed roles:
|
|
@@ -940,10 +945,10 @@ export interface SecurityRuleBase {
|
|
|
940
945
|
* every database connection). This is correct for most setups where
|
|
941
946
|
* a single database role is used for all connections.
|
|
942
947
|
*
|
|
943
|
-
* **Important:** These are NOT the same as the application-level
|
|
944
|
-
* (admin, editor, viewer, etc.) — those are enforced in the
|
|
945
|
-
* CHECK clauses via `auth.roles()`. This field controls the
|
|
946
|
-
* `TO` clause in `CREATE POLICY ... TO role_name`.
|
|
948
|
+
* **Important:** These are NOT the same as the application-level
|
|
949
|
+
* {@link roles} (admin, editor, viewer, etc.) — those are enforced in the
|
|
950
|
+
* USING/WITH CHECK clauses via `auth.roles()`. This field controls the
|
|
951
|
+
* PostgreSQL `TO` clause in `CREATE POLICY ... TO role_name`.
|
|
947
952
|
*
|
|
948
953
|
* Use this if you have dedicated PostgreSQL roles (e.g. `app_read`,
|
|
949
954
|
* `app_write`) and want policies to target specific ones.
|
|
@@ -1158,7 +1163,7 @@ export interface AuthCollectionConfig {
|
|
|
1158
1163
|
* Default: generate reset token → send email (or generate + return temp password).
|
|
1159
1164
|
* Override for custom reset flows.
|
|
1160
1165
|
*/
|
|
1161
|
-
onResetPassword?: (
|
|
1166
|
+
onResetPassword?: (uid: string, ctx: AuthCollectionContext) => Promise<AuthCollectionResetResult>;
|
|
1162
1167
|
/**
|
|
1163
1168
|
* Control which auth-specific entity actions are auto-injected.
|
|
1164
1169
|
*
|
package/dist/types/policy.d.ts
CHANGED
|
@@ -21,7 +21,7 @@ export type PolicyExpression = TruePolicyExpression | FalsePolicyExpression | An
|
|
|
21
21
|
/**
|
|
22
22
|
* The id a request without a logged-in user reports as `auth.uid()`.
|
|
23
23
|
*
|
|
24
|
-
* A user-context request always sets `app.
|
|
24
|
+
* A user-context request always sets `app.uid`: blank would read back as
|
|
25
25
|
* `NULL`, and `NULL` is how the trusted server context is recognised, so an
|
|
26
26
|
* anonymous visitor would be promoted to server privileges. The driver
|
|
27
27
|
* therefore substitutes this sentinel at the single chokepoint where the GUC
|
|
@@ -72,6 +72,49 @@ export interface CollectionPatchMessage extends WebSocketMessage {
|
|
|
72
72
|
/** See {@link WirePrimaryKeys}: how the subscriber finds {@link id} in its cache. */
|
|
73
73
|
pks?: WirePrimaryKeys;
|
|
74
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* One retained broadcast, as it travels on the wire.
|
|
77
|
+
*
|
|
78
|
+
* `seq` is per-channel, dense and monotonically increasing — it is the only
|
|
79
|
+
* thing a reconnecting client needs to say where it got to. See
|
|
80
|
+
* {@link ChannelHistoryMessage}.
|
|
81
|
+
*/
|
|
82
|
+
export interface ChannelHistoryEntry {
|
|
83
|
+
seq: number;
|
|
84
|
+
event: string;
|
|
85
|
+
payload: unknown;
|
|
86
|
+
/**
|
|
87
|
+
* The server-side client id of whoever sent it, for information only.
|
|
88
|
+
*
|
|
89
|
+
* Deliberately not used to filter a client's own messages out of a replay:
|
|
90
|
+
* a reconnect assigns a brand-new client id, so the very case replay exists
|
|
91
|
+
* for is the case where this would fail to match. Consumers that cannot
|
|
92
|
+
* tolerate re-applying their own operations must make them idempotent.
|
|
93
|
+
*/
|
|
94
|
+
senderId?: string;
|
|
95
|
+
/** When the server accepted it, ISO-8601. */
|
|
96
|
+
at: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Server → client: the retained messages a client missed.
|
|
100
|
+
*
|
|
101
|
+
* `retained: false` means the channel has no retention rule configured, so
|
|
102
|
+
* there is no history to replay and there never will be — an explicit answer
|
|
103
|
+
* rather than an empty one, so a client can tell "nothing missed" apart from
|
|
104
|
+
* "this channel does not keep history".
|
|
105
|
+
*/
|
|
106
|
+
export interface ChannelHistoryMessage extends WebSocketMessage {
|
|
107
|
+
type: "channel_history";
|
|
108
|
+
channel: string;
|
|
109
|
+
messages: ChannelHistoryEntry[];
|
|
110
|
+
retained: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The highest seq the server holds for this channel, whether or not it was
|
|
113
|
+
* returned. Lets a client that capped its request with `limit` see that it
|
|
114
|
+
* is still behind, and decide to resync wholesale instead of paging.
|
|
115
|
+
*/
|
|
116
|
+
latestSeq?: number;
|
|
117
|
+
}
|
|
75
118
|
/**
|
|
76
119
|
* Column metadata returned by table introspection.
|
|
77
120
|
*/
|
package/package.json
CHANGED
|
@@ -139,11 +139,11 @@ export interface AdminAPI {
|
|
|
139
139
|
orderBy?: string;
|
|
140
140
|
orderDir?: "asc" | "desc";
|
|
141
141
|
}): Promise<{ users: AdminUser[]; total: number; limit: number; offset: number }>;
|
|
142
|
-
getUser(
|
|
142
|
+
getUser(uid: string): Promise<{ user: AdminUser }>;
|
|
143
143
|
createUser(data: { email: string; displayName?: string; password?: string; roles?: string[]; metadata?: Record<string, any> }): Promise<{ user: AdminUser }>;
|
|
144
|
-
updateUser(
|
|
145
|
-
deleteUser(
|
|
146
|
-
resetPassword(
|
|
144
|
+
updateUser(uid: string, data: { email?: string; displayName?: string; password?: string; roles?: string[]; metadata?: Record<string, any> }): Promise<{ user: AdminUser }>;
|
|
145
|
+
deleteUser(uid: string): Promise<{ success: boolean }>;
|
|
146
|
+
resetPassword(uid: string, options?: { password?: string }): Promise<{ user: AdminUser; temporaryPassword?: string; invitationSent?: boolean; emailDeliveryFailed?: boolean }>;
|
|
147
147
|
listRoles(): Promise<{ roles: Array<{ id: string; name: string }> }>;
|
|
148
148
|
bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;
|
|
149
149
|
}
|
|
@@ -94,7 +94,7 @@ export interface AuthAdapterCapabilities {
|
|
|
94
94
|
*/
|
|
95
95
|
passwordReset: boolean;
|
|
96
96
|
/**
|
|
97
|
-
* Whether the adapter exposes `POST /admin/users/:
|
|
97
|
+
* Whether the adapter exposes `POST /admin/users/:uid/reset-password`,
|
|
98
98
|
* letting an admin reset another user's password.
|
|
99
99
|
*
|
|
100
100
|
* Independent of `passwordReset`: the built-in adapter supports this even
|
|
@@ -198,8 +198,8 @@ export interface UserManagementAdapter {
|
|
|
198
198
|
createUser(data: AuthCreateUserData): Promise<AuthUserData>;
|
|
199
199
|
updateUser(id: string, data: Partial<AuthCreateUserData>): Promise<AuthUserData | null>;
|
|
200
200
|
deleteUser(id: string): Promise<void>;
|
|
201
|
-
getUserRoles(
|
|
202
|
-
setUserRoles(
|
|
201
|
+
getUserRoles(uid: string): Promise<string[]>;
|
|
202
|
+
setUserRoles(uid: string, roleIds: string[]): Promise<void>;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// ─── User Creation Lifecycle ─────────────────────────────────────────────────
|
|
@@ -223,6 +223,27 @@ export interface UserCreationPrepareResult {
|
|
|
223
223
|
invitationSent: boolean;
|
|
224
224
|
}
|
|
225
225
|
|
|
226
|
+
/**
|
|
227
|
+
* What a create body for an auth collection may name beyond the collection's
|
|
228
|
+
* own declared fields — see `AuthAdapter.describeUserCreationContract()`.
|
|
229
|
+
*
|
|
230
|
+
* @group Auth
|
|
231
|
+
*/
|
|
232
|
+
export interface UserCreationWriteContract {
|
|
233
|
+
/**
|
|
234
|
+
* Whether to check the body for fields neither the collection nor
|
|
235
|
+
* {@link extraFields} declares. `false` skips the check entirely.
|
|
236
|
+
*/
|
|
237
|
+
validate: boolean;
|
|
238
|
+
/**
|
|
239
|
+
* Credential and provider keys the adapter consumes itself, which the
|
|
240
|
+
* collection therefore does not declare as columns. `password` is the
|
|
241
|
+
* canonical one: `prepareUserCreation` hashes it into `passwordHash` and
|
|
242
|
+
* deletes it before the row is ever built.
|
|
243
|
+
*/
|
|
244
|
+
extraFields: string[];
|
|
245
|
+
}
|
|
246
|
+
|
|
226
247
|
/**
|
|
227
248
|
* Result of `AuthAdapter.finalizeUserCreation()`.
|
|
228
249
|
*
|
|
@@ -281,7 +302,7 @@ export interface AuthResponsePayload {
|
|
|
281
302
|
*/
|
|
282
303
|
export interface TransformAuthResponseContext {
|
|
283
304
|
/** The authenticated user's ID. */
|
|
284
|
-
|
|
305
|
+
uid: string;
|
|
285
306
|
/** The auth method that triggered this response. */
|
|
286
307
|
method: "login" | "register" | "oauth" | "refresh" | "anonymous" | "magic-link" | "mfa";
|
|
287
308
|
/** The raw HTTP request (for reading headers, IP, etc.). */
|
|
@@ -425,6 +446,33 @@ export interface AuthAdapter {
|
|
|
425
446
|
collectionAuth?: unknown
|
|
426
447
|
): Promise<UserCreationPrepareResult>;
|
|
427
448
|
|
|
449
|
+
/**
|
|
450
|
+
* Describe what a create body for this auth collection is allowed to name,
|
|
451
|
+
* so unknown-field validation can run on it.
|
|
452
|
+
*
|
|
453
|
+
* A signup body is not the collection's shape: it carries credential fields
|
|
454
|
+
* like `password` that the users table never declares as columns, and
|
|
455
|
+
* `prepareUserCreation` maps them onto real ones. Validating the raw body
|
|
456
|
+
* against the collection alone would reject every legitimate signup — which
|
|
457
|
+
* is why the check used to be skipped outright for auth collections. That
|
|
458
|
+
* skip was total, so an undeclared field was silently dropped and the write
|
|
459
|
+
* still returned 201, while the same typo on a normal collection was a 400.
|
|
460
|
+
*
|
|
461
|
+
* This narrows the exemption to the fields the adapter actually consumes.
|
|
462
|
+
*
|
|
463
|
+
* `validate: false` disables the check for this collection, and is the right
|
|
464
|
+
* answer when a custom `onCreateUser` hook is configured: the body is then
|
|
465
|
+
* the hook's contract, not the collection's, and this layer cannot know what
|
|
466
|
+
* the hook accepts.
|
|
467
|
+
*
|
|
468
|
+
* If not implemented, validation is skipped — the pre-existing behaviour.
|
|
469
|
+
*
|
|
470
|
+
* @param collectionAuth - The parsed `AuthCollectionConfig` from the collection (if `auth` is an object).
|
|
471
|
+
*/
|
|
472
|
+
describeUserCreationContract?(
|
|
473
|
+
collectionAuth?: unknown
|
|
474
|
+
): UserCreationWriteContract;
|
|
475
|
+
|
|
428
476
|
/**
|
|
429
477
|
* Finalize a user creation after the entity has been persisted.
|
|
430
478
|
*
|
package/src/types/backend.ts
CHANGED
|
@@ -245,6 +245,42 @@ export interface SingleSubscriptionConfig {
|
|
|
245
245
|
id: string | number;
|
|
246
246
|
}
|
|
247
247
|
|
|
248
|
+
/**
|
|
249
|
+
* Opt-in retention for one set of broadcast channels.
|
|
250
|
+
*
|
|
251
|
+
* Retention is configured on the server and nowhere else. A channel is created
|
|
252
|
+
* by whoever names it, so letting a client ask for its own history depth would
|
|
253
|
+
* let any visitor commit the backend to unbounded storage; and presence-only or
|
|
254
|
+
* notification-only channels — the overwhelming majority — must not pay for a
|
|
255
|
+
* feature they never use. With no rules configured nothing is written, no table
|
|
256
|
+
* is created, and broadcast behaves exactly as it did before history existed.
|
|
257
|
+
*/
|
|
258
|
+
export interface ChannelRetentionRule {
|
|
259
|
+
/**
|
|
260
|
+
* Channel name to match. Either exact (`"doc:42"`) or a trailing-`*` prefix
|
|
261
|
+
* (`"doc:*"`). Deliberately not a full glob or RegExp: this decides what
|
|
262
|
+
* gets written to disk, and a rule whose blast radius is not obvious at a
|
|
263
|
+
* glance is the wrong shape for that.
|
|
264
|
+
*/
|
|
265
|
+
match: string;
|
|
266
|
+
/** Keep at most this many of the most recent messages per channel. */
|
|
267
|
+
limit?: number;
|
|
268
|
+
/**
|
|
269
|
+
* Keep messages for at most this long. Accepts a millisecond count or a
|
|
270
|
+
* short duration string (`"30s"`, `"15m"`, `"24h"`, `"7d"`).
|
|
271
|
+
*/
|
|
272
|
+
ttl?: number | string;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Server-side realtime options. */
|
|
276
|
+
export interface RealtimeChannelsConfig {
|
|
277
|
+
/**
|
|
278
|
+
* Retention rules, most specific first — the first match wins. Omitted or
|
|
279
|
+
* empty means no channel retains anything.
|
|
280
|
+
*/
|
|
281
|
+
channels?: ChannelRetentionRule[];
|
|
282
|
+
}
|
|
283
|
+
|
|
248
284
|
/**
|
|
249
285
|
* Abstract realtime provider interface.
|
|
250
286
|
* Handles real-time subscriptions and notifications for entity changes.
|
|
@@ -380,10 +416,24 @@ export interface SQLAdmin {
|
|
|
380
416
|
fetchAvailableDatabases?(): Promise<string[]>;
|
|
381
417
|
|
|
382
418
|
/**
|
|
383
|
-
* Fetch the available database roles.
|
|
419
|
+
* Fetch the available *native PostgreSQL* database roles (from `pg_roles`).
|
|
420
|
+
*
|
|
421
|
+
* These are connection-level roles — what the SQL editor can `SET ROLE` to,
|
|
422
|
+
* and what `SecurityRule.pgRoles` targets. They are NOT application roles;
|
|
423
|
+
* for those use {@link fetchApplicationRoles}.
|
|
384
424
|
*/
|
|
385
425
|
fetchAvailableRoles?(): Promise<string[]>;
|
|
386
426
|
|
|
427
|
+
/**
|
|
428
|
+
* Fetch the *application-level* roles in use in this project.
|
|
429
|
+
*
|
|
430
|
+
* These are the strings stored on the users table's `roles` column and
|
|
431
|
+
* exposed to policies as `auth.roles()` — what `SecurityRule.roles`
|
|
432
|
+
* matches against. Distinct from {@link fetchAvailableRoles}; the two are
|
|
433
|
+
* not interchangeable.
|
|
434
|
+
*/
|
|
435
|
+
fetchApplicationRoles?(): Promise<string[]>;
|
|
436
|
+
|
|
387
437
|
/**
|
|
388
438
|
* Fetch the current database name.
|
|
389
439
|
*/
|
package/src/types/collections.ts
CHANGED
|
@@ -1071,9 +1071,14 @@ export interface SecurityRuleBase {
|
|
|
1071
1071
|
* **Shortcut.** Restrict this rule to users that have one of these
|
|
1072
1072
|
* application-level roles.
|
|
1073
1073
|
*
|
|
1074
|
-
* **Important:** These are NOT native PostgreSQL database roles
|
|
1075
|
-
*
|
|
1076
|
-
*
|
|
1074
|
+
* **Important:** These are NOT native PostgreSQL database roles — names
|
|
1075
|
+
* like `public`, `anon` or `authenticated` belong to {@link pgRoles} and
|
|
1076
|
+
* produce a condition no user can satisfy if used here. These are
|
|
1077
|
+
* application roles managed by Rebase, stored as an inline `roles TEXT[]`
|
|
1078
|
+
* column on the users table, and injected into each transaction as
|
|
1079
|
+
* `app.user_roles` — which `auth.roles()` reads.
|
|
1080
|
+
*
|
|
1081
|
+
* There is no roles registry: a role exists once it is assigned to a user.
|
|
1077
1082
|
*
|
|
1078
1083
|
* Generates a safe array-overlap condition — the user passes if they hold
|
|
1079
1084
|
* *any* of the listed roles:
|
|
@@ -1105,10 +1110,10 @@ export interface SecurityRuleBase {
|
|
|
1105
1110
|
* every database connection). This is correct for most setups where
|
|
1106
1111
|
* a single database role is used for all connections.
|
|
1107
1112
|
*
|
|
1108
|
-
* **Important:** These are NOT the same as the application-level
|
|
1109
|
-
* (admin, editor, viewer, etc.) — those are enforced in the
|
|
1110
|
-
* CHECK clauses via `auth.roles()`. This field controls the
|
|
1111
|
-
* `TO` clause in `CREATE POLICY ... TO role_name`.
|
|
1113
|
+
* **Important:** These are NOT the same as the application-level
|
|
1114
|
+
* {@link roles} (admin, editor, viewer, etc.) — those are enforced in the
|
|
1115
|
+
* USING/WITH CHECK clauses via `auth.roles()`. This field controls the
|
|
1116
|
+
* PostgreSQL `TO` clause in `CREATE POLICY ... TO role_name`.
|
|
1112
1117
|
*
|
|
1113
1118
|
* Use this if you have dedicated PostgreSQL roles (e.g. `app_read`,
|
|
1114
1119
|
* `app_write`) and want policies to target specific ones.
|
|
@@ -1339,7 +1344,7 @@ export interface AuthCollectionConfig {
|
|
|
1339
1344
|
* Override for custom reset flows.
|
|
1340
1345
|
*/
|
|
1341
1346
|
onResetPassword?: (
|
|
1342
|
-
|
|
1347
|
+
uid: string,
|
|
1343
1348
|
ctx: AuthCollectionContext
|
|
1344
1349
|
) => Promise<AuthCollectionResetResult>;
|
|
1345
1350
|
|
package/src/types/policy.ts
CHANGED
|
@@ -34,7 +34,7 @@ export type PolicyExpression =
|
|
|
34
34
|
/**
|
|
35
35
|
* The id a request without a logged-in user reports as `auth.uid()`.
|
|
36
36
|
*
|
|
37
|
-
* A user-context request always sets `app.
|
|
37
|
+
* A user-context request always sets `app.uid`: blank would read back as
|
|
38
38
|
* `NULL`, and `NULL` is how the trusted server context is recognised, so an
|
|
39
39
|
* anonymous visitor would be promoted to server privileges. The driver
|
|
40
40
|
* therefore substitutes this sentinel at the single chokepoint where the GUC
|
package/src/types/websockets.ts
CHANGED
|
@@ -71,6 +71,51 @@ export interface CollectionPatchMessage extends WebSocketMessage {
|
|
|
71
71
|
pks?: WirePrimaryKeys;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
|
+
/**
|
|
75
|
+
* One retained broadcast, as it travels on the wire.
|
|
76
|
+
*
|
|
77
|
+
* `seq` is per-channel, dense and monotonically increasing — it is the only
|
|
78
|
+
* thing a reconnecting client needs to say where it got to. See
|
|
79
|
+
* {@link ChannelHistoryMessage}.
|
|
80
|
+
*/
|
|
81
|
+
export interface ChannelHistoryEntry {
|
|
82
|
+
seq: number;
|
|
83
|
+
event: string;
|
|
84
|
+
payload: unknown;
|
|
85
|
+
/**
|
|
86
|
+
* The server-side client id of whoever sent it, for information only.
|
|
87
|
+
*
|
|
88
|
+
* Deliberately not used to filter a client's own messages out of a replay:
|
|
89
|
+
* a reconnect assigns a brand-new client id, so the very case replay exists
|
|
90
|
+
* for is the case where this would fail to match. Consumers that cannot
|
|
91
|
+
* tolerate re-applying their own operations must make them idempotent.
|
|
92
|
+
*/
|
|
93
|
+
senderId?: string;
|
|
94
|
+
/** When the server accepted it, ISO-8601. */
|
|
95
|
+
at: string;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Server → client: the retained messages a client missed.
|
|
100
|
+
*
|
|
101
|
+
* `retained: false` means the channel has no retention rule configured, so
|
|
102
|
+
* there is no history to replay and there never will be — an explicit answer
|
|
103
|
+
* rather than an empty one, so a client can tell "nothing missed" apart from
|
|
104
|
+
* "this channel does not keep history".
|
|
105
|
+
*/
|
|
106
|
+
export interface ChannelHistoryMessage extends WebSocketMessage {
|
|
107
|
+
type: "channel_history";
|
|
108
|
+
channel: string;
|
|
109
|
+
messages: ChannelHistoryEntry[];
|
|
110
|
+
retained: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* The highest seq the server holds for this channel, whether or not it was
|
|
113
|
+
* returned. Lets a client that capped its request with `limit` see that it
|
|
114
|
+
* is still behind, and decide to resync wholesale instead of paging.
|
|
115
|
+
*/
|
|
116
|
+
latestSeq?: number;
|
|
117
|
+
}
|
|
118
|
+
|
|
74
119
|
/**
|
|
75
120
|
* Column metadata returned by table introspection.
|
|
76
121
|
*/
|