@rebasepro/types 0.23.0 → 0.24.1-canary.g19596ef

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 (35) hide show
  1. package/dist/call_context.d.ts +8 -4
  2. package/dist/controllers/client.d.ts +113 -24
  3. package/dist/controllers/collection_registry.d.ts +17 -0
  4. package/dist/controllers/data.d.ts +570 -122
  5. package/dist/controllers/data_driver.d.ts +85 -1
  6. package/dist/controllers/index.d.ts +1 -0
  7. package/dist/controllers/storage-references.d.ts +114 -0
  8. package/dist/controllers/storage.d.ts +73 -13
  9. package/dist/errors.d.ts +15 -1
  10. package/dist/index.es.js +1228 -43
  11. package/dist/index.es.js.map +1 -1
  12. package/dist/types/admin_block.d.ts +1 -1
  13. package/dist/types/api_keys.d.ts +68 -36
  14. package/dist/types/auth_adapter.d.ts +97 -2
  15. package/dist/types/backend.d.ts +356 -9
  16. package/dist/types/backup.d.ts +102 -0
  17. package/dist/types/collection_patch.d.ts +74 -0
  18. package/dist/types/collections.d.ts +23 -0
  19. package/dist/types/cron.d.ts +58 -7
  20. package/dist/types/database_adapter.d.ts +34 -8
  21. package/dist/types/entities.d.ts +11 -0
  22. package/dist/types/history.d.ts +15 -1
  23. package/dist/types/index.d.ts +3 -0
  24. package/dist/types/project_manifest.d.ts +73 -1
  25. package/dist/types/properties.d.ts +39 -2
  26. package/dist/types/register.d.ts +43 -0
  27. package/dist/types/relations.d.ts +37 -1
  28. package/dist/types/resource_kinds.d.ts +20 -10
  29. package/dist/types/schema_editing.d.ts +39 -13
  30. package/dist/types/scopes.d.ts +272 -0
  31. package/dist/types/storage_authorize.d.ts +10 -1
  32. package/dist/types/storage_source.d.ts +2 -6
  33. package/dist/types/websockets.d.ts +32 -1
  34. package/dist/users/user.d.ts +7 -0
  35. package/package.json +1 -1
@@ -6,6 +6,7 @@ import type { AuthAdapter } from "./auth_adapter.js";
6
6
  import type { HistoryConfig } from "../controllers/client.js";
7
7
  import type { ChannelBusSetting } from "./channel_bus.js";
8
8
  import type { SchemaEditingAdmin } from "./schema_editing.js";
9
+ import type { AccessModel } from "./scopes.js";
9
10
  /**
10
11
  * Abstract database connection interface.
11
12
  * Represents a connection to any database system.
@@ -246,6 +247,58 @@ export interface RealtimeChannelsConfig {
246
247
  * Defaults to `{ type: "memory" }` — i.e. they don't.
247
248
  */
248
249
  bus?: ChannelBusSetting;
