@rebasepro/types 0.17.3 → 0.18.1

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 (71) hide show
  1. package/README.md +4 -0
  2. package/dist/call_context.d.ts +20 -0
  3. package/dist/controllers/client.d.ts +36 -4
  4. package/dist/controllers/data.d.ts +120 -10
  5. package/dist/errors.d.ts +83 -4
  6. package/dist/index.es.js +522 -160
  7. package/dist/index.es.js.map +1 -1
  8. package/dist/types/admin_block.d.ts +2 -2
  9. package/dist/types/auth_adapter.d.ts +41 -6
  10. package/dist/types/backend.d.ts +48 -0
  11. package/dist/types/collections.d.ts +25 -1
  12. package/dist/types/cron.d.ts +34 -0
  13. package/dist/types/database_adapter.d.ts +39 -0
  14. package/dist/types/entity_callbacks.d.ts +14 -1
  15. package/dist/types/filter-operators.d.ts +24 -1
  16. package/dist/types/policy.d.ts +29 -1
  17. package/dist/types/properties.d.ts +216 -3
  18. package/dist/types/relations.d.ts +65 -7
  19. package/dist/types/resource_kinds.d.ts +173 -17
  20. package/dist/types/resources.d.ts +108 -7
  21. package/dist/types/rls-functions.d.ts +11 -0
  22. package/dist/types/storage_source.d.ts +12 -23
  23. package/package.json +24 -23
  24. package/src/call_context.ts +0 -120
  25. package/src/controllers/auth_state.ts +0 -24
  26. package/src/controllers/client.ts +0 -494
  27. package/src/controllers/collection_registry.ts +0 -62
  28. package/src/controllers/data.ts +0 -1012
  29. package/src/controllers/data_driver.ts +0 -576
  30. package/src/controllers/effective_role.ts +0 -4
  31. package/src/controllers/email.ts +0 -91
  32. package/src/controllers/index.ts +0 -11
  33. package/src/controllers/storage.ts +0 -252
  34. package/src/errors.ts +0 -119
  35. package/src/index.ts +0 -5
  36. package/src/types/admin_block.ts +0 -209
  37. package/src/types/api_keys.ts +0 -108
  38. package/src/types/auth_adapter.ts +0 -580
  39. package/src/types/backend.ts +0 -987
  40. package/src/types/backup.ts +0 -26
  41. package/src/types/channel_bus.ts +0 -202
  42. package/src/types/chips.ts +0 -34
  43. package/src/types/collection_contract.ts +0 -278
  44. package/src/types/collections.ts +0 -763
  45. package/src/types/component_ref.ts +0 -92
  46. package/src/types/cron.ts +0 -213
  47. package/src/types/data_source.ts +0 -357
  48. package/src/types/database_adapter.ts +0 -267
  49. package/src/types/entities.ts +0 -226
  50. package/src/types/entity_callbacks.ts +0 -229
  51. package/src/types/filter-operators.ts +0 -444
  52. package/src/types/history.ts +0 -66
  53. package/src/types/index.ts +0 -36
  54. package/src/types/indexes.ts +0 -180
  55. package/src/types/policy.ts +0 -328
  56. package/src/types/postgres_introspection.ts +0 -101
  57. package/src/types/project_manifest.ts +0 -598
  58. package/src/types/properties.ts +0 -1368
  59. package/src/types/relations.ts +0 -417
  60. package/src/types/resource_kinds.ts +0 -390
  61. package/src/types/resources.ts +0 -368
  62. package/src/types/rls-functions.ts +0 -98
  63. package/src/types/schema_editing.ts +0 -157
  64. package/src/types/schema_version.ts +0 -112
  65. package/src/types/search.ts +0 -247
  66. package/src/types/security_rules.ts +0 -344
  67. package/src/types/storage_authorize.ts +0 -77
  68. package/src/types/storage_source.ts +0 -248
  69. package/src/types/websockets.ts +0 -117
  70. package/src/users/index.ts +0 -2
  71. package/src/users/user.ts +0 -69
