@rebasepro/types 0.23.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.
@@ -49,7 +49,7 @@ export type AdminCollectionKey = typeof ADMIN_COLLECTION_KEYS[number];
49
49
  *
50
50
  * @group Models
51
51
  */
52
- export declare const ADMIN_PROPERTY_KEYS: readonly ["canAddElements", "clearable", "columnWidth", "customProps", "disabled", "expanded", "Field", "Filter", "filterOperators", "fixedFilter", "format", "hideFromCollection", "includeEntityLink", "includeId", "markdown", "minimalistView", "multiline", "Preview", "previewAsTag", "previewProperties", "readOnly", "renderInForm", "sortable", "span", "spreadChildren", "urlPreview", "widget"];
52
+ export declare const ADMIN_PROPERTY_KEYS: readonly ["canAddElements", "clearable", "columnWidth", "customProps", "disabled", "expanded", "Field", "filledByServer", "Filter", "filterOperators", "fixedFilter", "format", "hideFromCollection", "includeEntityLink", "includeId", "markdown", "minimalistView", "multiline", "Preview", "previewAsTag", "previewProperties", "readOnly", "renderInForm", "sortable", "span", "spreadChildren", "urlPreview", "widget"];
53
53
  /** A key of a property's `admin` block. @group Models */
54
54
  export type AdminPropertyKey = typeof ADMIN_PROPERTY_KEYS[number];
55
55
  /**
@@ -1,49 +1,52 @@
1
1
  /**
2
- * Service API keys — machine-to-machine authentication for scripts, cron jobs
3
- * and third-party integrations.
2
+ * API keys — long-lived bearer credentials for scripts, CI, agents and
3
+ * third-party integrations.
4
4
  *
5
- * The wire contract lives here because all three sides need it and used to
6
- * declare it separately: `@rebasepro/server` implements the routes,
7
- * `@rebasepro/client` calls them, and {@link ApiKeysAPI} types the SDK surface.
8
- * The client's copy had already drifted — it never gained `admin`.
5
+ * The wire contract lives here because all three sides need it:
6
+ * `@rebasepro/server` implements the routes, `@rebasepro/client` calls them,
7
+ * and {@link ApiKeysAPI} types the SDK surface. The database row itself
8
+ * (which carries `key_hash`) stays in the server package: nothing off the
9
+ * server may see it.
9
10
  *
10
- * The database row itself (`ApiKey`, which carries `key_hash`) stays in the
11
- * server package: nothing off the server may see it.
11
+ * What a key may do is a list of scopes — see `scopes.ts`.
12
12
  */
13
+ import type { ScopeSummary } from "./scopes.js";
13
14
  /**
14
- * A single permission entry scoping an API key to a collection and set of
15
- * operations.
15
+ * Who a key acts as.
16
16
  *
17
- * Use `"*"` as the collection value to grant access to all collections (and all
18
- * custom functions). Custom functions are addressed with the `functions`
19
- * namespace: `"functions"` grants every function, `"functions/<name>"` grants a
20
- * single one.
17
+ * - `"service"` — the project's own machine identity, `api-key:<id>`. It is no
18
+ * person: it runs as the RLS roles it was given (`service` always, plus any
19
+ * in {@link ApiKeyMasked.roles}). Managed by holders of `keys:write`.
20
+ * - `"personal"` — its owner. It runs as the owner's account, with the owner's
21
+ * roles as they are *now*, and can never hold a scope the owner does not.
22
+ * Managed by the owner, when the app enables `auth.personalKeys`.
21
23
  *
22
24
  * @group Models
23
25
  */
24
- export interface ApiKeyPermission {
25
- /** Collection slug, `"functions"`/`"functions/<name>"`, or `"*"` for everything. */
26
- collection: string;
27
- /** Allowed operations on the collection. */
28
- operations: ("read" | "write" | "delete")[];
29
- }
26
+ export type ApiKeyKind = "service" | "personal";
30
27
  /**
31
- * An API key with the secret portion masked — what list / get / update return.
28
+ * An API key with the secret masked — what list / get / update return.
32
29
  * @group Models
33
30
  */