250
+ /**
251
+ * How many subscriptions one socket may hold; the next is refused with
252
+ * `TOO_MANY_SUBSCRIPTIONS`. A positive whole number, checked at boot.
253
+ * Defaults to 1000. `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET` wins over it.
254
+ */
255
+ maxSubscriptionsPerSocket?: number;
256
+ /**
257
+ * How many sockets one user (one identity: a signed-in user, an API key's
258
+ * user, an impersonated user) may hold open on one server instance. The
259
+ * next one's `AUTHENTICATE` is refused with `TOO_MANY_SOCKETS` and the
260
+ * socket closed with 4002; the SDK keeps retrying with backoff, so it gets
261
+ * in once another closes. A positive whole number, checked at boot.
262
+ * Unset (the default) is no ceiling. `REALTIME_MAX_SOCKETS_PER_USER` wins
263
+ * over it.
264
+ */
265
+ maxSocketsPerUser?: number;
266
+ /**
267
+ * The longest a change waits for the refetch that delivers it, in ms. A
268
+ * refetch waits for 300 ms without a change, and at most this long under a
269
+ * steady stream of writes. Defaults to 1000. A positive whole number,
270
+ * checked at boot. `REALTIME_REFETCH_MAX_WAIT_MS` wins over it.
271
+ */
272
+ refetchMaxWaitMs?: number;
273
+ /**
274
+ * How long a big list subscription's total may be reused before it is
275
+ * counted again, in ms. Unset (the default), every frame counts. Frames
276
+ * that reuse it carry `meta.totalAsOf`. `REALTIME_RECOUNT_INTERVAL_MS`
277
+ * wins over it.
278
+ */
279
+ recountIntervalMs?: number;
280
+ /**
281
+ * The smallest last total at which {@link recountIntervalMs} applies;
282
+ * smaller lists are counted on every frame. Defaults to 100 000.
283
+ * `REALTIME_RECOUNT_MIN_ROWS` wins over it.
284
+ */
285
+ recountMinRows?: number;
286
+ /**
287
+ * How changes are captured for realtime. `auto` (the default) installs
288
+ * database-level capture where the connection can and falls back to
289
+ * announcing the API's own writes where it cannot; `trigger` asks for
290
+ * capture and warns when it is refused; `wal` is `trigger` until WAL
291
+ * streaming ships; `off` announces the API's own writes only.
292
+ * `REALTIME_CDC` wins over it.
293
+ */
294
+ cdc?: "auto" | "wal" | "trigger" | "off";
295
+ /**
296
+ * The URL path the realtime socket accepts upgrades on. Defaults to the
297
+ * one the server derives from where it is mounted — `/` for a server at
298
+ * `/api`, which is where the SDK dials. Set it when clients dial elsewhere
299
+ * (a `websocketUrl` with a path). See {@link RealtimeSocketOptions.path}.
300
+ */
301
+ path?: string;
249
302
  }
250
303
  /**
251
304
  * Abstract realtime provider interface.
@@ -293,6 +346,25 @@ export interface RealtimeProvider {
293
346
  * Called during graceful shutdown before closing database connections.
294
347
  */
295
348
  stopListening?(): Promise<void>;
349
+ /**
350
+ * The long-lived connections realtime depends on, for `/health`. A change
351
+ * feed that has gone quiet — a half-open LISTEN connection — loses every
352
+ * external and cross-instance change while writes through this instance
353
+ * still look live, so it is reported rather than inferred.
354
+ */
355
+ health?(): RealtimeListenerHealth[];
356
+ }
357
+ /**
358
+ * One long-lived realtime connection, as `/health` reports it.
359
+ * @group Backend
360
+ */
361
+ export interface RealtimeListenerHealth {
362
+ /** What it carries — `"cdc"`, `"cross-instance"`, `"channel-bus"`. */
363
+ name: string;
364
+ /** Listening on a connection that answered its last heartbeat. */
365
+ connected: boolean;
366
+ /** When (epoch ms) it went down, while it is down. */
367
+ downSince?: number;
296
368
  }
297
369
  /**
298
370
  * Abstract collection registry interface.
@@ -341,6 +413,124 @@ export interface DataTransformer {
341
413
  */
342
414
  deserializeFromDatabase<M extends Record<string, unknown>>(data: Record<string, unknown>, collection: CollectionConfig): Promise<M>;
343
415
  }
