@rebasepro/types 0.22.0 → 0.23.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.
@@ -490,6 +490,40 @@ export interface AuthAdapter {
490
490
  id: string;
491
491
  values: Record<string, unknown>;
492
492
  }, clearPassword?: string): Promise<UserCreationFinalizeResult>;
493
+ /**
494
+ * Check, and put in stored form, updates to rows of the auth collection
495
+ * that arrive through the data API — `PATCH /<users>/:id`, `PATCH /bulk`,
496
+ * `_batch`, the realtime socket.
497
+ *
498
+ * Those rows are the users, so an update to one is user administration,
499
+ * and has to hold to what the admin user routes hold to: no update that
500
+ * leaves the project without an administrator, and an email stored the
501
+ * way sign-in looks it up. Throws to refuse; the updates are judged
502
+ * together, so a bulk demotion of every admin is refused even though each
503
+ * row alone would pass.
504
+ *
505
+ * If not implemented, the values are written as they arrived.
506
+ *
507
+ * @returns Each update's values, as they should be written, in order.
508
+ */
509
+ prepareUserUpdates?(updates: ReadonlyArray<{
510
+ uid: string;
511
+ values: Record<string, unknown>;
512
+ }>): Promise<Record<string, unknown>[]>;
513
+ /**
514
+ * Check a deletion of these users through the data API before any row goes,
515
+ * and run the hooks that may veto it (`beforeUserDelete`). Throws to
516
+ * refuse — for one that would leave the project without an administrator,
517
+ * among others.
518
+ *
519
+ * If not implemented, the rows are deleted as any other.
520
+ */
521
+ prepareUserDeletions?(uids: readonly string[]): Promise<void>;
522
+ /**
523
+ * After those users' rows are deleted through the data API: end their
524
+ * sessions and run the after-delete hooks (`afterUserDelete`).
525
+ */
526
+ finalizeUserDeletions?(uids: readonly string[]): Promise<void>;
493
527
  /**
494
528
  * A static secret key for server-to-server / script authentication.
495
529
  *
@@ -350,11 +350,18 @@ export interface DataTransformer {
350
350
  export interface SQLAdmin {
351
351
  /**
352
352
  * Execute raw SQL against the database.
353
+ *
354
+ * `isolateSession` runs it on a connection of its own and resets that
355
+ * connection's session state — role, session authorization, settings —
356
+ * before it is reused. For SQL a person wrote (the Studio editor), which
357
+ * may `SET ROLE` and must not leave that on a pooled connection the
358
+ * server's own queries use next.
353
359
  */
354
360
  executeSql(sql: string, options?: {
355
361
  database?: string;
356
362
  role?: string;
357
363
  params?: unknown[];
364
+ isolateSession?: boolean;
358
365
  }): Promise<Record<string, unknown>[]>;
359
366
  /**
360
367
  * Fetch the available databases on the server.
@@ -579,6 +586,47 @@ export interface BackendInstance extends BackendLifecycle {
579
586
  * Factory function type for creating backend instances
580
587
  */
581
588
  export type BackendFactory<TConfig extends BackendConfig = BackendConfig> = (config: TConfig) => BackendInstance;
589
+ /**
590
+ * A caller of the data API as its rate limiter buckets one outside an HTTP
591
+ * request: a realtime socket's frame, counted in the same bucket the same
592
+ * person's HTTP requests are.
593
+ * @group Backend
594
+ */
595
+ export interface DataRateLimitCaller {
596
+ /**
597
+ * The signed-in user's uid. Absent, or the anonymous principal, buckets
598
+ * the caller by address at the anonymous allowance.
599
+ */
600
+ uid?: string;
601
+ /**
602
+ * A header of the request that opened the connection, by lower-case name:
603
+ * the proxy headers an address is read from when proxies are declared.
604
+ */
605
+ header(name: string): string | undefined;
606
+ /** The address the connection comes from. */
607
+ socketAddress?: string;
608
+ }
609
+ /**
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.
612
+ * @group Backend
613
+ */
614
+ export interface RealtimeSocketLimits {
615
+ /**
616
+ * The largest frame accepted, in bytes — the data API's body limit. `0` or
617
+ * less for none. Defaults to the server's default body limit.
618
+ */
619
+ maxPayload?: number;
620
+ /**
621
+ * Count one data request from `caller` in the data API's per-caller
622
+ * buckets and say whether it is allowed; `null` for a caller who is not
623
+ * limited. Absent when the deployment has rate limiting off.
624
+ */
625
+ dataRateLimit?: (caller: DataRateLimitCaller) => Promise<{
626
+ allowed: boolean;
627
+ retryAfterMs: number;
628
+ } | null>;
629
+ }
582
630
  /**
583
631
  * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.
584
632
  *
@@ -784,7 +832,7 @@ export interface BackendBootstrapper {
784
832
  /**
785
833
  * Initialize WebSocket server for realtime operations.
786
834
  */
