@rebasepro/types 0.9.0 → 0.9.1-canary.09aaf62

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 (46) hide show
  1. package/README.md +4 -4
  2. package/dist/controllers/auth.d.ts +6 -0
  3. package/dist/controllers/client.d.ts +21 -1
  4. package/dist/controllers/data.d.ts +44 -2
  5. package/dist/controllers/data_driver.d.ts +45 -3
  6. package/dist/controllers/email.d.ts +2 -2
  7. package/dist/controllers/registry.d.ts +5 -5
  8. package/dist/index.es.js +19 -1
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/types/auth_adapter.d.ts +25 -3
  11. package/dist/types/backend.d.ts +7 -0
  12. package/dist/types/backup.d.ts +20 -0
  13. package/dist/types/collections.d.ts +22 -12
  14. package/dist/types/cron.d.ts +2 -2
  15. package/dist/types/database_adapter.d.ts +17 -1
  16. package/dist/types/entity_callbacks.d.ts +3 -3
  17. package/dist/types/index.d.ts +1 -0
  18. package/dist/types/policy.d.ts +48 -2
  19. package/dist/types/properties.d.ts +13 -0
  20. package/dist/types/translations.d.ts +5 -1
  21. package/dist/types/user_management_delegate.d.ts +8 -2
  22. package/dist/types/websockets.d.ts +34 -0
  23. package/dist/users/user.d.ts +1 -1
  24. package/package.json +5 -6
  25. package/src/controllers/auth.tsx +6 -0
  26. package/src/controllers/client.ts +22 -2
  27. package/src/controllers/data.ts +42 -2
  28. package/src/controllers/data_driver.ts +47 -3
  29. package/src/controllers/email.ts +2 -2
  30. package/src/controllers/registry.ts +5 -5
  31. package/src/types/auth_adapter.ts +25 -3
  32. package/src/types/backend.ts +8 -0
  33. package/src/types/backup.ts +26 -0
  34. package/src/types/collections.ts +23 -12
  35. package/src/types/cron.ts +2 -2
  36. package/src/types/database_adapter.ts +15 -1
  37. package/src/types/entity_callbacks.ts +3 -3
  38. package/src/types/index.ts +1 -0
  39. package/src/types/policy.ts +50 -1
  40. package/src/types/properties.ts +14 -0
  41. package/src/types/translations.ts +5 -1
  42. package/src/types/user_management_delegate.ts +8 -2
  43. package/src/types/websockets.ts +31 -0
  44. package/src/users/user.ts +1 -1
  45. package/dist/index.umd.js +0 -591
  46. package/dist/index.umd.js.map +0 -1
@@ -17,7 +17,7 @@
17
17
  *
18
18
  * @example Custom auth
