@rebasepro/types 0.6.1 → 0.8.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.
Files changed (49) hide show
  1. package/dist/controllers/client.d.ts +69 -1
  2. package/dist/controllers/data.d.ts +19 -47
  3. package/dist/controllers/navigation.d.ts +14 -14
  4. package/dist/controllers/registry.d.ts +8 -4
  5. package/dist/index.es.js +164 -10
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +171 -9
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/types/api_keys.d.ts +57 -0
  10. package/dist/types/auth_adapter.d.ts +66 -4
  11. package/dist/types/backend.d.ts +5 -0
  12. package/dist/types/collections.d.ts +200 -90
  13. package/dist/types/data_source.d.ts +79 -3
  14. package/dist/types/entity_actions.d.ts +2 -2
  15. package/dist/types/entity_callbacks.d.ts +18 -6
  16. package/dist/types/entity_views.d.ts +1 -1
  17. package/dist/types/filter-operators.d.ts +108 -0
  18. package/dist/types/index.d.ts +4 -2
  19. package/dist/types/plugins.d.ts +1 -1
  20. package/dist/types/policy.d.ts +137 -0
  21. package/dist/types/properties.d.ts +122 -37
  22. package/dist/types/property_config.d.ts +1 -1
  23. package/dist/types/storage_source.d.ts +83 -0
  24. package/dist/types/translations.d.ts +8 -0
  25. package/package.json +1 -1
  26. package/src/controllers/client.ts +69 -1
  27. package/src/controllers/data.ts +20 -59
  28. package/src/controllers/navigation.ts +17 -16
  29. package/src/controllers/registry.ts +10 -4
  30. package/src/types/api_keys.ts +52 -0
  31. package/src/types/auth_adapter.ts +80 -4
  32. package/src/types/backend.ts +6 -1
  33. package/src/types/collections.ts +235 -113
  34. package/src/types/data_source.ts +89 -5
  35. package/src/types/entity_actions.tsx +2 -2
  36. package/src/types/entity_callbacks.ts +18 -7
  37. package/src/types/entity_views.tsx +1 -1
  38. package/src/types/filter-operators.ts +167 -0
  39. package/src/types/index.ts +5 -2
  40. package/src/types/plugins.tsx +1 -1
  41. package/src/types/policy.ts +173 -0
  42. package/src/types/properties.ts +132 -39
  43. package/src/types/property_config.tsx +0 -1
  44. package/src/types/storage_source.ts +90 -0
  45. package/src/types/translations.ts +8 -0
  46. package/dist/types/backend_hooks.d.ts +0 -109
  47. package/dist/types/entity_overrides.d.ts +0 -10
  48. package/src/types/backend_hooks.ts +0 -114
  49. package/src/types/entity_overrides.tsx +0 -11
@@ -4,8 +4,8 @@
4
4
  * Pluggable authentication abstraction for Rebase.
5
5
  *
6
6
  * An `AuthAdapter` decouples authentication from the database layer,
7
- * allowing users to bring their own auth system (Clerk, Auth0, Firebase Auth,
8
- * custom JWT, etc.) while keeping the Rebase admin frontend fully functional.
7
+ * allowing users to bring their own auth system (Clerk, Auth0, or other
8
+ * external providers) while keeping the Rebase admin frontend fully functional.
9
9
  *
10
10
  * @example Built-in auth (default — zero config change)