34
31
  export interface ApiKeyMasked {
35
32
  id: string;
36
33
  name: string;
34
+ kind: ApiKeyKind;
37
35
  /** First 12 characters of the plaintext key, for display only. */
38
36
  key_prefix: string;
39
- permissions: ApiKeyPermission[];
40
37
  /**
41
- * When true, the key is granted the `admin` role: it passes the admin-gated
42
- * routes (users, roles, cron, backups, logs, API keys) and the RLS
43
- * `default_admin` policies. Non-admin keys carry only the `service` role —
44
- * RLS grants them nothing unless a collection policy names that role.
38
+ * What the key may do, as `resource:action[:target]` scope strings —
39
+ * `data:read:posts`, `functions:invoke`, `logs:read`.
40
+ */
41
+ scopes: string[];
42
+ /**
43
+ * The RLS roles a service key runs as, beside `service`. `["admin"]` reads
44
+ * every row through the default admin policies. Always empty on a personal
45
+ * key, which runs as its owner's live roles.
45
46
  */
46
- admin: boolean;
47
+ roles: string[];
48
+ /** The account a personal key acts as. Null on a service key. */
49
+ owner_uid: string | null;
47
50
  /**
48
51
  * Requests per 15-minute window. `null` means "no per-key override" — the
49
52
  * data rate limiter then applies its default API-key limit (1000/window
@@ -67,32 +70,44 @@ export interface ApiKeyWithSecret extends ApiKeyMasked {
67
70
  key: string;
68
71
  }
69
72
  /**
70
- * Payload for creating a new API key.
73
+ * Payload for creating a service key.
71
74
  * @group Models
72
75
  */
73
76
  export interface CreateApiKeyRequest {
74
77
  name: string;
75
- permissions: ApiKeyPermission[];
76
- /** When true, grants the `admin` role. See {@link ApiKeyMasked.admin}. */
77
- admin?: boolean;
78
+ /** At least one. `keys:*` is refused, and so is any scope the creator does not hold. */
79
+ scopes: string[];
80
+ /** RLS roles beside `service`. A creator may only give roles they hold, unless they are an admin. */
81
+ roles?: string[];
78
82
  /** Requests per 15-minute window. Omit or `null` for the server default. */
79
83
  rate_limit?: number | null;
80
84
  /** ISO-8601 expiration timestamp. Omit for no expiration. */
81
85
  expires_at?: string | null;
82
86
  }
83
87
  /**
84
- * Payload for updating an existing API key. Only the fields provided change.
88
+ * Payload for updating a service key. Only the fields provided change.
85
89
  * @group Models
86
90
  */
87
91
  export interface UpdateApiKeyRequest {
88
92
  name?: string;
89
- permissions?: ApiKeyPermission[];
90
- /** When true, grants the `admin` role. See {@link ApiKeyMasked.admin}. */
91
- admin?: boolean;
93
+ scopes?: string[];
94
+ roles?: string[];
92
95
  rate_limit?: number | null;
93
96
  expires_at?: string | null;
94
97
  }
95
- /** @group Models */
98
+ /**
99
+ * Payload for creating a personal key. It acts as the caller, so it carries no
100
+ * roles and no rate-limit override.
101
+ * @group Models
102
+ */
103
+ export interface CreatePersonalKeyRequest {
104
+ name: string;
105
+ /** At least one, and none the caller does not hold. `keys:*` is refused. */
106
+ scopes: string[];
107
+ /** ISO-8601 expiration timestamp. Omit for no expiration. */
108
+ expires_at?: string | null;
109
+ }
110
+ /** The project's service keys. Requires `keys:read` / `keys:write`. @group Models */
96
111
  export interface ApiKeysAPI {
97
112
  listKeys(): Promise<{
98
113
  keys: ApiKeyMasked[];
@@ -110,3 +125,20 @@ export interface ApiKeysAPI {
110
125
  success: boolean;
111
126
  }>;
112
127
  }
128
+ /** The signed-in person's own keys, when the app enables them. @group Models */
129
+ export interface PersonalKeysAPI {
130
+ listKeys(): Promise<{
131
+ keys: ApiKeyMasked[];
132
+ }>;
133
+ createKey(data: CreatePersonalKeyRequest): Promise<{
134
+ key: ApiKeyWithSecret;
135
+ }>;
136
+ revokeKey(id: string): Promise<{
137
+ success: boolean;
138
+ }>;
139
+ /** Every scope the backend knows, described, and the ones the caller holds. */
140
+ listScopes(): Promise<{
141
+ scopes: ScopeSummary[];
142
+ held: string[];
143
+ }>;
144
+ }
@@ -53,6 +53,18 @@ export interface MountableRouter {
53
53
  /** The routes the sub-app declares. Only its presence is contractual. */
54
54
  routes: readonly unknown[];
55
55
  }
56
+ /**
57
+ * The request header that asks for a data-plane request to run as another
58
+ * user, named by uid.
59
+ *
60
+ * The server honours it only on a signed-in administrator's own session —
61
+ * never on an API key or the service key — and only through an adapter that
62
+ * implements {@link AuthAdapter.resolveUser}. Everywhere else a request that
63
+ * carries it is refused, never run as its caller.
64
+ *
65
+ * @group Auth
66
+ */
67
+ export declare const IMPERSONATE_HEADER = "x-rebase-impersonate";
56
68
  /**
57
69
  * The normalized user object returned by `AuthAdapter.verifyRequest()`.
58
70
  *
@@ -72,7 +84,11 @@ export interface AuthenticatedUser {
72
84
  photoUrl?: string | null;
73
85
  /** Role identifiers the user holds. */
74
86
  roles: string[];
75
- /** Whether the user has admin privileges. */
87
+ /**
88
+ * Whether `roles` includes `admin`. Informational — every authorization
89
+ * decision reads `roles` and the scopes they hold, so an adapter that
90
+ * wants a user treated as an administrator gives them the `admin` role.
91
+ */
76
92
  isAdmin: boolean;
77
93
  /**
78
94
  * Whether this session is a GUEST — anonymous sign-in rather than an
@@ -90,6 +106,12 @@ export interface AuthenticatedUser {
90
106
  rawToken?: string;
91
107
  /** Extra claims/metadata from the auth provider. */
92
108
  claims?: Record<string, unknown>;
109
+ /**
110
+ * What this credential may do, when it is narrower than its person — set
111
+ * for an API key. Absent means everything the user's roles hold
112
+ * (`scopesForRoles`).
113
+ */
114
+ scopes?: string[];
93
115
  }
94
116
  /**
95
117
  * Feature flags advertised by an auth adapter.
@@ -171,6 +193,26 @@ export interface AuthAdapterCapabilities {
171
193
  */
172
194
  needsSetup?: boolean;
173
195
  }