19
19
  * ```ts
20
- * import { createCustomAuthAdapter } from "@rebasepro/server-core";
20
+ * import { createCustomAuthAdapter } from "@rebasepro/server";
21
21
  *
22
22
  * initializeRebaseBackend({
23
23
  * auth: createCustomAuthAdapter({
@@ -77,8 +77,25 @@ export interface AuthAdapterCapabilities {
77
77
  emailPasswordLogin: boolean;
78
78
  /** Supports new user registration. */
79
79
  registration: boolean;
80
- /** Supports password reset flow. */
80
+ /**
81
+ * Supports the end-user password reset flow (emailing a reset link).
82
+ *
83
+ * This is about *self-service* reset, so it is typically tied to whether an
84
+ * email service is configured. It says nothing about whether an admin can
85
+ * reset someone else's password — see `adminPasswordReset`.
86
+ */
81
87
  passwordReset: boolean;
88
+ /**
89
+ * Whether the adapter exposes `POST /admin/users/:userId/reset-password`,
90
+ * letting an admin reset another user's password.
91
+ *
92
+ * Independent of `passwordReset`: the built-in adapter supports this even
93
+ * with no email service configured (it returns a one-time temporary
94
+ * password instead of sending a link). Adapters that mount their own admin
95
+ * routes must set this to `true` only once that route actually exists —
96
+ * the admin UI hides the "Reset Password" action when it is `false`.
97
+ */
98
+ adminPasswordReset: boolean;
82
99
  /** Supports session listing/revocation. */
83
100
  sessionManagement: boolean;
84
101
  /** Supports profile updates (display name, photo). */
@@ -196,6 +213,11 @@ export interface UserCreationFinalizeResult {
196
213
  temporaryPassword?: string;
197
214
  /** Whether an invitation email was sent. */
198
215
  invitationSent: boolean;
216
+ /**
217
+ * Whether an email service was configured but delivery failed, causing the
218
+ * fallback to `temporaryPassword`. Absent when no email service is configured.
219
+ */
220
+ emailDeliveryFailed?: boolean;
199
221
  }
200
222
  /**
201
223
  * The auth response payload shape that flows through `transformAuthResponse`.
@@ -266,7 +288,7 @@ export interface AuthAdapter {
266
288
  /**
267
289
  * Verify an incoming request and extract the authenticated user.
268
290
  *
269
- * This replaces the hardcoded JWT verification in server-core's middleware.
291
+ * This replaces the hardcoded JWT verification in server's middleware.
270
292
  * Each adapter implements its own token verification strategy:
271
293
  * - Built-in: verify Rebase JWT
272
294
  * - Clerk: call Clerk's `verifyToken()`
@@ -551,6 +551,13 @@ export interface InitializedDriver {
551
551
  realtimeProvider?: RealtimeProvider;
552
552
  /** A collection registry to register schema / tables into. */
553
553
  collectionRegistry?: CollectionRegistryInterface;
554
+ /**
555
+ * Collections the driver derived from the live database schema.
556
+ *
557
+ * Set by drivers that introspect in `baas` mode; the server serves these
558
+ * instead of collections loaded from config files.
559
+ */
560
+ collections?: import("./collections").CollectionConfig[];
554
561
  /** The underlying database connection (for lifecycle management). */
555
562
  connection?: DatabaseConnection;
556
563
  /**
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Backup type definitions shared across server, client, and studio.
3
+ */
4
+ /** Where a backup lives — a local path or an object-storage bucket. */
5
+ export type BackupDestinationKind = "local" | "s3" | "gcs";
6
+ /**
7
+ * A single backup as surfaced by the admin API / Studio Backups panel.
8
+ */
9
+ export interface BackupInfo {
10
+ /** Storage key (object storage) or absolute file path (local). */
11
+ key: string;
12
+ /** Display name — the file's basename. */
13
+ name: string;
14
+ /** Size in bytes, when known. */
15
+ sizeBytes?: number;
16
+ /** ISO timestamp the backup was created, when recoverable. */
17
+ createdAt?: string;
18
+ /** The kind of destination this backup was read from. */
19
+ destinationKind: BackupDestinationKind;
20
+ }
@@ -154,24 +154,23 @@ export interface BaseCollectionConfig<M extends Record<string, unknown> = Record
154
154
  */
155
155
  auth?: boolean | AuthCollectionConfig;
156
156
  /**
157
- * Opt out of the framework's default Row Level Security policies for
158
- * authentication collections.
157
+ * Opt out of the framework's default Row Level Security policies.
159
158
  *
160
- * When a collection has `auth` enabled, the schema generator automatically
161
- * injects an admin-only write policy (INSERT/UPDATE/DELETE require the
162
- * `admin` role, or the trusted server context) for any write operation that
163
- * the collection does not already cover with an explicit `securityRules`
164
- * entry. This makes privileged columns such as `roles` safe by default a
165
- * non-admin cannot modify the user row through the data API regardless of
166
- * which code path issues the write.
159
+ * The schema generator automatically injects, for every collection, a
160
+ * baseline SELECT policy granting the trusted server context and the
161
+ * `admin` role read access (reads run under a restricted role, so RLS
162
+ * default-denies without it). For auth collections it additionally injects
163
+ * a self-read policy (`id = auth.uid()`) and an admin-only write gate
164
+ * (INSERT/UPDATE/DELETE require the `admin` role or the trusted server
165
+ * context), making privileged columns such as `roles` safe by default.
167
166
  *
168
- * Defining your own write rule for an operation overrides the default for
169
- * that operation. Set this flag to `true` to remove the default policies
167
+ * Author-defined `securityRules` are permissive and broaden access on top
168
+ * of these defaults. Set this flag to `true` to remove the defaults
170
169
  * entirely and take full responsibility for the collection's RLS.
171
170
  *
172
171
  * @default false
173
172
  */
174
- disableDefaultAuthPolicies?: boolean;
173
+ disableDefaultPolicies?: boolean;
175
174
  /**
176
175
  * Order in which the properties are displayed.
177
176
  * If you are specifying your collection as code, the order is the same as the
@@ -331,6 +330,17 @@ export interface BaseCollectionConfig<M extends Record<string, unknown> = Record
331
330
  * This prop has no effect if the history plugin is not enabled
332
331
  */
333
332
  history?: boolean;
333
+ /**
334
+ * Whether a write naming a field this collection does not declare is
335
+ * rejected with a 400. Defaults to `true`.
336
+ *
337
+ * Set to `false` to let unknown keys through to the database, which is what
338
+ * happened before this existed: a typo reached the INSERT and came back as
339
+ * a Postgres error about a column, or — where a column really does exist
340
+ * that the config never declared, populated by a trigger or a default —
341
+ * quietly worked. The second case is the reason for the escape hatch.
342
+ */
343
+ strictWrites?: boolean;
334
344
  /**
335
345
  * Should local changes be backed up in local storage, to prevent data loss on
336
346
  * accidental navigations.
@@ -3,7 +3,7 @@ import type { RebaseClient } from "../controllers/client";
3
3
  * Cron Job type definitions for Rebase.
4
4
  *
5
5
  * These types define the shape of cron job definitions, their runtime
6
- * status, and execution log entries — used across server-core, client,
6
+ * status, and execution log entries — used across server, client,
7
7
  * and studio packages.
8
8
  */
9
9
  /**
@@ -45,7 +45,7 @@ export interface CronJobContext {
45
45
  log: (...args: unknown[]) => void;
46
46
  /**
47
47
  * The server-side {@link RebaseClient}. This is the **same singleton**
48
- * exposed as `rebase` (imported from `@rebasepro/server-core`) and as
48
+ * exposed as `rebase` (imported from `@rebasepro/server`) and as
49
49
  * `context` in collection callbacks — it is only named `client` here.
50
50
  *
51
51
  * Its data plane (`client.data`) runs with **admin privileges and bypasses
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * @example
11
11
  * ```ts
