@rebasepro/types 0.24.0 → 0.24.1-canary.g56af4a4

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.
@@ -303,6 +303,23 @@ export interface UserCreationPrepareResult {
303
303
  /** Whether an invitation was sent (only relevant when hookHandledEmail is true). */
304
304
  invitationSent: boolean;
305
305
  }
306
+ /**
307
+ * What the door creating a user knows about its caller, for
308
+ * `AuthAdapter.prepareUserCreation()`.
309
+ *
310
+ * @group Auth
311
+ */
312
+ export interface UserCreationOptions {
313
+ /**
314
+ * Whether the caller may waive the password policy with
315
+ * `skipPasswordPolicy: true` in the body. The doors set it for an
316
+ * administrator (the service key signs in as one) and for nobody else, so
317
+ * an adapter that reads the flag is taking an administrator at their word —
318
+ * an import of accounts whose passwords were set under another system's
319
+ * rules. Absent, the flag waives nothing.
320
+ */
321
+ mayWaivePasswordPolicy?: boolean;
322
+ }
306
323
  /**
307
324
  * What a create body for an auth collection may name beyond the collection's
308
325
  * own declared fields — see `AuthAdapter.describeUserCreationContract()`.
@@ -322,6 +339,16 @@ export interface UserCreationWriteContract {
322
339
  * deletes it before the row is ever built.
323
340
  */
324
341
  extraFields: string[];
342
+ /**
343
+ * Each body's `prepareUserCreation` touches nothing but its own result —
344
+ * no hook, no email, no write — so a bulk create may prepare several at
345
+ * once. The built-in adapter's path with no `onCreateUser`, no
346
+ * `onAdminCreateUser` and the default `hashPassword` is: it hashes a
347
+ * password and normalizes an email, and a 1,000-row bulk spent half a
348
+ * minute hashing one password at a time. Leave it unset when a hook runs:
349
+ * preparations then run one at a time, in the order the rows were given.
350
+ */
351
+ independent?: boolean;
325
352
  }
326
353
  /**
327
354
  * Result of `AuthAdapter.finalizeUserCreation()`.
@@ -505,11 +532,15 @@ export interface AuthAdapter {
505
532
  *
506
533
  * If not implemented, the collection saves values as-is (no password hashing).
507
534
  *
535
+ * A chosen `password` is held to the password policy unless the body says
536
+ * `skipPasswordPolicy: true` and `options.mayWaivePasswordPolicy` is set.
537
+ *
508
538
  * @param values - Raw request body from the client.
509
539
  * @param collectionAuth - The parsed `AuthCollectionConfig` from the collection (if `auth` is an object).
540
+ * @param options - What the door knows about its caller.
510
541
  * @returns Processed values ready for `driver.save()`, plus metadata for the post-save step.
511
542
  */
512
- prepareUserCreation?(values: Record<string, unknown>, collectionAuth?: unknown): Promise<UserCreationPrepareResult>;
543
+ prepareUserCreation?(values: Record<string, unknown>, collectionAuth?: unknown, options?: UserCreationOptions): Promise<UserCreationPrepareResult>;
513
544
  /**
514
545
  * Describe what a create body for this auth collection is allowed to name,
515
546
  * so unknown-field validation can run on it.
@@ -252,6 +252,36 @@ export interface RealtimeChannelsConfig {
252
252
  * Defaults to 1000. `REALTIME_MAX_SUBSCRIPTIONS_PER_SOCKET` wins over it.
253
253
  */
254
254
  maxSubscriptionsPerSocket?: number;