196
+ /**
197
+ * What `GET /api/auth/config` answers: the auth adapter's capabilities, plus
198
+ * the optional features of the backend around it.
199
+ *
200
+ * The admin panel reads this document before anything else, so it is where
201
+ * the panel learns that a feature is off, without first asking for that
202
+ * feature and getting a refusal.
203
+ *
204
+ * @group Auth
205
+ */
206
+ export interface AuthConfigDocument extends AuthAdapterCapabilities {
207
+ /**
208
+ * Whether this backend serves file storage. When it is `false`, no storage
209
+ * backend is configured: `GET /storage/sources` lists nothing and every
210
+ * other `/storage` route answers 501 `STORAGE_NOT_CONFIGURED`. Absent from
211
+ * a backend that predates the field, so a client must ask
212
+ * `GET /storage/sources` there.
213
+ */
214
+ storage?: boolean;
215
+ }
174
216
  /**
175
217
  * Options for paginated user listing.
176
218
  * @group Auth
@@ -217,6 +259,11 @@ export interface AuthCreateUserData {
217
259
  displayName?: string;
218
260
  photoUrl?: string;
219
261
  metadata?: Record<string, unknown>;
262
+ /**
263
+ * Store the address as verified: whoever creates the account named it
264
+ * (an operator, an administrator), so nobody needs to prove it.
265
+ */
266
+ emailVerified?: boolean;
220
267
  }