12
- * import { createPostgresAdapter } from "@rebasepro/server-postgresql";
12
+ * import { createPostgresAdapter } from "@rebasepro/server-postgres";
13
13
  *
14
14
  * initializeRebaseBackend({
15
15
  * database: createPostgresAdapter({ connection: db, schema }),
@@ -92,4 +92,20 @@ export interface DatabaseAdapterInitConfig {
92
92
  collections: CollectionConfig[];
93
93
  /** The shared collection registry to register into. */
94
94
  collectionRegistry: CollectionRegistryInterface;
95
+ /**
96
+ * How the server is being run — see `RebaseBackendConfig.mode`.
97
+ *
98
+ * In `"baas"` mode `collections` is empty by design: a driver that can
99
+ * describe its own schema should introspect the database and report the
100
+ * collections it found back on `InitializedDriver.collections`. Drivers
101
+ * that cannot introspect may ignore this.
102
+ */
103
+ mode?: "cms" | "baas";
104
+ /**
105
+ * `baas`-mode options — see `RebaseBackendConfig.baas`. Drivers that
106
+ * introspect should honour `unprotectedTables`.
107
+ */
108
+ baas?: {
109
+ unprotectedTables?: "exclude" | "serve";
110
+ };
95
111
  }
@@ -74,7 +74,7 @@ export interface AfterReadProps<M extends Record<string, unknown> = Record<strin
74
74
  */
75
75
  path: string;
76
76
  /**
77
- * Fetched row (flat — `{ id, ...columns }`)
77
+ * Fetched row (flat — the table's columns)
78
78
  */
79
79
  row: Record<string, unknown>;
80
80
  /**
@@ -149,7 +149,7 @@ export interface BeforeDeleteProps<M extends Record<string, unknown> = Record<st
149
149
  */
150
150
  id: string | number;
151
151
  /**
152
- * Deleted row (flat — `{ id, ...columns }`)
152
+ * Deleted row (flat — the table's columns)
153
153
  */
154
154
  row: Record<string, unknown>;
155
155
  /**
@@ -175,7 +175,7 @@ export interface AfterDeleteProps<M extends Record<string, unknown> = Record<str
175
175
  */
176
176
  id: string | number;
177
177
  /**
178
- * Deleted row (flat — `{ id, ...columns }`)
178
+ * Deleted row (flat — the table's columns)
179
179
  */
180
180
  row: Record<string, unknown>;
181
181
  /**
@@ -24,6 +24,7 @@ export * from "./entity_views";
24
24
  export * from "./data_source";
25
25
  export * from "./storage_source";
26
26
  export * from "./cron";
27
+ export * from "./backup";
27
28
  export * from "./component_ref";
28
29
  export * from "./auth_adapter";
29
30
  export * from "./database_adapter";
@@ -17,7 +17,24 @@
17
17
  *
18
18
  * @group Models
19
19
  */
20
- export type PolicyExpression = TruePolicyExpression | FalsePolicyExpression | AndPolicyExpression | OrPolicyExpression | NotPolicyExpression | ComparePolicyExpression | RolesOverlapPolicyExpression | RolesContainPolicyExpression | AuthenticatedPolicyExpression | ExistsInPolicyExpression | RawPolicyExpression;
20
+ export type PolicyExpression = TruePolicyExpression | FalsePolicyExpression | AndPolicyExpression | OrPolicyExpression | NotPolicyExpression | ComparePolicyExpression | RolesOverlapPolicyExpression | RolesContainPolicyExpression | AuthenticatedPolicyExpression | ServerContextPolicyExpression | ExistsInPolicyExpression | RawPolicyExpression;
21
+ /**
22
+ * The id a request without a logged-in user reports as `auth.uid()`.
23
+ *
24
+ * A user-context request always sets `app.user_id`: blank would read back as
25
+ * `NULL`, and `NULL` is how the trusted server context is recognised, so an
26
+ * anonymous visitor would be promoted to server privileges. The driver
27
+ * therefore substitutes this sentinel at the single chokepoint where the GUC
28
+ * is set.
29
+ *
30
+ * The consequence for policy authors is that **`auth.uid() IS NOT NULL` is a
31
+ * tautology on the user path** — it is true for anonymous visitors too. Use
32
+ * {@link policy.authenticated} (or `auth.uid() <> 'anonymous'`) to mean "signed
33
+ * in", and {@link policy.serverContext} to mean "the trusted server context".
34
+ *
35
+ * @group Models
36
+ */
37
+ export declare const ANONYMOUS_USER_ID = "anonymous";
21
38
  /** Always allows. Compiles to `true`. @group Models */
22
39
  export interface TruePolicyExpression {
23
40
  kind: "true";
@@ -72,12 +89,40 @@ export interface RolesContainPolicyExpression {
72
89
  roles: readonly string[];
73
90
  }
74
91
  /**
75
- * True when there is an authenticated user (`auth.uid() IS NOT NULL`).
92
+ * True when a signed-in user is making the request. Compiles to
93
+ * `auth.uid() IS NOT NULL AND auth.uid() <> 'anonymous'`.
94
+ *
95
+ * Both halves are load-bearing. `IS NOT NULL` excludes the server context;
96
+ * the {@link ANONYMOUS_USER_ID} comparison excludes anonymous visitors, who
97
+ * *do* carry a non-null `auth.uid()`. Checking only `IS NOT NULL` grants to
98
+ * everyone — see {@link ANONYMOUS_USER_ID}.
99
+ *
100
+ * `policy.not(policy.authenticated())` therefore means "anonymous visitor or
101
+ * the server context". To single out the server context, use
102
+ * {@link ServerContextPolicyExpression}.
76
103
  * @group Models
77
104
  */
78
105
  export interface AuthenticatedPolicyExpression {
79
106
  kind: "authenticated";
80
107
  }
108
+ /**
109
+ * True only in the trusted **server context** — the built-in flows that run
110
+ * without a user (signup, migrations, `dataAsAdmin`) set no user GUC, so
111
+ * `auth.uid()` is `NULL` for them and only for them. Compiles to
112
+ * `auth.uid() IS NULL`.
113
+ *
114
+ * This is what lets the owner connection satisfy a policy even under FORCE RLS.
115
+ * It is deliberately a primitive rather than `not(authenticated())`: the two
116
+ * meant the same thing while `authenticated` ignored {@link ANONYMOUS_USER_ID},
117
+ * and conflating them is what turns a server-only grant into an anonymous one.
118
+ *
119
+ * The JavaScript evaluator always returns `false` for this node — a client is
120
+ * never the server context.
121
+ * @group Models
122
+ */
123
+ export interface ServerContextPolicyExpression {
124
+ kind: "serverContext";
125
+ }
81
126
  /**
82
127
  * Membership / relational access: true when at least one row exists in another
83
128
  * collection (a join/membership table) matching `where`. This is what lets you
@@ -178,6 +223,7 @@ export declare const policy: {
178
223
  rolesOverlap: (roles: readonly string[]) => RolesOverlapPolicyExpression;
179
224
  rolesContain: (roles: readonly string[]) => RolesContainPolicyExpression;
180
225
  authenticated: () => AuthenticatedPolicyExpression;
226
+ serverContext: () => ServerContextPolicyExpression;
181
227
  existsIn: (args: {
182
228
  collection: string;
183
229
  where: PolicyExpression;
@@ -175,6 +175,19 @@ export interface BaseProperty<CustomProps = unknown> {
175
175
  * Rules for validating this property
176
176
  */
177
177
  validation?: PropertyValidationSchema;
178
+ /**
179
+ * Never include this column in an API response.
180
+ *
181
+ * For secrets the server must store and read but no client should ever
182
+ * receive — password hashes, verification tokens. The value is still
183
+ * written and queryable server-side; it is stripped from every row the API
184
+ * serves, for every caller, including admins and service keys.
185
+ *
186
+ * This is a server-side guarantee, unlike `ui.hideFromCollection`, which
187
+ * only stops the admin panel from *rendering* a field and leaves it in the
188
+ * JSON payload.
189
+ */
190
+ excludeFromApi?: boolean;
178
191
  /**
179
192
  * Use this to define dynamic properties that change based on certain conditions
180
193
  * or on the entity's values. For example, you can make a field read-only if
@@ -6,7 +6,7 @@ export type DeepPartial<T> = T extends object ? {
6
6
  [K in keyof T]?: DeepPartial<T[K]>;
7
7
  } : T;
8
8
  /**
9
- * All user-visible strings used internally by @rebasepro/core.
9
+ * All user-visible strings used internally by @rebasepro/app.
10
10
  * Pass a `DeepPartial<RebaseTranslations>` via the `translations` prop
11
11
  * on your Rebase entry-point component to override any key, or to add
12
12
  * a new locale.
@@ -487,6 +487,7 @@ export interface RebaseTranslations {
487
487
  invitation_sent_title?: string;
488
488
  temporary_password?: string;
489
489
  temporary_password_description?: string;
490
+ temporary_password_email_failed_description?: string;
490
491
  copy_password?: string;
491
492
  password_copied?: string;
492
493
  /** UsersView — pagination & search */
@@ -497,6 +498,9 @@ export interface RebaseTranslations {
497
498
  reset_password?: string;
498
499
  reset_password_success?: string;
499
500
  reset_password_confirmation?: string;
501
+ reset_password_send_email?: string;
502
+ reset_password_set_manually?: string;
503
+ reset_password_set_manually_description?: string;
500
504
  error_resetting_password?: string;
501
505
  /** Permission-denied empty states */
502
506
  no_permission_to_view_users?: string;
@@ -9,8 +9,14 @@ export interface UserCreationResult<USER extends User = User> {
9
9
  /** Whether an invitation email was sent to the user */
10
10
  invitationSent: boolean;
11
11
  /**
12
- * Temporary password (only present when email service is not configured).
13
- * This is returned one-time and should be shown to the admin to share manually.
12
+ * Temporary password, present when no invitation email could be delivered
13
+ * either because no email service is configured, or because delivery failed
14
+ * (see `emailDeliveryFailed`). Returned one-time, to be shared manually.
14
15
  */
15
16
  temporaryPassword?: string;
17
+ /**
18
+ * Whether an email service was configured but delivery failed, causing the
19
+ * fallback to `temporaryPassword`. Absent when no email service is configured.
20
+ */
21
+ emailDeliveryFailed?: boolean;
16
22
  }
@@ -14,11 +14,42 @@ export interface WebSocketMessage {
14
14
  rows?: Record<string, unknown>[];
15
15
  row?: Record<string, unknown> | null;
16
16
  error?: string;
17
+ /**
18
+ * Channel name, on broadcast and presence frames.
19
+ *
20
+ * These are addressed by channel rather than by `requestId` or
21
+ * `subscriptionId`, so this is the only field that routes them.
22
+ */
23
+ channel?: string;
17
24
  }
25
+ /**
26
+ * The key columns a collection's rows are addressed by.
27
+ *
28
+ * A row is exactly its columns and carries no address, so a subscriber that has
29
+ * to recognise one — to patch it, or to keep its reference across a refetch —
30
+ * derives the address from these. The SDK is usable with no collections
31
+ * declared at all, so the server is the only side that knows them.
32
+ *
33
+ * Undefined when the server cannot resolve them: a table with no primary key
34
+ * and no `id` column has no address, and rows of it cannot be recognised by
35
+ * anyone.
36
+ */
37
+ export type WirePrimaryKeys = {
38
+ fieldName: string;
39
+ type: "string" | "number";
40
+ isUUID?: boolean;
41
+ }[];
18
42
  export interface CollectionUpdateMessage extends WebSocketMessage {
19
43
  type: "collection_update";
20
44
  subscriptionId: string;
21
45
  rows: Record<string, unknown>[];
46
+ /**
47
+ * See {@link WirePrimaryKeys}. Sent with the rows themselves — and not only
48
+ * with a patch — because a CDC-originated change sends no patch at all: it
49
+ * invalidates and goes straight to a refetch, and the merge that preserves
50
+ * unchanged rows' references needs an address to match them by.
51
+ */
52
+ pks?: WirePrimaryKeys;
22
53
  }
23
54
  export interface SingleUpdateMessage extends WebSocketMessage {
24
55
  type: "single_update";
@@ -34,9 +65,12 @@ export interface SingleUpdateMessage extends WebSocketMessage {
34
65
  export interface CollectionPatchMessage extends WebSocketMessage {
35
66
  type: "collection_patch";
36
67
  subscriptionId: string;
68
+ /** The address of the row this patch refers to — derived, never read off it. */
37
69
  id: string;
38
70
  /** The updated row, or null if deleted */
39
71
  row: Record<string, unknown> | null;
72
+ /** See {@link WirePrimaryKeys}: how the subscriber finds {@link id} in its cache. */
73
+ pks?: WirePrimaryKeys;
40
74
  }
41
75
  /**
42
76
  * Column metadata returned by table introspection.
@@ -2,7 +2,7 @@
2
2
  * The canonical representation of an authenticated user in the Rebase ecosystem.
3
3
  *
4
4
  * Used by {@link AuthController}, collections, callbacks, and both the
5
- * `@rebasepro/client` and `@rebasepro/auth` packages. All other user types
5
+ * `@rebasepro/client` and `@rebasepro/app` packages. All other user types
6
6
  * (`RebaseUser`, `UserInfo`) are deprecated aliases of this type.
7
7
  *
8
8
  * **Backend-managed fields** (`uid`, `email`, `roles`, `metadata`, `createdAt`)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
3
  "type": "module",
4
- "version": "0.9.0",
4
+ "version": "0.9.1-canary.09aaf62",
5
5
  "description": "Rebase type definitions — shared interfaces and controller types",
6
6
  "funding": {
7
7
  "url": "https://github.com/sponsors/rebaseco"
@@ -13,12 +13,12 @@
13
13
  "url": "https://github.com/rebasepro/rebase.git",
14
14
  "directory": "packages/types"
15
15
  },
16
- "main": "./dist/index.umd.js",
16
+ "main": "./dist/index.es.js",
17
17
  "module": "./dist/index.es.js",
18
18
  "types": "./dist/index.d.ts",
19
19
  "source": "src/index.ts",
20
20
  "engines": {
21
- "node": ">=14"
21
+ "node": ">=20"
22
22
  },
23
23
  "keywords": [
24
24
  "rebase",
@@ -33,8 +33,7 @@
33
33
  ".": {
34
34
  "types": "./dist/index.d.ts",
35
35
  "development": "./dist/index.es.js",
36
- "import": "./dist/index.es.js",
37
- "require": "./dist/index.umd.js"
36
+ "import": "./dist/index.es.js"
38
37
  },
39
38
  "./package.json": "./package.json"
40
39
  },
@@ -92,7 +91,7 @@
92
91
  },
93
92
  "scripts": {
94
93
  "watch": "vite build --watch",
95
- "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json",
94
+ "build": "vite build && tsc --emitDeclarationOnly -p tsconfig.prod.json && node ../../scripts/assert-build-output.mjs",
96
95
  "test:lint": "eslint \"src/**\" --quiet",
97
96
  "test": "jest --passWithNoTests",
98
97
  "clean": "rm -rf dist && find ./src -name '*.js' -type f | xargs rm -f"
@@ -10,7 +10,13 @@ export interface AuthCapabilities {
10
10
  emailPasswordLogin?: boolean;
11
11
  googleLogin?: boolean;
12
12
  registration?: boolean;
13
+ /** Self-service password reset (emailing a reset link) is available. */
13
14
  passwordReset?: boolean;
15
+ /**
16
+ * An admin can reset another user's password. Gates the "Reset Password"
17
+ * entity action in the admin UI. See `AuthAdapterCapabilities`.
18
+ */
19
+ adminPasswordReset?: boolean;
14
20
  sessionManagement?: boolean;
15
21
  profileUpdate?: boolean;
16
22
  emailVerification?: boolean;
@@ -3,6 +3,7 @@ import type { RebaseSdkData } from "./data";
3
3
  import type { EmailService } from "./email";
4
4
  import type { StorageSource } from "./storage";
5
5
  import type { CronJobStatus, CronJobLogEntry } from "../types/cron";
6
+ import type { BackupInfo, BackupDestinationKind } from "../types/backup";
6
7
  import type { ApiKeysAPI } from "../types/api_keys";
7
8
  import type { StorageSourceDefinition } from "../types/storage_source";
8
9
 
@@ -142,7 +143,7 @@ export interface AdminAPI {
142
143
  createUser(data: { email: string; displayName?: string; password?: string; roles?: string[]; metadata?: Record<string, any> }): Promise<{ user: AdminUser }>;
143
144
  updateUser(userId: string, data: { email?: string; displayName?: string; password?: string; roles?: string[]; metadata?: Record<string, any> }): Promise<{ user: AdminUser }>;
144
145
  deleteUser(userId: string): Promise<{ success: boolean }>;
145
- resetPassword(userId: string, options?: { password?: string }): Promise<{ user: AdminUser; temporaryPassword?: string; invitationSent?: boolean }>;
146
+ resetPassword(userId: string, options?: { password?: string }): Promise<{ user: AdminUser; temporaryPassword?: string; invitationSent?: boolean; emailDeliveryFailed?: boolean }>;
146
147
  listRoles(): Promise<{ roles: Array<{ id: string; name: string }> }>;
147
148
  bootstrap(): Promise<{ success: boolean; message: string; user: { uid: string; roles: string[] } }>;
148
149
  }
@@ -161,6 +162,19 @@ export interface CronAPI {
161
162
  toggleJob(jobId: string, enabled: boolean): Promise<{ job: CronJobStatus }>;
162
163
  }
163
164
 
165
+ // ─── Backups API ─────────────────────────────────────────────────────────────
166
+
167
+ /**
168
+ * Client-side database-backup management interface.
169
+ * @group Backups
170
+ */
171
+ export interface BackupsAPI {
172
+ /** List available backups at the configured destination, newest first. */
173
+ list(): Promise<{ backups: BackupInfo[]; destinationKind: BackupDestinationKind; configured: boolean }>;
174
+ /** Fetch a backup's bytes for download (authenticated). */
175
+ download(key: string): Promise<Blob>;
176
+ }
177
+
164
178
  // ─── Functions API ───────────────────────────────────────────────────────────
165
179
 
166
180
  /**
@@ -294,6 +308,9 @@ export interface RebaseClient<DB = unknown> {
294
308
  /** Cron job management API */
295
309
  cron?: CronAPI;
296
310
 
311
+ /** Database backup management API */
312
+ backups?: BackupsAPI;
313
+
297
314
  /** Custom backend functions API */
298
315
  functions?: FunctionsAPI;
299
316
 
@@ -333,7 +350,7 @@ export interface RebaseClient<DB = unknown> {
333
350
 
334
351
  /**
335
352
  * The server-side Rebase surface — the shape of the `rebase` singleton exported
336
- * from `@rebasepro/server-core`.
353
+ * from `@rebasepro/server`.
337
354
  *
338
355
  * Narrows {@link RebaseClient} to the guarantees that always hold on the server:
339
356
  * the admin-scoped {@link dataAsAdmin} accessor, raw {@link sql}, and the
@@ -409,6 +426,9 @@ export interface RebaseBrowserClient<DB = unknown> {
409
426
  /** Cron job management API */
410
427
  cron?: CronAPI;
411
428
 
429
+ /** Database backup management API */
430
+ backups?: BackupsAPI;
431
+
412
432
  /** Custom backend functions API */
413
433
  functions?: FunctionsAPI;
414
434
 
@@ -161,6 +161,14 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
161
161
  */
162
162
  create(data: Partial<EntityValues<M>>, id?: string | number): Promise<Entity<M>>;
163
163
 
164
+ /**
165
+ * Create many records in a single transaction.
166
+ *
167
+ * See {@link SDKCollectionClient.createMany}. Optional: not every driver can
168
+ * write in bulk, and callers should fall back to `create` per record.
169
+ */
170
+ createMany?(data: Partial<EntityValues<M>>[], options?: { upsert?: boolean }): Promise<Entity<M>[]>;
171
+
164
172
  /**
165
173
  * Update an existing record by ID.
166
174
  * @returns The updated entity
@@ -294,11 +302,43 @@ export interface SDKCollectionClient<
294
302
  /**
295
303
  * Create a new record.
296
304
  * @param data The record data to create (the collection's `Insert` shape).
297
- * @param id Optional specific ID to use for the new record.
305
+ * @param id Optional specific id, sent as an `id` column. This is for tables
306
+ * whose key *is* `id`: the value goes in as that column. For a table keyed
307
+ * on anything else (a `sku`, a composite key), there is no `id` column to
308
+ * receive it — put the key in `data` instead, where it belongs among the
309
+ * columns.
298
310
  * @returns The created row
299
311
  */
300
312
  create(data: I, id?: string | number): Promise<M>;
301
313
 
314
+ /**
315
+ * Write many records in a single request and a single transaction.
316
+ *
317
+ * Built for imports and ETL, where one call per row means one HTTP round
318
+ * trip and one transaction per row. Every record still runs the normal
319
+ * pipeline — callbacks, relations, row-level security — and the batch is
320
+ * all-or-nothing: if any record is rejected, none of them land and the
321
+ * error names the offending index.
322
+ *
323
+ * A record carrying its primary key updates that row; one without inserts.
324
+ * With `{ upsert: true }` each record is written as INSERT ... ON CONFLICT
325
+ * DO UPDATE on the primary key instead, which is what makes a re-runnable
326
+ * import idempotent.
327
+ *
328
+ * Batches are capped server-side (1000 rows by default) because one batch
329
+ * holds its locks for the whole transaction — chunk larger jobs.
330
+ *
331
+ * @returns The written rows, in the order given.
332
+ *
333
+ * @example
334
+ * ```ts
335
+ * for (const chunk of chunks(rows, 1000)) {
336
+ * await client.data.products.createMany(chunk, { upsert: true });
337
+ * }
338
+ * ```
339
+ */
340
+ createMany(data: I[], options?: { upsert?: boolean }): Promise<M[]>;
341
+
302
342
  /**
303
343
  * Update an existing record by ID.
304
344
  * @param data The fields to update (the collection's `Update` shape).
@@ -386,7 +426,7 @@ export type RebaseData<DB = unknown> = {
386
426
  * - The frontend SDK client (`client.data.products.find()`)
387
427
  * - Backend framework callbacks & scripts (`context.data.products.find()`)
388
428
  *
389
- * Every accessor returns flat rows (`{ id, ...columns }`) via
429
+ * Every accessor returns flat rows (the table's columns) via
390
430
  * {@link SDKCollectionClient} — access fields directly (`row.title`), never
391
431
  * `row.values.title`. The admin CMS uses {@link RebaseData} (Entity) instead.
392
432
  *