@rebasepro/types 0.22.0 → 0.24.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.
@@ -0,0 +1,272 @@
1
+ /**
2
+ * Scopes — what a credential may do, named `resource:action`.
3
+ *
4
+ * One vocabulary for every way of reaching a Rebase backend: a person's
5
+ * session, a service key, a personal key and an OAuth token for `/mcp` all
6
+ * answer "may this caller do X?" with the same strings, so a grant reads the
7
+ * same on a key, on a role and on a consent screen.
8
+ *
9
+ * A scope may carry a target — `data:read:posts` is `data:read` on the `posts`
10
+ * collection only. The unqualified scope covers every target. Only the data
11
+ * plane and app scopes take targets; see {@link SCOPE_TARGETS}.
12
+ *
13
+ * Two planes, held differently:
14
+ *
15
+ * - **The data plane** (`data:*`, `storage:*`, `functions:invoke`). Every
16
+ * signed-in person holds all of it, because what a person may do with data
17
+ * is decided row by row by the collection's `securityRules` and by storage
18
+ * policies, never by a scope. On a key or a token these scopes narrow: a key
19
+ * holding `data:read:posts` reaches nothing else, whatever RLS would allow.
20
+ * - **The admin plane** (everything else). Nobody holds it implicitly. The
21
+ * `admin` role holds all of it; any other role holds what the app declares
22
+ * for it under `auth.roles` on the users collection.
23
+ *
24
+ * Apps declare their own scopes too (`auth.scopes`), named the same way —
25
+ * `project:deploy`. Every signed-in person holds every app scope: like the data
26
+ * plane, the function behind it decides whether the person may act, and the
27
+ * scope exists so a key can be narrowed to that one action.
28
+ *
29
+ * @module
30
+ */
31
+ /** @group Auth */
32
+ export declare const DATA_PLANE_SCOPES: readonly ["data:read", "data:write", "data:delete", "storage:read", "storage:write", "storage:delete", "functions:invoke"];
33
+ /** @group Auth */
34
+ export declare const ADMIN_SCOPES: readonly ["users:read", "users:write", "schema:read", "schema:write", "database:read", "database:write", "backups:read", "cron:read", "cron:write", "logs:read", "keys:read", "keys:write"];
35
+ /** @group Auth */
36
+ export type DataPlaneScope = (typeof DATA_PLANE_SCOPES)[number];
37
+ /** @group Auth */
38
+ export type AdminScope = (typeof ADMIN_SCOPES)[number];
39
+ /** @group Auth */
40
+ export type BuiltInScope = DataPlaneScope | AdminScope;
41
+ /** Every scope Rebase itself defines, in display order. @group Auth */
42
+ export declare const BUILT_IN_SCOPES: readonly BuiltInScope[];
43
+ /**
44
+ * Scopes no key may hold.
45
+ *
46
+ * A key that could manage keys could mint its own successor, widen itself, or
47
+ * revoke the keys other integrations run on — and revoking it would undo none
48
+ * of that. Managing keys is for a person (or the service key).
49
+ *
50
+ * @group Auth
51
+ */
52
+ export declare const KEY_MANAGEMENT_SCOPES: readonly AdminScope[];
53
+ /** The one built-in role. It holds every scope and is the RLS `admin` role. @group Auth */
54
+ export declare const ADMIN_ROLE = "admin";
55
+ /** What a scope's target names. @group Auth */
56
+ export type ScopeTargetKind = "collection" | "bucket" | "function";
57
+ /**
58
+ * The built-in scopes that accept a target, and what the target names.
59
+ *
60
+ * - `data:*` — a collection slug
61
+ * - `storage:*` — a storage source id (`(default)` for the default source)
62
+ * - `functions:invoke` — a custom function's name
63
+ *
64
+ * @group Auth
65
+ */
66
+ export declare const SCOPE_TARGETS: Readonly<Partial<Record<BuiltInScope, ScopeTargetKind>>>;
67
+ /** A scope's wording, for the screens that ask a person to grant it. @group Auth */
68
+ export interface ScopeDescription {
69
+ /** Short, imperative: what a holder may do. */
70
+ label: string;
71
+ /** One sentence on what that reaches, including anything surprising. */
72
+ description: string;
73
+ }
74
+ /** @group Auth */
75
+ export declare const BUILT_IN_SCOPE_DESCRIPTIONS: Readonly<Record<BuiltInScope, ScopeDescription>>;
76
+ /**
77
+ * An app-declared scope, under `auth.scopes` on the users collection.
78
+ *
79
+ * @example
80
+ * ```ts
81
+ * auth: {
82
+ * enabled: true,
83
+ * scopes: {
84
+ * "project:deploy": { label: "Deploy projects", target: "project" }
85
+ * }
86
+ * }
87
+ * ```
88
+ *
89
+ * @group Auth
90
+ */
91
+ export interface ScopeDefinition {
92
+ /** Short, imperative: what a holder may do. Shown wherever the scope is granted. */
93
+ label: string;
94
+ /** One sentence on what that reaches. */
95
+ description?: string;
96
+ /**
97
+ * What a target names, when the scope accepts one — `"project"` lets a
98
+ * key hold `project:deploy:<id>`. Unset, the scope takes no target.
99
+ */
100
+ target?: string;
101
+ }
102
+ /**
103
+ * An app-declared role, under `auth.roles` on the users collection.
104
+ *
105
+ * A role is a name the database sees (RLS policies match it) and a bundle of
106
+ * admin-plane and app scopes. Data-plane scopes are not role scopes: what a
107
+ * person may do with data is the collection's `securityRules`, so listing
108
+ * `data:write` here would read as a grant it is not, and is refused.
109
+ *
110
+ * @example
111
+ * ```ts
112
+ * auth: {
113
+ * enabled: true,
114
+ * roles: {
115
+ * support: { name: "Support", scopes: ["users:read", "users:write", "logs:read"] },
116
+ * developer: { name: "Developer", scopes: ["schema:read", "database:read", "logs:read", "cron:read"] }
117
+ * }
118
+ * }
119
+ * ```
120
+ *
121
+ * @group Auth
122
+ */
123
+ export interface RoleDefinition {
124
+ /** Display name. Defaults to the role id. */
125
+ name?: string;
126
+ /** One sentence on who holds it. */
127
+ description?: string;
128
+ /** Admin-plane and app scopes the role holds. */
129
+ scopes: readonly string[];
130
+ }
131
+ /**
132
+ * The declared access model: the app's own scopes and roles beside the
133
+ * built-in ones. What `scopesForRoles` and `validateScopes` read.
134
+ *
135
+ * @group Auth
136
+ */
137
+ export interface AccessModel {
138
+ roles: Readonly<Record<string, RoleDefinition>>;
139
+ scopes: Readonly<Record<string, ScopeDefinition>>;
140
+ }
141
+ /** The access model of an app that declares nothing: built-ins only. @group Auth */
142
+ export declare const EMPTY_ACCESS_MODEL: AccessModel;
143
+ /** A scope string split into its scope and optional target. @group Auth */
144
+ export interface ParsedScope {
145
+ /** `resource:action`. */
146
+ scope: string;
147
+ /** The one collection, bucket, function or app resource it is narrowed to. */
148
+ target?: string;
149
+ }
150
+ /**
151
+ * Split `resource:action[:target]`. Returns null for anything else.
152
+ *
153
+ * The target is everything after the second colon, so a target may itself
154
+ * contain colons. Resource and action are lower-case words.
155
+ *
156
+ * @group Auth
157
+ */
158
+ export declare function parseScope(value: string): ParsedScope | null;
159
+ /** @group Auth */
160
+ export declare function isBuiltInScope(scope: string): scope is BuiltInScope;
161
+ /** @group Auth */
162
+ export declare function isDataPlaneScope(scope: string): scope is DataPlaneScope;
163
+ /** @group Auth */
164
+ export declare function isAdminScope(scope: string): scope is AdminScope;
165
+ /** Does this list of roles include the built-in `admin` role? @group Auth */
166
+ export declare function hasAdminRole(roles: readonly string[] | null | undefined): boolean;
167
+ /**
168
+ * Does a set of held scopes grant `scope`, optionally on one `target`?
169
+ *
170
+ * The unqualified grant covers every target. A qualified grant covers its own
171
+ * target only, and never answers an unqualified question: a key holding
172
+ * `data:read:posts` is not asked "may you read data?" and told yes.
173
+ *
174
+ * @group Auth
175
+ */
176
+ export declare function scopeGrants(held: readonly string[], scope: string, target?: string): boolean;
177
+ /**
178
+ * Does a set of held scopes grant `scope` on at least one target?
179
+ *
180
+ * For a surface that lists what is reachable — the collections an MCP client
181
+ * may query — and filters per item afterwards.
182
+ *
183
+ * @group Auth
184
+ */
185
+ export declare function scopeGrantsAny(held: readonly string[], scope: string): boolean;
186
+ /**
187
+ * The targets a held set narrows `scope` to: `"all"` when it holds the
188
+ * unqualified scope, otherwise the listed targets (possibly none).
189
+ *
190
+ * @group Auth
191
+ */
192
+ export declare function scopeTargets(held: readonly string[], scope: string): "all" | string[];
193
+ /**
194
+ * Every scope a person holding `roles` has.
195
+ *
196
+ * The data plane and every app scope always; the whole admin plane with the
197
+ * `admin` role; otherwise each declared role's scopes. A role nobody declared
198
+ * adds nothing — it may still mean something to an RLS policy.
199
+ *
200
+ * @group Auth
201
+ */
202
+ export declare function scopesForRoles(roles: readonly string[] | null | undefined, model?: AccessModel): string[];
203
+ /**
204
+ * Every scope this access model knows, unqualified: the built-ins, then the
205
+ * app's own.
206
+ *
207
+ * @group Auth
208
+ */
209
+ export declare function knownScopes(model?: AccessModel): string[];
210
+ /** Does this scope accept a target? @group Auth */
211
+ export declare function scopeAcceptsTarget(scope: string, model?: AccessModel): boolean;
212
+ /** How a scope string failed to validate. @group Auth */
213
+ export interface ScopeProblem {
214
+ scope: string;
215
+ reason: "malformed" | "unknown" | "target-not-accepted";
216
+ }
217
+ /**
218
+ * Check a list of scope strings against an access model. Returns the
219
+ * problems, empty when every entry is a known scope with an acceptable target.
220
+ *
221
+ * @group Auth
222
+ */
223
+ export declare function validateScopes(values: readonly unknown[], model?: AccessModel): ScopeProblem[];
224
+ /**
225
+ * The entries of `requested` that `held` does not cover — what a credential
226
+ * would hold beyond its minter. Empty means the request is within reach.
227
+ *
228
+ * `data:read:posts` is covered by `data:read`; `data:read` is not covered by
229
+ * `data:read:posts`.
230
+ *
231
+ * @group Auth
232
+ */
233
+ export declare function scopesBeyond(requested: readonly string[], held: readonly string[]): string[];
234
+ /**
235
+ * Narrow `requested` to what `held` covers, entry by entry. What a personal
236
+ * key holds today when its owner has lost a role since it was minted.
237
+ *
238
+ * @group Auth
239
+ */
240
+ export declare function intersectScopes(requested: readonly string[], held: readonly string[]): string[];
241
+ /**
242
+ * How a scope is worded for a person: the built-in description, the app's
243
+ * declared label, or the raw string when nothing describes it. A target is
244
+ * named after the label.
245
+ *
246
+ * @group Auth
247
+ */
248
+ export declare function describeScope(value: string, model?: AccessModel): ScopeDescription;
249
+ /** A role as a person managing access sees it. @group Auth */
250
+ export interface RoleSummary {
251
+ id: string;
252
+ name: string;
253
+ description?: string;
254
+ /** Admin-plane and app scopes. The `admin` role lists every admin-plane scope. */
255
+ scopes: string[];
256
+ /** True for `admin`, which the app does not declare. */
257
+ builtIn: boolean;
258
+ }
259
+ /** A scope as a screen that grants it lists it. @group Auth */
260
+ export interface ScopeSummary {
261
+ scope: string;
262
+ label: string;
263
+ description: string;
264
+ /** What a target names, when the scope takes one. */
265
+ target?: string;
266
+ /** `data`, `admin` or `app`: which plane the scope belongs to. */
267
+ plane: "data" | "admin" | "app";
268
+ }
269
+ /** The roles of an access model, `admin` first. @group Auth */
270
+ export declare function summarizeRoles(model?: AccessModel): RoleSummary[];
271
+ /** Every scope of an access model, described, in display order. @group Auth */
272
+ export declare function summarizeScopes(model?: AccessModel): ScopeSummary[];
@@ -53,7 +53,7 @@ export type SecurityOperation = "select" | "insert" | "update" | "delete" | "all
53
53
  *