787
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver.js").DataDriver, config?: unknown, authAdapter?: AuthAdapter): Promise<void> | void;
835
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver.js").DataDriver, config?: unknown, authAdapter?: AuthAdapter, limits?: RealtimeSocketLimits): Promise<void> | void;
788
836
  }
789
837
  /**
790
838
  * Result of `BackendBootstrapper.initializeDriver()`.
@@ -43,7 +43,9 @@ export interface CronJobDefinition {
43
43
  enabled?: boolean;
44
44
  /**
45
45
  * Maximum number of seconds the handler may run before being
46
- * considered timed-out. Default: 300 (5 min).
46
+ * considered timed-out. Default: 300 (5 min). `Infinity` means no
47
+ * timeout. Zero, a negative number or `NaN` is refused when the job
48
+ * loads, and the job is listed as rejected.
47
49
  */
48
50
  timeoutSeconds?: number;
49
51
  /**
@@ -95,7 +97,8 @@ export interface CronJobContext {
95
97
  /** A simple logger scoped to this job run. */
96
98
  log: (...args: unknown[]) => void;
97
99
  /**
98
- * Aborted when the run exceeds `timeoutSeconds`.
100
+ * Aborted when the run exceeds `timeoutSeconds`, or when the server
101
+ * shuts down and the run has not finished within the shutdown's wait.
99
102
  *
100
103
  * The timeout has always stopped the scheduler *waiting* — it loses the
101
104
  * race and the run is recorded as failed. It has never stopped the handler:
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import type { DataDriver } from "../controllers/data_driver.js";
23
23
  import type { CollectionConfig } from "./collections.js";
24
- import type { CollectionRegistryInterface, DatabaseAdmin, InitializedDriver, RealtimeProvider, BootstrappedAuth } from "./backend.js";
24
+ import type { CollectionRegistryInterface, DatabaseAdmin, InitializedDriver, RealtimeProvider, RealtimeSocketLimits, BootstrappedAuth } from "./backend.js";
25
25
  import type { HistoryConfig } from "../controllers/client.js";
26
26
  /**
27
27
  * A `DatabaseAdapter` provides data persistence for Rebase.
@@ -73,8 +73,12 @@ export interface DatabaseAdapter {
73
73
  * already passed it, so it was dropped at every adapter that routed through
74
74
  * here — turning an adapter-authenticated server's socket into one that
75
75
  * accepted every client as already authenticated.
76
+ *
77
+ * `limits` are the data API's body and per-caller rate limits, for the
78
+ * socket's frames: it serves the same rows, and without them a frame had
79
+ * neither. Forwarded for the same reason `adapter` is.
76
80
  */
77
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: import("./auth_adapter.js").AuthAdapter): Promise<void> | void;
81
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: DataDriver, config?: unknown, adapter?: import("./auth_adapter.js").AuthAdapter, limits?: RealtimeSocketLimits): Promise<void> | void;
78
82
  /**
79
83
  * Ask the database whether it is there, before boot's first real query. See
80
84
  * `BackendBootstrapper.verifyConnection` for the contract.
@@ -56,8 +56,18 @@ export interface TableJunctionInfo {
56
56
  */