@@ -1,987 +0,0 @@
1
- import type { CollectionConfig, FilterValues, WhereFilterOp } from "./collections";
2
- import type { OrderByTuple } from "./filter-operators";
3
- import type { LogicalCondition } from "../controllers/data";
4
- import type { AuthAdapter } from "./auth_adapter";
5
- import type { HistoryConfig } from "../controllers/client";
6
- import type { ChannelBusSetting } from "./channel_bus";
7
- import type { SchemaEditingAdmin } from "./schema_editing";
8
-
9
- // =============================================================================
10
- // DATABASE CONNECTION INTERFACES
11
- // =============================================================================
12
-
13
- /**
14
- * Abstract database connection interface.
15
- * Represents a connection to any database system.
16
- */
17
- export interface DatabaseConnection {
18
- /**
19
- * Type identifier for this database (e.g., 'postgres', 'mongodb', 'mysql')
20
- */
21
- readonly type: string;
22
-
23
- /**
24
- * Whether the connection is currently active
25
- */
26
- readonly isConnected?: boolean;
27
-
28
- /**
29
- * Close the database connection and release resources.
30
- */
31
- close?(): Promise<void>;
32
- }
33
-
34
- // =============================================================================
35
- // QUERY BUILDING INTERFACES
36
- // =============================================================================
37
-
38
- /**
39
- * A single filter condition for database queries
40
- */
41
- export interface QueryFilter {
42
- field: string;
43
- operator: WhereFilterOp;
44
- value: unknown;
45
- }
46
-
47
- /**
48
- * Options for fetching a collection of entities
49
- */
50
- export interface FetchCollectionOptions<M extends Record<string, unknown> = Record<string, unknown>> {
51
- filter?: FilterValues<Extract<keyof M, string>>;
52
- /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
53
- orderBy?: string | OrderByTuple[];
54
- order?: "desc" | "asc";
55
- limit?: number;
56
- offset?: number;
57
- startAfter?: unknown;
58
- searchString?: string;
59
- databaseId?: string;
60
- collection?: CollectionConfig;
61
- }
62
-
63
- /**
64
- * Options for searching entities
65
- */
66
- export interface SearchOptions<M extends Record<string, unknown> = Record<string, unknown>> {
67
- filter?: FilterValues<Extract<keyof M, string>>;
68
- /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
69
- orderBy?: string | OrderByTuple[];
70
- order?: "desc" | "asc";
71
- limit?: number;
72
- databaseId?: string;
73
- collection?: CollectionConfig;
74
- }
75
-
76
- /**
77
- * Options for counting entities
78
- */
79
- export interface CountOptions<M extends Record<string, unknown> = Record<string, unknown>> {
80
- filter?: FilterValues<Extract<keyof M, string>>;
81
- /**
82
- * An `or(...)`/`and(...)` group, alongside `filter`.
83
- *
84
- * Counted as well as fetched, or `total` describes a different set of rows
85
- * from the one that was served — the same reason `filter` is here.
86
- */
87
- logical?: LogicalCondition;
88
- searchString?: string;
89
- databaseId?: string;
90
- }
91
-
92
- /**
93
- * Abstract condition builder interface.
94
- * Implementations translate Rebase filter conditions to database-specific queries.
95
- *
96
- * Note: This interface can be implemented as instance methods or as a class with static methods.
97
- * For static implementations (like DrizzleConditionBuilder), use the ConditionBuilderStatic type.
98
- *
99
- * @template T The type of condition returned by the builder (e.g., SQL for PostgreSQL, Filter<Document> for MongoDB)
100
- */
101
- export interface ConditionBuilder<T = unknown> {
102
- /**
103
- * Build filter conditions from Rebase FilterValues
104
- */
105
- buildFilterConditions<M extends Record<string, unknown>>(
106
- filter: FilterValues<Extract<keyof M, string>>,
107
- collectionPath: string,
108
- ...args: unknown[]
109
- ): T[];
110
-
111
- /**
112
- * Build search conditions for text search
113
- */
114
- buildSearchConditions(
115
- searchString: string,
116
- properties: Record<string, unknown>,
117
- ...args: unknown[]
118
- ): T[];
119
-
120
- /**
121
- * Combine multiple conditions with AND operator
122
- */
123
- combineConditionsWithAnd(conditions: T[]): T | undefined;
124
-
125
- /**
126
- * Combine multiple conditions with OR operator
127
- */
128
- combineConditionsWithOr(conditions: T[]): T | undefined;
129
- }
130
-
131
- /**
132
- * Static condition builder type for implementations using static methods.
133
- * Use this type when the class provides static methods rather than instance methods.
134
- *
135
- * @example
136
- * // DrizzleConditionBuilder satisfies this type
137
- * const builder: ConditionBuilderStatic<SQL> = DrizzleConditionBuilder;
138
- */
139
- export type ConditionBuilderStatic<T = unknown> = {
140
- buildFilterConditions<M extends Record<string, unknown>>(
141
- filter: FilterValues<Extract<keyof M, string>>,
142
- ...args: unknown[]
143
- ): T[];
144
- buildSearchConditions(
145
- searchString: string,
146
- properties: Record<string, unknown>,
147
- ...args: unknown[]
148
- ): T[];
149
- combineConditionsWithAnd(conditions: T[]): T | undefined;
150
- combineConditionsWithOr(conditions: T[]): T | undefined;
151
- };
152
-
153
- // =============================================================================
154
- // ENTITY REPOSITORY INTERFACES
155
- // =============================================================================
156
-
157
- /**
158
- * Abstract entity repository interface.
159
- * Handles all CRUD operations for entities in the database.
160
- *
161
- * Implementations should handle:
162
- * - Entity serialization/deserialization
163
- * - Relation resolution
164
- * - ID generation and conversion
165
- */
166
- export interface DataRepository {
167
- /**
168
- * Fetch a single entity by ID
169
- */
170
- fetchOne<M extends Record<string, unknown>>(
171
- collectionPath: string,
172
- id: string | number,
173
- databaseId?: string
174
- ): Promise<Record<string, unknown> | undefined>;
175
-
176
- /**
177
- * Fetch a collection of entities with optional filtering, ordering, and pagination
178
- */
179
- fetchCollection<M extends Record<string, unknown>>(
180
- collectionPath: string,
181
- options?: FetchCollectionOptions<M>
182
- ): Promise<Record<string, unknown>[]>;
183
-
184
- /**
185
- * Search entities by text
186
- */
187
- searchRows<M extends Record<string, unknown>>(
188
- collectionPath: string,
189
- searchString: string,
190
- options?: SearchOptions<M>
191
- ): Promise<Record<string, unknown>[]>;
192
-
193
- /**
194
- * Count entities in a collection
195
- */
196
- count<M extends Record<string, unknown>>(
197
- collectionPath: string,
198
- options?: CountOptions<M>
199
- ): Promise<number>;
200
-
201
- /**
202
- * Save a entity (create or update)
203
- */
204
- save<M extends Record<string, unknown>>(
205
- collectionPath: string,
206
- values: Partial<M>,
207
- id?: string | number,
208
- databaseId?: string
209
- ): Promise<Record<string, unknown>>;
210
-
211
- /**
212
- * Delete a entity by ID
213
- */
214
- delete(
215
- collectionPath: string,
216
- id: string | number,
217
- databaseId?: string
218
- ): Promise<void>;
219
-
220
- /**
221
- * Check if a field value is unique in a collection
222
- */
223
- checkUniqueField(
224
- collectionPath: string,
225
- fieldName: string,
226
- value: unknown,
227
- excludeEntityId?: string,
228
- databaseId?: string
229
- ): Promise<boolean>;
230
-
231
- }
232
-
233
- // =============================================================================
234
- // REALTIME INTERFACES
235
- // =============================================================================
236
-
237
- /**
238
- * Configuration for subscribing to a collection
239
- */
240
- export interface CollectionSubscriptionConfig {
241
- clientId: string;
242
- path: string;
243
- filter?: unknown;
244
- /**
245
- * An `or(...)`/`and(...)` group, applied alongside `filter`.
246
- *
247
- * Declared here because a subscription is a query, and every field a query
248
- * has this one needs too. It was missing, so the type-checked boundary
249
- * dropped it: the client sent the group, nothing rejected it, and the
250
- * subscription re-fetched with the group gone — pushing every row the
251
- * caller's policies allowed rather than the ones they asked for. The same
252
- * defect `FetchCollectionProps.logical` documents, one layer up.
253
- */
254
- logical?: LogicalCondition;
255
- /**
256
- * Where the subscription's page starts. Missing for the same reason, with
257
- * a quieter symptom: a subscriber watching page two was pushed page one,
258
- * and a `collection_update` frame carries no window for it to notice with.
259
- */
260
- offset?: number;
261
- /** See `FetchCollectionProps.orderBy`: a field name plus `order`, or a list of tuples. */
262
- orderBy?: string | OrderByTuple[];
263
- order?: "desc" | "asc";
264
- limit?: number;
265
- startAfter?: unknown;
266
- databaseId?: string;
267
- searchString?: string;
268
- /** Ask each row which declared search field matched. */
269
- searchExplain?: boolean;
270
- }
271
-
272
- /**
273
- * Configuration for subscribing to a single entity
274
- */
275
- export interface SingleSubscriptionConfig {
276
- clientId: string;
277
- path: string;
278
- id: string | number;
279
- }
280
-
281
- /**
282
- * Opt-in retention for one set of broadcast channels.
283
- *
284
- * Retention is configured on the server and nowhere else. A channel is created
285
- * by whoever names it, so letting a client ask for its own history depth would
286
- * let any visitor commit the backend to unbounded storage; and presence-only or
287
- * notification-only channels — the overwhelming majority — must not pay for a
288
- * feature they never use. With no rules configured nothing is written, no table
289
- * is created, and broadcast behaves exactly as it did before history existed.
290
- */
291
- export interface ChannelRetentionRule {
292
- /**
293
- * Channel name to match. Either exact (`"doc:42"`) or a trailing-`*` prefix
294
- * (`"doc:*"`). Deliberately not a full glob or RegExp: this decides what
295
- * gets written to disk, and a rule whose blast radius is not obvious at a
296
- * glance is the wrong shape for that.
297
- */
298
- match: string;
299
- /** Keep at most this many of the most recent messages per channel. */
300
- limit?: number;
301
- /**
302
- * Keep messages for at most this long. Accepts a millisecond count or a
303
- * short duration string (`"30s"`, `"15m"`, `"24h"`, `"7d"`).
304
- */
305
- ttl?: number | string;
306
- }
307
-
308
- /**
309
- * Server-side realtime options.
310
- *
311
- * The channel bus contract and its config live in `./channel_bus` so that a
312
- * transport shipped as its own package depends on the contract alone.
313
- */
314
- export interface RealtimeChannelsConfig {
315
- /**
316
- * Retention rules, most specific first — the first match wins. Omitted or
317
- * empty means no channel retains anything.
318
- */
319
- channels?: ChannelRetentionRule[];
320
- /**
321
- * How channel broadcast and presence reach other backend instances.
322
- * Defaults to `{ type: "memory" }` — i.e. they don't.
323
- */
324
- bus?: ChannelBusSetting;
325
- }
326
-
327
- /**
328
- * Abstract realtime provider interface.
329
- * Handles real-time subscriptions and notifications for entity changes.
330
- */
331
- export interface RealtimeProvider {
332
- /**
333
- * Subscribe to collection changes
334
- */
335
- subscribeToCollection(
336
- subscriptionId: string,
337
- config: CollectionSubscriptionConfig,
338
- callback?: (rows: Record<string, unknown>[]) => void
339
- ): void;
340
-
341
- /**
342
- * Subscribe to single entity changes
343
- */
344
- subscribeToOne(
345
- subscriptionId: string,
346
- config: SingleSubscriptionConfig,
347
- callback?: (row: Record<string, unknown> | null) => void
348
- ): void;
349
-
350
- /**
351
- * Unsubscribe from a subscription
352
- */
353
- unsubscribe(subscriptionId: string): void;
354
-
355
- /**
356
- * Notify all relevant subscribers of a entity update
357
- */
358
- notifyUpdate(
359
- path: string,
360
- id: string,
361
- row: Record<string, unknown> | null,
362
- databaseId?: string
363
- ): Promise<void>;
364
-
365
- /**
366
- * Called when the HTTP server is ready and listening.
367
- * Useful for providers that need the server address for callbacks.
368
- */
369
- onServerReady?(serverInfo: { port: number; hostname?: string }): void;
370
-
371
- /**
372
- * Gracefully shut down the realtime provider.
373
- * Called during server shutdown to clean up resources.
374
- */
375
- destroy?(): Promise<void>;
376
-
377
- /**
378
- * Stop the internal LISTEN client (e.g., PostgreSQL LISTEN/NOTIFY).
379
- * Called during graceful shutdown before closing database connections.
380
- */
381
- stopListening?(): Promise<void>;
382
- }
383
-
384
- // =============================================================================
385
- // COLLECTION REGISTRY INTERFACES
386
- // =============================================================================
387
-
388
- /**
389
- * Abstract collection registry interface.
390
- * Manages registration and lookup of entity collections.
391
- */
392
- export interface CollectionRegistryInterface {
393
- /**
394
- * Register a collection
395
- */
396
- register(collection: CollectionConfig): void;
397
-
398
- /**
399
- * Get a collection by its path
400
- */
401
- getCollectionByPath(path: string): CollectionConfig | undefined;
402
-
403
- /**
404
- * Get all registered collections
405
- */
406
- getCollections(): CollectionConfig[];
407
-
408
- /**
409
- * Get the currently registered global callbacks, if any.
410
- */
411
- getGlobalCallbacks(): any | undefined;
412
- }
413
-
414
- // =============================================================================
415
- // DATA TRANSFORMER INTERFACES
416
- // =============================================================================
417
-
418
- /**
419
- * Abstract data transformer interface.
420
- * Handles serialization/deserialization between frontend and database formats.
421
- */
422
- export interface DataTransformer {
423
- /**
424
- * Transform entity data for storage in the database
425
- */
426
- serializeToDatabase<M extends Record<string, unknown>>(
427
- entity: M,
428
- collection: CollectionConfig
429
- ): Record<string, unknown>;
430
-
431
- /**
432
- * Transform database data back to entity format
433
- */
434
- deserializeFromDatabase<M extends Record<string, unknown>>(
435
- data: Record<string, unknown>,
436
- collection: CollectionConfig
437
- ): Promise<M>;
438
- }
439
-
440
- // =============================================================================
441
- // DATABASE ADMIN — CAPABILITY-SPECIFIC INTERFACES (1.3)
442
- // =============================================================================
443
-
444
- /**
445
- * Administrative operations for SQL-based databases (PostgreSQL, MySQL, etc.).
446
- * Used by the SQL Editor, RLS Editor, and schema browser.
447
- *
448
- * @group Admin
449
- */
450
- export interface SQLAdmin {
451
- /**
452
- * Execute raw SQL against the database.
453
- */
454
- executeSql(sql: string, options?: { database?: string; role?: string; params?: unknown[] }): Promise<Record<string, unknown>[]>;
455
-
456
- /**
457
- * Fetch the available databases on the server.
458
- */
459
- fetchAvailableDatabases?(): Promise<string[]>;
460
-
461
- /**
462
- * Fetch the available *native PostgreSQL* database roles (from `pg_roles`).
463
- *
464
- * These are connection-level roles — what the SQL editor can `SET ROLE` to,
465
- * and what `SecurityRule.pgRoles` targets. They are NOT application roles;
466
- * for those use {@link fetchApplicationRoles}.
467
- */
468
- fetchAvailableRoles?(): Promise<string[]>;
469
-
470
- /**
471
- * Fetch the *application-level* roles in use in this project.
472
- *
473
- * These are the strings stored on the users table's `roles` column and
474
- * exposed to policies as `rebase.roles()` — what `SecurityRule.roles`
475
- * matches against. Distinct from {@link fetchAvailableRoles}; the two are
476
- * not interchangeable.
477
- */
478
- fetchApplicationRoles?(): Promise<string[]>;
479
-
480
- /**
481
- * Fetch the current database name.
482
- */
483
- fetchCurrentDatabase?(): Promise<string | undefined>;
484
- }
485
-
486
- /**
487
- * Administrative operations for document-based databases (MongoDB, Firestore, etc.).
488
- * Used by future document administration tools.
489
- *
490
- * @group Admin
491
- */
492
- export interface DocumentAdmin {
493
- /**
494
- * Execute an aggregation pipeline or equivalent query.
495
- */
496
- executeAggregate?(pipeline: Record<string, unknown>[]): Promise<Record<string, unknown>[]>;
497
-
498
- /**
499
- * Fetch statistics for a collection (document count, size, etc.).
500
- */
501
- fetchCollectionStats?(collectionName: string): Promise<{ count: number; sizeBytes?: number }>;
502
- }
503
-
504
- /**
505
- * Administrative operations for schema management.
506
- * Shared across SQL and document databases.
507
- *
508
- * @group Admin
509
- */
510
- export interface SchemaAdmin {
511
- /**
512
- * Fetch database tables/collections not yet mapped to a Rebase collection.
513
- */
514
- fetchUnmappedTables?(mappedPaths?: string[]): Promise<string[]>;
515
-
516
- /**
517
- * Fetch column/field metadata for a single table/collection.
518
- * The return type is generic — SQL backends return TableMetadata,
519
- * document backends may return a different shape.
520
- */
521
- fetchTableMetadata?(tableName: string): Promise<unknown>;
522
- }
523
-
524
- /**
525
- * Metadata for a database branch.
526
- * @group Admin
527
- */
528
- export interface BranchInfo {
529
- /** Branch name (without prefix). */
530
- name: string;
531
- /** The database this branch was created from. */
532
- parentDatabase: string;
533
- /** When the branch was created. */
534
- createdAt: Date;
535
- /** Size in bytes, if available from the server. */
536
- sizeBytes?: number;
537
- }
538
-
539
- /**
540
- * Administrative operations for database branching.
541
- * Allows creating isolated database copies for development/preview workflows.
542
- *
543
- * @group Admin
544
- */
545
- export interface BranchAdmin {
546
- /** Create a new branch (database copy) from the current or specified source database. */
547
- createBranch(name: string, options?: { source?: string }): Promise<BranchInfo>;
548
-
549
- /** Delete a branch database. Cannot delete the main/default database. */
550
- deleteBranch(name: string): Promise<void>;
551
-
552
- /** List all branches (databases that were created via branching). */
553
- listBranches(): Promise<BranchInfo[]>;
554
-
555
- /** Get info about a specific branch. */
556
- getBranchInfo(name: string): Promise<BranchInfo | undefined>;
557
- }
558
-
559
- /**
560
- * Union type for all admin capabilities.
561
- * A backend may implement any combination of these interfaces.
562
- *
563
- * Use type guards (`isSQLAdmin`, `isDocumentAdmin`, `isSchemaAdmin`, `isBranchAdmin`)
564
- * to safely narrow the type before calling methods.
565
- *
566
- * @group Admin
567
- */
568
- export type DatabaseAdmin = Partial<SQLAdmin> & Partial<DocumentAdmin> & Partial<SchemaAdmin>
569
- & Partial<BranchAdmin> & Partial<SchemaEditingAdmin>;
570
-
571
- /**
572
- * Type guard: can this admin plan a live schema change?
573
- *
574
- * Planning is engine-specific — it renders DDL, a Drizzle schema and the
575
- * declarative SQL artifacts — so the implementation lives in the driver
576
- * package. The server detects the capability structurally, exactly as it does
577
- * for SQL, rather than importing an engine it is supposed to know nothing
578
- * about.
579
- *
580
- * @group Admin
581
- */
582
- export function isSchemaEditingAdmin(admin: DatabaseAdmin | undefined): admin is SchemaEditingAdmin {
583
- return !!admin && typeof (admin as SchemaEditingAdmin).planSchemaChange === "function";
584
- }
585
-
586
- /**
587
- * Type guard: does this admin support SQL operations?
588
- * @group Admin
589
- */
590
- export function isSQLAdmin(admin: DatabaseAdmin | undefined): admin is SQLAdmin {
591
- return !!admin && typeof (admin as SQLAdmin).executeSql === "function";
592
- }
593
-
594
- /**
595
- * Type guard: does this admin support document operations?
596
- * @group Admin
597
- */
598
- export function isDocumentAdmin(admin: DatabaseAdmin | undefined): admin is DocumentAdmin {
599
- return !!admin && (
600
- typeof (admin as DocumentAdmin).executeAggregate === "function" ||
601
- typeof (admin as DocumentAdmin).fetchCollectionStats === "function"
602
- );
603
- }
604
-
605
- /**
606
- * Type guard: does this admin support schema management?
607
- * @group Admin
608
- */
609
- export function isSchemaAdmin(admin: DatabaseAdmin | undefined): admin is SchemaAdmin {
610
- return !!admin && (
611
- typeof (admin as SchemaAdmin).fetchUnmappedTables === "function" ||
612
- typeof (admin as SchemaAdmin).fetchTableMetadata === "function"
613
- );
614
- }
615
-
616
- /**
617
- * Type guard: does this admin support database branching?
618
- * @group Admin
619
- */
620
- export function isBranchAdmin(admin: DatabaseAdmin | undefined): admin is BranchAdmin {
621
- return !!admin && typeof (admin as BranchAdmin).createBranch === "function";
622
- }
623
-
624
- // =============================================================================
625
- // LIFECYCLE INTERFACES (1.4)
626
- // =============================================================================
627
-
628
- /**
629
- * Health check result returned by `healthCheck()`.
630
- * @group Lifecycle
631
- */
632
- export interface HealthCheckResult {
633
- /** Whether the backend is healthy and able to serve requests. */
634
- healthy: boolean;
635
- /** Round-trip latency to the database in milliseconds. */
636
- latencyMs: number;
637
- /** Optional details (e.g., pool stats, replication lag). */
638
- details?: Record<string, unknown>;
639
- }
640
-
641
- /**
642
- * Lifecycle contract for backend components that hold resources
643
- * (database connections, WebSocket pools, timers, etc.).
644
- *
645
- * All methods are optional — simple backends (e.g., in-memory) can skip them.
646
- * @group Lifecycle
647
- */
648
- export interface BackendLifecycle {
649
- /**
650
- * Initialize the backend: open connections, run migrations, seed data.
651
- * Called once during startup. Idempotent.
652
- */
653
- initialize?(): Promise<void>;
654
-
655
- /**
656
- * Check whether the backend is healthy and reachable.
657
- * Should be fast (< 1 s) and safe to call frequently.
658
- */
659
- healthCheck?(): Promise<HealthCheckResult>;
660
-
661
- /**
662
- * Gracefully shut down: close connections, flush buffers, cancel timers.
663
- * After calling `destroy()`, no other methods should be called.
664
- */
665
- destroy?(): Promise<void>;
666
- }
667
-
668
- // =============================================================================
669
- // BACKEND FACTORY INTERFACES
670
- // =============================================================================
671
-
672
- /**
673
- * Configuration for creating a database backend
674
- */
675
- export interface BackendConfig {
676
- /**
677
- * Type of database backend
678
- */
679
- type: string;
680
-
681
- /**
682
- * Database connection (implementation-specific)
683
- */
684
- connection: unknown;
685
-
686
- /**
687
- * Schema definition (implementation-specific, e.g., Drizzle schema for PostgreSQL)
688
- */
689
- schema?: unknown;
690
- }
691
-
692
- /**
693
- * A complete backend instance with all required services.
694
- *
695
- * Now includes optional lifecycle management and admin capabilities.
696
- */
697
- export interface BackendInstance extends BackendLifecycle {
698
- /**
699
- * Entity repository for CRUD operations
700
- */
701
- entityRepository: DataRepository;
702
-
703
- /**
704
- * Realtime provider for subscriptions
705
- */
706
- realtimeProvider: RealtimeProvider;
707
-
708
- /**
709
- * Collection registry
710
- */
711
- collectionRegistry: CollectionRegistryInterface;
712
-
713
- /**
714
- * The underlying database connection
715
- */
716
- connection: DatabaseConnection;
717
-
718
- /**
719
- * Administrative operations (SQL, schema, documents).
720
- * What's available depends on the backend type — use type guards
721
- * (`isSQLAdmin`, `isSchemaAdmin`, etc.) to narrow.
722
- */
723
- admin?: DatabaseAdmin;
724
- }
725
-
726
- /**
727
- * Factory function type for creating backend instances
728
- */
729
- export type BackendFactory<TConfig extends BackendConfig = BackendConfig> =
730
- (config: TConfig) => BackendInstance;
731
-
732
- // =============================================================================
733
- // BACKEND BOOTSTRAPPER (1.2)
734
- // =============================================================================
735
-
736
- /**
737
- * A `BackendBootstrapper` encapsulates all driver-specific initialization logic.
738
- *
739
- * Instead of hard-coding Postgres setup into `initializeRebaseBackend()`,
740
- * each database backend provides its own bootstrapper that knows how to:
741
- * - Create the DataDriver from a config object
742
- * - Optionally initialize auth tables
743
- * - Optionally create a realtime service
744
- * - Mount driver-specific API routes
745
- *
746
- * The main `initializeRebaseBackend()` becomes a **coordinator** that iterates
747
- * registered bootstrappers, calls their hooks, and wires the results together.
748
- *
749
- * @group Backend
750
- *
751
- * @example
752
- * ```typescript
753
- * // Third-party MySQL bootstrapper
754
- * const mysqlBootstrapper: BackendBootstrapper = {
755
- * type: "mysql",
756
- * initializeDriver: async (config) => new MySQLDataDriver(config.connection),
757
- * initializeRealtime: async (config) => new MySQLChangeStreamRealtime(config.connection),
758
- * };
759
- *
760
- * initializeRebaseBackend({
761
- * ...config,
762
- * bootstrappers: [postgresBootstrapper, mysqlBootstrapper]
763
- * });
764
- * ```
765
- */
766
- export interface BackendBootstrapper {
767
- /**
768
- * Which driver type this bootstrapper handles.
769
- * Must match the `type` field on the driver config object
770
- * (e.g., `"postgres"`, `"mongodb"`, `"mysql"`).
771
- */
772
- type: string;
773
-
774
- /**
775
- * Unique identifier for this bootstrapper instance.
776
- * Used to register the driver in the driver registry.
777
- * Defaults to `type` if not set.
778
- */
779
- id?: string;
780
-
781
- /**
782
- * Whether this bootstrapper provides the default driver.
783
- * When true, the coordinator uses this driver as the primary one.
784
- */
785
- isDefault?: boolean;
786
-
787
- /**
788
- * Run database migrations for this driver.
789
- * Called by the coordinator after all drivers are initialized.
790
- */
791
- runMigrations?(config: unknown, driverResult: InitializedDriver): Promise<void>;
792
-
793
- /**
794
- * Create a DataDriver from the given config.
795
- * This is the only **required** method.
796
- */
797
- initializeDriver(config: unknown): Promise<InitializedDriver>;
798
-
799
- /**
800
- * Initialize auth tables / services if this driver supports them.
801
- * Return undefined if auth is not supported by this backend.
802
- */
803
- initializeAuth?(config: unknown, driverResult: InitializedDriver): Promise<BootstrappedAuth | undefined>;
804
-
805
- /**
806
- * Initialize history tables / services if this driver supports them.
807
- * Return undefined if history is not supported by this backend.
808
- */
809
- initializeHistory?(config: HistoryConfig, driverResult: InitializedDriver): Promise<{ historyService: unknown } | undefined>;
810
-
811
- /**
812
- * Create a realtime provider for this driver.
813
- * Return undefined if the driver does not support realtime.
814
- */
815
- initializeRealtime?(config: unknown, driverResult: InitializedDriver): Promise<RealtimeProvider | undefined>;
816
-
817
- /**
818
- * Mount any driver-specific HTTP routes (e.g., custom admin endpoints).
819
- * Called after all drivers are initialized.
820
- */
821
- mountRoutes?(app: unknown, basePath: string, driverResult: InitializedDriver): void;
822
-
823
- /**
824
- * Return admin capabilities for this driver.
825
- */
826
- getAdmin?(driverResult: InitializedDriver): DatabaseAdmin | undefined;
827
-
828
- /**
829
- * Bring the database's collection tables up to date, additively.
830
- *
831
- * Optional because it is only meaningful for schema-ful drivers. A managed
832
- * runtime boots a compiled project against a database it has never seen; auth
833
- * tables are ensured on boot but collection tables were created by nothing,
834
- * so every data request answered 500 on a missing relation. The CLI's `db
835
- * push` cannot fill the gap — it needs Atlas, and the runtime image ships no
836
- * CLI.
837
- *
838
- * Implementations MUST be additive-only: create missing tables, columns and
839
- * enum types, and never drop, narrow or rewrite anything. This runs
840
- * unattended against live customer data with nobody reading a diff, so the
841
- * destructive half stays a deliberate migration.
842
- *
843
- * `driverResult` is optional: this runs before `initializeDriver`, and only
844
- * the bundle path has a pre-init stand-in to pass. An adapter built by an
845
- * application already holds its own connection and MUST use it when this is
846
- * `undefined` — dereferencing it unconditionally works for managed tenants
847
- * and breaks every app that builds its own adapter.
848
- */
849
- ensureCollectionSchema?(
850
- collections: unknown[],
851
- driverResult?: InitializedDriver,
852
- log?: (message: string) => void
853
- ): Promise<{ applied: number }>;
854
-
855
- /**
856
- * Apply the collections' row-level-security policies, additively and
857
- * idempotently — the companion to {@link ensureCollectionSchema}.
858
- *
859
- * That method creates the tables; a table with RLS disabled and no policies
860
- * is not servable, because authenticated requests run as a restricted role:
861
- * a read with no `SELECT` policy returns nothing (a public collection
862
- * answers 401) and a write with no `INSERT`/`UPDATE` policy is denied. The
863
- * `db push` CLI applies these from the same collections, but it cannot reach
864
- * a managed tenant's in-cluster database — the runtime, already connected,
865
- * is the only thing that can.
866
- *
867
- * MUST be idempotent (re-run on every boot) and MUST NOT be destructive.
868
- * Runs after auth initialization, because the generated policies call the
869
- * `auth.*` helper functions and `CREATE POLICY` validates they exist.
870
- */
871
- ensureCollectionPolicies?(
872
- collections: unknown[],
873
- driverResult?: InitializedDriver,
874
- log?: (message: string) => void
875
- ): Promise<{ applied: number }>;
876
-
877
- /**
878
- * Read the collections schema version this database was last provisioned
879
- * from, or `null` when nothing has ever stamped it.
880
- *
881
- * The companion to {@link stampCollectionsSchemaVersion}: one process writes
882
- * what it applied, every other process compares itself to it. This is what
883
- * lets a split deployment — several processes over one database, only one of
884
- * which provisions — notice that a unit is serving against a schema it was
885
- * not built for. That failure is otherwise silent in both directions: a
886
- * column that does not exist is a SQL error on one route, and a policy that
887
- * was never applied is a 200 with no rows.
888
- *
889
- * `null` is not an error and MUST NOT be treated as one — every database
890
- * provisioned before the stamp existed reads this way, and so does every
891
- * fresh one until its first provisioning boot finishes.
892
- */
893
- readCollectionsSchemaVersion?(
894
- driverResult?: InitializedDriver
895
- ): Promise<string | null>;
896
-
897
- /**
898
- * Record the collections schema version this process just applied.
899
- *
900
- * Called only by the process that provisions, and only after both
901
- * {@link ensureCollectionSchema} and {@link ensureCollectionPolicies} have
902
- * run — a stamp written before the policies would claim a schema that is
903
- * only half in place, and the half that is missing is the one that fails
904
- * without an error.
905
- */
906
- stampCollectionsSchemaVersion?(
907
- version: string,
908
- driverResult?: InitializedDriver
909
- ): Promise<void>;
910
-
911
- /**
912
- * Initialize WebSocket server for realtime operations.
913
- */
914
- initializeWebsockets?(server: unknown, realtimeService: RealtimeProvider, driver: import("../controllers/data_driver").DataDriver, config?: unknown, authAdapter?: AuthAdapter): Promise<void> | void;
915
- }
916
-
917
- /**
918
- * Result of `BackendBootstrapper.initializeDriver()`.
919
- * @group Backend
920
- */
921
- export interface InitializedDriver {
922
- /** The DataDriver instance, ready for use. */
923
- driver: import("../controllers/data_driver").DataDriver;
924
-
925
- /** The realtime service, if the driver created one during init. */
926
- realtimeProvider?: RealtimeProvider;
927
-
928
- /** A collection registry to register schema / tables into. */
929
- collectionRegistry?: CollectionRegistryInterface;
930
-
931
- /**
932
- * Collections the driver derived from the live database schema.
933
- *
934
- * Set by drivers that introspect in `baas` mode; the server serves these
935
- * instead of collections loaded from config files.
936
- */
937
- collections?: import("./collections").CollectionConfig[];
938
-
939
- /** The underlying database connection (for lifecycle management). */
940
- connection?: DatabaseConnection;
941
-
942
- /**
943
- * Opaque handle that the bootstrapper can use in subsequent hooks
944
- * (e.g., `initializeAuth`, `mountRoutes`) to access driver internals.
945
- * Not used by the coordinator.
946
- */
947
- internals?: unknown;
948
- }
949
-
950
- /**
951
- * Result of `BackendBootstrapper.initializeAuth()`.
952
- * @group Backend
953
- */
954
- export interface BootstrappedAuth {
955
- /** User management service. */
956
- userService: unknown;
957
- /** Role management service (optional, roles are now simple strings). */
958
- roleService?: unknown;
959
- /** Email service (optional). */
960
- emailService?: unknown;
961
- /** Combined Auth Repository for unified token and user management. */
962
- authRepository?: unknown;
963
- /**
964
- * Whether the auth schema in the database is one this runtime can serve.
965
- *
966
- * Folded into `healthCheck()` so a schema mismatch shows up as a degraded
967
- * health response. Without it, a server whose auth is entirely broken still
968
- * reports healthy — the database connection it probes is fine, and the
969
- * mismatch is only discovered one failed login at a time.
970
- */
971
- schemaHealthCheck?(): Promise<AuthSchemaHealth>;
972
- }
973
-
974
- /**
975
- * Result of {@link BootstrappedAuth.schemaHealthCheck}.
976
- * @group Lifecycle
977
- */
978
- export interface AuthSchemaHealth {
979
- /** False when this runtime cannot be trusted to serve auth against this database. */
980
- healthy: boolean;
981
- /** Human-readable descriptions of each mismatch found. Empty when healthy. */
982
- problems: string[];
983
- /** Auth schema version recorded in the database, when it records one. */
984
- databaseVersion?: number | null;
985
- /** Auth schema version this runtime expects. */
986
- runtimeVersion?: number;
987
- }