@rebasepro/server-postgres 0.0.1-canary.4829d6e
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.
- package/LICENSE +22 -0
- package/README.md +86 -0
- package/dist/PostgresAdapter.d.ts +6 -0
- package/dist/PostgresBackendDriver.d.ts +150 -0
- package/dist/PostgresBootstrapper.d.ts +51 -0
- package/dist/auth/ensure-tables.d.ts +10 -0
- package/dist/auth/services.d.ts +250 -0
- package/dist/backup/backup-cli.d.ts +3 -0
- package/dist/backup/backup-cron.d.ts +53 -0
- package/dist/backup/backup-service.d.ts +85 -0
- package/dist/backup/index.d.ts +12 -0
- package/dist/backup/pg-tools.d.ts +110 -0
- package/dist/backup/retention.d.ts +35 -0
- package/dist/chunk-DSJWtz9O.js +40 -0
- package/dist/cli-errors.d.ts +42 -0
- package/dist/cli-helpers.d.ts +7 -0
- package/dist/cli.d.ts +1 -0
- package/dist/collections/PostgresCollectionRegistry.d.ts +47 -0
- package/dist/connection.d.ts +65 -0
- package/dist/data-transformer.d.ts +55 -0
- package/dist/databasePoolManager.d.ts +20 -0
- package/dist/history/HistoryService.d.ts +71 -0
- package/dist/history/ensure-history-table.d.ts +7 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.es.js +23535 -0
- package/dist/index.es.js.map +1 -0
- package/dist/interfaces.d.ts +18 -0
- package/dist/schema/auth-bootstrap-sql.d.ts +24 -0
- package/dist/schema/auth-default-policies.d.ts +10 -0
- package/dist/schema/auth-schema.d.ts +2376 -0
- package/dist/schema/doctor-cli.d.ts +2 -0
- package/dist/schema/doctor.d.ts +58 -0
- package/dist/schema/dynamic-tables.d.ts +31 -0
- package/dist/schema/generate-drizzle-schema-logic.d.ts +2 -0
- package/dist/schema/generate-drizzle-schema.d.ts +1 -0
- package/dist/schema/generate-postgres-ddl-logic.d.ts +5 -0
- package/dist/schema/generate-postgres-ddl.d.ts +1 -0
- package/dist/schema/introspect-db-inference.d.ts +5 -0
- package/dist/schema/introspect-db-logic.d.ts +118 -0
- package/dist/schema/introspect-db.d.ts +1 -0
- package/dist/schema/introspect-runtime.d.ts +57 -0
- package/dist/schema/test-schema.d.ts +24 -0
- package/dist/security/policy-drift.d.ts +57 -0
- package/dist/security/rls-enforcement.d.ts +122 -0
- package/dist/services/BranchService.d.ts +47 -0
- package/dist/services/FetchService.d.ts +214 -0
- package/dist/services/PersistService.d.ts +39 -0
- package/dist/services/RelationService.d.ts +109 -0
- package/dist/services/cdc/CdcListener.d.ts +54 -0
- package/dist/services/cdc/trigger-cdc.d.ts +64 -0
- package/dist/services/collection-helpers.d.ts +38 -0
- package/dist/services/dataService.d.ts +110 -0
- package/dist/services/index.d.ts +4 -0
- package/dist/services/realtimeService.d.ts +298 -0
- package/dist/src-Eh-CZosp.js +595 -0
- package/dist/src-Eh-CZosp.js.map +1 -0
- package/dist/types.d.ts +3 -0
- package/dist/utils/drizzle-conditions.d.ts +138 -0
- package/dist/utils/pg-array-null-patch.d.ts +16 -0
- package/dist/utils/pg-error-utils.d.ts +65 -0
- package/dist/utils/table-classification.d.ts +8 -0
- package/dist/websocket.d.ts +18 -0
- package/package.json +113 -0
- package/src/PostgresAdapter.ts +58 -0
- package/src/PostgresBackendDriver.ts +1387 -0
- package/src/PostgresBootstrapper.ts +581 -0
- package/src/auth/ensure-tables.ts +367 -0
- package/src/auth/services.ts +1321 -0
- package/src/backup/backup-cli.ts +383 -0
- package/src/backup/backup-cron.ts +189 -0
- package/src/backup/backup-service.ts +299 -0
- package/src/backup/index.ts +12 -0
- package/src/backup/pg-tools.ts +231 -0
- package/src/backup/retention.ts +75 -0
- package/src/cli-errors.ts +265 -0
- package/src/cli-helpers.ts +196 -0
- package/src/cli.ts +786 -0
- package/src/collections/PostgresCollectionRegistry.ts +103 -0
- package/src/connection.ts +166 -0
- package/src/data-transformer.ts +733 -0
- package/src/databasePoolManager.ts +87 -0
- package/src/history/HistoryService.ts +272 -0
- package/src/history/ensure-history-table.ts +46 -0
- package/src/index.ts +15 -0
- package/src/interfaces.ts +60 -0
- package/src/schema/auth-bootstrap-sql.ts +41 -0
- package/src/schema/auth-default-policies.ts +125 -0
- package/src/schema/auth-schema.ts +233 -0
- package/src/schema/doctor-cli.ts +113 -0
- package/src/schema/doctor.ts +733 -0
- package/src/schema/dynamic-tables.test.ts +302 -0
- package/src/schema/dynamic-tables.ts +293 -0
- package/src/schema/generate-drizzle-schema-logic.ts +850 -0
- package/src/schema/generate-drizzle-schema.ts +131 -0
- package/src/schema/generate-postgres-ddl-logic.ts +490 -0
- package/src/schema/generate-postgres-ddl.ts +92 -0
- package/src/schema/introspect-db-inference.ts +238 -0
- package/src/schema/introspect-db-logic.ts +910 -0
- package/src/schema/introspect-db.ts +266 -0
- package/src/schema/introspect-runtime.test.ts +212 -0
- package/src/schema/introspect-runtime.ts +293 -0
- package/src/schema/test-schema.ts +11 -0
- package/src/security/policy-drift.test.ts +122 -0
- package/src/security/policy-drift.ts +159 -0
- package/src/security/rls-enforcement.ts +295 -0
- package/src/services/BranchService.ts +251 -0
- package/src/services/FetchService.ts +1661 -0
- package/src/services/PersistService.ts +329 -0
- package/src/services/RelationService.ts +1306 -0
- package/src/services/cdc/CdcListener.ts +167 -0
- package/src/services/cdc/trigger-cdc.ts +169 -0
- package/src/services/collection-helpers.ts +151 -0
- package/src/services/dataService.ts +246 -0
- package/src/services/index.ts +13 -0
- package/src/services/realtimeService.ts +1502 -0
- package/src/types.ts +4 -0
- package/src/utils/drizzle-conditions.ts +1162 -0
- package/src/utils/pg-array-null-patch.ts +42 -0
- package/src/utils/pg-error-utils.ts +227 -0
- package/src/utils/table-classification.ts +16 -0
- package/src/websocket.ts +640 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { SQL } from "drizzle-orm";
|
|
2
|
+
import { PgTable } from "drizzle-orm/pg-core";
|
|
3
|
+
import { FilterValues, LogicalCondition } from "@rebasepro/types";
|
|
4
|
+
import type { VectorSearchParams } from "@rebasepro/types";
|
|
5
|
+
import { RelationService } from "./RelationService";
|
|
6
|
+
import { DrizzleClient } from "../interfaces";
|
|
7
|
+
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
8
|
+
/**
|
|
9
|
+
* Service for handling all row read operations.
|
|
10
|
+
* Handles fetching, searching, counting, and filtering rows.
|
|
11
|
+
*/
|
|
12
|
+
export declare class FetchService {
|
|
13
|
+
private db;
|
|
14
|
+
private registry;
|
|
15
|
+
private relationService;
|
|
16
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry);
|
|
17
|
+
/**
|
|
18
|
+
* Get the relational query builder for a given table name.
|
|
19
|
+
* Safely narrows the DrizzleClient union type to access db.query[tableName].
|
|
20
|
+
*/
|
|
21
|
+
private getQueryBuilder;
|
|
22
|
+
/**
|
|
23
|
+
* Build filter conditions from FilterValues
|
|
24
|
+
* Delegates to DrizzleConditionBuilder.buildFilterConditions
|
|
25
|
+
*/
|
|
26
|
+
buildFilterConditions<M extends Record<string, unknown>>(filter: FilterValues<Extract<keyof M, string>>, table: PgTable<any>, collectionPath: string): SQL[];
|
|
27
|
+
/**
|
|
28
|
+
* Resolves the correct Drizzle column for sorting.
|
|
29
|
+
* Automatically maps owning relation property keys to their underlying foreign key column.
|
|
30
|
+
*/
|
|
31
|
+
private resolveOrderByField;
|
|
32
|
+
/**
|
|
33
|
+
* Build the `with` config for Drizzle's relational query API.
|
|
34
|
+
* Converts collection relations to a Drizzle-compatible `with` object.
|
|
35
|
+
*
|
|
36
|
+
* When `include` is provided, only those relations are loaded.
|
|
37
|
+
* When `include` is absent, ALL relations are loaded (CMS path).
|
|
38
|
+
*
|
|
39
|
+
* Automatically detects many-to-many junction tables and nests
|
|
40
|
+
* the target relation so actual row data is returned.
|
|
41
|
+
*/
|
|
42
|
+
private buildWithConfig;
|
|
43
|
+
/**
|
|
44
|
+
* Detect if a many-to-many relation uses a junction table in the Drizzle schema.
|
|
45
|
+
*/
|
|
46
|
+
private isJunctionRelation;
|
|
47
|
+
/**
|
|
48
|
+
* Get the Drizzle relation name on the junction table that points to the actual target row.
|
|
49
|
+
* For example, for posts_tags junction, this returns "tag_id" (the relation pointing to tags).
|
|
50
|
+
*/
|
|
51
|
+
private getJunctionTargetRelationName;
|
|
52
|
+
/**
|
|
53
|
+
* Convert a db.query result row (with nested relation objects) to a flat row.
|
|
54
|
+
* Handles:
|
|
55
|
+
* - Placing `id` at the top level as a string
|
|
56
|
+
* - Type normalization (dates, numbers, NaN) via normalizeDbValues
|
|
57
|
+
* - Converting nested relation objects to { id, path, __type: "relation" } for CMS
|
|
58
|
+
* - Flattening junction-table many-to-many results
|
|
59
|
+
*/
|
|
60
|
+
private drizzleResultToRow;
|
|
61
|
+
/**
|
|
62
|
+
* Post-fetch joinPath relations for a single flat row.
|
|
63
|
+
* joinPath relations cannot be expressed via Drizzle's `with` config,
|
|
64
|
+
* so they must be loaded separately after the primary query.
|
|
65
|
+
*/
|
|
66
|
+
private resolveJoinPathRelations;
|
|
67
|
+
/**
|
|
68
|
+
* Post-fetch joinPath relations for a batch of flat rows.
|
|
69
|
+
* Uses batch fetching to avoid N+1 queries for list views.
|
|
70
|
+
*/
|
|
71
|
+
private resolveJoinPathRelationsBatch;
|
|
72
|
+
/**
|
|
73
|
+
* Resolves joinPath relations for raw REST rows and directly injects them.
|
|
74
|
+
* Uses RelationService to query the database and maps results back to the flattened objects.
|
|
75
|
+
*/
|
|
76
|
+
private resolveJoinPathRelationsBatchRest;
|
|
77
|
+
/**
|
|
78
|
+
* Convert a db.query result row to a flat REST-style object with populated relations.
|
|
79
|
+
*/
|
|
80
|
+
private drizzleResultToRestRow;
|
|
81
|
+
/**
|
|
82
|
+
* Build db.query-compatible options from standard fetch options.
|
|
83
|
+
* Handles filter, search, orderBy, limit, and cursor-based pagination.
|
|
84
|
+
*/
|
|
85
|
+
private buildDrizzleQueryOptions;
|
|
86
|
+
/**
|
|
87
|
+
* Extract cursor pagination conditions from startAfter options.
|
|
88
|
+
*/
|
|
89
|
+
private buildCursorConditions;
|
|
90
|
+
/**
|
|
91
|
+
* Fetch a single row by ID
|
|
92
|
+
*/
|
|
93
|
+
fetchOne<M extends Record<string, unknown>>(collectionPath: string, id: string | number, databaseId?: string): Promise<Record<string, unknown> | undefined>;
|
|
94
|
+
/**
|
|
95
|
+
* Unified method to fetch rows with optional search functionality
|
|
96
|
+
*/
|
|
97
|
+
fetchRowsWithConditions<M extends Record<string, unknown>>(collectionPath: string, options?: {
|
|
98
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
99
|
+
orderBy?: string;
|
|
100
|
+
order?: "desc" | "asc";
|
|
101
|
+
limit?: number;
|
|
102
|
+
offset?: number;
|
|
103
|
+
startAfter?: Record<string, unknown>;
|
|
104
|
+
searchString?: string;
|
|
105
|
+
databaseId?: string;
|
|
106
|
+
vectorSearch?: VectorSearchParams;
|
|
107
|
+
logical?: LogicalCondition;
|
|
108
|
+
}): Promise<Record<string, unknown>[]>;
|
|
109
|
+
/**
|
|
110
|
+
* Fallback path used when db.query is unavailable.
|
|
111
|
+
* The primary path uses drizzleResultToRow which handles relation
|
|
112
|
+
* mapping without N+1 queries.
|
|
113
|
+
*
|
|
114
|
+
* Process raw database results into flat rows with relations.
|
|
115
|
+
*/
|
|
116
|
+
private processRowResults;
|
|
117
|
+
/**
|
|
118
|
+
* Fetch a collection of rows
|
|
119
|
+
*/
|
|
120
|
+
fetchCollection<M extends Record<string, unknown>>(collectionPath: string, options?: {
|
|
121
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
122
|
+
orderBy?: string;
|
|
123
|
+
order?: "desc" | "asc";
|
|
124
|
+
limit?: number;
|
|
125
|
+
offset?: number;
|
|
126
|
+
startAfter?: Record<string, unknown>;
|
|
127
|
+
searchString?: string;
|
|
128
|
+
databaseId?: string;
|
|
129
|
+
vectorSearch?: VectorSearchParams;
|
|
130
|
+
}): Promise<Record<string, unknown>[]>;
|
|
131
|
+
/**
|
|
132
|
+
* Search rows by text
|
|
133
|
+
*/
|
|
134
|
+
searchRows<M extends Record<string, unknown>>(collectionPath: string, searchString: string, options?: {
|
|
135
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
136
|
+
orderBy?: string;
|
|
137
|
+
order?: "desc" | "asc";
|
|
138
|
+
limit?: number;
|
|
139
|
+
databaseId?: string;
|
|
140
|
+
}): Promise<Record<string, unknown>[]>;
|
|
141
|
+
/**
|
|
142
|
+
* Fetch collection from multi-segment path
|
|
143
|
+
*/
|
|
144
|
+
private fetchCollectionFromPath;
|
|
145
|
+
/**
|
|
146
|
+
* Count rows in a collection
|
|
147
|
+
*/
|
|
148
|
+
count<M extends Record<string, unknown>>(collectionPath: string, options?: {
|
|
149
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
150
|
+
searchString?: string;
|
|
151
|
+
databaseId?: string;
|
|
152
|
+
}): Promise<number>;
|
|
153
|
+
/**
|
|
154
|
+
* Count rows from multi-segment path
|
|
155
|
+
*/
|
|
156
|
+
private countEntitiesFromPath;
|
|
157
|
+
/**
|
|
158
|
+
* Check if a field value is unique
|
|
159
|
+
*/
|
|
160
|
+
checkUniqueField(collectionPath: string, fieldName: string, value: unknown, excludeEntityId?: string, _databaseId?: string): Promise<boolean>;
|
|
161
|
+
/**
|
|
162
|
+
* Get the RelationService instance for external use
|
|
163
|
+
*/
|
|
164
|
+
getRelationService(): RelationService;
|
|
165
|
+
/**
|
|
166
|
+
* Fetch a collection of rows with optional relation includes.
|
|
167
|
+
* When `include` is provided, only the specified relations are populated
|
|
168
|
+
* with full row data (not just { id, path, __type }).
|
|
169
|
+
* When `include` is absent, no relation queries are made (fast path).
|
|
170
|
+
*
|
|
171
|
+
* @param include - Array of relation keys to populate, or ["*"] for all
|
|
172
|
+
*/
|
|
173
|
+
fetchCollectionForRest<M extends Record<string, unknown>>(collectionPath: string, options?: {
|
|
174
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
175
|
+
orderBy?: string;
|
|
176
|
+
order?: "desc" | "asc";
|
|
177
|
+
limit?: number;
|
|
178
|
+
offset?: number;
|
|
179
|
+
startAfter?: Record<string, unknown>;
|
|
180
|
+
searchString?: string;
|
|
181
|
+
databaseId?: string;
|
|
182
|
+
vectorSearch?: VectorSearchParams;
|
|
183
|
+
}, include?: string[]): Promise<Record<string, unknown>[]>;
|
|
184
|
+
/**
|
|
185
|
+
* Fetch a single row with optional relation includes for REST API.
|
|
186
|
+
*/
|
|
187
|
+
fetchOneForRest<M extends Record<string, unknown>>(collectionPath: string, id: string | number, include?: string[], databaseId?: string): Promise<Record<string, unknown> | null>;
|
|
188
|
+
/**
|
|
189
|
+
* Fetch raw rows without any relation processing (for REST fast path)
|
|
190
|
+
*/
|
|
191
|
+
private fetchRowsWithConditionsRaw;
|
|
192
|
+
/**
|
|
193
|
+
* Check if the Drizzle instance has the relational query API available
|
|
194
|
+
* for a given collection path.
|
|
195
|
+
* Note: Primary path now uses inline `getQueryBuilder()` checks.
|
|
196
|
+
*/
|
|
197
|
+
private hasDrizzleQueryAPI;
|
|
198
|
+
/**
|
|
199
|
+
* Attempt to use Drizzle's relational query API (db.query.<table>.findMany)
|
|
200
|
+
* for efficient JOIN-based relation loading.
|
|
201
|
+
* Returns null if the API is not available or the query fails.
|
|
202
|
+
* Note: Primary path now uses `buildWithConfig` + `buildDrizzleQueryOptions`.
|
|
203
|
+
*/
|
|
204
|
+
private fetchWithDrizzleQuery;
|
|
205
|
+
/**
|
|
206
|
+
* Fallback path used when db.query is unavailable.
|
|
207
|
+
* The primary path uses db.query.findMany with `with` config, which
|
|
208
|
+
* loads all relations in a single query.
|
|
209
|
+
*
|
|
210
|
+
* Batch fetch many-to-many related rows for multiple parent IDs.
|
|
211
|
+
* Groups results by parent ID to avoid N+1.
|
|
212
|
+
*/
|
|
213
|
+
private batchFetchManyRelatedRows;
|
|
214
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { RelationService } from "./RelationService";
|
|
2
|
+
import { FetchService } from "./FetchService";
|
|
3
|
+
import { DrizzleClient } from "../interfaces";
|
|
4
|
+
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
5
|
+
/**
|
|
6
|
+
* Service for handling all row write operations.
|
|
7
|
+
* Handles saving, deleting, and updating rows.
|
|
8
|
+
*/
|
|
9
|
+
export declare class PersistService {
|
|
10
|
+
private db;
|
|
11
|
+
private registry;
|
|
12
|
+
private relationService;
|
|
13
|
+
private fetchService;
|
|
14
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry);
|
|
15
|
+
/**
|
|
16
|
+
* Delete an row by ID
|
|
17
|
+
*/
|
|
18
|
+
delete(collectionPath: string, id: string | number, _databaseId?: string): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Delete all rows from a collection
|
|
21
|
+
*/
|
|
22
|
+
deleteAll(collectionPath: string, _databaseId?: string): Promise<void>;
|
|
23
|
+
/**
|
|
24
|
+
* Save an row (create or update)
|
|
25
|
+
*/
|
|
26
|
+
save<M extends Record<string, unknown>>(collectionPath: string, values: Partial<M>, id?: string | number, databaseId?: string): Promise<Record<string, unknown>>;
|
|
27
|
+
/**
|
|
28
|
+
* Get the RelationService instance for external use
|
|
29
|
+
*/
|
|
30
|
+
getRelationService(): RelationService;
|
|
31
|
+
/**
|
|
32
|
+
* Get the FetchService instance for external use
|
|
33
|
+
*/
|
|
34
|
+
getFetchService(): FetchService;
|
|
35
|
+
/**
|
|
36
|
+
* Translate raw PostgreSQL / Drizzle errors into user-friendly messages.
|
|
37
|
+
*/
|
|
38
|
+
private toUserFriendlyError;
|
|
39
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { DrizzleClient } from "../interfaces";
|
|
2
|
+
import { CollectionConfig, FilterValues, Relation } from "@rebasepro/types";
|
|
3
|
+
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
4
|
+
/**
|
|
5
|
+
* Service for handling all relation-related operations.
|
|
6
|
+
* Handles fetching, updating, and managing row relations.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* A related record resolved by {@link RelationService}: the target row's
|
|
10
|
+
* values plus the identity (`id`) and originating collection (`path`)
|
|
11
|
+
* needed to build relation references. Internal to the postgres driver —
|
|
12
|
+
* flattened to plain rows at the fetch-service boundary.
|
|
13
|
+
*/
|
|
14
|
+
export interface RelatedRow<M extends Record<string, unknown> = Record<string, unknown>> {
|
|
15
|
+
id: string | number;
|
|
16
|
+
path: string;
|
|
17
|
+
values: M;
|
|
18
|
+
}
|
|
19
|
+
export declare class RelationService {
|
|
20
|
+
private db;
|
|
21
|
+
private registry;
|
|
22
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry);
|
|
23
|
+
/**
|
|
24
|
+
* Fetch rows related to a parent row through a specific relation
|
|
25
|
+
*/
|
|
26
|
+
fetchRelatedEntities<M extends Record<string, unknown>>(parentCollectionPath: string, parentId: string | number, relationKey: string, options?: {
|
|
27
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
28
|
+
orderBy?: string;
|
|
29
|
+
order?: "desc" | "asc";
|
|
30
|
+
limit?: number;
|
|
31
|
+
startAfter?: Record<string, unknown>;
|
|
32
|
+
searchString?: string;
|
|
33
|
+
databaseId?: string;
|
|
34
|
+
}): Promise<RelatedRow<M>[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Fetch rows using join paths for complex relations
|
|
37
|
+
*/
|
|
38
|
+
fetchEntitiesUsingJoins<M extends Record<string, unknown>>(parentCollection: CollectionConfig, parentId: string | number, relation: Relation, options?: {
|
|
39
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
40
|
+
orderBy?: string;
|
|
41
|
+
order?: "desc" | "asc";
|
|
42
|
+
limit?: number;
|
|
43
|
+
startAfter?: Record<string, unknown>;
|
|
44
|
+
searchString?: string;
|
|
45
|
+
databaseId?: string;
|
|
46
|
+
}): Promise<RelatedRow<M>[]>;
|
|
47
|
+
/**
|
|
48
|
+
* Count related rows for a parent row
|
|
49
|
+
*/
|
|
50
|
+
countRelatedEntities<M extends Record<string, unknown>>(parentCollectionPath: string, parentId: string | number, relationKey: string, options?: {
|
|
51
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
52
|
+
databaseId?: string;
|
|
53
|
+
}): Promise<number>;
|
|
54
|
+
/**
|
|
55
|
+
* Batch fetch related rows for multiple parent rows to avoid N+1 queries
|
|
56
|
+
*/
|
|
57
|
+
batchFetchRelatedEntities(parentCollectionPath: string, parentIds: (string | number)[], _relationKey: string, relation: Relation): Promise<Map<string, RelatedRow<Record<string, unknown>>>>;
|
|
58
|
+
/**
|
|
59
|
+
* Batch fetch many-cardinality related rows for multiple parent rows.
|
|
60
|
+
* Returns a Map<parentId, RelatedRow[]> instead of Map<parentId, RelatedRow>.
|
|
61
|
+
* Uses a single SQL query with IN clause to avoid N+1.
|
|
62
|
+
*/
|
|
63
|
+
batchFetchRelatedEntitiesMany(parentCollectionPath: string, parentIds: (string | number)[], _relationKey: string, relation: Relation): Promise<Map<string, RelatedRow<Record<string, unknown>>[]>>;
|
|
64
|
+
/**
|
|
65
|
+
* Update many-to-many and junction relations
|
|
66
|
+
*/
|
|
67
|
+
updateRelationsUsingJoins<M extends Record<string, unknown>>(tx: DrizzleClient, collection: CollectionConfig, id: string | number, relationValues: Partial<M>): Promise<void>;
|
|
68
|
+
/**
|
|
69
|
+
* Update inverse relations (where FK is on the target table)
|
|
70
|
+
*/
|
|
71
|
+
updateInverseRelations(tx: DrizzleClient, sourceCollection: CollectionConfig, sourceEntityId: string | number, inverseRelationUpdates: Array<{
|
|
72
|
+
relationKey: string;
|
|
73
|
+
relation: Relation;
|
|
74
|
+
newValue: unknown;
|
|
75
|
+
currentId?: string | number;
|
|
76
|
+
}>): Promise<void>;
|
|
77
|
+
/**
|
|
78
|
+
* Handle inverse relations with joinPath
|
|
79
|
+
*/
|
|
80
|
+
private updateInverseJoinPathRelation;
|
|
81
|
+
/**
|
|
82
|
+
* Handle many-to-many inverse relation updates using junction tables
|
|
83
|
+
*/
|
|
84
|
+
private updateManyToManyInverseRelation;
|
|
85
|
+
/**
|
|
86
|
+
* Update one-to-one relations that use joinPath
|
|
87
|
+
*/
|
|
88
|
+
updateJoinPathOneToOneRelations(tx: DrizzleClient, parentCollection: CollectionConfig, parentId: string | number, updates: Array<{
|
|
89
|
+
relationKey: string;
|
|
90
|
+
relation: Relation;
|
|
91
|
+
newTargetId: string | number | null;
|
|
92
|
+
}>): Promise<void>;
|
|
93
|
+
/**
|
|
94
|
+
* Resolve joinPath write mapping for one-to-one relations
|
|
95
|
+
*/
|
|
96
|
+
resolveJoinPathWriteMapping(parentCollection: CollectionConfig, relation: Relation): {
|
|
97
|
+
targetFKColName: string;
|
|
98
|
+
parentSourceColName: string;
|
|
99
|
+
};
|
|
100
|
+
/**
|
|
101
|
+
* Handle junction table creation for many-to-many path-based saves
|
|
102
|
+
*/
|
|
103
|
+
handleJunctionTableCreation(tx: DrizzleClient, newEntityId: string | number, junctionTableInfo: {
|
|
104
|
+
parentCollection: CollectionConfig;
|
|
105
|
+
parentId: string | number;
|
|
106
|
+
relation: Relation;
|
|
107
|
+
relationKey: string;
|
|
108
|
+
}): Promise<void>;
|
|
109
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A single database change captured by the CDC triggers and delivered over the
|
|
3
|
+
* `rebase_cdc` NOTIFY channel.
|
|
4
|
+
*/
|
|
5
|
+
export interface CdcChangeEvent {
|
|
6
|
+
schema: string;
|
|
7
|
+
table: string;
|
|
8
|
+
op: "INSERT" | "UPDATE" | "DELETE";
|
|
9
|
+
/**
|
|
10
|
+
* The changed tuple (NEW for insert/update, OLD for delete). May be a
|
|
11
|
+
* partial identity-only object when the full row overflowed the pg_notify
|
|
12
|
+
* size cap — see {@link truncated}.
|
|
13
|
+
*/
|
|
14
|
+
row: Record<string, unknown>;
|
|
15
|
+
/** True when the row was reduced to its identity because it was too large to notify. */
|
|
16
|
+
truncated?: boolean;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a `rebase_cdc` NOTIFY payload. Returns `null` for anything malformed so
|
|
20
|
+
* a single bad message can never crash the listener.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseCdcPayload(payload: string): CdcChangeEvent | null;
|
|
23
|
+
/**
|
|
24
|
+
* Dedicated Postgres LISTEN client for database-level CDC.
|
|
25
|
+
*
|
|
26
|
+
* Mirrors the resilience of the RealtimeService cross-instance LISTEN client: a
|
|
27
|
+
* standalone `pg.Client` (outside the Drizzle pool) that stays connected, and
|
|
28
|
+
* transparently reconnects on error/disconnect. Each backend instance runs one,
|
|
29
|
+
* so every instance observes every committed change regardless of which
|
|
30
|
+
* instance (or external process) made the write.
|
|
31
|
+
*/
|
|
32
|
+
export declare class CdcListener {
|
|
33
|
+
private readonly connectionString;
|
|
34
|
+
private readonly onEvent;
|
|
35
|
+
private client?;
|
|
36
|
+
private running;
|
|
37
|
+
private reconnectTimer?;
|
|
38
|
+
private static readonly RECONNECT_DELAY_MS;
|
|
39
|
+
constructor(connectionString: string, onEvent: (event: CdcChangeEvent) => void | Promise<void>);
|
|
40
|
+
/**
|
|
41
|
+
* Connect and begin listening. Idempotent.
|
|
42
|
+
*
|
|
43
|
+
* The **initial** connection is validated synchronously: if it cannot be
|
|
44
|
+
* established (or `LISTEN` is refused), this rejects so callers — notably
|
|
45
|
+
* `REALTIME_CDC=auto` — can detect an unusable connection and fall back to
|
|
46
|
+
* app-level realtime. Once the initial connection succeeds, later drops
|
|
47
|
+
* self-heal via {@link scheduleReconnect}.
|
|
48
|
+
*/
|
|
49
|
+
start(): Promise<void>;
|
|
50
|
+
/** Stop listening and release the connection. */
|
|
51
|
+
stop(): Promise<void>;
|
|
52
|
+
private connect;
|
|
53
|
+
private scheduleReconnect;
|
|
54
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RawSqlRunner } from "../../security/rls-enforcement";
|
|
2
|
+
/**
|
|
3
|
+
* Trigger-based Change Data Capture (CDC).
|
|
4
|
+
*
|
|
5
|
+
* The preferred CDC source is the write-ahead log (logical replication), which
|
|
6
|
+
* — like Supabase Realtime — sees *every* commit regardless of how it was made.
|
|
7
|
+
* When logical replication is unavailable (managed Postgres without
|
|
8
|
+
* `wal_level=logical`, no replication privilege, no `REPLICA IDENTITY`), this
|
|
9
|
+
* trigger-based fallback provides the same guarantee at the row level:
|
|
10
|
+
*
|
|
11
|
+
* AFTER INSERT/UPDATE/DELETE trigger → pg_notify('rebase_cdc', payload)
|
|
12
|
+
*
|
|
13
|
+
* A single dedicated LISTEN client per backend instance consumes the channel
|
|
14
|
+
* (see {@link CdcListener}) and feeds the change into the existing
|
|
15
|
+
* `RealtimeService.notifyUpdate` pipeline, so subscribers see the change no
|
|
16
|
+
* matter what wrote it — psql, a cron in another service, raw Drizzle/SQL, or
|
|
17
|
+
* the Studio SQL editor.
|
|
18
|
+
*
|
|
19
|
+
* Provisioning runs from the framework's own bootstrap as the owner (server)
|
|
20
|
+
* context, alongside the RLS role provisioning. It is idempotent.
|
|
21
|
+
*/
|
|
22
|
+
/** Postgres NOTIFY channel carrying database-level change events. */
|
|
23
|
+
export declare const CDC_CHANNEL = "rebase_cdc";
|
|
24
|
+
/** Schema-qualified name of the generic trigger function. */
|
|
25
|
+
export declare const CDC_TRIGGER_FUNCTION = "rebase.rebase_cdc_notify";
|
|
26
|
+
/** Name of the per-table trigger (unqualified — triggers are namespaced by table). */
|
|
27
|
+
export declare const CDC_TRIGGER_NAME = "rebase_cdc_trigger";
|
|
28
|
+
/**
|
|
29
|
+
* SQL that (re)creates the generic CDC trigger function. Safe to run repeatedly:
|
|
30
|
+
* `CREATE OR REPLACE` updates in place without dropping dependent triggers.
|
|
31
|
+
*
|
|
32
|
+
* The function emits `{ schema, table, op, row }`. The `row` is the full changed
|
|
33
|
+
* tuple (NEW for insert/update, OLD for delete) so the consumer can route it to
|
|
34
|
+
* a collection and extract the primary key. It is *not* trusted for delivery:
|
|
35
|
+
* the consumer marks the row invalidated and each subscriber re-reads it under
|
|
36
|
+
* its own RLS context, so a subscriber never receives a row it cannot read.
|
|
37
|
+
*/
|
|
38
|
+
export declare function buildCdcFunctionSql(): string;
|
|
39
|
+
/**
|
|
40
|
+
* SQL that (re)attaches the CDC trigger to a single table. `DROP ... IF EXISTS`
|
|
41
|
+
* before `CREATE` keeps it idempotent and picks up any function signature change.
|
|
42
|
+
*/
|
|
43
|
+
export declare function buildCdcTriggerSql(schema: string, table: string): string;
|
|
44
|
+
export interface CdcTableRef {
|
|
45
|
+
schema: string;
|
|
46
|
+
table: string;
|
|
47
|
+
}
|
|
48
|
+
export interface ProvisionResult {
|
|
49
|
+
/** Tables the trigger was successfully attached to. */
|
|
50
|
+
installed: CdcTableRef[];
|
|
51
|
+
/** Tables that could not be provisioned (e.g. not yet migrated), with the error. */
|
|
52
|
+
skipped: Array<CdcTableRef & {
|
|
53
|
+
reason: string;
|
|
54
|
+
}>;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Idempotently install the CDC trigger function and per-table triggers.
|
|
58
|
+
*
|
|
59
|
+
* Runs as the owner (server) connection at bootstrap. A table that does not yet
|
|
60
|
+
* exist in the database (schema drift) is skipped with a warning rather than
|
|
61
|
+
* aborting the whole install, so one un-migrated collection cannot disable CDC
|
|
62
|
+
* for the rest.
|
|
63
|
+
*/
|
|
64
|
+
export declare function provisionTriggerCdc(run: RawSqlRunner, tables: CdcTableRef[]): Promise<ProvisionResult>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PgTable, AnyPgColumn } from "drizzle-orm/pg-core";
|
|
2
|
+
import { CollectionConfig } from "@rebasepro/types";
|
|
3
|
+
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
4
|
+
/**
|
|
5
|
+
* Shared helper functions for row operations.
|
|
6
|
+
* These are used by FetchService, PersistService, and RelationService.
|
|
7
|
+
*
|
|
8
|
+
* All functions that need collection/table lookups require an explicit
|
|
9
|
+
* `PostgresCollectionRegistry` instance — there is no global singleton.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Interface for Drizzle column metadata introspection.
|
|
13
|
+
* Replaces unsafe `as Record<string, unknown>` double-cast chains.
|
|
14
|
+
*/
|
|
15
|
+
export interface DrizzleColumnMeta {
|
|
16
|
+
columnType?: string;
|
|
17
|
+
dataType?: string;
|
|
18
|
+
primary?: boolean;
|
|
19
|
+
}
|
|
20
|
+
/** Safely extract Drizzle column metadata from a column object. */
|
|
21
|
+
export declare function getColumnMeta(col: AnyPgColumn): DrizzleColumnMeta;
|
|
22
|
+
export declare function getCollectionByPath(collectionPath: string, registry: PostgresCollectionRegistry): CollectionConfig;
|
|
23
|
+
export declare function getTableForCollection(collection: CollectionConfig, registry: PostgresCollectionRegistry): PgTable<any>;
|
|
24
|
+
export declare function getPrimaryKeys(collection: CollectionConfig, registry: PostgresCollectionRegistry): {
|
|
25
|
+
fieldName: string;
|
|
26
|
+
type: "string" | "number";
|
|
27
|
+
isUUID?: boolean;
|
|
28
|
+
}[];
|
|
29
|
+
export declare function parseIdValues(idValue: string | number, primaryKeys: {
|
|
30
|
+
fieldName: string;
|
|
31
|
+
type: "string" | "number";
|
|
32
|
+
isUUID?: boolean;
|
|
33
|
+
}[]): Record<string, string | number>;
|
|
34
|
+
export declare function buildCompositeId(values: Record<string, unknown>, primaryKeys: {
|
|
35
|
+
fieldName: string;
|
|
36
|
+
type: "string" | "number";
|
|
37
|
+
isUUID?: boolean;
|
|
38
|
+
}[]): string;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { FilterValues } from "@rebasepro/types";
|
|
2
|
+
import type { VectorSearchParams } from "@rebasepro/types";
|
|
3
|
+
import { FetchService } from "./FetchService";
|
|
4
|
+
import { PersistService } from "./PersistService";
|
|
5
|
+
import { RelationService } from "./RelationService";
|
|
6
|
+
import { DataRepository, DrizzleClient } from "../interfaces";
|
|
7
|
+
import { PostgresCollectionRegistry } from "../collections/PostgresCollectionRegistry";
|
|
8
|
+
export { sanitizeAndConvertDates, serializeDataToServer, parseDataFromServer } from "../data-transformer";
|
|
9
|
+
export { FetchService } from "./FetchService";
|
|
10
|
+
export { PersistService } from "./PersistService";
|
|
11
|
+
export { RelationService } from "./RelationService";
|
|
12
|
+
export * from "../interfaces";
|
|
13
|
+
/**
|
|
14
|
+
* DataService - Facade for row operations.
|
|
15
|
+
*
|
|
16
|
+
* This class provides a unified API for row CRUD operations by delegating
|
|
17
|
+
* to specialized services:
|
|
18
|
+
* - FetchService: Read operations (fetch, search, count)
|
|
19
|
+
* - PersistService: Write operations (save, delete)
|
|
20
|
+
* - RelationService: Relation operations (fetch related, update relations)
|
|
21
|
+
*
|
|
22
|
+
* Implements the DataRepository interface for database abstraction.
|
|
23
|
+
*/
|
|
24
|
+
export declare class DataService implements DataRepository {
|
|
25
|
+
private db;
|
|
26
|
+
private registry;
|
|
27
|
+
private fetchService;
|
|
28
|
+
private persistService;
|
|
29
|
+
constructor(db: DrizzleClient, registry: PostgresCollectionRegistry);
|
|
30
|
+
/**
|
|
31
|
+
* Fetch a single row by ID
|
|
32
|
+
*/
|
|
33
|
+
fetchOne<M extends Record<string, unknown>>(collectionPath: string, id: string | number, databaseId?: string): Promise<Record<string, unknown> | undefined>;
|
|
34
|
+
/**
|
|
35
|
+
* Fetch a collection of rows with optional filtering, ordering, and pagination
|
|
36
|
+
*/
|
|
37
|
+
fetchCollection<M extends Record<string, unknown>>(collectionPath: string, options?: {
|
|
38
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
39
|
+
orderBy?: string;
|
|
40
|
+
order?: "desc" | "asc";
|
|
41
|
+
limit?: number;
|
|
42
|
+
offset?: number;
|
|
43
|
+
startAfter?: Record<string, unknown>;
|
|
44
|
+
searchString?: string;
|
|
45
|
+
databaseId?: string;
|
|
46
|
+
vectorSearch?: VectorSearchParams;
|
|
47
|
+
}): Promise<Record<string, unknown>[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Search rows by text
|
|
50
|
+
*/
|
|
51
|
+
searchRows<M extends Record<string, unknown>>(collectionPath: string, searchString: string, options?: {
|
|
52
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
53
|
+
orderBy?: string;
|
|
54
|
+
order?: "desc" | "asc";
|
|
55
|
+
limit?: number;
|
|
56
|
+
databaseId?: string;
|
|
57
|
+
}): Promise<Record<string, unknown>[]>;
|
|
58
|
+
/**
|
|
59
|
+
* Count rows in a collection
|
|
60
|
+
*/
|
|
61
|
+
count<M extends Record<string, unknown>>(collectionPath: string, options?: {
|
|
62
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
63
|
+
searchString?: string;
|
|
64
|
+
databaseId?: string;
|
|
65
|
+
}): Promise<number>;
|
|
66
|
+
/**
|
|
67
|
+
* Check if a field value is unique in a collection
|
|
68
|
+
*/
|
|
69
|
+
checkUniqueField(collectionPath: string, fieldName: string, value: unknown, excludeEntityId?: string, databaseId?: string): Promise<boolean>;
|
|
70
|
+
/**
|
|
71
|
+
* Fetch rows related to a parent row
|
|
72
|
+
*/
|
|
73
|
+
fetchRelatedEntities<M extends Record<string, unknown>>(parentCollectionPath: string, parentId: string | number, relationKey: string, options?: {
|
|
74
|
+
filter?: FilterValues<Extract<keyof M, string>>;
|
|
75
|
+
orderBy?: string;
|
|
76
|
+
order?: "desc" | "asc";
|
|
77
|
+
limit?: number;
|
|
78
|
+
startAfter?: Record<string, unknown>;
|
|
79
|
+
searchString?: string;
|
|
80
|
+
databaseId?: string;
|
|
81
|
+
}): Promise<Record<string, unknown>[]>;
|
|
82
|
+
/**
|
|
83
|
+
* Save an row (create or update)
|
|
84
|
+
*/
|
|
85
|
+
save<M extends Record<string, unknown>>(collectionPath: string, values: Partial<M>, id?: string | number, databaseId?: string): Promise<Record<string, unknown>>;
|
|
86
|
+
/**
|
|
87
|
+
* Delete an row by ID
|
|
88
|
+
*/
|
|
89
|
+
delete(collectionPath: string, id: string | number, databaseId?: string): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Delete all rows from a collection
|
|
92
|
+
*/
|
|
93
|
+
deleteAll(collectionPath: string, databaseId?: string): Promise<void>;
|
|
94
|
+
/**
|
|
95
|
+
* Execute raw SQL
|
|
96
|
+
*/
|
|
97
|
+
executeSql(sqlText: string, params?: unknown[]): Promise<Record<string, unknown>[]>;
|
|
98
|
+
/**
|
|
99
|
+
* Get the underlying FetchService for advanced use
|
|
100
|
+
*/
|
|
101
|
+
getFetchService(): FetchService;
|
|
102
|
+
/**
|
|
103
|
+
* Get the underlying PersistService for advanced use
|
|
104
|
+
*/
|
|
105
|
+
getPersistService(): PersistService;
|
|
106
|
+
/**
|
|
107
|
+
* Get the underlying RelationService for advanced use
|
|
108
|
+
*/
|
|
109
|
+
getRelationService(): RelationService;
|
|
110
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { FetchService } from "./FetchService";
|
|
2
|
+
export { PersistService } from "./PersistService";
|
|
3
|
+
export { RelationService } from "./RelationService";
|
|
4
|
+
export { getCollectionByPath, getTableForCollection, getPrimaryKeys, parseIdValues, buildCompositeId } from "./collection-helpers";
|