416
+ /**
417
+ * The table column a value of a {@link SqlScriptResult} was read from, as the
418
+ * database itself reported it — not as the query's text suggests.
419
+ *
420
+ * @group Admin
421
+ */
422
+ export interface SqlScriptColumnSource {
423
+ schema: string;
424
+ table: string;
425
+ column: string;
426
+ }
427
+ /**
428
+ * One column of a {@link SqlScriptResult}, in result order.
429
+ *
430
+ * @group Admin
431
+ */
432
+ export interface SqlScriptColumn {
433
+ /** The name the result gives it. Two columns of one result may share it. */
434
+ name: string;
435
+ /** Its type, as the database names it: `integer`, `text[]`, `timestamp with time zone`. */
436
+ type?: string;
437
+ /**
438
+ * Where the database says the value came from: a plain reference to a
439
+ * column of a table or view, through any number of subqueries.
440
+ *
441
+ * Absent for everything no stored row holds — an expression, a cast that
442
+ * changes the type, a literal, an aggregate, a function's output, a
443
+ * `UNION`.
444
+ */
445
+ source?: SqlScriptColumnSource;
446
+ }
447
+ /**
448
+ * A table or view some column of a {@link SqlScriptResult} came from.
449
+ *
450
+ * @group Admin
451
+ */
452
+ export interface SqlScriptTable {
453
+ schema: string;
454
+ table: string;
455
+ /** What the relation is. Only a `table` or a `partitioned table` holds rows that can be updated by key. */
456
+ kind: "table" | "partitioned table" | "view" | "materialized view" | "foreign table" | "other";
457
+ /** Its primary key columns, in key order. Empty when it has none. */
458
+ primaryKey: string[];
459
+ /**
460
+ * Other tables inherit this one (by `INHERITS`, not as partitions). A row
461
+ * read from it may live in one of them, and a primary key is not unique
462
+ * across them.
463
+ */
464
+ hasInheritors: boolean;
465
+ }
466
+ /**
467
+ * Something the database said while a script ran: `there is no transaction in
468
+ * progress` for a ROLLBACK with nothing to end, a `RAISE NOTICE`.
469
+ *
470
+ * @group Admin
471
+ */
472
+ export interface SqlScriptNotice {
473
+ severity: string;
474
+ message: string;
475
+ }
476
+ /**
477
+ * What a SQL script a person wrote returned. See {@link SQLAdmin.runSqlScript}.
478
+ *
479
+ * @group Admin
480
+ */
481
+ export interface SqlScriptResult {
482
+ /**
483
+ * The last statement's rows, every value as the text the database wrote
484
+ * for it and `null` for SQL NULL — so a value written back is the value
485
+ * that was read, whatever its type. Keyed by column name: of two columns
486
+ * that share a name, the row holds the last.
487
+ */
488
+ rows: Record<string, string | null>[];
489
+ /** The last statement's columns, in order. */
490
+ columns: SqlScriptColumn[];
491
+ /** The tables and views {@link SqlScriptColumn.source} names. */
492
+ tables: SqlScriptTable[];
493
+ /** The last statement's command: `SELECT`, `UPDATE`, `CREATE TABLE` … */
494
+ command?: string;
495
+ /**
496
+ * How many rows the last statement returned or changed, when its command
497
+ * reports a count — or, for a {@link truncated} result, how many it
498
+ * returned, of which {@link rows} holds the first.
499
+ */
500
+ rowCount?: number;
501
+ /**
502
+ * The last statement returned more than a run keeps, and {@link rows}
503
+ * holds only its first rows. The Studio console's door keeps at most
504
+ * 10,000 rows, or 32 Mi characters of values, of one result: a run that
505
+ * held every row of a million-row table took the server down with it.
506
+ */
507
+ truncated?: boolean;
508
+ /** What the database said while the script ran, in order. */
509
+ notices: SqlScriptNotice[];
510
+ /**
511
+ * The database role the script ran as, when it asked for one — said by
512
+ * the transport that ran it. The role asked for, unless the server has
513
+ * role switching turned off (`DISABLE_DB_ROLE_SWITCHING`) and runs every
514
+ * statement as the connection owner, who is named here instead.
515
+ */
516
+ effectiveRole?: string;
517
+ }
518
+ /**
519
+ * A query's result being read a batch at a time — see `SQLAdmin.openSqlCursor`.
520
+ *
521
+ * @group Admin
522
+ */
523
+ export interface SqlCursor {
524
+ /** The result's column names, in order. */
525
+ readonly columns: string[];
526
+ /**
527
+ * The next rows, each value the text the database wrote for it and `null`
528
+ * for NULL — `null` once there are none left.
529
+ */
530
+ read(): Promise<(string | null)[][] | null>;
531
+ /** End the read and give the session back. Safe to call more than once. */
532
+ close(): Promise<void>;
533
+ }
344
534
  /**
345
535
  * Administrative operations for SQL-based databases (PostgreSQL, MySQL, etc.).
346
536
  * Used by the SQL Editor, RLS Editor, and schema browser.
@@ -356,13 +546,60 @@ export interface SQLAdmin {
356
546
  * before it is reused. For SQL a person wrote (the Studio editor), which
357
547
  * may `SET ROLE` and must not leave that on a pooled connection the
358
548
  * server's own queries use next.
549
+ *
550
+ * `maxResultBytes` refuses a result whose rows take more than that many
551
+ * bytes, with an error whose `code` is `RESULT_TOO_LARGE`, rather than
552
+ * return it — never a part of it. On a session of its own the read stops
553
+ * there; elsewhere the rows are measured once read. The WebSocket
554
+ * `EXECUTE_SQL` door sets it for every caller.
359
555
  */