221
268
  /**
222
269
  * User management operations for the admin panel.
@@ -390,6 +437,23 @@ export interface AuthAdapter {
390
437
  * @returns The authenticated user, or `null` if the token is invalid.
391
438
  */
392
439
  verifyToken?(token: string): Promise<AuthenticatedUser | null>;
440
+ /**
441
+ * The identity a verified request from this account would carry right
442
+ * now, with no credential involved: its uid, its roles as they are now,
443
+ * its guest flag, and the custom claims a token minted for it now would
444
+ * hold. `null` when no active account has this uid — none at all, or one
445
+ * that is disabled.
446
+ *
447
+ * This is what lets an administrator run a request as another user (the
448
+ * {@link IMPERSONATE_HEADER} header). The server calls it only after it
449
+ * has decided the caller may do that, and it calls it for the caller too,
450
+ * so the decision rests on the caller's roles as they are now rather than
451
+ * on the ones their token claims.
452
+ *
453
+ * Optional. An adapter without it cannot be impersonated through, and a
454
+ * request carrying the header is refused rather than run as its caller.
455
+ */
456
+ resolveUser?(uid: string): Promise<AuthenticatedUser | null>;
393
457
  /**
394
458
  * User CRUD for the admin panel's user management UI.
395
459
  * Optional — if not provided, user management UI is hidden.
@@ -246,6 +246,12 @@ export interface RealtimeChannelsConfig {
246
246
  * Defaults to `{ type: "memory" }` — i.e. they don't.
247
247
  */
248
248
  bus?: ChannelBusSetting;
249
+ /**
250
+ * How many subscriptions one socket may hold; the next is refused with
251
+ * `TOO_MANY_SUBSCRIPTIONS`. A positive whole number, checked at boot.
252
+ * Defaults to 1000. `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET` wins over it.
253
+ */
254
+ maxSubscriptionsPerSocket?: number;
249
255
  }
250
256
  /**
251
257
  * Abstract realtime provider interface.
@@ -293,6 +299,25 @@ export interface RealtimeProvider {
293
299
  * Called during graceful shutdown before closing database connections.
294
300
  */
295
301
  stopListening?(): Promise<void>;
302
+ /**
303
+ * The long-lived connections realtime depends on, for `/health`. A change
304
+ * feed that has gone quiet — a half-open LISTEN connection — loses every
305
+ * external and cross-instance change while writes through this instance
306
+ * still look live, so it is reported rather than inferred.
307
+ */
308
+ health?(): RealtimeListenerHealth[];
309
+ }
310
+ /**
311
+ * One long-lived realtime connection, as `/health` reports it.
312
+ * @group Backend
313
+ */
314
+ export interface RealtimeListenerHealth {
315
+ /** What it carries — `"cdc"`, `"cross-instance"`, `"channel-bus"`. */
316
+ name: string;
317
+ /** Listening on a connection that answered its last heartbeat. */
318
+ connected: boolean;
319
+ /** When (epoch ms) it went down, while it is down. */
320
+ downSince?: number;
296
321
  }
297
322
  /**
298
323
  * Abstract collection registry interface.
@@ -341,6 +366,97 @@ export interface DataTransformer {
341
366
  */
342
367
  deserializeFromDatabase<M extends Record<string, unknown>>(data: Record<string, unknown>, collection: CollectionConfig): Promise<M>;
343
368
  }