255
+ /**
256
+ * How many sockets one user (one identity: a signed-in user, an API key's
257
+ * user, an impersonated user) may hold open on one server instance. The
258
+ * next one's `AUTHENTICATE` is refused with `TOO_MANY_SOCKETS` and the
259
+ * socket closed with 4002; the SDK keeps retrying with backoff, so it gets
260
+ * in once another closes. A positive whole number, checked at boot.
261
+ * Unset (the default) is no ceiling. `REALTIME_MAX_SOCKETS_PER_USER` wins
262
+ * over it.
263
+ */
264
+ maxSocketsPerUser?: number;
265
+ /**
266
+ * The longest a change waits for the refetch that delivers it, in ms. A
267
+ * refetch waits for 300 ms without a change, and at most this long under a
268
+ * steady stream of writes. Defaults to 1000. A positive whole number,
269
+ * checked at boot. `REALTIME_REFETCH_MAX_WAIT_MS` wins over it.
270
+ */
271
+ refetchMaxWaitMs?: number;
272
+ /**
273
+ * How long a big list subscription's total may be reused before it is
274
+ * counted again, in ms. Unset (the default), every frame counts. Frames
275
+ * that reuse it carry `meta.totalAsOf`. `REALTIME_RECOUNT_INTERVAL_MS`
276
+ * wins over it.
277
+ */
278
+ recountIntervalMs?: number;
279
+ /**
280
+ * The smallest last total at which {@link recountIntervalMs} applies;
281
+ * smaller lists are counted on every frame. Defaults to 100 000.
282
+ * `REALTIME_RECOUNT_MIN_ROWS` wins over it.
283
+ */
284
+ recountMinRows?: number;
255
285
  }
256
286
  /**
257
287
  * Abstract realtime provider interface.
@@ -445,8 +475,19 @@ export interface SqlScriptResult {
445
475
  tables: SqlScriptTable[];
446
476
  /** The last statement's command: `SELECT`, `UPDATE`, `CREATE TABLE` … */
447
477
  command?: string;
448
- /** How many rows the last statement returned or changed, when its command reports a count. */
478
+ /**
479
+ * How many rows the last statement returned or changed, when its command
480
+ * reports a count — or, for a {@link truncated} result, how many it
481
+ * returned, of which {@link rows} holds the first.
482
+ */
449
483
  rowCount?: number;
484
+ /**
485
+ * The last statement returned more than a run keeps, and {@link rows}
486
+ * holds only its first rows. The Studio console's door keeps at most
487
+ * 10,000 rows, or 32 Mi characters of values, of one result: a run that
488
+ * held every row of a million-row table took the server down with it.
489
+ */
490
+ truncated?: boolean;
450
491
  /** What the database said while the script ran, in order. */
451
492
  notices: SqlScriptNotice[];
452
493
  /**
@@ -457,6 +498,22 @@ export interface SqlScriptResult {
457
498
  */
458
499
  effectiveRole?: string;
459
500
  }
501
+ /**
502
+ * A query's result being read a batch at a time — see `SQLAdmin.openSqlCursor`.
503
+ *
504
+ * @group Admin
505
+ */
506
+ export interface SqlCursor {
507
+ /** The result's column names, in order. */
508
+ readonly columns: string[];
509
+ /**
510
+ * The next rows, each value the text the database wrote for it and `null`
511
+ * for NULL — `null` once there are none left.
512
+ */
513
+ read(): Promise<(string | null)[][] | null>;
514
+ /** End the read and give the session back. Safe to call more than once. */
515
+ close(): Promise<void>;
516
+ }
460
517
  /**
461
518
  * Administrative operations for SQL-based databases (PostgreSQL, MySQL, etc.).
462
519
  * Used by the SQL Editor, RLS Editor, and schema browser.
@@ -472,12 +529,19 @@ export interface SQLAdmin {
472
529
  * before it is reused. For SQL a person wrote (the Studio editor), which
473
530
  * may `SET ROLE` and must not leave that on a pooled connection the
474
531
  * server's own queries use next.
532
+ *
533
+ * `maxResultBytes` refuses a result whose rows take more than that many
534
+ * bytes, with an error whose `code` is `RESULT_TOO_LARGE`, rather than
535
+ * return it — never a part of it. On a session of its own the read stops
536
+ * there; elsewhere the rows are measured once read. The WebSocket
537
+ * `EXECUTE_SQL` door sets it for every caller.
475
538
  */
476
539
  executeSql(sql: string, options?: {
477
540
  database?: string;
478
541
  role?: string;
479
542
  params?: unknown[];
480
543
  isolateSession?: boolean;
544
+ maxResultBytes?: number;
481
545
  }): Promise<Record<string, unknown>[]>;