360
556
  executeSql(sql: string, options?: {
361
557
  database?: string;
362
558
  role?: string;
363
559
  params?: unknown[];
364
560
  isolateSession?: boolean;
561
+ maxResultBytes?: number;
365
562
  }): Promise<Record<string, unknown>[]>;
563
+ /**
564
+ * Run a script a person wrote — the Studio SQL console — and describe what
565
+ * it returned.
566
+ *
567
+ * On a session of its own, reset afterwards, as `role` for every statement
568
+ * of it. Values come back as the database's text, and each column says
569
+ * which table column it was read from, when the database says so: what
570
+ * the console needs to write a cell back to the row it came from and to
571
+ * no other. A script that leaves a transaction open is refused, and the
572
+ * transaction rolled back: nothing outlives the run to be committed or
573
+ * rolled back by a later one.
574
+ *
575
+ * Aborting `signal` cancels the run: the statement in progress is stopped
576
+ * (`pg_cancel_backend` on a Postgres server; the in-process development
577
+ * database cannot be interrupted, and the run ends on its own), and the
578
+ * promise rejects.
579
+ */
580
+ runSqlScript?(sql: string, options?: {
581
+ database?: string;
582
+ role?: string;
583
+ signal?: AbortSignal;
584
+ }): Promise<SqlScriptResult>;
585
+ /**
586
+ * Read the whole result of ONE query a person wrote, a batch at a time —
587
+ * the Studio SQL console's "Download full result as CSV", which the
588
+ * console's 10,000-row cap would otherwise leave no way to get.
589
+ *
590
+ * Through a server-side cursor, in a read-only transaction, on a session
591
+ * of its own, as `role`: a write is refused by the database, and a second
592
+ * statement by the protocol. Each batch is read when the previous one has
593
+ * been consumed, so memory holds one batch whatever the result's size;
594
+ * every read runs under the session's `statement_timeout`. The session is
595
+ * reset and returned when the cursor is closed — which the caller must do,
596
+ * on success, failure or a reader that went away.
597
+ */
598
+ openSqlCursor?(sql: string, options?: {
599
+ database?: string;
600
+ role?: string;
601
+ batchSize?: number;
602
+ }): Promise<SqlCursor>;
366
603
  /**
367
604
  * Fetch the available databases on the server.
368
605
  */
@@ -388,6 +625,21 @@ export interface SQLAdmin {
388
625
  * Fetch the current database name.
389
626
  */
390
627
  fetchCurrentDatabase?(): Promise<string | undefined>;
628
+ /**
629
+ * Hold a `LISTEN` on `channel`, on a connection of its own that stays open
630
+ * and comes back after a drop, and call `onNotify` with each payload — what
631
+ * wakes the job worker when another process enqueues, instead of a poll
632
+ * every two seconds.
633
+ *
634
+ * Resolves once listening, with the stop. Rejects when no such connection
635
+ * can be had — an in-process database, a first connect that fails — so the
636
+ * caller can fall back to polling. `onReconnect` is called each time a
637
+ * dropped connection is listening again: whatever was sent meanwhile is
638
+ * lost. `channel` must be a plain identifier.
639
+ */
640
+ listen?(channel: string, onNotify: (payload: string) => void, options?: {
641
+ onReconnect?: () => void;
642
+ }): Promise<() => Promise<void>>;
391
643
  }