369
+ /**
370
+ * The table column a value of a {@link SqlScriptResult} was read from, as the
371
+ * database itself reported it — not as the query's text suggests.
372
+ *
373
+ * @group Admin
374
+ */
375
+ export interface SqlScriptColumnSource {
376
+ schema: string;
377
+ table: string;
378
+ column: string;
379
+ }
380
+ /**
381
+ * One column of a {@link SqlScriptResult}, in result order.
382
+ *
383
+ * @group Admin
384
+ */
385
+ export interface SqlScriptColumn {
386
+ /** The name the result gives it. Two columns of one result may share it. */
387
+ name: string;
388
+ /** Its type, as the database names it: `integer`, `text[]`, `timestamp with time zone`. */
389
+ type?: string;
390
+ /**
391
+ * Where the database says the value came from: a plain reference to a
392
+ * column of a table or view, through any number of subqueries.
393
+ *
394
+ * Absent for everything no stored row holds — an expression, a cast that
395
+ * changes the type, a literal, an aggregate, a function's output, a
396
+ * `UNION`.
397
+ */
398
+ source?: SqlScriptColumnSource;
399
+ }
400
+ /**
401
+ * A table or view some column of a {@link SqlScriptResult} came from.
402
+ *
403
+ * @group Admin
404
+ */
405
+ export interface SqlScriptTable {
406
+ schema: string;
407
+ table: string;
408
+ /** What the relation is. Only a `table` or a `partitioned table` holds rows that can be updated by key. */
409
+ kind: "table" | "partitioned table" | "view" | "materialized view" | "foreign table" | "other";
410
+ /** Its primary key columns, in key order. Empty when it has none. */
411
+ primaryKey: string[];
412
+ /**
413
+ * Other tables inherit this one (by `INHERITS`, not as partitions). A row
414
+ * read from it may live in one of them, and a primary key is not unique
415
+ * across them.
416
+ */
417
+ hasInheritors: boolean;
418
+ }
419
+ /**
420
+ * Something the database said while a script ran: `there is no transaction in
421
+ * progress` for a ROLLBACK with nothing to end, a `RAISE NOTICE`.
422
+ *
423
+ * @group Admin
424
+ */
425
+ export interface SqlScriptNotice {
426
+ severity: string;
427
+ message: string;
428
+ }
429
+ /**
430
+ * What a SQL script a person wrote returned. See {@link SQLAdmin.runSqlScript}.
431
+ *
432
+ * @group Admin
433
+ */
434
+ export interface SqlScriptResult {
435
+ /**
436
+ * The last statement's rows, every value as the text the database wrote
437
+ * for it and `null` for SQL NULL — so a value written back is the value
438
+ * that was read, whatever its type. Keyed by column name: of two columns
439
+ * that share a name, the row holds the last.
440
+ */
441
+ rows: Record<string, string | null>[];
442
+ /** The last statement's columns, in order. */
443
+ columns: SqlScriptColumn[];
444
+ /** The tables and views {@link SqlScriptColumn.source} names. */
445
+ tables: SqlScriptTable[];
446
+ /** The last statement's command: `SELECT`, `UPDATE`, `CREATE TABLE` … */
447
+ command?: string;
448
+ /** How many rows the last statement returned or changed, when its command reports a count. */
449
+ rowCount?: number;
450
+ /** What the database said while the script ran, in order. */
451
+ notices: SqlScriptNotice[];
452
+ /**
453
+ * The database role the script ran as, when it asked for one — said by
454
+ * the transport that ran it. The role asked for, unless the server has
455
+ * role switching turned off (`DISABLE_DB_ROLE_SWITCHING`) and runs every
456
+ * statement as the connection owner, who is named here instead.
457
+ */
458
+ effectiveRole?: string;
459
+ }
344
460
  /**
345
461
  * Administrative operations for SQL-based databases (PostgreSQL, MySQL, etc.).
346
462
  * Used by the SQL Editor, RLS Editor, and schema browser.
@@ -363,6 +479,22 @@ export interface SQLAdmin {
363
479
  params?: unknown[];
364
480
  isolateSession?: boolean;
365
481
  }): Promise<Record<string, unknown>[]>;
482
+ /**
483
+ * Run a script a person wrote — the Studio SQL console — and describe what
484
+ * it returned.
485
+ *
486
+ * On a session of its own, reset afterwards, as `role` for every statement
487
+ * of it. Values come back as the database's text, and each column says
488
+ * which table column it was read from, when the database says so: what
489
+ * the console needs to write a cell back to the row it came from and to
490
+ * no other. A script that leaves a transaction open is refused, and the
491
+ * transaction rolled back: nothing outlives the run to be committed or
492
+ * rolled back by a later one.
493
+ */
494
+ runSqlScript?(sql: string, options?: {
495
+ database?: string;
496
+ role?: string;
497
+ }): Promise<SqlScriptResult>;
366
498
  /**
367
499
  * Fetch the available databases on the server.
368
500
  */