482
546
  /**
483
547
  * Run a script a person wrote — the Studio SQL console — and describe what
@@ -490,11 +554,35 @@ export interface SQLAdmin {
490
554
  * no other. A script that leaves a transaction open is refused, and the
491
555
  * transaction rolled back: nothing outlives the run to be committed or
492
556
  * rolled back by a later one.
557
+ *
558
+ * Aborting `signal` cancels the run: the statement in progress is stopped
559
+ * (`pg_cancel_backend` on a Postgres server; the in-process development
560
+ * database cannot be interrupted, and the run ends on its own), and the
561
+ * promise rejects.
493
562
  */
494
563
  runSqlScript?(sql: string, options?: {
495
564
  database?: string;
496
565
  role?: string;
566
+ signal?: AbortSignal;
497
567
  }): Promise<SqlScriptResult>;
568
+ /**
569
+ * Read the whole result of ONE query a person wrote, a batch at a time —
570
+ * the Studio SQL console's "Download full result as CSV", which the
571
+ * console's 10,000-row cap would otherwise leave no way to get.
572
+ *
573
+ * Through a server-side cursor, in a read-only transaction, on a session
574
+ * of its own, as `role`: a write is refused by the database, and a second
575
+ * statement by the protocol. Each batch is read when the previous one has
576
+ * been consumed, so memory holds one batch whatever the result's size;
577
+ * every read runs under the session's `statement_timeout`. The session is
578
+ * reset and returned when the cursor is closed — which the caller must do,
579
+ * on success, failure or a reader that went away.
580
+ */
581
+ openSqlCursor?(sql: string, options?: {
582
+ database?: string;
583
+ role?: string;
584
+ batchSize?: number;
585
+ }): Promise<SqlCursor>;
498
586
  /**
499
587
  * Fetch the available databases on the server.
500
588
  */
@@ -520,6 +608,21 @@ export interface SQLAdmin {
520
608
  * Fetch the current database name.
521
609
  */
522
610
  fetchCurrentDatabase?(): Promise<string | undefined>;
611
+ /**
612
+ * Hold a `LISTEN` on `channel`, on a connection of its own that stays open
613
+ * and comes back after a drop, and call `onNotify` with each payload — what
614
+ * wakes the job worker when another process enqueues, instead of a poll
615
+ * every two seconds.
616
+ *
617
+ * Resolves once listening, with the stop. Rejects when no such connection
618
+ * can be had — an in-process database, a first connect that fails — so the
619
+ * caller can fall back to polling. `onReconnect` is called each time a
620
+ * dropped connection is listening again: whatever was sent meanwhile is
621
+ * lost. `channel` must be a plain identifier.
622
+ */
623
+ listen?(channel: string, onNotify: (payload: string) => void, options?: {
624
+ onReconnect?: () => void;
625
+ }): Promise<() => Promise<void>>;
523
626
  }
524
627
  /**
525
628
  * Administrative operations for document-based databases (MongoDB, Firestore, etc.).
@@ -799,6 +902,17 @@ export interface RealtimeSocketOptions {
799
902
  * identity that has since signed out, been revoked, demoted or deleted.
800
903
  */
801
904
  identityRecheckIntervalMs?: number;
905
+ /**
906
+ * How often a socket that asked for heartbeats (`?rebase_caps=heartbeat`)
907
+ * is sent one, in ms (default 30 000). The SDK redials after two missed.
908
+ */
909
+ heartbeatIntervalMs?: number;
910
+ /**
911
+ * How many sockets one identity may hold open on this server; the next
912
+ * one's `AUTHENTICATE` is refused with `TOO_MANY_SOCKETS` and the socket
913
+ * closed (4002). Unset for no ceiling. See `realtime.maxSocketsPerUser`.
914
+ */
915
+ maxSocketsPerUser?: number;
802
916
  }
803
917
  /**
804
918
  * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.
@@ -87,6 +87,25 @@ export interface BackupScheduleStatus {
87
87
  */