392
644
  /**
393
645
  * Administrative operations for document-based databases (MongoDB, Firestore, etc.).
@@ -433,6 +685,12 @@ export interface SchemaAdmin {
433
685
  export interface BranchInfo {
434
686
  /** Branch name (without prefix). */
435
687
  name: string;
688
+ /**
689
+ * The PostgreSQL database the branch is — what to connect to, and what to
690
+ * name as the source when copying it. As the branch's record says, never
691
+ * derived from `name` again.
692
+ */
693
+ database: string;
436
694
  /** The database this branch was created from. */
437
695
  parentDatabase: string;
438
696
  /** When the branch was created. */
@@ -598,6 +856,15 @@ export interface DataRateLimitCaller {
598
856
  * the caller by address at the anonymous allowance.
599
857
  */
600
858
  uid?: string;
859
+ /**
860
+ * The API key the connection authenticated with. Its frames count in the
861
+ * key's own bucket at the key's own `rate_limit`, the bucket its HTTP
862
+ * requests count in, rather than as `uid`.
863
+ */
864
+ apiKey?: {
865
+ id: string;
866
+ rate_limit: number | null;
867
+ };
601
868
  /**
602
869
  * A header of the request that opened the connection, by lower-case name:
603
870
  * the proxy headers an address is read from when proxies are declared.
@@ -607,11 +874,48 @@ export interface DataRateLimitCaller {
607
874
  socketAddress?: string;
608
875
  }
609
876
  /**
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.
877
+ * What the realtime socket shares with the HTTP data API: its request limits,
878
+ * so a frame carries the ones an HTTP request to the same rows does, and its
879
+ * API-key verification, so a key means the same thing on both.
612
880
  * @group Backend
613
881
  */
614
- export interface RealtimeSocketLimits {
882
+ export interface RealtimeSocketOptions {
883
+ /**
884
+ * Verify an `rk_` API key presented to `AUTHENTICATE`: the identity it acts
885
+ * as and the scopes it holds, or why it does not authenticate. Absent when
886
+ * the deployment has no API-key store, and then keys cannot authenticate
887
+ * the socket.
888
+ */
889
+ resolveApiKey?: (token: string) => Promise<{
890
+ uid: string;
891
+ roles: string[];
892
+ scopes: string[];
893
+ /** The key itself: its id and `rate_limit` decide which rate-limit bucket its frames count in. */
894
+ apiKey?: {
895
+ id: string;
896
+ rate_limit: number | null;
897
+ };
898
+ } | {
899
+ message: string;
900
+ }>;
901
+ /**
902
+ * Verify a session token presented to `AUTHENTICATE` with the backend's own
903
+ * keys, when the socket has no auth adapter to ask: who it names, or null
904
+ * when it does not verify. The server passes its backend's; absent, the
905
+ * driver falls back to the process default.
906
+ */
907
+ verifyAccessToken?: (token: string) => Promise<{
908
+ uid: string;
909
+ roles?: string[];
910
+ isAnonymous?: boolean;
911
+ claims?: Record<string, unknown>;
912
+ } | null>;
913
+ /**
914
+ * The backend's access model — what a session's roles hold on the socket's
915
+ * admin frames. The server passes its backend's; absent, the driver falls
916
+ * back to the process default.
917
+ */
918
+ accessModel?: AccessModel;
615
919
  /**
616
920
  * The largest frame accepted, in bytes — the data API's body limit. `0` or
617
921
  * less for none. Defaults to the server's default body limit.
@@ -626,6 +930,36 @@ export interface RealtimeSocketLimits {
626
930
  allowed: boolean;
627
931
  retryAfterMs: number;
628
932
  } | null>;
933
+ /**
934
+ * How often a socket that only listens has its identity re-read, in ms
935
+ * (default 30 000). A socket that sends frames is re-checked before each
936
+ * one; this bounds how long a subscription keeps receiving rows as an
937
+ * identity that has since signed out, been revoked, demoted or deleted.
938
+ */
939
+ identityRecheckIntervalMs?: number;
940
+ /**
941
+ * How often a socket that asked for heartbeats (`?rebase_caps=heartbeat`)
942
+ * is sent one, in ms (default 30 000). The SDK redials after two missed.
943
+ */
944
+ heartbeatIntervalMs?: number;
945
+ /**
946
+ * How many sockets one identity may hold open on this server; the next
947
+ * one's `AUTHENTICATE` is refused with `TOO_MANY_SOCKETS` and the socket
948
+ * closed (4002). Unset for no ceiling. See `realtime.maxSocketsPerUser`.
949
+ */
950
+ maxSocketsPerUser?: number;
951
+ /**
952
+ * The URL path the socket accepts upgrades on (default `/`). The SDK dials
953
+ * its `baseUrl` with no extra segment, so for a server mounted at `/api`
954
+ * this is `/`, and for one at `/app/api` it is `/app`. A trailing `/` is
955
+ * ignored, and so is the query string.
956
+ *
957
+ * An upgrade to any other path is not this socket's: it is left untouched
958
+ * for another `upgrade` listener on the same `http.Server` — a host app's
959
+ * own WebSocket server — or, when the socket's listener is the only one,
960
+ * answered `404` and closed, so the client is not left hanging.
961
+ */
962
+ path?: string;
629
963
  }
630
964
  /**
631
965
  * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.
@@ -703,8 +1037,11 @@ export interface BackendBootstrapper {
703
1037
  */
704
1038
  initializeRealtime?(config: unknown, driverResult: InitializedDriver): Promise<RealtimeProvider | undefined>;
705
1039
  /**
706
- * Mount any driver-specific HTTP routes (e.g., custom admin endpoints).
707
- * Called after all drivers are initialized.
1040
+ * Never called: the server mounts no route on a bootstrapper's behalf. A
1041
+ * driver that needs HTTP routes exports a route factory, and the app
1042
+ * mounts it where it chooses.
1043
+ *
1044
+ * @deprecated Never called. Will be removed in a future release.
708
1045
  */
709
1046
  mountRoutes?(app: unknown, basePath: string, driverResult: InitializedDriver): void;
710
1047
  /**
@@ -832,7 +1169,7 @@ export interface BackendBootstrapper {
832
1169
  /**
833
1170
  * Initialize WebSocket server for realtime operations.
834
1171
  */
835
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver.js").DataDriver, config?: unknown, authAdapter?: AuthAdapter, limits?: RealtimeSocketLimits): Promise<void> | void;
1172
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver.js").DataDriver, config?: unknown, authAdapter?: AuthAdapter, limits?: RealtimeSocketOptions): Promise<void> | void;
836
1173
  }
837
1174
  /**
838
1175
  * Result of `BackendBootstrapper.initializeDriver()`.
@@ -852,11 +1189,23 @@ export interface InitializedDriver {
852
1189
  * instead of collections loaded from config files.
853
1190
  */
854
1191
  collections?: import("./collections.js").CollectionConfig[];
1192
+ /**
1193
+ * Tables the driver found while deriving {@link collections} and did not
1194
+ * serve, because row-level security is disabled on them and so they have
1195
+ * no authorization model (`baas.unprotectedTables` left at `"exclude"`).
1196
+ *
1197
+ * The server names them when it has nothing to serve: "the database has
1198
+ * no tables" is untrue of a database whose tables were all skipped.
1199
+ */
1200
+ unprotectedTables?: {
1201
+ schema: string;
1202
+ table: string;
1203
+ }[];
855
1204
  /** The underlying database connection (for lifecycle management). */
856
1205
  connection?: DatabaseConnection;
857
1206
  /**
858
1207
  * Opaque handle that the bootstrapper can use in subsequent hooks
859
- * (e.g., `initializeAuth`, `mountRoutes`) to access driver internals.
1208
+ * (e.g., `initializeAuth`, `getAdmin`) to access driver internals.
860
1209
  * Not used by the coordinator.
861
1210
  */
862
1211
  internals?: unknown;
@@ -868,8 +1217,6 @@ export interface InitializedDriver {
868
1217
  export interface BootstrappedAuth {
869
1218
  /** User management service. */
870
1219
  userService: unknown;
871
- /** Role management service (optional, roles are now simple strings). */
872
- roleService?: unknown;
873
1220
  /** Email service (optional). */
874
1221
  emailService?: unknown;
875
1222
  /** Combined Auth Repository for unified token and user management. */
@@ -25,3 +25,105 @@ 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
+ /**
91
+ * A short-lived link to one backup's bytes, from `POST /admin/backups/download-url`.
92
+ *
93
+ * The URL needs no `Authorization` header — the token in it stands in for one,
94
+ * for this backup only — so a browser can be sent to it and stream the dump to
95
+ * disk instead of holding it in the page.
96
+ */
97
+ export interface BackupDownloadLink {
98
+ /**
99
+ * `…/admin/backups/download?key=…&token=…`. The server answers it as a
100
+ * path on itself; the SDK's `downloadUrl()` answers it absolute, on the
101
+ * client's own base URL.
102
+ */
103
+ url: string;
104
+ /** The token in {@link url}, on its own. */
105
+ token: string;
106
+ /** Seconds until the link stops working (60). */
107
+ expiresIn: number;
108
+ }
109
+ /** What the backup listing (`GET /admin/backups`) answers. */
110
+ export interface BackupListing {
111
+ /** Backups at the destination, newest first. */
112
+ backups: BackupInfo[];
113
+ /** The kind of destination they were read from. */
114
+ destinationKind: BackupDestinationKind;
115
+ /** False when `BACKUP_DESTINATION` is unset. */
116
+ configured: boolean;
117
+ /**
118
+ * The scheduled backup job, or `null` when the deployment registers none.
119
+ * Absent from a server that predates the field.
120
+ */
121
+ schedule?: BackupScheduleStatus | null;
122
+ /**
123
+ * True when the destination is a local path and the scheduled backup runs
124
+ * in another process (the worker of a split deployment). The backups
125
+ * listed are the ones on this process's disk; the scheduled runs write to
126
+ * theirs, which is the same directory only when both mount it.
127
+ */
128
+ localDiskOfThisProcess?: boolean;
129
+ }
@@ -0,0 +1,74 @@
1
+ /** One change to a collection. `path` is a list of object keys. */
2
+ export type CollectionPatchOp = {
3
+ op: "set";
4
+ path: string[];
5
+ value: unknown;
6
+ } | {
7
+ op: "remove";
8
+ path: string[];
9
+ };
10
+ /** @group Models */
11
+ export type CollectionPatch = CollectionPatchOp[];
12
+ /**
13
+ * Keys the runtime adds to a collection that must never be written into its
14
+ * source. `resolvedRelation` is the registry's resolved form of a relation; a
15
+ * file holding one does not type-check (`target` is missing from it).
16
+ */
17
+ export declare const RUNTIME_ONLY_COLLECTION_KEYS: readonly string[];
18
+ /** A JSON value as JSON would carry it: functions, `undefined` and symbols dropped. */
19
+ export declare function toCollectionJson(value: unknown): unknown;
20
+ /**
21
+ * The operations that take `before` to `after`, both JSON values.
22
+ *
23
+ * Objects are compared key by key; anything else that differs — a primitive,
24
+ * an array, a change of kind — is set whole.
25
+ */
26
+ export declare function diffJsonValues(before: unknown, after: unknown, path?: string[]): CollectionPatch;
27
+ /**
28
+ * The patch, with every flat presentation key moved into its `admin` block.
29
+ *
30
+ * The panel edits a flat view model — `icon` beside `name`, a property's
31
+ * `readOnly` beside its `type` — while the file keeps them under `admin`. The
32
+ * difference is taken on the flat model, because that is where a cleared field
33
+ * shows up as a removed key (the `admin` copy the model was loaded with still
34
+ * holds the old value), and moved here.
35
+ *
36
+ * The flat value wins over the `admin` copy, as in {@link nestAdminKeysOf}:
37
+ * operations that were already under `admin` are ordered first, so the flat
38
+ * one — what the form just wrote — is applied last.
39
+ */
40
+ export declare function nestCollectionPatchPaths(patch: CollectionPatch): CollectionPatch;
41
+ /**
42
+ * The patch that takes the collection the editor loaded to the one it is
43
+ * saving, in the authoring shape. Empty when nothing changed.
44
+ *
45
+ * Both sides go through JSON first, so whatever JSON cannot carry — a
46
+ * function, a value it drops — is equally absent from both and never appears
47
+ * as a change.
48
+ */
49
+ export declare function diffCollections(loaded: unknown, saving: unknown): CollectionPatch;
50
+ /**
51
+ * Why a patch may not be applied, or an empty list.
52
+ *
53
+ * Checked by every door that accepts one: a path is a list of non-empty object
54
+ * keys that cannot reach a prototype, and no runtime-only key may be written.
55
+ */
56
+ export declare function collectionPatchProblems(patch: unknown): string[];
57
+ /**
58
+ * The first runtime-only key (see {@link RUNTIME_ONLY_COLLECTION_KEYS}) found
59
+ * anywhere in a value, or `undefined`. For the doors that take a whole
60
+ * collection rather than a patch.
61
+ */
62
+ export declare function runtimeOnlyKeyIn(value: unknown): string | undefined;
63
+ /** A patch every door may apply: {@link collectionPatchProblems} found nothing. */
64
+ export declare function isCollectionPatch(patch: unknown): patch is CollectionPatch;
65
+ /**
66
+ * `target` with the patch applied, copying only what it changes.
67
+ *
68
+ * Everything the patch does not reach is shared with `target` by reference —
69
+ * functions, thunks and all — which is what lets a planner see a relation's
70
+ * `target()` on the patched collection exactly as it was. Intermediate objects
71
+ * a `set` needs are created; a `remove` of a key that is not there is nothing
72
+ * to do.
73
+ */
74
+ export declare function applyCollectionPatch<T>(target: T, patch: CollectionPatch): T;
@@ -4,6 +4,7 @@ import type { User } from "../users/index.js";
4
4
  import type { EmailSendResult } from "../controllers/email.js";
5
5
  import type { Relation } from "./relations.js";
6
6
  import type { SecurityRule } from "./security_rules.js";
7
+ import type { RoleDefinition, ScopeDefinition } from "./scopes.js";
7
8
  import type { SearchConfig } from "./search.js";
8
9
  import type { CollectionIndex } from "./indexes.js";
9
10
  import type { CollectionTenantConfig } from "./tenancy.js";
@@ -614,6 +615,28 @@ export type InferCollectionConfigType<S extends CollectionConfig> = S extends Co
614
615
  export interface AuthCollectionConfig {
615
616
  /** Set to true to mark this collection as the authentication collection. */
616
617
  enabled: boolean;
618
+ /**
619
+ * The app's roles and the admin-plane scopes each holds.
620
+ *
621
+ * `admin` is built in — it holds every scope — and cannot be declared. A
622
+ * role used only by RLS policies needs no entry here. See
623
+ * {@link RoleDefinition}.
624
+ */
625
+ roles?: Record<string, RoleDefinition>;
626
+ /**
627
+ * Scopes the app defines for its own operations, named `resource:action`
628
+ * like the built-in ones and checked with `requireScope` in a function.
629
+ * Every signed-in person holds them; they exist so a key can be narrowed
630
+ * to one action. See {@link ScopeDefinition}.
631
+ */
632
+ scopes?: Record<string, ScopeDefinition>;
633
+ /**
634
+ * Let every signed-in account mint personal API keys at
635
+ * `/api/auth/keys`. A personal key acts as its owner and can never hold
636
+ * a scope its owner does not. Off by default: each one is a long-lived
637
+ * credential for the account, and an app opts into handing those out.
638
+ */
639
+ personalKeys?: boolean;
617
640
  /**
618
641
  * Called when an admin creates a user, through the collection REST API or
619
642
  * `POST /admin/users`.