@rebasepro/server-postgresql 0.3.0 → 0.5.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 (60) hide show
  1. package/README.md +69 -89
  2. package/dist/common/src/collections/default-collections.d.ts +5 -8
  3. package/dist/common/src/data/query_builder.d.ts +6 -2
  4. package/dist/common/src/util/permissions.d.ts +14 -6
  5. package/dist/index.es.js +379 -611
  6. package/dist/index.es.js.map +1 -1
  7. package/dist/index.umd.js +375 -607
  8. package/dist/index.umd.js.map +1 -1
  9. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -0
  10. package/dist/server-postgresql/src/auth/ensure-tables.d.ts +7 -4
  11. package/dist/server-postgresql/src/auth/services.d.ts +17 -42
  12. package/dist/server-postgresql/src/data-transformer.d.ts +0 -3
  13. package/dist/server-postgresql/src/databasePoolManager.d.ts +1 -1
  14. package/dist/server-postgresql/src/schema/auth-schema.d.ts +87 -340
  15. package/dist/server-postgresql/src/services/EntityFetchService.d.ts +2 -1
  16. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +4 -0
  17. package/dist/server-postgresql/src/services/entityService.d.ts +4 -0
  18. package/dist/server-postgresql/src/types.d.ts +3 -0
  19. package/dist/server-postgresql/src/utils/drizzle-conditions.d.ts +5 -1
  20. package/dist/server-postgresql/src/websocket.d.ts +8 -3
  21. package/dist/types/src/controllers/auth.d.ts +2 -2
  22. package/dist/types/src/controllers/client.d.ts +25 -40
  23. package/dist/types/src/controllers/data.d.ts +21 -3
  24. package/dist/types/src/controllers/data_driver.d.ts +5 -0
  25. package/dist/types/src/controllers/email.d.ts +2 -0
  26. package/dist/types/src/types/auth_adapter.d.ts +3 -56
  27. package/dist/types/src/types/backend.d.ts +38 -3
  28. package/dist/types/src/types/backend_hooks.d.ts +2 -17
  29. package/dist/types/src/types/collections.d.ts +30 -6
  30. package/dist/types/src/types/entity_views.d.ts +19 -28
  31. package/dist/types/src/types/properties.d.ts +9 -15
  32. package/dist/types/src/types/user_management_delegate.d.ts +16 -53
  33. package/dist/types/src/users/index.d.ts +0 -1
  34. package/dist/types/src/users/user.d.ts +0 -1
  35. package/package.json +6 -6
  36. package/src/PostgresBackendDriver.ts +10 -0
  37. package/src/PostgresBootstrapper.ts +27 -22
  38. package/src/auth/ensure-tables.ts +82 -129
  39. package/src/auth/services.ts +99 -197
  40. package/src/cli.ts +50 -23
  41. package/src/data-transformer.ts +57 -95
  42. package/src/databasePoolManager.ts +2 -1
  43. package/src/schema/auth-schema.ts +13 -69
  44. package/src/schema/doctor.ts +44 -3
  45. package/src/schema/generate-drizzle-schema-logic.ts +33 -3
  46. package/src/schema/generate-drizzle-schema.ts +2 -6
  47. package/src/schema/introspect-db-logic.ts +7 -0
  48. package/src/services/EntityFetchService.ts +13 -1
  49. package/src/services/EntityPersistService.ts +38 -12
  50. package/src/services/entityService.ts +7 -0
  51. package/src/types.ts +4 -0
  52. package/src/utils/drizzle-conditions.ts +40 -5
  53. package/src/websocket.ts +38 -25
  54. package/test/auth-services.test.ts +7 -150
  55. package/test/doctor.test.ts +6 -2
  56. package/test/relation-pipeline-gaps.test.ts +315 -0
  57. package/dist/server-postgresql/src/schema/default-collections.d.ts +0 -2
  58. package/dist/types/src/users/roles.d.ts +0 -14
  59. package/drizzle.test.config.ts +0 -10
  60. package/src/schema/default-collections.ts +0 -69
@@ -1,6 +1,6 @@
1
1
  import { SQL } from "drizzle-orm";
2
2
  import { PgTable } from "drizzle-orm/pg-core";