54
54
  * @group Models
55
55
  */
56
- export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | StructuredSecurityRule | RawSQLSecurityRule | RolesOnlySecurityRule;
56
+ export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | StructuredSecurityRule | RawSQLSecurityRule | RawSQLCheckOnlySecurityRule | RolesOnlySecurityRule;
57
57
  /**
58
58
  * Shared fields for all SecurityRule variants.
59
59
  * @group Models
@@ -297,6 +297,33 @@ export interface RawSQLSecurityRule extends SecurityRuleBase {
297
297
  condition?: never;
298
298
  check?: never;
299
299
  }
300
+ /**
301
+ * Security rule with a raw SQL `WITH CHECK` clause and no `USING` clause — the
302
+ * shape of every INSERT policy, which checks the row being written and has no
303
+ * existing row to filter.
304
+ *
305
+ * On an operation that takes a `USING` clause, the missing one grants no rows,
306
+ * as a Postgres policy without one does.
307
+ *
308
+ * @example
309
+ * // Anyone may insert a row they own
310
+ * { operation: "insert", withCheck: "{owner_id} = rebase.uid()" }
311
+ *
312
+ * @group Models
313
+ */
314
+ export interface RawSQLCheckOnlySecurityRule extends SecurityRuleBase {
315
+ /**
316
+ * Raw SQL expression for the `WITH CHECK` clause.
317
+ * This controls which *new/updated* row values are allowed.
318
+ * Applied to INSERT and UPDATE.
319
+ */
320
+ withCheck: string;
321
+ using?: never;
322
+ ownerField?: never;
323
+ access?: never;
324
+ condition?: never;
325
+ check?: never;
326
+ }
300
327
  /**
301
328
  * Security rule that only filters by application roles, without any
302
329
  * row-level condition (USING/WITH CHECK).
@@ -22,7 +22,11 @@ export interface StorageAuthorizeContext {
22
22
  operation: StorageOperation;
23
23
  /** Null when the route allows unauthenticated access. */