88
88
  historyError?: string;
89
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
+ }
90
109
  /** What the backup listing (`GET /admin/backups`) answers. */
91
110
  export interface BackupListing {
92
111
  /** Backups at the destination, newest first. */
@@ -1,4 +1,5 @@
1
1
  import type { RebaseServerClient } from "../controllers/client.js";
2
+ import type { RegisteredDatabase } from "./register.js";
2
3
  /**
3
4
  * Cron Job type definitions for Rebase.
4
5
  *
@@ -10,7 +11,7 @@ import type { RebaseServerClient } from "../controllers/client.js";
10
11
  * A cron job definition file exports this shape as its default export.
11
12
  * See the example cron files in `app/backend/crons/` for usage.
12
13
  */
13
- export interface CronJobDefinition {
14
+ export interface CronJobDefinition<DB = RegisteredDatabase> {
14
15
  schedule: string;
15
16
  /**
16
17
  * IANA time zone the schedule is read in, e.g. `"Europe/Madrid"`.
@@ -84,20 +85,21 @@ export interface CronJobDefinition {
84
85
  * Receives a context object with the data driver and logger.
85
86
  * May return arbitrary JSON-serialisable data stored in the log.
86
87
  */
87
- handler: (ctx: CronJobContext) => Promise<unknown> | unknown;
88
+ handler: (ctx: CronJobContext<DB>) => Promise<unknown> | unknown;
88
89
  }
89
90
  /**
90
91
  * Context passed to each cron handler invocation.
91
92
  */
92
- export interface CronJobContext {
93
+ export interface CronJobContext<DB = RegisteredDatabase> {
93
94
  /** The job's unique ID (derived from filename). */
94
95
  jobId: string;
95
96
  /** The current scheduled tick timestamp. */
96
97
  scheduledAt: Date;
97
98
  /**
98
- * A simple logger scoped to this job run. Each line is kept with the
99
- * run's log entry and also written to the server log at `info`, with a
100
- * `cron` field holding the job ID.
99
+ * A simple logger scoped to this job run. Each line is written to the
100
+ * server log at `info`, with a `cron` field holding the job ID, and kept
101
+ * with the run's log entry — the first 500 lines or 64 KB of them,
102
+ * whichever comes first, then one line saying how many more there were.
101
103
  */
102
104
  log: (...args: unknown[]) => void;
103
105
  /**
@@ -156,7 +158,7 @@ export interface CronJobContext {
156
158
  * }
157
159
  * });
158
160
  */
159
- rebase: RebaseServerClient;
161
+ rebase: RebaseServerClient<DB>;
160
162
  }
161
163
  export type CronJobRunState = "idle" | "running" | "success" | "error" | "disabled";
162
164
  /**
@@ -244,3 +246,16 @@ export interface CronJobLogEntry {
244
246
  /** Whether this was a manual trigger. */
245
247
  manual?: boolean;
246
248
  }
249
+ /**
250
+ * The answer to a manual trigger made with `?async=1`
251
+ * (`client.cron.triggerJob(id, { async: true })`): the run has started and
252
+ * carries on after the answer. Its outcome is in the run history, as the entry
253
+ * with `manual: true` and this `startedAt`.
254
+ */
255
+ export interface CronJobStarted {
256
+ accepted: true;
257
+ /** ISO timestamp the run started at — the `startedAt` of its log entry. */
258
+ startedAt: string;
259
+ /** The job's status as the run began. */
260
+ job: CronJobStatus;
261
+ }
@@ -32,6 +32,17 @@ export interface Entity<M extends Record<string, unknown> = Record<string, unkno
32
32
  * and a record fetched by id never has one.
33
33
  */
34
34
  searchMatches?: SearchMatch[];
35
+ /**
36
+ * The version of the row this entity was read at: the `ETag` the read
37
+ * reported, for a `findById`, a `PATCH` answer or a live row frame. A
38
+ * form saves with it as `If-Match`, so a write made against a version
39
+ * someone has since replaced is refused instead of reverting their change.
40
+ *
41
+ * Absent for a row from a list, from a cache, or from a server or data
42
+ * source that does not report versions — a save then is unconditional,
43
+ * as it always was.
44
+ */
45
+ etag?: string;
35
46
  /**
36
47
  * Which driver this entity belongs to (e.g., 'postgres', 'firestore').
37
48
  * If not specified, the default driver is assumed.
@@ -27,8 +27,20 @@ export interface EntityHistoryEntry {
27
27
  action: "create" | "update" | "delete";
28
28
  /** Which fields changed. `null` for creates and deletes. */
29
29
  changed_fields: string[] | null;
30
+ /** The row after the change; for a delete, the row that was deleted. */
30
31
  values: Record<string, unknown> | null;
32
+ /**
33
+ * On an update, what the changed fields held before it. `null` for creates
34
+ * and deletes. Which fields it holds depends on {@link format}.
35
+ */
31
36
  previous_values: Record<string, unknown> | null;
37
+ /**
38
+ * How the entry was stored. Absent or `1`: `previous_values` is the whole
39
+ * row before the change. `2`: it holds the `changed_fields` only — a field
40
+ * that is absent from it was absent before — and every other field is
41
+ * unchanged, so it is in `values`.
42
+ */
43
+ format?: 1 | 2;
32
44
  updated_by: string | null;
33
45
  /** ISO-8601. A driver storing a native date converts on read. */
34
46
  updated_at: string;
@@ -46,7 +58,9 @@ export interface RecordHistoryParams {
46
58
  updatedBy?: string | null;
47
59
  }
48
60
  /**
49
- * How much history to keep. Pruning runs per row after each write.
61
+ * How much history to keep. Both rules are applied to a row after each write
62
+ * to it; `ttlDays` is also applied to every row by an hourly sweep, so the
63
+ * history of a row nobody writes again (a deleted one) still ages out.
50
64
  * @group Backend
51
65
  */
52
66
  export interface HistoryRetentionConfig {
@@ -35,3 +35,4 @@ export * from "./project_manifest.js";
35
35
  export * from "./collection_contract.js";
36
36
  export * from "./schema_version.js";
37
37
  export * from "./storage_authorize.js";
38
+ export * from "./register.js";
@@ -83,6 +83,14 @@ export interface RebaseBackendAppConfig {
83
83
  * its default export. Default `collections/users`.
84
84
  */
85
85
  usersCollection?: string;
86
+ /**
87
+ * Type server-side data from the generated `Database`. When `true`, every
88
+ * `rebase generate-sdk` — `rebase dev`'s included, and the first one on a
89
+ * fresh clone — writes the types and the `Register` declaration to
90
+ * `<config>/generated/`. `rebase generate-sdk --register` sets it.
91
+ * Default `false`.
92
+ */
93
+ registerDatabase?: boolean;
86
94
  /**
87
95
  * `runtime: "custom"` only. Dockerfile path relative to the repository root.
88
96
  * Default `Dockerfile`.
@@ -5,6 +5,12 @@ import type { ColorKey, ColorScheme } from "./chips.js";
5
5
  import type { AuthState } from "../controllers/auth_state.js";
6
6
  import type { AfterReadProps, BeforeSaveProps } from "./entity_callbacks.js";
7
7
  import type { User } from "../users/index.js";
8
+ /**
9
+ * A SQL expression, written in template form: ``"sql`gen_random_uuid()`"``.
10
+ * The `sql` and backticks are markup; the text between them is the SQL.
11
+ * @group Entity properties
12
+ */
13
+ export type SqlExpression = `sql\`${string}\``;
8
14
  /**
9
15
  * Callbacks/Hooks for individual property fields
10
16
  * @group Entity properties
@@ -372,6 +378,25 @@ export interface BaseProperty<CustomProps = unknown> {
372
378
  * ```
373
379
  */
374
380
  access?: FieldAccess;
381
+ /**
382
+ * The database fills this column when a create leaves it empty: a
383
+ * `NOT NULL DEFAULT <expression>` column such as `gen_random_uuid()`,
384
+ * `nextval(…)` or `now()`, whose value no property can state.
385
+ *
386
+ * A `validation.required` field with it may be left out of a create, by
387
+ * the admin form and by the API alike, and a create that sends it as `null`
388
+ * leaves it to the database rather than storing `NULL`. It is still
389
+ * required when a stored record is edited. The generated SDK's `Insert`
390
+ * type makes the field optional. Defaults to `false`.
391
+ *
392
+ * Give the expression, in the template form `isId` takes
393
+ * (``databaseDefault: "sql`gen_random_uuid()`"``), and the schema planner
394
+ * writes it as the column's `DEFAULT`, so `rebase db push` keeps it. `true`
395
+ * declares a DEFAULT someone else wrote and the planner does not write
396
+ * one, so a push plans dropping it. "Import from table" and
397
+ * `rebase schema introspect` set the expression they read.
398
+ */
399
+ databaseDefault?: boolean | SqlExpression;
375
400
  /**
376
401
  * Use this to define dynamic properties that change based on certain conditions
377
402
  * or on the entity's values. For example, you can make a field read-only if
@@ -421,8 +446,9 @@ export interface StringProperty extends BaseProperty {
421
446
  */
422
447
  defaultValue?: string;
423
448
  /**
424
- * Optional database column type. If not set, it defaults to `varchar` or `uuid` depending on `isId` configuration.
425
- * Use `text` for strings with unbound length, `char` for fixed-length strings, or `varchar` for variable-length strings with a limit.
449
+ * Optional database column type. If not set, it is `text` — or `uuid` for an `isId: "uuid"` key.
450
+ * `validation.max` does not bound a `text` column; declare `varchar` (or `char`) to get a
451
+ * width, which is then taken from `validation.max` (255 when it states none).
426
452
  */
427
453
  columnType?: "varchar" | "text" | "char" | "uuid";
428
454
  /**
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The project-wide registry of generated types, filled by module augmentation.
3
+ *
4
+ * Empty by default, and every server-side data surface is untyped while it is:
5
+ * `context.data`, `rebase.dataAsAdmin`, a cron's `ctx.rebase` and a function's
6
+ * `ctx.rebase` accept any collection by any name and hand back open records.
7
+ *
8
+ * A project that generated its types with `rebase generate-sdk` registers its
9
+ * `Database` once, in any file the TypeScript program includes:
10
+ *
11
+ * ```ts
12
+ * // src/rebase-env.d.ts — or the top of any module file
13
+ * import type { Database } from "./generated/database.types";
14
+ *
15
+ * declare module "@rebasepro/types" {
16
+ * interface Register {
17
+ * database: Database;
18
+ * }
19
+ * }
20
+ * ```
21
+ *
22
+ * From then on every one of those surfaces is typed from it: rows, filters,
23
+ * sorts, `include`, and the inputs `create` and `update` accept. Nothing about
24
+ * the runtime changes; the registration is a declaration.
25
+ *
26
+ * A single function or cron can still name its own type instead —
27
+ * `defineFunction<Database>(…)`, `defineCron<Database>({…})` — which wins over
28
+ * the registration.
29
+ *
30
+ * @group Data
31
+ */
32
+ export interface Register {
33
+ }
34
+ /**
35
+ * The `Database` a project registered on {@link Register}, or `unknown` when it
36
+ * registered none — which is what keeps an unregistered project's server-side
37
+ * data untyped, exactly as it was before the registry existed.
38
+ *
39
+ * @group Data
40
+ */
41
+ export type RegisteredDatabase = Register extends {
42
+ database: infer DB;
43
+ } ? DB : unknown;
@@ -22,6 +22,23 @@ export type OnAction = "cascade" | "restrict" | "no action" | "set null" | "set
22
22
  * @group Models
23
23
  */
24
24
  export declare const JUNCTION_PIVOT_KEY = "_pivot";
25
+ /**
26
+ * The key a row carries the totals of its counted relations under.
27
+ *
28
+ * An include that asks for a count — `include: { tags: { limit: 4, count: true } }`
29
+ * — serves, beside the (capped) `tags`, how many related rows there are in all:
30
+ * `{ id: 1, tags: [ …4 rows ], _count: { tags: 37 } }`. One object per row,
31
+ * keyed by relation name, so counting a second relation adds a key rather than
32
+ * a second top-level name.
33
+ *
34
+ * Leading underscore, like {@link JUNCTION_PIVOT_KEY} and `_matches`: it
35
+ * describes the read, not the record, and no column or form writes it. A
36
+ * collection with a property of this name cannot ask for a count — the read
37
+ * answers 400 rather than writing over the column.
38
+ *
39
+ * @group Models
40
+ */
41
+ export declare const INCLUDE_COUNT_KEY = "_count";
25
42
  /**
26
43
  * What kind of link a relation is.
27
44
  *
@@ -108,6 +125,23 @@ export interface BelongsToRelation extends RelationBase {
108
125
  * Defaults to `<relationName>_id`.
109
126
  */
110
127
  localKey?: string;
128
+ /**
129
+ * Whether Rebase creates a btree index on {@link localKey}. Defaults to
130
+ * `true`.
131
+ *
132
+ * Postgres indexes the key a foreign key *points at* but never the column
133
+ * holding it, so without one every read from the other side seq-scans this
134
+ * table: a `hasMany`/`hasOne` include, a nested listing such as
135
+ * `/authors/5/posts`, and the `ON DELETE` check when a target row is
136
+ * deleted. The derived index is skipped on its own when the column already
137
+ * leads one — the primary key, a `validation.unique`, or an entry in
138
+ * `indexes:` — so it never duplicates an index you declared.
139
+ *
140
+ * Set `false` for a table so write-heavy that the index costs more than
141
+ * the lookups it serves, or when you keep an index of your own on the
142
+ * column outside Rebase's `indexes:` block.
143
+ */
144
+ index?: boolean;
111
145
  }
112
146
  /**
113
147
  * The target holds the foreign key, and at most one target row points back.
@@ -234,7 +268,7 @@ export interface ManyToManyRelation extends RelationBase {
234
268
  * through: {
235
269
  * table: "org_members",
236
270
  * properties: {
237
- * role: { type: "string", enum: ["owner", "admin", "member"],
271
+ * role: { type: "string", enum: { owner: "Owner", admin: "Admin", member: "Member" },
238
272
  * defaultValue: "member", validation: { required: true } },
239
273
  * joinedAt: { type: "date", autoValue: "on_create" }
240
274
  * }
@@ -364,6 +398,8 @@ export interface ResolvedBelongsTo extends ResolvedRelationBase {
364
398
  shared: false;
365
399
  /** Column on this collection's table. */
366
400
  localKey: string;
401
+ /** As authored, with the default filled in — see {@link BelongsToRelation.index}. */
402
+ index: boolean;
367
403
  }
368
404
  /** @group Models */
369
405
  export interface ResolvedHasOne extends ResolvedRelationBase {
@@ -167,11 +167,13 @@ export interface TopicOptions extends DeclareOptions {
167
167
  *
168
168
  * `attempt` counts from 1. Worth branching on: the first delivery and the
169
169
  * fourth are the same call, but the fourth is where it is worth logging loudly.
170
+ * `signal` is aborted when the attempt runs past the job queue's `timeoutMs`.
170
171
  */
171
172
  export type TopicHandler<T> = (event: T, context: {
172
173
  attempt: number;
173
174
  topic: string;
174
175
  subscription: string;
176
+ signal: AbortSignal;
175
177
  }) => Promise<void> | void;
176
178
  /** A declared subscription, as recorded in the graph and wired at boot. */
177
179
  export interface TopicSubscription<T = unknown> {
@@ -279,11 +281,15 @@ export interface QueueOptions extends DeclareOptions {
279
281
  /** Attempts before a job is left failed. Default 5. */
280
282
  maxAttempts?: number;
281
283
  }
282
- /** What a queue's handler receives. `attempt` counts from 1. */
284
+ /**
285
+ * What a queue's handler receives. `attempt` counts from 1; `signal` is
286
+ * aborted when the attempt runs past the job queue's `timeoutMs`.
287
+ */
283
288
  export type QueueHandler<T> = (payload: T, context: {
284
289
  attempt: number;
285
290
  queue: string;
286
291
  jobId: string;
292
+ signal: AbortSignal;
287
293
  }) => Promise<void> | void;
288
294
  /** Per-job options at enqueue time. */
289
295
  export interface QueueEnqueueOptions {
@@ -18,6 +18,11 @@ export interface StorageAuthorizeUser {
18
18
  export interface StorageAuthorizeContext {
19
19
  /** Object key, bucket prefix stripped and traversal already sanitized. */
20
20
  key: string;
21
+ /**
22
+ * The bucket, by one name per bucket. On S3 and GCS, whose source serves a
23
+ * single bucket, this is always `"default"` — also when the request named
24
+ * the provider's bucket (`acme-media`), which reaches the same objects.
25
+ */
21
26
  bucket: string;
22
27
  operation: StorageOperation;
23
28
  /** Null when the route allows unauthenticated access. */
@@ -69,6 +69,13 @@ export interface CollectionUpdateMeta {
69
69
  hasMore: boolean;
70
70
  /** The opaque cursor continuing this listing — see `PaginationMeta.nextCursor`. */
71
71
  nextCursor?: string;
72
+ /**
73
+ * When `total` was counted, ISO-8601 — present only when it was not
74
+ * counted for this frame. A server with `REALTIME_RECOUNT_INTERVAL_MS` set
75
+ * reuses a big list's total for that long rather than counting it on every
76
+ * write; `hasMore` is then judged against the reused total.
77
+ */
78
+ totalAsOf?: string;
72
79
  /**
73
80
  * The count could not be taken, so `total` is missing and `hasMore` is a
74
81
  * floor rather than an answer.
@@ -97,6 +104,14 @@ export interface SingleUpdateMessage extends WebSocketMessage {
97
104
  type: "single_update";
98
105
  subscriptionId: string;
99
106
  row: Record<string, unknown> | null;
107
+ /**
108
+ * The row's version: the `ETag` a `GET /:id` of it answers, to send back
109
+ * as `If-Match` on a `PATCH` or `DELETE`. Sent only to a `subscribe_one`
110
+ * that asked with `etag: true`, and never beside a `null` row. A server
111
+ * that predates it ignores the ask and sends no tag; a client that does
112
+ * not ask gets frames without one.
113
+ */
114
+ etag?: string;
100
115
  }
101
116
  /**
102
117
  * Lightweight patch message sent to collection subscribers when a single
@@ -151,6 +166,22 @@ export interface BroadcastMessage extends WebSocketMessage {
151
166
  */
152
167
  seq?: number;
153
168
  }
169
+ /**
170
+ * Server → client: the sequence number the sender's own broadcast took on a
171
+ * retained channel, sent to the sender in place of the message itself (which
172
+ * is never echoed back).
173
+ *
174
+ * Sent only to a socket that opted into `channel_seq` when it opened (see the
175
+ * socket's `hello`). With it, every member of a retained channel hears of
176
+ * every number, one way or the other, so a client that sees a hole in the
177
+ * sequence knows it missed a message and can ask for it — which is what the
178
+ * SDK does.
179
+ */
180
+ export interface BroadcastAckMessage extends WebSocketMessage {
181
+ type: "broadcast_ack";
182
+ channel: string;
183
+ seq: number;
184
+ }
154
185
  /**
155
186
  * Server → client: the whole roster of a channel.
156
187
  *
@@ -276,4 +307,4 @@ export interface ChannelHistoryMessage extends WebSocketMessage {
276
307
  * {@link WebSocketMessage}, whose `type` is a bare `string`, and a `string`
277
308
  * discriminates nothing.
278
309
  */
279
- export type ChannelMessage = BroadcastMessage | PresenceStateMessage | PresenceDiffMessage | ChannelHistoryMessage | ChannelErrorMessage;
310
+ export type ChannelMessage = BroadcastMessage | BroadcastAckMessage | PresenceStateMessage | PresenceDiffMessage | ChannelHistoryMessage | ChannelErrorMessage;