@@ -433,6 +565,12 @@ export interface SchemaAdmin {
433
565
  export interface BranchInfo {
434
566
  /** Branch name (without prefix). */
435
567
  name: string;
568
+ /**
569
+ * The PostgreSQL database the branch is — what to connect to, and what to
570
+ * name as the source when copying it. As the branch's record says, never
571
+ * derived from `name` again.
572
+ */
573
+ database: string;
436
574
  /** The database this branch was created from. */
437
575
  parentDatabase: string;
438
576
  /** When the branch was created. */
@@ -598,6 +736,15 @@ export interface DataRateLimitCaller {
598
736
  * the caller by address at the anonymous allowance.
599
737
  */
600
738
  uid?: string;
739
+ /**
740
+ * The API key the connection authenticated with. Its frames count in the
741
+ * key's own bucket at the key's own `rate_limit`, the bucket its HTTP
742
+ * requests count in, rather than as `uid`.
743
+ */
744
+ apiKey?: {
745
+ id: string;
746
+ rate_limit: number | null;
747
+ };
601
748
  /**
602
749
  * A header of the request that opened the connection, by lower-case name:
603
750
  * the proxy headers an address is read from when proxies are declared.
@@ -607,11 +754,30 @@ export interface DataRateLimitCaller {
607
754
  socketAddress?: string;
608
755
  }
609
756
  /**
610
- * The data API's request limits, handed to the realtime socket so its frames
611
- * carry the ones an HTTP request to the same rows does.
757
+ * What the realtime socket shares with the HTTP data API: its request limits,
758
+ * so a frame carries the ones an HTTP request to the same rows does, and its
759
+ * API-key verification, so a key means the same thing on both.
612
760
  * @group Backend
613
761
  */
614
- export interface RealtimeSocketLimits {
762
+ export interface RealtimeSocketOptions {
763
+ /**
764
+ * Verify an `rk_` API key presented to `AUTHENTICATE`: the identity it acts
765
+ * as and the scopes it holds, or why it does not authenticate. Absent when
766
+ * the deployment has no API-key store, and then keys cannot authenticate
767
+ * the socket.
768
+ */
769
+ resolveApiKey?: (token: string) => Promise<{
770
+ uid: string;
771
+ roles: string[];
772
+ scopes: string[];
773
+ /** The key itself: its id and `rate_limit` decide which rate-limit bucket its frames count in. */
774
+ apiKey?: {
775
+ id: string;
776
+ rate_limit: number | null;
777
+ };
778
+ } | {
779
+ message: string;
780
+ }>;
615
781
  /**
616
782
  * The largest frame accepted, in bytes — the data API's body limit. `0` or
617
783
  * less for none. Defaults to the server's default body limit.
@@ -626,6 +792,13 @@ export interface RealtimeSocketLimits {
626
792
  allowed: boolean;
627
793
  retryAfterMs: number;
628
794
  } | null>;
795
+ /**
796
+ * How often a socket that only listens has its identity re-read, in ms
797
+ * (default 30 000). A socket that sends frames is re-checked before each
798
+ * one; this bounds how long a subscription keeps receiving rows as an
799
+ * identity that has since signed out, been revoked, demoted or deleted.
800
+ */
801
+ identityRecheckIntervalMs?: number;
629
802
  }
630
803
  /**
631
804
  * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.
@@ -832,7 +1005,7 @@ export interface BackendBootstrapper {
832
1005
  /**
833
1006
  * Initialize WebSocket server for realtime operations.
834
1007
  */
835
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver.js").DataDriver, config?: unknown, authAdapter?: AuthAdapter, limits?: RealtimeSocketLimits): Promise<void> | void;
1008
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver.js").DataDriver, config?: unknown, authAdapter?: AuthAdapter, limits?: RealtimeSocketOptions): Promise<void> | void;
836
1009
  }
837
1010
  /**
838
1011
  * Result of `BackendBootstrapper.initializeDriver()`.
@@ -868,8 +1041,6 @@ export interface InitializedDriver {
868
1041
  export interface BootstrappedAuth {
869
1042
  /** User management service. */
870
1043
  userService: unknown;
871
- /** Role management service (optional, roles are now simple strings). */
872
- roleService?: unknown;
873
1044
  /** Email service (optional). */
874
1045
  emailService?: unknown;
875
1046
  /** Combined Auth Repository for unified token and user management. */
@@ -25,3 +25,86 @@ export interface BackupInfo {
25
25
  */
26
26
  globalsKey?: string;
27
27
  }
28
+ /**
29
+ * How one run of the scheduled backup went, read from the cron's run history.
30
+ */
31
+ export interface BackupRunOutcome {
32
+ /** ISO timestamp the run started. */
33
+ startedAt: string;
34
+ /** ISO timestamp the run ended. */
35
+ finishedAt: string;
36
+ /** Whether the run produced a backup. */
37
+ success: boolean;
38
+ /** Why it did not, when it failed — e.g. `Could not find the 'pg_dump' binary.` */
39
+ error?: string;
40
+ /** True when someone triggered the run by hand rather than the schedule. */
41
+ manual: boolean;
42
+ }
43
+ /**
44
+ * The deployment's scheduled backup job — the cron that default-exports
45
+ * `createBackupCron` — and the outcome of its last run.
46
+ *
47
+ * Shown by the Backups panel so a backup that fails every night is visible
48
+ * where the backups are listed, rather than only in the cron history.
49
+ */
50
+ export interface BackupScheduleStatus {
51
+ /** The cron job's id, which is its file's name. */
52
+ jobId: string;
53
+ /** The job's display name. */
54
+ name: string;
55
+ /** Its cron expression. */
56
+ schedule: string;
57
+ /** False when the job is paused (or declared `enabled: false`), or refused. */
58
+ enabled: boolean;
59
+ /**
60
+ * True when the job is off because its own definition says
61
+ * `enabled: false` — the documented backup cron file exports it that way
62
+ * while `BACKUP_SCHEDULE` is unset — and not because someone paused it.
63
+ * Resuming it in Cron Jobs would run that definition as written.
64
+ */
65
+ disabledInCode?: boolean;
66
+ /**
67
+ * Why the scheduler refused the job — an invalid schedule, timezone or
68
+ * timeout. A refused job is not registered and never runs; the cron file
69
+ * has to change.
70
+ */
71
+ refused?: string;
72
+ /** ISO timestamp of the next scheduled run, when one is armed. */
73
+ nextRunAt?: string;
74
+ /**
75
+ * The most recent scheduled run, absent when the schedule has never run
76
+ * it — or when its run history could not be read, which
77
+ * {@link historyError} says. A run someone started by hand is not one:
78
+ * see {@link lastManualRun}.
79
+ */
80
+ lastRun?: BackupRunOutcome;
81
+ /** A run started by hand ("Run Now") since {@link lastRun}, when there is one. */
82
+ lastManualRun?: BackupRunOutcome;
83
+ /**
84
+ * Why the job's run history could not be read. Whether it ran, and how it
85
+ * went, is then unknown: `lastRun` is absent for that reason, not because
86
+ * the job never ran.
87
+ */
88
+ historyError?: string;
89
+ }
90
+ /** What the backup listing (`GET /admin/backups`) answers. */
91
+ export interface BackupListing {
92
+ /** Backups at the destination, newest first. */
93
+ backups: BackupInfo[];
94
+ /** The kind of destination they were read from. */
95
+ destinationKind: BackupDestinationKind;
96
+ /** False when `BACKUP_DESTINATION` is unset. */
97
+ configured: boolean;
98
+ /**
99
+ * The scheduled backup job, or `null` when the deployment registers none.
100
+ * Absent from a server that predates the field.
101
+ */
102
+ schedule?: BackupScheduleStatus | null;
103
+ /**
104
+ * True when the destination is a local path and the scheduled backup runs
105
+ * in another process (the worker of a split deployment). The backups
106
+ * listed are the ones on this process's disk; the scheduled runs write to
107
+ * theirs, which is the same directory only when both mount it.
108
+ */
109
+ localDiskOfThisProcess?: boolean;
110
+ }