57
57
  export interface TablePolicyInfo {
58
58
  policy_name: string;
59
+ /**
60
+ * The policy's `TO` list — *database* roles, `public` for every
61
+ * connection — as `pg_policies.roles` reports it.
62
+ */
59
63
  roles: string[];
64
+ /** `SELECT`, `INSERT`, `UPDATE`, `DELETE` or `ALL`, as `pg_policies.cmd` reports it. */
60
65
  cmd: string;
66
+ /**
67
+ * `pg_policies.permissive`. Absent means `PERMISSIVE`, Postgres's own
68
+ * default.
69
+ */
70
+ permissive?: "PERMISSIVE" | "RESTRICTIVE";
61
71
  qual?: string;
62
72
  with_check?: string;
63
73
  }
@@ -24,7 +24,7 @@
24
24
  * changing a type, moving a primary key.
25
25
  */
26
26
  export type SchemaChangeVerdict = "safe" | "diverges" | "needs-migration";
27
- export type SchemaChangeKind = "add-collection" | "remove-collection" | "add-property" | "remove-property" | "change-property-type" | "rename-column" | "add-enum-value" | "remove-enum-value" | "change-required" | "change-primary-key";
27
+ export type SchemaChangeKind = "add-collection" | "remove-collection" | "add-property" | "remove-property" | "change-property-type" | "rename-column" | "add-enum-value" | "remove-enum-value" | "change-required" | "change-primary-key" | "rename-table";
28
28
  export interface SchemaChange {
29
29
  kind: SchemaChangeKind;
30
30
  verdict: SchemaChangeVerdict;
@@ -106,7 +106,7 @@ export interface SchemaChangePlan {
106
106
  export interface WithheldSchemaConstraint {
107
107
  /** `schema.table.column`. */
108
108
  target: string;
109
- kind: "not-null";
109
+ kind: "not-null" | "unique";
110
110
  /** What is in the way, naming the obstacle rather than the rule. */
111
111
  reason: string;
112
112
  /** What would make it applicable. */
@@ -53,7 +53,7 @@ export type SecurityOperation = "select" | "insert" | "update" | "delete" | "all
53
53
  *
54
54
  * @group Models
55
55
  */
56
- export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | StructuredSecurityRule | RawSQLSecurityRule | RolesOnlySecurityRule;
56
+ export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | StructuredSecurityRule | RawSQLSecurityRule | RawSQLCheckOnlySecurityRule | RolesOnlySecurityRule;
57
57
  /**
58
58
  * Shared fields for all SecurityRule variants.
59
59
  * @group Models
@@ -297,6 +297,33 @@ export interface RawSQLSecurityRule extends SecurityRuleBase {
297
297
  condition?: never;
298
298
  check?: never;
299
299
  }
300
+ /**
301
+ * Security rule with a raw SQL `WITH CHECK` clause and no `USING` clause — the
302
+ * shape of every INSERT policy, which checks the row being written and has no
303
+ * existing row to filter.
304
+ *
305
+ * On an operation that takes a `USING` clause, the missing one grants no rows,
306
+ * as a Postgres policy without one does.
307
+ *
308
+ * @example
309
+ * // Anyone may insert a row they own
310
+ * { operation: "insert", withCheck: "{owner_id} = rebase.uid()" }
311
+ *
312
+ * @group Models
313
+ */
314
+ export interface RawSQLCheckOnlySecurityRule extends SecurityRuleBase {
315
+ /**
316
+ * Raw SQL expression for the `WITH CHECK` clause.
317
+ * This controls which *new/updated* row values are allowed.
318
+ * Applied to INSERT and UPDATE.
319
+ */
320
+ withCheck: string;
321
+ using?: never;
322
+ ownerField?: never;
323
+ access?: never;
324
+ condition?: never;
325
+ check?: never;
326
+ }
300
327
  /**
301
328
  * Security rule that only filters by application roles, without any
302
329
  * row-level condition (USING/WITH CHECK).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rebasepro/types",
3
- "version": "0.22.0",
3
+ "version": "0.23.0",
4
4
  "description": "Rebase type definitions — shared interfaces and controller types",
5
5
  "keywords": [
6
6
  "rebase",
@@ -46,7 +46,7 @@
46
46
  "@types/node": "^26.1.2",
47
47
  "@types/object-hash": "^3.0.6",
48
48
  "@types/react-measure": "^2.0.12",
49
- "hono": "^4.13.0",
49
+ "hono": "^4.13.5",
50
50
  "jest": "^30.4.2",
51
51
  "ts-jest": "^29.4.12",
52
52
  "typescript": "^6.0.3",