@bernierllc/database-adapter-postgresql 1.0.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.
- package/README.md +456 -0
- package/dist/PostgreSQLAdapter.d.ts +65 -0
- package/dist/PostgreSQLAdapter.js +252 -0
- package/dist/PostgreSQLTransaction.d.ts +46 -0
- package/dist/PostgreSQLTransaction.js +199 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +29 -0
- package/dist/migrations/001_initial_schema.d.ts +2 -0
- package/dist/migrations/001_initial_schema.js +91 -0
- package/dist/realtime/RealtimeManager.d.ts +76 -0
- package/dist/realtime/RealtimeManager.js +243 -0
- package/dist/rls/ClerkIntegration.d.ts +41 -0
- package/dist/rls/ClerkIntegration.js +161 -0
- package/dist/rls/RLSPolicyGenerator.d.ts +38 -0
- package/dist/rls/RLSPolicyGenerator.js +212 -0
- package/dist/search/FullTextSearchManager.d.ts +61 -0
- package/dist/search/FullTextSearchManager.js +265 -0
- package/package.json +65 -0
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.PostgreSQLAdapter = void 0;
|
|
11
|
+
const pg_1 = require("pg");
|
|
12
|
+
const supabase_js_1 = require("@supabase/supabase-js");
|
|
13
|
+
const database_adapter_core_1 = require("@bernierllc/database-adapter-core");
|
|
14
|
+
const retry_policy_1 = require("@bernierllc/retry-policy");
|
|
15
|
+
const logger_1 = require("@bernierllc/logger");
|
|
16
|
+
const PostgreSQLTransaction_1 = require("./PostgreSQLTransaction");
|
|
17
|
+
/**
|
|
18
|
+
* PostgreSQL database adapter with Supabase support
|
|
19
|
+
* Implements connection pooling, RLS, and real-time features
|
|
20
|
+
*/
|
|
21
|
+
class PostgreSQLAdapter extends database_adapter_core_1.BaseDatabaseAdapter {
|
|
22
|
+
constructor(config) {
|
|
23
|
+
super(config);
|
|
24
|
+
this.pool = null;
|
|
25
|
+
this.supabase = null;
|
|
26
|
+
this.rlsManager = new database_adapter_core_1.RLSContextManager();
|
|
27
|
+
this.logger = (0, logger_1.createLogger)({ context: { component: 'PostgreSQLAdapter' } });
|
|
28
|
+
// Initialize Supabase client if configured
|
|
29
|
+
if (config.supabaseUrl && config.supabaseAnonKey) {
|
|
30
|
+
this.supabase = (0, supabase_js_1.createClient)(config.supabaseUrl, config.supabaseAnonKey, {
|
|
31
|
+
auth: {
|
|
32
|
+
autoRefreshToken: true,
|
|
33
|
+
persistSession: true,
|
|
34
|
+
detectSessionInUrl: false
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Connect to PostgreSQL with connection pooling
|
|
41
|
+
*/
|
|
42
|
+
async connect() {
|
|
43
|
+
if (this.connected)
|
|
44
|
+
return;
|
|
45
|
+
this.pool = new pg_1.Pool({
|
|
46
|
+
host: this.config.host,
|
|
47
|
+
port: this.config.port || 5432,
|
|
48
|
+
database: this.config.database,
|
|
49
|
+
user: this.config.username,
|
|
50
|
+
password: this.config.password,
|
|
51
|
+
ssl: this.config.ssl,
|
|
52
|
+
min: this.config.pooling?.min || 2,
|
|
53
|
+
max: this.config.pooling?.max || 10,
|
|
54
|
+
idleTimeoutMillis: this.config.pooling?.idleTimeoutMs || 30000,
|
|
55
|
+
connectionTimeoutMillis: this.config.connectionTimeoutMs || 5000
|
|
56
|
+
});
|
|
57
|
+
// Test connection with retry
|
|
58
|
+
let attempt = 0;
|
|
59
|
+
const maxRetries = 3;
|
|
60
|
+
while (attempt < maxRetries) {
|
|
61
|
+
try {
|
|
62
|
+
const client = await this.pool.connect();
|
|
63
|
+
await client.query('SELECT NOW()');
|
|
64
|
+
client.release();
|
|
65
|
+
this.connected = true;
|
|
66
|
+
this.logger.info('PostgreSQL connection established', {
|
|
67
|
+
database: this.config.database,
|
|
68
|
+
host: this.config.host,
|
|
69
|
+
poolMin: this.config.pooling?.min || 2,
|
|
70
|
+
poolMax: this.config.pooling?.max || 10
|
|
71
|
+
});
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
attempt++;
|
|
76
|
+
if ((0, retry_policy_1.shouldRetry)(attempt, error, { maxRetries })) {
|
|
77
|
+
const delay = (0, retry_policy_1.calculateRetryDelay)(attempt, {
|
|
78
|
+
initialDelayMs: 1000,
|
|
79
|
+
maxDelayMs: 5000,
|
|
80
|
+
jitter: true
|
|
81
|
+
});
|
|
82
|
+
this.logger.warn('PostgreSQL connection failed, retrying', {
|
|
83
|
+
attempt,
|
|
84
|
+
maxRetries,
|
|
85
|
+
delayMs: delay,
|
|
86
|
+
error: this.sanitizeError(error)
|
|
87
|
+
});
|
|
88
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
throw this.sanitizeError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
throw new Error('Failed to connect to PostgreSQL after retries');
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Disconnect and close pool
|
|
99
|
+
*/
|
|
100
|
+
async disconnect() {
|
|
101
|
+
if (!this.pool)
|
|
102
|
+
return;
|
|
103
|
+
await this.pool.end();
|
|
104
|
+
this.pool = null;
|
|
105
|
+
this.connected = false;
|
|
106
|
+
this.logger.info('PostgreSQL connection closed');
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Execute query with RLS context
|
|
110
|
+
*/
|
|
111
|
+
async query(sql, params) {
|
|
112
|
+
this.ensureConnected();
|
|
113
|
+
const startTime = Date.now();
|
|
114
|
+
const client = await this.pool.connect();
|
|
115
|
+
try {
|
|
116
|
+
// Set RLS context if available
|
|
117
|
+
const rlsStatements = this.rlsManager.generateRLSStatements();
|
|
118
|
+
for (const stmt of rlsStatements) {
|
|
119
|
+
await client.query(stmt);
|
|
120
|
+
}
|
|
121
|
+
// Execute query
|
|
122
|
+
const result = await client.query(sql, params);
|
|
123
|
+
const duration = Date.now() - startTime;
|
|
124
|
+
this.logger.debug('Query executed', {
|
|
125
|
+
duration,
|
|
126
|
+
rowCount: result.rowCount,
|
|
127
|
+
command: result.command
|
|
128
|
+
});
|
|
129
|
+
return {
|
|
130
|
+
rows: result.rows,
|
|
131
|
+
rowCount: result.rowCount || 0,
|
|
132
|
+
fields: result.fields.map(f => ({
|
|
133
|
+
name: f.name,
|
|
134
|
+
dataTypeID: f.dataTypeID,
|
|
135
|
+
dataTypeName: this.getDataTypeName(f.dataTypeID),
|
|
136
|
+
nullable: true // PostgreSQL doesn't expose this easily
|
|
137
|
+
})),
|
|
138
|
+
command: result.command
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
const sanitized = this.sanitizeError(error);
|
|
143
|
+
this.logger.error('Query execution failed', sanitized, {
|
|
144
|
+
duration: Date.now() - startTime
|
|
145
|
+
});
|
|
146
|
+
throw sanitized;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
client.release();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Begin transaction with RLS context
|
|
154
|
+
*/
|
|
155
|
+
async beginTransaction() {
|
|
156
|
+
this.ensureConnected();
|
|
157
|
+
const client = await this.pool.connect();
|
|
158
|
+
// Set RLS context for transaction
|
|
159
|
+
const rlsStatements = this.rlsManager.generateRLSStatements();
|
|
160
|
+
for (const stmt of rlsStatements) {
|
|
161
|
+
await client.query(stmt);
|
|
162
|
+
}
|
|
163
|
+
await client.query('BEGIN');
|
|
164
|
+
this.logger.debug('Transaction started');
|
|
165
|
+
return new PostgreSQLTransaction_1.PostgreSQLTransaction(client, this.rlsManager, this.logger);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Health check with connection pool stats
|
|
169
|
+
*/
|
|
170
|
+
async healthCheck() {
|
|
171
|
+
if (!this.connected || !this.pool) {
|
|
172
|
+
return {
|
|
173
|
+
connected: false,
|
|
174
|
+
latencyMs: -1,
|
|
175
|
+
version: 'unknown'
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const start = Date.now();
|
|
179
|
+
try {
|
|
180
|
+
const result = await this.pool.query('SELECT version()');
|
|
181
|
+
const latencyMs = Date.now() - start;
|
|
182
|
+
return {
|
|
183
|
+
connected: true,
|
|
184
|
+
latencyMs,
|
|
185
|
+
version: result.rows[0].version,
|
|
186
|
+
activeConnections: this.pool.totalCount,
|
|
187
|
+
maxConnections: this.pool.options.max
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
const sanitized = this.sanitizeError(error);
|
|
192
|
+
this.logger.error('Health check failed', sanitized);
|
|
193
|
+
return {
|
|
194
|
+
connected: false,
|
|
195
|
+
latencyMs: -1,
|
|
196
|
+
version: 'error'
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Get Supabase client for real-time features
|
|
202
|
+
*/
|
|
203
|
+
getSupabaseClient() {
|
|
204
|
+
return this.supabase;
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Set RLS context for current operation
|
|
208
|
+
*/
|
|
209
|
+
setRLSContext(userId, userRole, tenantId) {
|
|
210
|
+
this.rlsManager.setContext({ userId, userRole, tenantId });
|
|
211
|
+
this.logger.debug('RLS context set', { userId, userRole, tenantId });
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Clear RLS context
|
|
215
|
+
*/
|
|
216
|
+
clearRLSContext() {
|
|
217
|
+
this.rlsManager.clearContext();
|
|
218
|
+
this.logger.debug('RLS context cleared');
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Get current connection pool statistics
|
|
222
|
+
*/
|
|
223
|
+
getPoolStats() {
|
|
224
|
+
if (!this.pool)
|
|
225
|
+
return null;
|
|
226
|
+
return {
|
|
227
|
+
total: this.pool.totalCount,
|
|
228
|
+
idle: this.pool.idleCount,
|
|
229
|
+
waiting: this.pool.waitingCount
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Map PostgreSQL data type OID to name
|
|
234
|
+
*/
|
|
235
|
+
getDataTypeName(oid) {
|
|
236
|
+
const typeMap = {
|
|
237
|
+
16: 'BOOL',
|
|
238
|
+
20: 'BIGINT',
|
|
239
|
+
21: 'SMALLINT',
|
|
240
|
+
23: 'INTEGER',
|
|
241
|
+
25: 'TEXT',
|
|
242
|
+
114: 'JSON',
|
|
243
|
+
1043: 'VARCHAR',
|
|
244
|
+
1082: 'DATE',
|
|
245
|
+
1114: 'TIMESTAMP',
|
|
246
|
+
1184: 'TIMESTAMPTZ',
|
|
247
|
+
3802: 'JSONB'
|
|
248
|
+
};
|
|
249
|
+
return typeMap[oid] || 'UNKNOWN';
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
exports.PostgreSQLAdapter = PostgreSQLAdapter;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { PoolClient } from 'pg';
|
|
2
|
+
import { ITransaction, IQueryResult, RLSContextManager } from '@bernierllc/database-adapter-core';
|
|
3
|
+
import { Logger } from '@bernierllc/logger';
|
|
4
|
+
/**
|
|
5
|
+
* PostgreSQL transaction implementation with RLS context support
|
|
6
|
+
*/
|
|
7
|
+
export declare class PostgreSQLTransaction implements ITransaction {
|
|
8
|
+
private client;
|
|
9
|
+
private _rlsManager;
|
|
10
|
+
id: string;
|
|
11
|
+
private active;
|
|
12
|
+
private logger?;
|
|
13
|
+
constructor(client: PoolClient, _rlsManager: RLSContextManager, logger?: Logger);
|
|
14
|
+
/**
|
|
15
|
+
* Execute query within transaction
|
|
16
|
+
*/
|
|
17
|
+
query<T = unknown>(sql: string, params?: unknown[]): Promise<IQueryResult<T>>;
|
|
18
|
+
/**
|
|
19
|
+
* Commit transaction and release client
|
|
20
|
+
*/
|
|
21
|
+
commit(): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Rollback transaction and release client
|
|
24
|
+
*/
|
|
25
|
+
rollback(): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Create a savepoint within the transaction
|
|
28
|
+
*/
|
|
29
|
+
savepoint(name: string): Promise<void>;
|
|
30
|
+
/**
|
|
31
|
+
* Rollback to a savepoint
|
|
32
|
+
*/
|
|
33
|
+
rollbackToSavepoint(name: string): Promise<void>;
|
|
34
|
+
/**
|
|
35
|
+
* Release a savepoint
|
|
36
|
+
*/
|
|
37
|
+
releaseSavepoint(name: string): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Check if transaction is still active
|
|
40
|
+
*/
|
|
41
|
+
isActive(): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Map PostgreSQL data type OID to name
|
|
44
|
+
*/
|
|
45
|
+
private getDataTypeName;
|
|
46
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.PostgreSQLTransaction = void 0;
|
|
11
|
+
/**
|
|
12
|
+
* PostgreSQL transaction implementation with RLS context support
|
|
13
|
+
*/
|
|
14
|
+
class PostgreSQLTransaction {
|
|
15
|
+
constructor(client,
|
|
16
|
+
// @ts-expect-error - Reserved for future RLS context support in transactions
|
|
17
|
+
_rlsManager, logger) {
|
|
18
|
+
this.client = client;
|
|
19
|
+
this._rlsManager = _rlsManager;
|
|
20
|
+
this.active = true;
|
|
21
|
+
this.id = `txn_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
22
|
+
this.logger = logger;
|
|
23
|
+
if (this.logger) {
|
|
24
|
+
this.logger.debug('Transaction created', { id: this.id });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Execute query within transaction
|
|
29
|
+
*/
|
|
30
|
+
async query(sql, params) {
|
|
31
|
+
if (!this.active) {
|
|
32
|
+
throw new Error('Transaction is not active');
|
|
33
|
+
}
|
|
34
|
+
const startTime = Date.now();
|
|
35
|
+
try {
|
|
36
|
+
const result = await this.client.query(sql, params);
|
|
37
|
+
const duration = Date.now() - startTime;
|
|
38
|
+
if (this.logger) {
|
|
39
|
+
this.logger.debug('Transaction query executed', {
|
|
40
|
+
transactionId: this.id,
|
|
41
|
+
duration,
|
|
42
|
+
rowCount: result.rowCount,
|
|
43
|
+
command: result.command
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
return {
|
|
47
|
+
rows: result.rows,
|
|
48
|
+
rowCount: result.rowCount || 0,
|
|
49
|
+
fields: result.fields.map(f => ({
|
|
50
|
+
name: f.name,
|
|
51
|
+
dataTypeID: f.dataTypeID,
|
|
52
|
+
dataTypeName: this.getDataTypeName(f.dataTypeID),
|
|
53
|
+
nullable: true
|
|
54
|
+
})),
|
|
55
|
+
command: result.command
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
if (this.logger) {
|
|
60
|
+
this.logger.error('Transaction query failed', error instanceof Error ? error : new Error(String(error)), {
|
|
61
|
+
transactionId: this.id,
|
|
62
|
+
duration: Date.now() - startTime
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Commit transaction and release client
|
|
70
|
+
*/
|
|
71
|
+
async commit() {
|
|
72
|
+
if (!this.active) {
|
|
73
|
+
throw new Error('Transaction is not active');
|
|
74
|
+
}
|
|
75
|
+
const startTime = Date.now();
|
|
76
|
+
try {
|
|
77
|
+
await this.client.query('COMMIT');
|
|
78
|
+
this.active = false;
|
|
79
|
+
if (this.logger) {
|
|
80
|
+
this.logger.debug('Transaction committed', {
|
|
81
|
+
transactionId: this.id,
|
|
82
|
+
duration: Date.now() - startTime
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch (error) {
|
|
87
|
+
if (this.logger) {
|
|
88
|
+
this.logger.error('Transaction commit failed', error instanceof Error ? error : new Error(String(error)), {
|
|
89
|
+
transactionId: this.id
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
this.client.release();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Rollback transaction and release client
|
|
100
|
+
*/
|
|
101
|
+
async rollback() {
|
|
102
|
+
if (!this.active) {
|
|
103
|
+
throw new Error('Transaction is not active');
|
|
104
|
+
}
|
|
105
|
+
const startTime = Date.now();
|
|
106
|
+
try {
|
|
107
|
+
await this.client.query('ROLLBACK');
|
|
108
|
+
this.active = false;
|
|
109
|
+
if (this.logger) {
|
|
110
|
+
this.logger.debug('Transaction rolled back', {
|
|
111
|
+
transactionId: this.id,
|
|
112
|
+
duration: Date.now() - startTime
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (this.logger) {
|
|
118
|
+
this.logger.error('Transaction rollback failed', error instanceof Error ? error : new Error(String(error)), {
|
|
119
|
+
transactionId: this.id
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
finally {
|
|
125
|
+
this.client.release();
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Create a savepoint within the transaction
|
|
130
|
+
*/
|
|
131
|
+
async savepoint(name) {
|
|
132
|
+
if (!this.active) {
|
|
133
|
+
throw new Error('Transaction is not active');
|
|
134
|
+
}
|
|
135
|
+
await this.client.query(`SAVEPOINT ${name}`);
|
|
136
|
+
if (this.logger) {
|
|
137
|
+
this.logger.debug('Savepoint created', {
|
|
138
|
+
transactionId: this.id,
|
|
139
|
+
savepoint: name
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
/**
|
|
144
|
+
* Rollback to a savepoint
|
|
145
|
+
*/
|
|
146
|
+
async rollbackToSavepoint(name) {
|
|
147
|
+
if (!this.active) {
|
|
148
|
+
throw new Error('Transaction is not active');
|
|
149
|
+
}
|
|
150
|
+
await this.client.query(`ROLLBACK TO SAVEPOINT ${name}`);
|
|
151
|
+
if (this.logger) {
|
|
152
|
+
this.logger.debug('Rolled back to savepoint', {
|
|
153
|
+
transactionId: this.id,
|
|
154
|
+
savepoint: name
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Release a savepoint
|
|
160
|
+
*/
|
|
161
|
+
async releaseSavepoint(name) {
|
|
162
|
+
if (!this.active) {
|
|
163
|
+
throw new Error('Transaction is not active');
|
|
164
|
+
}
|
|
165
|
+
await this.client.query(`RELEASE SAVEPOINT ${name}`);
|
|
166
|
+
if (this.logger) {
|
|
167
|
+
this.logger.debug('Savepoint released', {
|
|
168
|
+
transactionId: this.id,
|
|
169
|
+
savepoint: name
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Check if transaction is still active
|
|
175
|
+
*/
|
|
176
|
+
isActive() {
|
|
177
|
+
return this.active;
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Map PostgreSQL data type OID to name
|
|
181
|
+
*/
|
|
182
|
+
getDataTypeName(oid) {
|
|
183
|
+
const typeMap = {
|
|
184
|
+
16: 'BOOL',
|
|
185
|
+
20: 'BIGINT',
|
|
186
|
+
21: 'SMALLINT',
|
|
187
|
+
23: 'INTEGER',
|
|
188
|
+
25: 'TEXT',
|
|
189
|
+
114: 'JSON',
|
|
190
|
+
1043: 'VARCHAR',
|
|
191
|
+
1082: 'DATE',
|
|
192
|
+
1114: 'TIMESTAMP',
|
|
193
|
+
1184: 'TIMESTAMPTZ',
|
|
194
|
+
3802: 'JSONB'
|
|
195
|
+
};
|
|
196
|
+
return typeMap[oid] || 'UNKNOWN';
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
exports.PostgreSQLTransaction = PostgreSQLTransaction;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { PostgreSQLAdapter, ISupabaseConfig } from './PostgreSQLAdapter';
|
|
2
|
+
export { PostgreSQLTransaction } from './PostgreSQLTransaction';
|
|
3
|
+
export { RLSPolicyGenerator } from './rls/RLSPolicyGenerator';
|
|
4
|
+
export { ClerkIntegration } from './rls/ClerkIntegration';
|
|
5
|
+
export { RealtimeManager, RealtimePayload, RealtimeCallbacks, PresenceData } from './realtime/RealtimeManager';
|
|
6
|
+
export { FullTextSearchManager, SearchResult, SearchOptions } from './search/FullTextSearchManager';
|
|
7
|
+
export { migration_001_initial_schema } from './migrations/001_initial_schema';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.migration_001_initial_schema = exports.FullTextSearchManager = exports.RealtimeManager = exports.ClerkIntegration = exports.RLSPolicyGenerator = exports.PostgreSQLTransaction = exports.PostgreSQLAdapter = void 0;
|
|
11
|
+
// Main adapter
|
|
12
|
+
var PostgreSQLAdapter_1 = require("./PostgreSQLAdapter");
|
|
13
|
+
Object.defineProperty(exports, "PostgreSQLAdapter", { enumerable: true, get: function () { return PostgreSQLAdapter_1.PostgreSQLAdapter; } });
|
|
14
|
+
var PostgreSQLTransaction_1 = require("./PostgreSQLTransaction");
|
|
15
|
+
Object.defineProperty(exports, "PostgreSQLTransaction", { enumerable: true, get: function () { return PostgreSQLTransaction_1.PostgreSQLTransaction; } });
|
|
16
|
+
// RLS components
|
|
17
|
+
var RLSPolicyGenerator_1 = require("./rls/RLSPolicyGenerator");
|
|
18
|
+
Object.defineProperty(exports, "RLSPolicyGenerator", { enumerable: true, get: function () { return RLSPolicyGenerator_1.RLSPolicyGenerator; } });
|
|
19
|
+
var ClerkIntegration_1 = require("./rls/ClerkIntegration");
|
|
20
|
+
Object.defineProperty(exports, "ClerkIntegration", { enumerable: true, get: function () { return ClerkIntegration_1.ClerkIntegration; } });
|
|
21
|
+
// Real-time
|
|
22
|
+
var RealtimeManager_1 = require("./realtime/RealtimeManager");
|
|
23
|
+
Object.defineProperty(exports, "RealtimeManager", { enumerable: true, get: function () { return RealtimeManager_1.RealtimeManager; } });
|
|
24
|
+
// Search
|
|
25
|
+
var FullTextSearchManager_1 = require("./search/FullTextSearchManager");
|
|
26
|
+
Object.defineProperty(exports, "FullTextSearchManager", { enumerable: true, get: function () { return FullTextSearchManager_1.FullTextSearchManager; } });
|
|
27
|
+
// Migrations
|
|
28
|
+
var _001_initial_schema_1 = require("./migrations/001_initial_schema");
|
|
29
|
+
Object.defineProperty(exports, "migration_001_initial_schema", { enumerable: true, get: function () { return _001_initial_schema_1.migration_001_initial_schema; } });
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
Copyright (c) 2025 Bernier LLC
|
|
4
|
+
|
|
5
|
+
This file is licensed to the client under a limited-use license.
|
|
6
|
+
The client may use and modify this code *only within the scope of the project it was delivered for*.
|
|
7
|
+
Redistribution or use in other products or commercial offerings is not permitted without written consent from Bernier LLC.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.migration_001_initial_schema = void 0;
|
|
11
|
+
exports.migration_001_initial_schema = {
|
|
12
|
+
id: '001_initial_schema',
|
|
13
|
+
name: 'Create initial content management schema',
|
|
14
|
+
async up(adapter) {
|
|
15
|
+
await adapter.query(`
|
|
16
|
+
-- Enable extensions
|
|
17
|
+
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
|
|
18
|
+
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
|
|
19
|
+
|
|
20
|
+
-- Users table (Clerk integration)
|
|
21
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
22
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
23
|
+
clerk_id VARCHAR(255) UNIQUE NOT NULL,
|
|
24
|
+
email VARCHAR(255) UNIQUE NOT NULL,
|
|
25
|
+
username VARCHAR(100),
|
|
26
|
+
full_name VARCHAR(255),
|
|
27
|
+
avatar_url TEXT,
|
|
28
|
+
role VARCHAR(50) DEFAULT 'user',
|
|
29
|
+
tenant_id UUID,
|
|
30
|
+
preferences JSONB DEFAULT '{}',
|
|
31
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
32
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
33
|
+
);
|
|
34
|
+
|
|
35
|
+
CREATE INDEX IF NOT EXISTS idx_users_clerk_id ON users(clerk_id);
|
|
36
|
+
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
|
|
37
|
+
|
|
38
|
+
-- Content table
|
|
39
|
+
CREATE TABLE IF NOT EXISTS content (
|
|
40
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
41
|
+
user_id UUID REFERENCES users(id) ON DELETE CASCADE,
|
|
42
|
+
title VARCHAR(500) NOT NULL,
|
|
43
|
+
slug VARCHAR(500) UNIQUE,
|
|
44
|
+
body TEXT NOT NULL,
|
|
45
|
+
excerpt TEXT,
|
|
46
|
+
type VARCHAR(50) DEFAULT 'blog',
|
|
47
|
+
format VARCHAR(50) DEFAULT 'markdown',
|
|
48
|
+
status VARCHAR(50) DEFAULT 'draft',
|
|
49
|
+
workflow_stage VARCHAR(100),
|
|
50
|
+
assigned_to UUID REFERENCES users(id),
|
|
51
|
+
ai_reviewed BOOLEAN DEFAULT FALSE,
|
|
52
|
+
ai_review_score DECIMAL(3,2),
|
|
53
|
+
keywords JSONB DEFAULT '[]',
|
|
54
|
+
view_count INTEGER DEFAULT 0,
|
|
55
|
+
share_count INTEGER DEFAULT 0,
|
|
56
|
+
version INTEGER DEFAULT 1,
|
|
57
|
+
published_at TIMESTAMPTZ,
|
|
58
|
+
scheduled_for TIMESTAMPTZ,
|
|
59
|
+
deleted_at TIMESTAMPTZ,
|
|
60
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
61
|
+
updated_at TIMESTAMPTZ DEFAULT NOW()
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
-- Content versions
|
|
65
|
+
CREATE TABLE IF NOT EXISTS content_versions (
|
|
66
|
+
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
67
|
+
content_id UUID REFERENCES content(id) ON DELETE CASCADE,
|
|
68
|
+
version_number INTEGER NOT NULL,
|
|
69
|
+
title VARCHAR(500),
|
|
70
|
+
body TEXT NOT NULL,
|
|
71
|
+
change_summary TEXT,
|
|
72
|
+
is_autosave BOOLEAN DEFAULT FALSE,
|
|
73
|
+
created_at TIMESTAMPTZ DEFAULT NOW(),
|
|
74
|
+
created_by UUID REFERENCES users(id),
|
|
75
|
+
UNIQUE(content_id, version_number)
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
-- Enable RLS
|
|
79
|
+
ALTER TABLE users ENABLE ROW LEVEL SECURITY;
|
|
80
|
+
ALTER TABLE content ENABLE ROW LEVEL SECURITY;
|
|
81
|
+
ALTER TABLE content_versions ENABLE ROW LEVEL SECURITY;
|
|
82
|
+
`);
|
|
83
|
+
},
|
|
84
|
+
async down(adapter) {
|
|
85
|
+
await adapter.query(`
|
|
86
|
+
DROP TABLE IF EXISTS content_versions CASCADE;
|
|
87
|
+
DROP TABLE IF EXISTS content CASCADE;
|
|
88
|
+
DROP TABLE IF EXISTS users CASCADE;
|
|
89
|
+
`);
|
|
90
|
+
}
|
|
91
|
+
};
|