3
- import { Entity, FilterValues } from "@rebasepro/types";
3
+ import { Entity, FilterValues, LogicalCondition } from "@rebasepro/types";
4
4
  import type { VectorSearchParams } from "@rebasepro/types";
5
5
  import { RelationService } from "./RelationService";
6
6
  import { DrizzleClient } from "../interfaces";
@@ -104,6 +104,7 @@ export declare class EntityFetchService {
104
104
  searchString?: string;
105
105
  databaseId?: string;
106
106
  vectorSearch?: VectorSearchParams;
107
+ logical?: LogicalCondition;
107
108
  }): Promise<Entity<M>[]>;
108
109
  /**
109
110
  * Fallback path used when db.query is unavailable.
@@ -17,6 +17,10 @@ export declare class EntityPersistService {
17
17
  * Delete an entity by ID
18
18
  */
19
19
  deleteEntity(collectionPath: string, entityId: string | number, _databaseId?: string): Promise<void>;
20
+ /**
21
+ * Delete all entities from a collection
22
+ */
23
+ deleteAll(collectionPath: string, _databaseId?: string): Promise<void>;
20
24
  /**
21
25
  * Save an entity (create or update)
22
26
  */
@@ -87,6 +87,10 @@ export declare class EntityService implements EntityRepository {
87
87
  * Delete an entity by ID
88
88
  */
89
89
  deleteEntity(collectionPath: string, entityId: string | number, databaseId?: string): Promise<void>;
90
+ /**
91
+ * Delete all entities from a collection
92
+ */
93
+ deleteAll(collectionPath: string, databaseId?: string): Promise<void>;
90
94
  /**
91
95
  * Execute raw SQL
92
96
  */
@@ -0,0 +1,3 @@
1
+ import type { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
2
+ /** Drizzle PgTable with column access by name. Runtime Drizzle tables satisfy this shape. */
3
+ export type RebasePgTable = PgTable & Record<string, AnyPgColumn>;
@@ -1,6 +1,6 @@
1
1
  import { SQL } from "drizzle-orm";
2
2
  import { AnyPgColumn, PgTable } from "drizzle-orm/pg-core";
3
- import { FilterValues, WhereFilterOp, Relation } from "@rebasepro/types";
3
+ import { FilterValues, WhereFilterOp, Relation, LogicalCondition, FilterCondition } from "@rebasepro/types";
4
4
  import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
5
5
  /** Drizzle dynamic query builder — accepts innerJoin + where chaining */
6
6
  export interface DrizzleDynamicQuery {
@@ -22,6 +22,10 @@ export declare class DrizzleConditionBuilder {
22
22
  * Build filter conditions from FilterValues
23
23
  */
24
24
  static buildFilterConditions<M extends Record<string, unknown>>(filter: FilterValues<Extract<keyof M, string>>, table: PgTable<any>, collectionPath: string): SQL[];
25
+ /**
26
+ * Build logical conditions recursively from LogicalCondition or FilterCondition
27
+ */
28
+ static buildLogicalConditions(cond: LogicalCondition | FilterCondition, table: PgTable<any>, collectionPath: string): SQL | null;
25
29
  /**
26
30
  * Build a single filter condition for a specific operator and value
27
31
  */
@@ -1,6 +1,11 @@
1
1
  import { RealtimeService } from "./services/realtimeService";
2
2
  import { PostgresBackendDriver } from "./PostgresBackendDriver";
3
- import { AuthAdapter } from "@rebasepro/types";
3
+ import type { AuthAdapter } from "@rebasepro/types";
4
4
  import { Server } from "http";
5
- import { AuthConfig } from "@rebasepro/server-core";
6
- export declare function createPostgresWebSocket(server: Server, realtimeService: RealtimeService, driver: PostgresBackendDriver, authConfig?: AuthConfig, authAdapter?: AuthAdapter): void;
5
+ /** Minimal subset of RebaseAuthConfig used by the WebSocket layer. */
6
+ interface WsAuthConfig {
7
+ requireAuth?: boolean;
8
+ jwtSecret?: string;
9
+ }
10
+ export declare function createPostgresWebSocket(server: Server, realtimeService: RealtimeService, driver: PostgresBackendDriver, authConfig?: WsAuthConfig, authAdapter?: AuthAdapter): void;
11
+ export {};
@@ -1,4 +1,4 @@
1
- import { Role, User } from "../users";
1
+ import type { User } from "../users";
2
2
  /**
3
3
  * Capabilities advertised by an auth provider.
4
4
  * UI components use this to show/hide features dynamically
@@ -62,7 +62,7 @@ export type AuthController<USER extends User = User, ExtraData = unknown> = {
62
62
  extra: ExtraData;
63
63
  setExtra: (extra: ExtraData) => void;
64
64
  setUser?(user: USER | null): void;
65
- setUserRoles?(roles: Role[]): void;
65
+ setUserRoles?(roles: string[]): void;
66
66
  /**
67
67
  * Capabilities advertised by the auth provider.
68
68
  * UI components use this to feature-detect what the backend supports.
@@ -1,6 +1,6 @@
1
- import { User } from "../users";
2
- import { RebaseData } from "./data";
3
- import { EmailService } from "./email";
1
+ import type { User } from "../users";
2
+ import type { RebaseData } from "./data";
3
+ import type { EmailService } from "./email";
4
4
  /**
5
5
  * Event type for authentication state changes
6
6
  */
@@ -14,7 +14,7 @@ export interface RebaseSession {
14
14
  expiresAt: number;
15
15
  user: User;
16
16
  }
17
- import { StorageSource } from "./storage";
17
+ import type { StorageSource } from "./storage";
18
18
  /**
19
19
  * Unified Authentication Client Interface
20
20
  * Pure functional SDK interface, decoupled from UI and React hooks
@@ -56,19 +56,9 @@ export interface AdminUser {
56
56
  createdAt: string;
57
57
  updatedAt: string;
58
58
  }
59
- /**
60
- * Role record as returned by the Admin API.
61
- * @group Admin
62
- */
63
- export interface AdminRole {
64
- id: string;
65
- name: string;
66
- isAdmin: boolean;
67
- defaultPermissions: Record<string, unknown> | null;
68
- }
69
59
  /**
70
60
  * Client-side Admin API interface.
71
- * Provides user and role management operations.
61
+ * Provides user management operations.
72
62
  * @group Admin
73
63
  */
74
64
  export interface AdminAPI {
@@ -111,30 +101,6 @@ export interface AdminAPI {
111
101
  deleteUser(userId: string): Promise<{
112
102
  success: boolean;
113
103
  }>;
114
- listRoles(): Promise<{
115
- roles: AdminRole[];
116
- }>;
117
- getRole(roleId: string): Promise<{
118
- role: AdminRole;
119
- }>;
120
- createRole(data: {
121
- id: string;
122
- name: string;
123
- isAdmin?: boolean;
124
- defaultPermissions?: Record<string, unknown>;
125
- }): Promise<{
126
- role: AdminRole;
127
- }>;
128
- updateRole(roleId: string, data: {
129
- name?: string;
130
- isAdmin?: boolean;
131
- defaultPermissions?: Record<string, unknown>;
132
- }): Promise<{
133
- role: AdminRole;
134
- }>;
135
- deleteRole(roleId: string): Promise<{
136
- success: boolean;
137
- }>;
138
104
  bootstrap(): Promise<{
139
105
  success: boolean;
140
106
  message: string;
@@ -165,7 +131,7 @@ export interface RebaseClient<DB = unknown> {
165
131
  * > The client-side SDK does not include an email service.
166
132
  */
167
133
  email?: EmailService;
168
- /** Admin API for user and role management */
134
+ /** Admin API for user management */
169
135
  admin?: AdminAPI;
170
136
  /**
171
137
  * The base HTTP URL of the backend server.
@@ -180,4 +146,23 @@ export interface RebaseClient<DB = unknown> {
180
146
  * detection (e.g. `typeof ws.executeSql === "function"`).
181
147
  */
182
148
  ws?: unknown;
149
+ /**
150
+ * Execute raw SQL against the database.
151
+ *
152
+ * Only available server-side when the backend uses a SQL database
153
+ * (PostgreSQL, MySQL, etc.). `undefined` for document databases
154
+ * (MongoDB, Firestore) and on the client-side SDK.
155
+ *
156
+ * @example
157
+ * ```typescript
158
+ * // In a cron job or custom function:
159
+ * if (ctx.client.sql) {
160
+ * const rows = await ctx.client.sql("SELECT count(*) FROM orders");
161
+ * }
162
+ * ```
163
+ */
164
+ sql?(query: string, options?: {
165
+ database?: string;
166
+ role?: string;
167
+ }): Promise<Record<string, unknown>[]>;
183
168
  }
@@ -16,7 +16,17 @@ import { Entity, EntityValues } from "../types/entities";
16
16
  *
17
17
  * @group Data
18
18
  */
19
- export type WhereFieldValue = string | number | boolean | null | [WhereFilterOpShort, any];
19
+ export type WhereFieldValue = string | number | boolean | null | [WhereFilterOpShort, any] | [WhereFilterOpShort, any][];
20
+ export type WhereValue<T> = T | T[] | null;
21
+ export interface LogicalCondition {
22
+ type: "and" | "or";
23
+ conditions: (FilterCondition | LogicalCondition)[];
24
+ }
25
+ export interface FilterCondition {
26
+ column: string;
27
+ operator: FilterOperator;
28
+ value: unknown;
29
+ }
20
30
  /** Short operator strings accepted in the tuple syntax. */
21
31
  export type WhereFilterOpShort = "==" | "!=" | ">" | ">=" | "<" | "<=" | "eq" | "neq" | "gt" | "gte" | "lt" | "lte" | "in" | "nin" | "not-in" | "array-contains" | "array-contains-any" | "cs" | "csa";
22
32
  export interface FindParams {
@@ -51,6 +61,8 @@ export interface FindParams {
51
61
  * ```
52
62
  */
53
63
  where?: Record<string, WhereFieldValue>;
64
+ /** Logical grouping conditions (AND/OR) */
65
+ logical?: LogicalCondition;
54
66
  /**
55
67
  * Sort order. Format: "field:direction".
56
68
  * @example "created_at:desc", "name:asc"
@@ -82,7 +94,8 @@ export type FilterOperator = WhereFilterOpShort;
82
94
  * @group Data
83
95
  */
84
96
  export interface QueryBuilderInterface<M extends Record<string, unknown> = Record<string, unknown>> {
85
- where(column: keyof M & string, operator: FilterOperator, value: unknown): this;
97
+ where<K extends keyof M & string>(column: K, operator: FilterOperator, value: WhereValue<M[K]>): this;
98
+ where(logicalCondition: LogicalCondition): this;
86
99
  orderBy(column: keyof M & string, ascending?: "asc" | "desc"): this;
87
100
  limit(count: number): this;
88
101
  offset(count: number): this;
@@ -125,6 +138,10 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
125
138
  * Delete a record by ID.
126
139
  */
127
140
  delete(id: string | number): Promise<void>;
141
+ /**
142
+ * Delete all records in this collection.
143
+ */
144
+ deleteAll?(): Promise<void>;
128
145
  /**
129
146
  * Subscribe to a collection for real-time updates.
130
147
  * Optional method, may not be supported by all implementations (like stateless HTTP clients).
@@ -139,7 +156,8 @@ export interface CollectionAccessor<M extends Record<string, unknown> = Record<s
139
156
  * Count the number of records matching the given filter.
140
157
  */
141
158
  count?(params?: FindParams): Promise<number>;
142
- where(column: keyof M & string, operator: FilterOperator, value: unknown): QueryBuilderInterface<M>;
159
+ where<K extends keyof M & string>(column: K, operator: FilterOperator, value: WhereValue<M[K]>): QueryBuilderInterface<M>;
160
+ where(logicalCondition: LogicalCondition): QueryBuilderInterface<M>;
143
161
  orderBy(column: keyof M & string, ascending?: "asc" | "desc"): QueryBuilderInterface<M>;
144
162
  limit(count: number): QueryBuilderInterface<M>;
145
163
  offset(count: number): QueryBuilderInterface<M>;
@@ -129,6 +129,11 @@ export interface DataDriver {
129
129
  * @return was the whole deletion flow successful
130
130
  */
131
131
  deleteEntity<M extends Record<string, unknown> = Record<string, unknown>>(props: DeleteEntityProps<M>): Promise<void>;
132
+ /**
133
+ * Delete all entities from a collection.
134
+ * @param path Collection path
135
+ */
136
+ deleteAll?(path: string): Promise<void>;
132
137
  /**
133
138
  * Check if the given property is unique in the given collection
134
139
  * @param path Collection path
@@ -31,4 +31,6 @@ export interface EmailService {
31
31
  send(options: EmailSendOptions): Promise<void>;
32
32
  /** Returns `true` when the service has valid credentials / is ready to send. */
33
33
  isConfigured(): boolean;
34
+ /** Verify connection/credentials with the email provider. */
35
+ verifyConnection?(): Promise<boolean>;
34
36
  }
@@ -148,38 +148,6 @@ export interface AuthCreateUserData {
148
148
  photoUrl?: string;
149
149
  metadata?: Record<string, unknown>;
150
150
  }
151
- /**
152
- * Role data exposed by the auth adapter.
153
- * @group Auth
154
- */
155
- export interface AuthRoleData {
156
- id: string;
157
- name: string;
158
- isAdmin: boolean;
159
- defaultPermissions?: {
160
- read?: boolean;
161
- create?: boolean;
162
- edit?: boolean;
163
- delete?: boolean;
164
- } | null;
165
- collectionPermissions?: Record<string, {
166
- read?: boolean;
167
- create?: boolean;
168
- edit?: boolean;
169
- delete?: boolean;
170
- }> | null;
171
- }
172
- /**
173
- * Data for creating a role.
174
- * @group Auth
175
- */
176
- export interface AuthCreateRoleData {
177
- id: string;
178
- name: string;
179
- isAdmin?: boolean;
180
- defaultPermissions?: AuthRoleData["defaultPermissions"];
181
- collectionPermissions?: AuthRoleData["collectionPermissions"];
182
- }
183
151
  /**
184
152
  * User management operations for the admin panel.
185
153
  *
@@ -193,23 +161,9 @@ export interface UserManagementAdapter {
193
161
  createUser(data: AuthCreateUserData): Promise<AuthUserData>;
194
162
  updateUser(id: string, data: Partial<AuthCreateUserData>): Promise<AuthUserData | null>;
195
163
  deleteUser(id: string): Promise<void>;
196
- getUserRoles(userId: string): Promise<AuthRoleData[]>;
164
+ getUserRoles(userId: string): Promise<string[]>;
197
165
  setUserRoles(userId: string, roleIds: string[]): Promise<void>;
198
166
  }
199
- /**
200
- * Role management operations for the admin panel.
201
- *
202
- * Optional — if not provided by the adapter, role management is disabled.
203
- *
204
- * @group Auth
205
- */
206
- export interface RoleManagementAdapter {
207
- listRoles(): Promise<AuthRoleData[]>;
208
- getRoleById(id: string): Promise<AuthRoleData | null>;
209
- createRole(data: AuthCreateRoleData): Promise<AuthRoleData>;
210
- updateRole(id: string, data: Partial<AuthRoleData>): Promise<AuthRoleData | null>;
211
- deleteRole(id: string): Promise<void>;
212
- }
213
167
  /**
214
168
  * Pluggable authentication adapter for Rebase.
215
169
  *
@@ -217,7 +171,7 @@ export interface RoleManagementAdapter {
217
171
  * database layer. Each auth adapter knows how to:
218
172
  *
219
173
  * 1. Verify incoming HTTP requests (`verifyRequest`)
220
- * 2. Optionally manage users and roles (for the admin panel)
174
+ * 2. Optionally manage users (for the admin panel)
221
175
  * 3. Optionally mount auth-specific routes (login, register, etc.)
222
176
  * 4. Advertise its capabilities so the frontend can adapt
223
177
  *
@@ -269,11 +223,6 @@ export interface AuthAdapter {
269
223
  * Optional — if not provided, user management UI is hidden.
270
224
  */
271
225
  userManagement?: UserManagementAdapter;
272
- /**
273
- * Role CRUD for the admin panel.
274
- * Optional — if not provided, role management is disabled.
275
- */
276
- roleManagement?: RoleManagementAdapter;
277
226
  /**
278
227
  * Mount adapter-specific auth routes (login, register, refresh, etc.).
279
228
  *
@@ -289,7 +238,7 @@ export interface AuthAdapter {
289
238
  */
290
239
  createAuthRoutes?(): Hono<any, any, any> | undefined;
291
240
  /**
292
- * Mount admin routes for user/role management.
241
+ * Mount admin routes for user management.
293
242
  *
294
243
  * Same typing rationale as `createAuthRoutes` — the sub-app env is
295
244
  * unconstrained to support arbitrary adapter implementations.
@@ -345,8 +294,6 @@ export interface CustomAuthAdapterOptions {
345
294
  verifyToken?: (token: string) => Promise<AuthenticatedUser | null>;
346
295
  /** Optional user management for the admin panel. */
347
296
  userManagement?: UserManagementAdapter;
348
- /** Optional role management for the admin panel. */
349
- roleManagement?: RoleManagementAdapter;
350
297
  /** Static service key for server-to-server auth. */
351
298
  serviceKey?: string;
352
299
  /** Override default capabilities. */
@@ -1,5 +1,6 @@
1
1
  import type { Entity } from "./entities";
2
2
  import type { EntityCollection, FilterValues, WhereFilterOp } from "./collections";
3
+ import type { AuthAdapter } from "./auth_adapter";
3
4
  /**
4
5
  * Abstract database connection interface.
5
6
  * Represents a connection to any database system.
@@ -182,6 +183,24 @@ export interface RealtimeProvider {
182
183
  * Notify all relevant subscribers of an entity update
183
184
  */
184
185
  notifyEntityUpdate(path: string, entityId: string, entity: Entity | null, databaseId?: string): Promise<void>;
186
+ /**
187
+ * Called when the HTTP server is ready and listening.
188
+ * Useful for providers that need the server address for callbacks.
189
+ */
190
+ onServerReady?(serverInfo: {
191
+ port: number;
192
+ hostname?: string;
193
+ }): void;
194
+ /**
195
+ * Gracefully shut down the realtime provider.
196
+ * Called during server shutdown to clean up resources.
197
+ */
198
+ destroy?(): Promise<void>;
199
+ /**
200
+ * Stop the internal LISTEN client (e.g., PostgreSQL LISTEN/NOTIFY).
201
+ * Called during graceful shutdown before closing database connections.
202
+ */
203
+ stopListening?(): Promise<void>;
185
204
  }
186
205
  /**
187
206
  * Abstract collection registry interface.
@@ -464,6 +483,22 @@ export interface BackendBootstrapper {
464
483
  * (e.g., `"postgres"`, `"mongodb"`, `"mysql"`).
465
484
  */
466
485
  type: string;
486
+ /**
487
+ * Unique identifier for this bootstrapper instance.
488
+ * Used to register the driver in the driver registry.
489
+ * Defaults to `type` if not set.
490
+ */
491
+ id?: string;
492
+ /**
493
+ * Whether this bootstrapper provides the default driver.
494
+ * When true, the coordinator uses this driver as the primary one.
495
+ */
496
+ isDefault?: boolean;
497
+ /**
498
+ * Run database migrations for this driver.
499
+ * Called by the coordinator after all drivers are initialized.
500
+ */
501
+ runMigrations?(config: unknown, driverResult: InitializedDriver): Promise<void>;
467
502
  /**
468
503
  * Create a DataDriver from the given config.
469
504
  * This is the only **required** method.
@@ -498,7 +533,7 @@ export interface BackendBootstrapper {
498
533
  /**
499
534
  * Initialize WebSocket server for realtime operations.
500
535
  */
501
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown): Promise<void> | void;
536
+ initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown, authAdapter?: AuthAdapter): Promise<void> | void;
502
537
  }
503
538
  /**
504
539
  * Result of `BackendBootstrapper.initializeDriver()`.
@@ -527,8 +562,8 @@ export interface InitializedDriver {
527
562
  export interface BootstrappedAuth {
528
563
  /** User management service. */
529
564
  userService: unknown;
530
- /** Role management service. */
531
- roleService: unknown;
565
+ /** Role management service (optional, roles are now simple strings). */
566
+ roleService?: unknown;
532
567
  /** Email service (optional). */
533
568
  emailService?: unknown;
534
569
  /** Combined Auth Repository for unified token and user management. */
@@ -1,4 +1,4 @@
1
- import type { AdminUser, AdminRole } from "../controllers/client";
1
+ import type { AdminUser } from "../controllers/client";
2
2
  /**
3
3
  * Context passed to every backend hook.
4
4
  * Provides information about the request that triggered the hook.
@@ -64,19 +64,6 @@ export interface UserHooks {
64
64
  */
65
65
  afterDelete?(userId: string, context: BackendHookContext): void | Promise<void>;
66
66
  }
67
- /**
68
- * Hooks for intercepting Admin Role data at the API boundary.
69
- * @group Backend Hooks
70
- */
71
- export interface RoleHooks {
72
- /**
73
- * Transform a role record after it's read from the database,
74
- * before it's returned to the client.
75
- *
76
- * Return the modified role, or `null` to filter it out entirely.
77
- */
78
- afterRead?(role: AdminRole, context: BackendHookContext): AdminRole | null | Promise<AdminRole | null>;
79
- }
80
67
  /**
81
68
  * Hooks for intercepting collection entity data at the REST API boundary.
82
69
  *
@@ -146,7 +133,7 @@ export interface DataHooks {
146
133
  * These hooks run server-side after database operations complete and before
147
134
  * API responses are sent.
148
135
  *
149
- * - `users` / `roles` — intercept admin user and role management endpoints
136
+ * - `users` — intercept admin user management endpoints
150
137
  * - `data` — intercept ALL collection entity data flowing through the REST API
151
138
  *
152
139
  * `data` hooks complement per-collection `EntityCallbacks`. Entity callbacks
@@ -180,8 +167,6 @@ export interface DataHooks {
180
167
  export interface BackendHooks {
181
168
  /** Hooks for intercepting user management data */
182
169
  users?: UserHooks;
183
- /** Hooks for intercepting role management data */
184
- roles?: RoleHooks;
185
170
  /** Hooks for intercepting ALL collection entity data via the REST API */
186
171
  data?: DataHooks;
187
172
  }
@@ -7,7 +7,7 @@ import type { EntityOverrides } from "./entity_overrides";
7
7
  import type { User } from "../users";
8
8
  import type { RebaseContext } from "../rebase_context";
9
9
  import type { Relation } from "./relations";
10
- import type { EntityCustomView, EntityDetailViewConfig } from "./entity_views";
10
+ import type { EntityCustomView, FormViewConfig } from "./entity_views";
11
11
  import type { EntityAction } from "./entity_actions";
12
12
  import type { ComponentRef } from "./component_ref";
13
13
  /**
@@ -124,10 +124,14 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
124
124
  */
125
125
  defaultEntityAction?: "view" | "edit";
126
126
  /**
127
- * Customization options for the read-only detail view.
128
- * Only used when `defaultEntityAction` is `"view"`.
127
+ * Replace the default entity form with a custom component.
128
+ * The Builder receives the same props as entity view tabs
129
+ * (entity, formContext, collection, etc.) and has full control over the UI.
130
+ *
131
+ * Works in both edit mode and read-only mode (when `defaultEntityAction`
132
+ * is `"view"`). In read-only mode, `formContext.readOnly` will be `true`.
129
133
  */
130
- detailView?: EntityDetailViewConfig;
134
+ formView?: FormViewConfig;
131
135
  /**
132
136
  * Prevent default actions from being displayed or executed on this collection.
133
137
  */
@@ -339,6 +343,23 @@ export interface BaseEntityCollection<M extends Record<string, unknown> = Record
339
343
  * Builder for the collection actions rendered in the toolbar
340
344
  */
341
345
  Actions?: ComponentRef<CollectionActionsProps>[];
346
+ /**
347
+ * The database table name for this collection.
348
+ * Automatically set for PostgreSQL collections.
349
+ * For non-SQL backends, this may be undefined.
350
+ */
351
+ table?: string;
352
+ /**
353
+ * Relations defined for this collection.
354
+ * Populated at normalization time from inline relation properties
355
+ * or explicit relation definitions.
356
+ */
357
+ relations?: Relation[];
358
+ /**
359
+ * Security rules for this collection (Row Level Security).
360
+ * When defined, the backend enforces access control policies.
361
+ */
362
+ securityRules?: SecurityRule[];
342
363
  }
343
364
  /**
344
365
  * A collection backed by PostgreSQL (or any SQL database).
@@ -432,7 +453,10 @@ export interface MongoDBCollection<M extends Record<string, unknown> = Record<st
432
453
  * @group Models
433
454
  */
434
455
  export type EntityCollection<M extends Record<string, unknown> = Record<string, unknown>, USER extends User = User> = PostgresCollection<M, USER> | FirebaseCollection<M, USER> | MongoDBCollection<M, USER>;
435
- /** An EntityCollection that supports SQL-style relations (e.g. Postgres). */
456
+ /**
457
+ * An EntityCollection that supports SQL-style relations (e.g. Postgres).
458
+ * @deprecated Use `EntityCollection` directly — `table`, `relations`, and `securityRules` are now on `BaseEntityCollection`.
459
+ */
436
460
  export type CollectionWithRelations<M extends Record<string, unknown> = Record<string, unknown>> = EntityCollection<M> & {
437
461
  table?: string;
438
462
  relations?: Relation[];
@@ -566,7 +590,7 @@ export type WhereFilterOp = "<" | "<=" | "==" | "!=" | ">=" | ">" | "array-conta
566
590
  *
567
591
  * @group Models
568
592
  */
569
- export type FilterValues<Key extends string> = Partial<Record<Key, [WhereFilterOp, unknown]>>;
593
+ export type FilterValues<Key extends string> = Partial<Record<Key, [WhereFilterOp, unknown] | [WhereFilterOp, unknown][]>>;
570
594
  /**
571
595
  * A pre-defined filter preset for quick access in the collection toolbar.
572
596
  * Users can select a preset to instantly apply a set of filters and
@@ -56,41 +56,32 @@ export type EntityCustomView<M extends Record<string, unknown> = Record<string,
56
56
  Builder?: ComponentRef<EntityCustomViewParams<M>>;
57
57
  position?: "start" | "end";
58
58
  };
59
- export interface EntityCustomViewParams<M extends Record<string, unknown> = Record<string, unknown>> {
60
- collection: EntityCollection<M>;
61
- entity?: Entity<M>;
62
- modifiedValues?: EntityValues<M>;
63
- formContext: FormContext<M>;
64
- parentCollectionSlugs?: string[];
65
- parentEntityIds?: string[];
66
- }
67
59
  /**
68
- * Configuration for customizing the read-only detail view of an entity.
69
- * Only used when `defaultEntityAction` is set to `"view"` on the collection.
60
+ * Configuration to replace the default entity form with a custom component.
61
+ * The Builder receives the same props as entity view tabs (entity, formContext, etc.)
62
+ * and has full control over the UI.
63
+ *
64
+ * The form tab still appears in the tab bar but renders your Builder
65
+ * instead of the auto-generated field form.
66
+ *
70
67
  * @group Models
71
68
  */
72
- export type EntityDetailViewConfig<M extends Record<string, unknown> = Record<string, unknown>> = {
73
- /**
74
- * Custom component rendered above the property display in the detail view.
75
- */
76
- Header?: ComponentRef<EntityDetailViewParams<M>>;
69
+ export type FormViewConfig<M extends Record<string, unknown> = Record<string, unknown>> = {
77
70
  /**
78
- * Custom component rendered below the property display in the detail view.
71
+ * Custom component that replaces the default form.
79
72
  */
80
- Footer?: ComponentRef<EntityDetailViewParams<M>>;
73
+ Builder: ComponentRef<EntityCustomViewParams<M>>;
81
74
  /**
82
- * Completely replace the default detail view with a custom component.
83
- * When set, Header and Footer are ignored.
75
+ * If true, the save/delete action bar is rendered alongside the custom view.
76
+ * Defaults to true.
84
77
  */
85
- Builder?: ComponentRef<EntityDetailViewParams<M>>;
78
+ includeActions?: boolean;
86
79
  };
87
- /**
88
- * Props passed to detail view customization components (Header, Footer, Builder).
89
- * @group Models
90
- */
91
- export interface EntityDetailViewParams<M extends Record<string, unknown> = Record<string, unknown>> {
80
+ export interface EntityCustomViewParams<M extends Record<string, unknown> = Record<string, unknown>> {
92
81
  collection: EntityCollection<M>;
93
- entity: Entity<M>;
94
- path: string;
95
- onEditClick: () => void;
82
+ entity?: Entity<M>;
83
+ modifiedValues?: EntityValues<M>;
84
+ formContext: FormContext<M>;
85
+ parentCollectionSlugs?: string[];
86
+ parentEntityIds?: string[];
96
87
  }