11
11
  * ```ts
@@ -85,6 +85,8 @@ export interface AuthAdapterCapabilities {
85
85
  profileUpdate: boolean;
86
86
  /** Supports email verification. */
87
87
  emailVerification: boolean;
88
+ /** Supports passwordless magic link login. */
89
+ magicLink: boolean;
88
90
  /** List of enabled OAuth provider IDs (e.g. `["google", "github"]`). */
89
91
  enabledProviders: string[];
90
92
  /**
@@ -195,6 +197,45 @@ export interface UserCreationFinalizeResult {
195
197
  /** Whether an invitation email was sent. */
196
198
  invitationSent: boolean;
197
199
  }
200
+ /**
201
+ * The auth response payload shape that flows through `transformAuthResponse`.
202
+ *
203
+ * For login, register, OAuth, anonymous, and magic-link flows the payload
204
+ * contains both `user` and `tokens`. For refresh and MFA flows the payload
205
+ * contains only `tokens` (no `user`).
206
+ *
207
+ * @group Auth
208
+ */
209
+ export interface AuthResponsePayload {
210
+ user?: {
211
+ uid: string;
212
+ email: string;
213
+ displayName: string | null;
214
+ photoURL: string | null;
215
+ roles: string[];
216
+ metadata: Record<string, unknown>;
217
+ };
218
+ tokens: {
219
+ accessToken: string;
220
+ refreshToken: string;
221
+ accessTokenExpiresAt: number;
222
+ /** Additional tokens injected by `transformAuthResponse`. */
223
+ [key: string]: unknown;
224
+ };
225
+ }
226
+ /**
227
+ * Context passed to the `transformAuthResponse` hook.
228
+ *
229
+ * @group Auth
230
+ */
231
+ export interface TransformAuthResponseContext {
232
+ /** The authenticated user's ID. */
233
+ userId: string;
234
+ /** The auth method that triggered this response. */
235
+ method: "login" | "register" | "oauth" | "refresh" | "anonymous" | "magic-link" | "mfa";
236
+ /** The raw HTTP request (for reading headers, IP, etc.). */
237
+ request: Request;
238
+ }
198
239
  /**
199
240
  * Pluggable authentication adapter for Rebase.
200
241
  *
@@ -207,7 +248,7 @@ export interface UserCreationFinalizeResult {
207
248
  * 4. Advertise its capabilities so the frontend can adapt
208
249
  *
209
250
  * The built-in Rebase auth implements this interface internally.
210
- * External providers (Clerk, Auth0, Firebase Auth) provide their own adapters.
251
+ * External providers (Clerk, Auth0, or others) provide their own adapters.
211
252
  * Users with custom auth can use `createCustomAuthAdapter()` for a minimal setup.
212
253
  *
213
254
  * @group Auth
@@ -216,7 +257,7 @@ export interface AuthAdapter {
216
257
  /**
217
258
  * Unique identifier for this auth adapter.
218
259
  *
219
- * @example "rebase-builtin", "clerk", "auth0", "firebase", "custom"
260
+ * @example "rebase-builtin", "clerk", "auth0", "external-provider", "custom"
220
261
  */
221
262
  readonly id: string;
222
263
  /**
@@ -326,6 +367,22 @@ export interface AuthAdapter {
326
367
  * normal token verification and are granted admin-level access.
327
368
  */
328
369
  serviceKey?: string;
370
+ /**
371
+ * Transform the auth response before sending it to the client.
372
+ *
373
+ * Called after successful login, register, refresh, OAuth, anonymous,
374
+ * magic-link, and MFA flows. The hook receives the fully-formed
375
+ * response and returns a (potentially enriched) response.
376
+ *
377
+ * Use cases:
378
+ * - Inject tokens from external auth systems (custom provider tokens, etc.)
379
+ * - Add project-specific metadata to the response
380
+ * - Enrich the user object with data from external sources
381
+ *
382
+ * The hook runs in the request path — keep it fast.
383
+ * Heavy work should be offloaded to `onAuthenticated` (fire-and-forget).
384
+ */
385
+ transformAuthResponse?(response: AuthResponsePayload, context: TransformAuthResponseContext): Promise<AuthResponsePayload>;
329
386
  }
330
387
  /**
331
388
  * Options for creating a minimal custom auth adapter via `createCustomAuthAdapter()`.
@@ -353,4 +410,9 @@ export interface CustomAuthAdapterOptions {
353
410
  serviceKey?: string;
354
411
  /** Override default capabilities. */
355
412
  capabilities?: Partial<AuthAdapterCapabilities>;
413
+ /**
414
+ * Transform the auth response before sending it to the client.
415
+ * Same semantics as `AuthAdapter.transformAuthResponse`.
416
+ */
417
+ transformAuthResponse?: (response: AuthResponsePayload, context: TransformAuthResponseContext) => Promise<AuthResponsePayload>;
356
418
  }
@@ -219,6 +219,10 @@ export interface CollectionRegistryInterface {
219
219
  * Get all registered collections
220
220
  */
221
221
  getCollections(): EntityCollection[];
222
+ /**
223
+ * Get the currently registered global callbacks, if any.
224
+ */
225
+ getGlobalCallbacks(): any | undefined;
222
226
  }
223
227
  /**
224
228
  * Abstract data transformer interface.
@@ -247,6 +251,7 @@ export interface SQLAdmin {
247
251
  executeSql(sql: string, options?: {
248
252
  database?: string;
249
253
  role?: string;
254
+ params?: unknown[];
250
255
  }): Promise<Record<string, unknown>[]>;
251
256
  /**
252
257
  * Fetch the available databases on the server.
@@ -1,16 +1,17 @@
1
1
  import React from "react";
2
2
  import type { Entity, EntityStatus } from "./entities";
3
3
  import type { EntityCallbacks } from "./entity_callbacks";
4
- import type { Properties } from "./properties";
4
+ import type { Properties, PostgresProperties, FirebaseProperties, MongoProperties } from "./properties";
5
5
  import type { ExportConfig } from "./export_import";
6
- import type { EntityOverrides } from "./entity_overrides";
7
6
  import type { User } from "../users";
8
7
  import type { RebaseContext } from "../rebase_context";
9
8
  import type { Relation } from "./relations";
10
9
  import type { EntityCustomView, FormViewConfig } from "./entity_views";
11
10
  import type { EntityAction } from "./entity_actions";
11
+ import type { PolicyExpression } from "./policy";
12
12
  import type { ComponentRef } from "./component_ref";
13
13
  import type { CollectionComponentOverrideMap } from "./component_overrides";
14
+ import type { FilterValues, FilterPreset } from "./filter-operators";
14
15
  /**
15
16
  * Base interface containing all driver-agnostic collection properties.
16
17
  * Use {@link PostgresCollection} or {@link FirebaseCollection} for
@@ -49,33 +50,43 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
49
50
  */
50
51
  childCollections?: () => EntityCollection<Record<string, unknown>>[];
51
52
  /**
52
- * Which driver handles this collection.
53
- * Use this to route collections to different backends:
54
- * - `"postgres"` - Route to PostgreSQL backend
55
- * - `"firestore"` - Route to Firestore (client-side)
56
- * - `"mongodb"` - Route to MongoDB backend
57
- * - Custom IDs for your own driver implementations
53
+ * The data source this collection belongs to — the routing key shared by
54
+ * the frontend router and the backend driver registry. It points at a
55
+ * {@link DataSourceDefinition} registered on `<Rebase dataSources>` (front)
56
+ * and `initRebase({ dataSources })` (back).
58
57
  *
59
- * If not specified, the default driver `"(default)"` is used.
58
+ * If not specified, the default data source `"(default)"` is used, which
59
+ * for a standard Rebase app is the server-mediated Postgres backend.
60
60
  *
61
61
  * @example
62
- * // Simple - no driver needed for default
62
+ * // Default data source (server-mediated Postgres)
63
63
  * { slug: "products" }
64
64
  *
65
- * // Firestore collection (client-side real-time)
66
- * { slug: "analytics", driver: "firestore" }
65
+ * // A direct-transport Firestore data source registered as "analytics"
66
+ * { slug: "events", dataSource: "analytics" }
67
+ */
68
+ dataSource?: string;
69
+ /**
70
+ * The database engine backing this collection (`"postgres"`, `"firestore"`,
71
+ * `"mongodb"`, or a custom id).
72
+ *
73
+ * On concrete collection types ({@link PostgresCollection},
74
+ * {@link FirebaseCollection}, {@link MongoDBCollection}) this is a literal
75
+ * discriminant. On the base type it is optional and gets stamped
76
+ * automatically during collection normalization from the registered
77
+ * {@link DataSourceDefinition}.
67
78
  *
68
- * // Multiple databases within a driver
69
- * { slug: "orders", driver: "postgres", databaseId: "orders_db" }
79
+ * Prefer setting {@link dataSource} and letting the engine be resolved.
70
80
  */
71
- driver?: string;
81
+ engine?: string;
72
82
  /**
73
- * Which database within the driver.
83
+ * Which database within the engine.
74
84
  * - For Firestore: The Firestore database ID (e.g., for multi-database projects)
75
85
  * - For PostgreSQL: Schema or database name
76
86
  * - For MongoDB: Database name
77
87
  *
78
- * If not specified, the default database of the driver is used.
88
+ * If not specified, the default database of the engine is used. Resolved
89
+ * from the collection's {@link DataSourceDefinition} when omitted here.
79
90
  */
80
91
  databaseId?: string;
81
92
  /**
@@ -142,6 +153,25 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
142
153
  * When true, this collection is used for user management, login, password hashing, and invitation flows.
143
154
  */
144
155
  auth?: boolean | AuthCollectionConfig;
156
+ /**
157
+ * Opt out of the framework's default Row Level Security policies for
158
+ * authentication collections.
159
+ *
160
+ * When a collection has `auth` enabled, the schema generator automatically
161
+ * injects an admin-only write policy (INSERT/UPDATE/DELETE require the
162
+ * `admin` role, or the trusted server context) for any write operation that
163
+ * the collection does not already cover with an explicit `securityRules`
164
+ * entry. This makes privileged columns such as `roles` safe by default — a
165
+ * non-admin cannot modify the user row through the data API regardless of
166
+ * which code path issues the write.
167
+ *
168
+ * Defining your own write rule for an operation overrides the default for
169
+ * that operation. Set this flag to `true` to remove the default policies
170
+ * entirely and take full responsibility for the collection's RLS.
171
+ *
172
+ * @default false
173
+ */
174
+ disableDefaultAuthPolicies?: boolean;
145
175
  /**
146
176
  * Order in which the properties are displayed.
147
177
  * If you are specifying your collection as code, the order is the same as the
@@ -233,8 +263,8 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
233
263
  defaultSize?: CollectionSize;
234
264
  /**
235
265
  * Can the elements in this collection be edited inline in the collection
236
- * view. If this flag is set to false but `permissions.edit` is `true`, entities
237
- * can still be edited in the side panel
266
+ * view. Even when inline editing is disabled, entities can still be
267
+ * edited in the side panel (subject to `securityRules`).
238
268
  */
239
269
  inlineEditing?: boolean;
240
270
  /**
@@ -278,9 +308,11 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
278
308
  */
279
309
  ownerId?: string;
280
310
  /**
281
- * Overrides for the entity view, like the data source or the storage source.
311
+ * Arbitrary key-value metadata for external consumers.
312
+ * Not interpreted by Rebase — passed through serialization unchanged.
313
+ * Used by domain apps to store custom per-collection config.
282
314
  */
283
- overrides?: EntityOverrides;
315
+ metadata?: Record<string, unknown>;
284
316
  /**
285
317
  * Width of the side dialog (in pixels) when opening an entity in this collection.
286
318
  */
@@ -324,7 +356,7 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
324
356
  * Possible values: "table", "cards", "kanban".
325
357
  * Defaults to all three: ["table", "cards", "kanban"].
326
358
  * Note: "kanban" will only be available if the collection has at least
327
- * one string property with enumValues defined, regardless of this setting.
359
+ * one string property with `enum` defined, regardless of this setting.
328
360
  */
329
361
  enabledViews?: ViewMode[];
330
362
  /**
@@ -401,12 +433,12 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
401
433
  * @group Models
402
434
  */
403
435
  export interface PostgresCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> extends BaseEntityCollection<M, USER> {
404
- properties: Properties;
436
+ properties: PostgresProperties;
405
437
  /**
406
- * The driver for this collection. For Postgres collections this
438
+ * The database engine for this collection. For Postgres collections this
407
439
  * can be omitted (Postgres is the default) or set to `"postgres"`.
408
440
  */
409
- driver?: "postgres" | undefined;
441
+ engine?: "postgres" | undefined;
410
442
  /**
411
443
  * The PostgreSQL table name for this collection.
412
444
  */
@@ -450,9 +482,31 @@ export interface PostgresCollection<M extends Record<string, unknown> = Record<s
450
482
  */
451
483
  export interface FirebaseCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> extends BaseEntityCollection<M, USER> {
452
484
  /**
453
- * The driver for this collection. Must be set to `"firestore"`.
485
+ * The database engine for this collection. Must be set to `"firestore"`.
486
+ */
487
+ engine: "firestore";
488
+ /**
489
+ * Set of properties that compose an entity.
490
+ * Firestore collections support `reference` properties but not `relation`.
454
491
  */
455
- driver: "firestore";
492
+ properties: FirebaseProperties;
493
+ /**
494
+ * The Firestore collection path to query. Defaults to `slug` if not set.
495
+ * Use this when the Firestore path differs from the slug
496
+ * (e.g., when a PostgreSQL collection already uses the same slug).
497
+ *
498
+ * @example
499
+ * ```typescript
500
+ * const fsCustomer: FirebaseCollection = {
501
+ * slug: "fs_customer", // URL: /c/fs_customer
502
+ * path: "customer", // Firestore path: customer
503
+ * name: "Customers (Firestore)",
504
+ * engine: "firestore",
505
+ * properties: { ... }
506
+ * };
507
+ * ```
508
+ */
509
+ path?: string;
456
510
  /**
457
511
  * You can add subcollections to your entity in the same way you define the root
458
512
  * collections. The collections added here will be displayed when opening
@@ -470,9 +524,31 @@ export interface FirebaseCollection<M extends Record<string, unknown> = Record<s
470
524
  */
471
525
  export interface MongoDBCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> extends BaseEntityCollection<M, USER> {
472
526
  /**
473
- * The driver for this collection. Must be set to `"mongodb"`.
527
+ * The database engine for this collection. Must be set to `"mongodb"`.
474
528
  */
475
- driver: "mongodb";
529
+ engine: "mongodb";
530
+ /**
531
+ * Set of properties that compose an entity.
532
+ * MongoDB collections support `reference` properties but not `relation`.
533
+ */
534
+ properties: MongoProperties;
535
+ /**
536
+ * The MongoDB collection name to use. Defaults to `slug` if not set.
537
+ * Use this when the MongoDB collection name differs from the slug
538
+ * (e.g., when a PostgreSQL collection already uses the same slug).
539
+ *
540
+ * @example
541
+ * ```typescript
542
+ * const mongoCustomer: MongoDBCollection = {
543
+ * slug: "mongo_customer", // URL: /c/mongo_customer
544
+ * path: "customer", // MongoDB collection: customer
545
+ * name: "Customers (MongoDB)",
546
+ * engine: "mongodb",
547
+ * properties: { ... }
548
+ * };
549
+ * ```
550
+ */
551
+ path?: string;
476
552
  }
477
553
  /**
478
554
  * A collection backed by any data source.
@@ -483,22 +559,9 @@ export interface MongoDBCollection<M extends Record<string, unknown> = Record<st
483
559
  * @group Models
484
560
  */
485
561
  export type EntityCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> = PostgresCollection<M, USER> | FirebaseCollection<M, USER> | MongoDBCollection<M, USER>;
486
- /**
487
- * An EntityCollection that supports SQL-style relations (e.g. Postgres).
488
- * @deprecated Use `EntityCollection` directly — `table`, `relations`, and `securityRules` are now on `BaseEntityCollection`.
489
- */
490
- export type CollectionWithRelations<M extends Record<string, unknown> = Record<string, unknown>> = EntityCollection<M> & {
491
- table?: string;
492
- relations?: Relation[];
493
- securityRules?: SecurityRule[];
494
- };
495
- /** An EntityCollection that supports subcollections (e.g. Firestore). */
496
- export type CollectionWithSubcollections<M extends Record<string, unknown> = Record<string, unknown>> = EntityCollection<M> & {
497
- subcollections?: () => EntityCollection<Record<string, unknown>>[];
498
- };
499
562
  /**
500
563
  * Type guard for PostgreSQL collections.
501
- * Returns true if the collection uses the Postgres driver (or the default driver).
564
+ * Returns true if the collection uses the Postgres engine (or the default engine).
502
565
  * @group Models
503
566
  */
504
567
  export declare function isPostgresCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>(collection: EntityCollection<M, USER>): collection is PostgresCollection<M, USER>;
@@ -512,6 +575,23 @@ export declare function isFirebaseCollection<M extends Record<string, unknown> =
512
575
  * @group Models
513
576
  */
514
577
  export declare function isMongoDBCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>(collection: EntityCollection<M, USER>): collection is MongoDBCollection<M, USER>;
578
+ /**
579
+ * Returns the data path for a collection.
580
+ * For Firestore or MongoDB collections with a `path`, returns that value;
581
+ * otherwise falls back to `slug`.
582
+ */
583
+ export declare function getCollectionDataPath<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>(collection: EntityCollection<M, USER>): string;
584
+ /**
585
+ * Reads a collection's driver-declared subcollections thunk (the `subcollections`
586
+ * field) independent of engine identity, so engine-agnostic code doesn't have to
587
+ * type-guard against a specific driver. Returns `undefined` when the collection
588
+ * declares none.
589
+ *
590
+ * Pair with `getDataSourceCapabilities(engine).supportsSubcollections` to decide
591
+ * whether the engine honours subcollections at all before reading them.
592
+ * @group Models
593
+ */
594
+ export declare function getDeclaredSubcollections<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User>(collection: EntityCollection<M, USER>): (() => EntityCollection<Record<string, unknown>>[]) | undefined;
515
595
  /**
516
596
  * Configuration for Kanban board view mode.
517
597
  * @group Collections
@@ -519,9 +599,9 @@ export declare function isMongoDBCollection<M extends Record<string, unknown> =
519
599
  export interface KanbanConfig<M extends Record<string, unknown> = Record<string, unknown>> {
520
600
  /**
521
601
  * Property key to use for Kanban board columns.
522
- * Must reference a string property with enumValues defined.
602
+ * Must reference a string property with `enum` values defined.
523
603
  * Entities will be grouped into columns based on this property's value.
524
- * The column order is determined by the order of enumValues in the property.
604
+ * The column order is determined by the order of `enum` values in the property.
525
605
  */
526
606
  columnProperty: Extract<keyof M, string> | (string & {});
527
607
  }
@@ -607,42 +687,7 @@ export interface SelectionController<M extends Record<string, unknown> = Record<
607
687
  isEntitySelected(entity: Entity<M>): boolean;
608
688
  toggleEntitySelection(entity: Entity<M>, newSelectedState?: boolean): void;
609
689
  }
610
- /**
611
- * Filter conditions in a `Query.where()` clause are specified using the
612
- * strings `<`, `<=`, `==`, `>=`, `>`, `array-contains`, `in`, and `array-contains-any`.
613
- * @group Models
614
- */
615
- export type WhereFilterOp = "<" | "<=" | "==" | "!=" | ">=" | ">" | "array-contains" | "in" | "not-in" | "array-contains-any";
616
- /**
617
- * Used to define filters applied in collections
618
- *
619
- * e.g. `{ age: [">=", 18] }`
620
- *
621
- * @group Models
622
- */
623
- export type FilterValues<Key extends string> = Partial<Record<Key, [WhereFilterOp, unknown] | [WhereFilterOp, unknown][]>>;
624
- /**
625
- * A pre-defined filter preset for quick access in the collection toolbar.
626
- * Users can select a preset to instantly apply a set of filters and
627
- * optionally a sort order.
628
- *
629
- * @group Models
630
- */
631
- export interface FilterPreset<Key extends string = string> {
632
- /**
633
- * Display label shown in the preset menu.
634
- * If omitted, a summary is auto-generated from the filter keys.
635
- */
636
- label?: string;
637
- /**
638
- * The filter values to apply when this preset is selected.
639
- */
640
- filterValues: FilterValues<Key>;
641
- /**
642
- * Optional sort override to apply alongside the filter values.
643
- */
644
- sort?: [Key, "asc" | "desc"];
645
- }
690
+ export type { WhereFilterOp, FilterValues, WireFilterValues, FilterPreset } from "./filter-operators";
646
691
  /**
647
692
  * Used to indicate valid filter combinations (e.g. created in Firestore)
648
693
  * If the user selects a specific filter/sort combination, the CMS checks if it's
@@ -780,13 +825,21 @@ export type SecurityOperation = "select" | "insert" | "update" | "delete" | "all
780
825
  * operation. Permissive rules are OR'd together (any one passing is enough).
781
826
  * Restrictive rules are AND'd (all must pass). This mirrors Supabase behavior.
782
827
  *
783
- * **Mutual exclusivity:** `ownerField`, `access`, and raw SQL (`using`/`withCheck`)
784
- * cannot be combined. The type system enforces this — attempting to set
785
- * conflicting fields will produce a compile-time error.
828
+ * **Mutual exclusivity:** `ownerField`, `access`, structured `condition`, and
829
+ * raw SQL (`using`/`withCheck`) cannot be combined. The type system enforces
830
+ * this — attempting to set conflicting fields will produce a compile-time
831
+ * error.
832
+ *
833
+ * **Which form to reach for:** prefer the structured {@link StructuredSecurityRule}
834
+ * (`condition`/`check`) or the shortcuts (`ownerField`, `access`, `roles`). These
835
+ * are engine-agnostic and evaluated identically by the database and the admin UI,
836
+ * so the UI never shows an action the database will reject. Raw SQL
837
+ * ({@link RawSQLSecurityRule}) keeps full PostgreSQL power but is Postgres-only
838
+ * and server-authoritative (the UI cannot evaluate arbitrary SQL locally).
786
839
  *
787
840
  * @group Models
788
841
  */
789
- export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | RawSQLSecurityRule | RolesOnlySecurityRule;
842
+ export type SecurityRule = OwnerSecurityRule | PublicSecurityRule | StructuredSecurityRule | RawSQLSecurityRule | RolesOnlySecurityRule;
790
843
  /**
791
844
  * Shared fields for all SecurityRule variants.
792
845
  * @group Models
@@ -851,11 +904,16 @@ export interface SecurityRuleBase {
851
904
  * application roles managed by Rebase, stored in the `rebase.user_roles`
852
905
  * table, and injected into each transaction via `auth.roles()`.
853
906
  *
854
- * Generates a condition like:
855
- * `auth.roles() ~ '<role1>|<role2>'`
907
+ * Generates a safe array-overlap condition — the user passes if they hold
908
+ * *any* of the listed roles:
909
+ * `string_to_array(auth.roles(), ',') && ARRAY['<role1>', '<role2>']`
856
910
  *
857
- * Can be combined with `ownerField`, `access`, or raw `using`/`withCheck`.
858
- * When combined, the role check is AND'd with the other condition.
911
+ * (Note: this is a true set intersection, NOT a regex/substring match, so
912
+ * a role named `admin` never matches `nonadmin` or `superadmin`.)
913
+ *
914
+ * Can be combined with `ownerField`, `access`, `condition`, or raw
915
+ * `using`/`withCheck`. When combined, the role check is AND'd with the
916
+ * other condition.
859
917
  *
860
918
  * @example
861
919
  * // Only admins can delete
@@ -906,6 +964,8 @@ export interface OwnerSecurityRule extends SecurityRuleBase {
906
964
  access?: never;
907
965
  using?: never;
908
966
  withCheck?: never;
967
+ condition?: never;
968
+ check?: never;
909
969
  }
910
970
  /**
911
971
  * Security rule that grants unrestricted row access (no row filtering).
@@ -929,11 +989,57 @@ export interface PublicSecurityRule extends SecurityRuleBase {
929
989
  ownerField?: never;
930
990
  using?: never;
931
991
  withCheck?: never;
992
+ condition?: never;
993
+ check?: never;
994
+ }
995
+ /**
996
+ * Security rule expressed as a structured, engine-agnostic
997
+ * {@link PolicyExpression}. This is the **recommended** way to write a
998
+ * non-trivial condition: it compiles to PostgreSQL `USING`/`WITH CHECK` SQL
999
+ * *and* is evaluated identically by the admin UI, so the UI can never show an
1000
+ * action the database will reject.
1001
+ *
1002
+ * Cannot be combined with `ownerField`, `access`, or raw `using`/`withCheck`.
1003
+ *
1004
+ * @example
1005
+ * // Owner, or any user holding the `moderator` role
1006
+ * {
1007
+ * operation: "update",
1008
+ * condition: policy.or(
1009
+ * policy.compare(policy.field("user_id"), "eq", policy.authUid()),
1010
+ * policy.rolesOverlap(["moderator"])
1011
+ * )
1012
+ * }
1013
+ *
1014
+ * @group Models
1015
+ */
1016
+ export interface StructuredSecurityRule extends SecurityRuleBase {
1017
+ /**
1018
+ * Structured condition for the `USING` clause — which *existing* rows are
1019
+ * visible / can be modified / deleted (SELECT, UPDATE, DELETE).
1020
+ */
1021
+ condition: PolicyExpression;
1022
+ /**
1023
+ * Structured condition for the `WITH CHECK` clause — which *new/updated*
1024
+ * row values are allowed (INSERT, UPDATE). Defaults to `condition` when
1025
+ * omitted, mirroring PostgreSQL's own behavior.
1026
+ */
1027
+ check?: PolicyExpression;
1028
+ ownerField?: never;
1029
+ access?: never;
1030
+ using?: never;
1031
+ withCheck?: never;
932
1032
  }
933
1033
  /**
934
1034
  * Security rule using raw SQL expressions for full PostgreSQL RLS power.
935
1035
  *
936
- * Cannot be combined with `ownerField` or `access`.
1036
+ * **Postgres-only and server-authoritative.** Arbitrary SQL cannot be
1037
+ * evaluated by the admin UI, so a rule using this form is treated as *unknown*
1038
+ * client-side (never silently allowed) and its effect on visible actions is
1039
+ * reflected from the server. For conditions that should also drive the UI
1040
+ * precisely, prefer the structured {@link StructuredSecurityRule}.
1041
+ *
1042
+ * Cannot be combined with `ownerField`, `access`, or structured `condition`.
937
1043
  *
938
1044
  * You can reference columns via `{column_name}` which will be resolved to
939
1045
  * `table.column_name` in the generated Drizzle code.
@@ -969,6 +1075,8 @@ export interface RawSQLSecurityRule extends SecurityRuleBase {
969
1075
  withCheck?: string;
970
1076
  ownerField?: never;
971
1077
  access?: never;
1078
+ condition?: never;
1079
+ check?: never;
972
1080
  }
973
1081
  /**
974
1082
  * Security rule that only filters by application roles, without any
@@ -988,6 +1096,8 @@ export interface RolesOnlySecurityRule extends SecurityRuleBase {
988
1096
  access?: never;
989
1097
  using?: never;
990
1098
  withCheck?: never;
1099
+ condition?: never;
1100
+ check?: never;
991
1101
  }
992
1102
  /**
993
1103
  * Configuration for authentication collections.