24
24
  user: StorageAuthorizeUser | null;
25
- /** Named backend the request targeted, when one was given. */
25
+ /**
26
+ * Named backend the request targeted, when one was given — as its canonical
27
+ * key, the one the request is served from (`" media "` arrives as
28
+ * `"media"`). Absent for the default source, however the request named it.
29
+ */
26
30
  storageId?: string;
27
31
  /**
28
32
  * Trusted, RLS-bypassing data access, so the hook can answer the question it
@@ -45,6 +45,13 @@ export type User = {
45
45
  * Whether the user's email address has been verified.
46
46
  */
47
47
  readonly emailVerified?: boolean;
48
+ /**
49
+ * The address this account is moving to, while the link mailed there is
50
+ * waiting to be followed (`client.auth.changeEmail`). `email` stays the
51
+ * account's address until then. `null` or absent when no change is
52
+ * waiting.
53
+ */
54
+ readonly pendingEmail?: string | null;
48
55
  /**
49
56
  * Role IDs assigned to this user (e.g. `["admin", "editor"]`).
50
57
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "Rebase type definitions — shared interfaces and controller types",
5
5
  "keywords": [
6
6
  "rebase",
@@ -46,7 +46,7 @@
46
46
  "@types/node": "^26.1.2",
47
47
  "@types/object-hash": "^3.0.6",
48
48
  "@types/react-measure": "^2.0.12",
49
- "hono": "^4.13.0",
49
+ "hono": "^4.13.5",
50
50
  "jest": "^30.4.2",
51
51
  "ts-jest": "^29.4.12",
52
52
  "typescript": "^6.0.3",