singulio-postgres 1.1.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 +866 -0
- package/dist/client.d.ts +52 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +127 -0
- package/dist/health.d.ts +44 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +182 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +45 -0
- package/dist/pool.d.ts +52 -0
- package/dist/pool.d.ts.map +1 -0
- package/dist/pool.js +216 -0
- package/dist/rls.d.ts +53 -0
- package/dist/rls.d.ts.map +1 -0
- package/dist/rls.js +134 -0
- package/dist/transaction.d.ts +54 -0
- package/dist/transaction.d.ts.map +1 -0
- package/dist/transaction.js +138 -0
- package/dist/types.d.ts +121 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/vector.d.ts +74 -0
- package/dist/vector.d.ts.map +1 -0
- package/dist/vector.js +223 -0
- package/package.json +97 -0
- package/src/client.ts +153 -0
- package/src/health.ts +226 -0
- package/src/index.ts +110 -0
- package/src/pool.ts +268 -0
- package/src/rls.ts +169 -0
- package/src/transaction.ts +207 -0
- package/src/types.ts +142 -0
- package/src/vector.ts +312 -0
package/src/rls.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - Row-Level Security Helpers
|
|
3
|
+
* Multi-tenant isolation via PostgreSQL session GUCs
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { sql } from 'bun';
|
|
7
|
+
import type { RLSContext, QueryResult, QueryRow, Logger } from './types.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Set RLS context variables for the current session
|
|
11
|
+
*/
|
|
12
|
+
export async function setRLSContext(context: RLSContext): Promise<void> {
|
|
13
|
+
// Set tenant reference (required for RLS policies)
|
|
14
|
+
await sql`SELECT set_config('app.tenant_ref', ${context.tenantId}, TRUE)`;
|
|
15
|
+
|
|
16
|
+
// Set admin flag (bypasses some policies)
|
|
17
|
+
await sql`SELECT set_config('app.is_admin', ${context.isAdmin ? 't' : 'f'}, TRUE)`;
|
|
18
|
+
|
|
19
|
+
// Set user ID if provided
|
|
20
|
+
if (context.userId) {
|
|
21
|
+
await sql`SELECT set_config('app.user_id', ${context.userId}, TRUE)`;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Set any extra GUCs
|
|
25
|
+
if (context.extraGUCs) {
|
|
26
|
+
for (const [key, value] of Object.entries(context.extraGUCs)) {
|
|
27
|
+
await sql.unsafe(`SELECT set_config($1, $2, TRUE)`, [key, value]);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Clear RLS context variables
|
|
34
|
+
*/
|
|
35
|
+
export async function clearRLSContext(): Promise<void> {
|
|
36
|
+
await sql`SELECT set_config('app.tenant_ref', '', TRUE)`;
|
|
37
|
+
await sql`SELECT set_config('app.is_admin', 'f', TRUE)`;
|
|
38
|
+
await sql`SELECT set_config('app.user_id', '', TRUE)`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Get current RLS context
|
|
43
|
+
*/
|
|
44
|
+
export async function getRLSContext(): Promise<RLSContext | null> {
|
|
45
|
+
const result = await sql`
|
|
46
|
+
SELECT
|
|
47
|
+
current_setting('app.tenant_ref', TRUE) as tenant_id,
|
|
48
|
+
current_setting('app.user_id', TRUE) as user_id,
|
|
49
|
+
current_setting('app.is_admin', TRUE) as is_admin
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
const row = result[0];
|
|
53
|
+
if (!row || !row.tenant_id) return null;
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
tenantId: row.tenant_id as string,
|
|
57
|
+
userId: (row.user_id as string) || undefined,
|
|
58
|
+
isAdmin: row.is_admin === 't',
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Execute a function with RLS context, automatically cleaning up afterward
|
|
64
|
+
*/
|
|
65
|
+
export async function withTenant<T>(context: RLSContext, fn: () => Promise<T>): Promise<T> {
|
|
66
|
+
await setRLSContext(context);
|
|
67
|
+
try {
|
|
68
|
+
return await fn();
|
|
69
|
+
} finally {
|
|
70
|
+
await clearRLSContext();
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Execute a query with RLS context
|
|
76
|
+
*/
|
|
77
|
+
export async function queryWithTenant<T = QueryRow>(
|
|
78
|
+
context: RLSContext,
|
|
79
|
+
queryText: string,
|
|
80
|
+
params?: unknown[]
|
|
81
|
+
): Promise<QueryResult<T>> {
|
|
82
|
+
return withTenant(context, async () => {
|
|
83
|
+
const result = await sql.unsafe(queryText, params ?? []);
|
|
84
|
+
return {
|
|
85
|
+
rows: result as T[],
|
|
86
|
+
rowCount: result.length,
|
|
87
|
+
};
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* RLS-aware client wrapper
|
|
93
|
+
*/
|
|
94
|
+
export class RLSClient {
|
|
95
|
+
private logger?: Logger;
|
|
96
|
+
|
|
97
|
+
constructor(logger?: Logger) {
|
|
98
|
+
this.logger = logger;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Execute query with tenant context
|
|
103
|
+
*/
|
|
104
|
+
async query<T = QueryRow>(
|
|
105
|
+
context: RLSContext,
|
|
106
|
+
queryText: string,
|
|
107
|
+
params?: unknown[]
|
|
108
|
+
): Promise<QueryResult<T>> {
|
|
109
|
+
const start = performance.now();
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const result = await queryWithTenant<T>(context, queryText, params);
|
|
113
|
+
|
|
114
|
+
this.logger?.debug('RLS query executed', {
|
|
115
|
+
tenant: context.tenantId,
|
|
116
|
+
query: queryText.substring(0, 100),
|
|
117
|
+
rows: result.rowCount,
|
|
118
|
+
latencyMs: (performance.now() - start).toFixed(2),
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
return result;
|
|
122
|
+
} catch (error) {
|
|
123
|
+
this.logger?.error('RLS query failed', {
|
|
124
|
+
tenant: context.tenantId,
|
|
125
|
+
query: queryText.substring(0, 100),
|
|
126
|
+
error: error instanceof Error ? error.message : String(error),
|
|
127
|
+
});
|
|
128
|
+
throw error;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Execute with tenant context, return first row
|
|
134
|
+
*/
|
|
135
|
+
async queryOne<T = QueryRow>(
|
|
136
|
+
context: RLSContext,
|
|
137
|
+
queryText: string,
|
|
138
|
+
params?: unknown[]
|
|
139
|
+
): Promise<T | null> {
|
|
140
|
+
const result = await this.query<T>(context, queryText, params);
|
|
141
|
+
return result.rows[0] ?? null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Execute with tenant context, return all rows
|
|
146
|
+
*/
|
|
147
|
+
async queryAll<T = QueryRow>(
|
|
148
|
+
context: RLSContext,
|
|
149
|
+
queryText: string,
|
|
150
|
+
params?: unknown[]
|
|
151
|
+
): Promise<T[]> {
|
|
152
|
+
const result = await this.query<T>(context, queryText, params);
|
|
153
|
+
return result.rows;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Wrap a function with RLS context
|
|
158
|
+
*/
|
|
159
|
+
withTenant<T>(context: RLSContext, fn: () => Promise<T>): Promise<T> {
|
|
160
|
+
return withTenant(context, fn);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Create an RLS-aware client
|
|
166
|
+
*/
|
|
167
|
+
export function createRLSClient(logger?: Logger): RLSClient {
|
|
168
|
+
return new RLSClient(logger);
|
|
169
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - Transaction Helpers
|
|
3
|
+
* ACID transaction support with auto-rollback
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { sql } from 'bun';
|
|
7
|
+
import type { TransactionOptions, RLSContext, QueryResult, QueryRow, Logger } from './types.js';
|
|
8
|
+
import { setRLSContext, clearRLSContext } from './rls.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Build BEGIN statement with options
|
|
12
|
+
*/
|
|
13
|
+
function buildBeginStatement(options: TransactionOptions): string {
|
|
14
|
+
const parts = ['BEGIN'];
|
|
15
|
+
|
|
16
|
+
if (options.isolationLevel) {
|
|
17
|
+
parts.push(`ISOLATION LEVEL ${options.isolationLevel}`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
if (options.readOnly) {
|
|
21
|
+
parts.push('READ ONLY');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (options.deferrable && options.readOnly && options.isolationLevel === 'SERIALIZABLE') {
|
|
25
|
+
parts.push('DEFERRABLE');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return parts.join(' ');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Transaction query interface
|
|
33
|
+
*/
|
|
34
|
+
export interface TransactionQuery {
|
|
35
|
+
/**
|
|
36
|
+
* Execute a query within the transaction
|
|
37
|
+
*/
|
|
38
|
+
query<T = QueryRow>(queryText: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Execute and return first row
|
|
42
|
+
*/
|
|
43
|
+
queryOne<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T | null>;
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Execute and return all rows
|
|
47
|
+
*/
|
|
48
|
+
queryAll<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T[]>;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Execute a command (INSERT/UPDATE/DELETE)
|
|
52
|
+
*/
|
|
53
|
+
execute(queryText: string, params?: unknown[]): Promise<number>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Execute a function within a transaction
|
|
58
|
+
* Auto-commits on success, auto-rollbacks on error
|
|
59
|
+
*/
|
|
60
|
+
export async function transaction<T>(
|
|
61
|
+
fn: (tx: TransactionQuery) => Promise<T>,
|
|
62
|
+
options: TransactionOptions = {},
|
|
63
|
+
logger?: Logger
|
|
64
|
+
): Promise<T> {
|
|
65
|
+
const start = performance.now();
|
|
66
|
+
|
|
67
|
+
// Begin transaction
|
|
68
|
+
const beginStmt = buildBeginStatement(options);
|
|
69
|
+
await sql.unsafe(beginStmt);
|
|
70
|
+
|
|
71
|
+
// Set RLS context if provided
|
|
72
|
+
if (options.rlsContext) {
|
|
73
|
+
await setRLSContext(options.rlsContext);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Create query interface
|
|
77
|
+
const tx: TransactionQuery = {
|
|
78
|
+
async query<T = QueryRow>(queryText: string, params?: unknown[]): Promise<QueryResult<T>> {
|
|
79
|
+
const result = await sql.unsafe(queryText, params ?? []);
|
|
80
|
+
return {
|
|
81
|
+
rows: result as T[],
|
|
82
|
+
rowCount: result.length,
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async queryOne<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T | null> {
|
|
87
|
+
const result = await tx.query<T>(queryText, params);
|
|
88
|
+
return result.rows[0] ?? null;
|
|
89
|
+
},
|
|
90
|
+
|
|
91
|
+
async queryAll<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T[]> {
|
|
92
|
+
const result = await tx.query<T>(queryText, params);
|
|
93
|
+
return result.rows;
|
|
94
|
+
},
|
|
95
|
+
|
|
96
|
+
async execute(queryText: string, params?: unknown[]): Promise<number> {
|
|
97
|
+
const result = await tx.query(queryText, params);
|
|
98
|
+
return result.rowCount;
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
try {
|
|
103
|
+
const result = await fn(tx);
|
|
104
|
+
|
|
105
|
+
// Clear RLS context before commit
|
|
106
|
+
if (options.rlsContext) {
|
|
107
|
+
await clearRLSContext();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Commit
|
|
111
|
+
await sql`COMMIT`;
|
|
112
|
+
|
|
113
|
+
const latency = performance.now() - start;
|
|
114
|
+
logger?.debug('Transaction committed', {
|
|
115
|
+
isolationLevel: options.isolationLevel ?? 'READ COMMITTED',
|
|
116
|
+
tenant: options.rlsContext?.tenantId,
|
|
117
|
+
latencyMs: latency.toFixed(2),
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return result;
|
|
121
|
+
} catch (error) {
|
|
122
|
+
// Clear RLS context before rollback
|
|
123
|
+
if (options.rlsContext) {
|
|
124
|
+
try {
|
|
125
|
+
await clearRLSContext();
|
|
126
|
+
} catch {
|
|
127
|
+
// Ignore errors during cleanup
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Rollback
|
|
132
|
+
try {
|
|
133
|
+
await sql`ROLLBACK`;
|
|
134
|
+
} catch {
|
|
135
|
+
// Connection might be broken
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const latency = performance.now() - start;
|
|
139
|
+
logger?.error('Transaction rolled back', {
|
|
140
|
+
isolationLevel: options.isolationLevel ?? 'READ COMMITTED',
|
|
141
|
+
tenant: options.rlsContext?.tenantId,
|
|
142
|
+
error: error instanceof Error ? error.message : String(error),
|
|
143
|
+
latencyMs: latency.toFixed(2),
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
throw error;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Execute a function within a serializable transaction
|
|
152
|
+
* Best for operations requiring strong consistency
|
|
153
|
+
*/
|
|
154
|
+
export async function serializableTransaction<T>(
|
|
155
|
+
fn: (tx: TransactionQuery) => Promise<T>,
|
|
156
|
+
rlsContext?: RLSContext,
|
|
157
|
+
logger?: Logger
|
|
158
|
+
): Promise<T> {
|
|
159
|
+
return transaction(
|
|
160
|
+
fn,
|
|
161
|
+
{
|
|
162
|
+
isolationLevel: 'SERIALIZABLE',
|
|
163
|
+
rlsContext,
|
|
164
|
+
},
|
|
165
|
+
logger
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Execute a function within a read-only transaction
|
|
171
|
+
* Best for reporting/analytics queries
|
|
172
|
+
*/
|
|
173
|
+
export async function readOnlyTransaction<T>(
|
|
174
|
+
fn: (tx: TransactionQuery) => Promise<T>,
|
|
175
|
+
rlsContext?: RLSContext,
|
|
176
|
+
logger?: Logger
|
|
177
|
+
): Promise<T> {
|
|
178
|
+
return transaction(
|
|
179
|
+
fn,
|
|
180
|
+
{
|
|
181
|
+
readOnly: true,
|
|
182
|
+
rlsContext,
|
|
183
|
+
},
|
|
184
|
+
logger
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create a savepoint within a transaction
|
|
190
|
+
*/
|
|
191
|
+
export async function savepoint(name: string): Promise<void> {
|
|
192
|
+
await sql.unsafe(`SAVEPOINT ${name}`);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Rollback to a savepoint
|
|
197
|
+
*/
|
|
198
|
+
export async function rollbackToSavepoint(name: string): Promise<void> {
|
|
199
|
+
await sql.unsafe(`ROLLBACK TO SAVEPOINT ${name}`);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Release a savepoint
|
|
204
|
+
*/
|
|
205
|
+
export async function releaseSavepoint(name: string): Promise<void> {
|
|
206
|
+
await sql.unsafe(`RELEASE SAVEPOINT ${name}`);
|
|
207
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - Type Definitions
|
|
3
|
+
* PostgreSQL 18.x types for Bun native client
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Connection configuration */
|
|
7
|
+
export interface PostgresConfig {
|
|
8
|
+
/** Connection string (postgres://user:pass@host:port/db) */
|
|
9
|
+
connectionString?: string;
|
|
10
|
+
/** Host (default: localhost) */
|
|
11
|
+
host?: string;
|
|
12
|
+
/** Port (default: 5432) */
|
|
13
|
+
port?: number;
|
|
14
|
+
/** Database name */
|
|
15
|
+
database?: string;
|
|
16
|
+
/** Username */
|
|
17
|
+
user?: string;
|
|
18
|
+
/** Password */
|
|
19
|
+
password?: string;
|
|
20
|
+
/** SSL mode */
|
|
21
|
+
ssl?: boolean | 'require' | 'prefer' | 'disable';
|
|
22
|
+
/** Application name for pg_stat_activity */
|
|
23
|
+
applicationName?: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** Pool configuration */
|
|
27
|
+
export interface PoolConfig extends PostgresConfig {
|
|
28
|
+
/** Minimum connections (default: 2) */
|
|
29
|
+
min?: number;
|
|
30
|
+
/** Maximum connections (default: 20) */
|
|
31
|
+
max?: number;
|
|
32
|
+
/** Idle timeout in ms (default: 30000) */
|
|
33
|
+
idleTimeout?: number;
|
|
34
|
+
/** Connection timeout in ms (default: 10000) */
|
|
35
|
+
connectionTimeout?: number;
|
|
36
|
+
/** Max lifetime per connection in ms (default: 3600000 = 1 hour) */
|
|
37
|
+
maxLifetime?: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** RLS context for multi-tenant isolation */
|
|
41
|
+
export interface RLSContext {
|
|
42
|
+
/** Tenant reference ID */
|
|
43
|
+
tenantId: string;
|
|
44
|
+
/** User ID (optional) */
|
|
45
|
+
userId?: string;
|
|
46
|
+
/** Is admin user (bypasses some RLS) */
|
|
47
|
+
isAdmin?: boolean;
|
|
48
|
+
/** Additional session GUCs */
|
|
49
|
+
extraGUCs?: Record<string, string>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Query result row */
|
|
53
|
+
export type QueryRow = Record<string, unknown>;
|
|
54
|
+
|
|
55
|
+
/** Query result */
|
|
56
|
+
export interface QueryResult<T = QueryRow> {
|
|
57
|
+
/** Result rows */
|
|
58
|
+
rows: T[];
|
|
59
|
+
/** Number of affected rows */
|
|
60
|
+
rowCount: number;
|
|
61
|
+
/** Field metadata */
|
|
62
|
+
fields?: FieldInfo[];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Field metadata */
|
|
66
|
+
export interface FieldInfo {
|
|
67
|
+
name: string;
|
|
68
|
+
dataTypeID: number;
|
|
69
|
+
tableID?: number;
|
|
70
|
+
columnID?: number;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Transaction isolation levels */
|
|
74
|
+
export type IsolationLevel =
|
|
75
|
+
| 'READ UNCOMMITTED'
|
|
76
|
+
| 'READ COMMITTED'
|
|
77
|
+
| 'REPEATABLE READ'
|
|
78
|
+
| 'SERIALIZABLE';
|
|
79
|
+
|
|
80
|
+
/** Transaction options */
|
|
81
|
+
export interface TransactionOptions {
|
|
82
|
+
/** Isolation level (default: READ COMMITTED) */
|
|
83
|
+
isolationLevel?: IsolationLevel;
|
|
84
|
+
/** Read-only transaction */
|
|
85
|
+
readOnly?: boolean;
|
|
86
|
+
/** Deferrable (only with SERIALIZABLE + readOnly) */
|
|
87
|
+
deferrable?: boolean;
|
|
88
|
+
/** RLS context to apply */
|
|
89
|
+
rlsContext?: RLSContext;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** Health check result */
|
|
93
|
+
export interface HealthCheckResult {
|
|
94
|
+
healthy: boolean;
|
|
95
|
+
latencyMs: number;
|
|
96
|
+
poolStats?: PoolStats;
|
|
97
|
+
error?: string;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Pool statistics */
|
|
101
|
+
export interface PoolStats {
|
|
102
|
+
/** Total connections in pool */
|
|
103
|
+
total: number;
|
|
104
|
+
/** Idle connections */
|
|
105
|
+
idle: number;
|
|
106
|
+
/** Active connections */
|
|
107
|
+
active: number;
|
|
108
|
+
/** Pending connection requests */
|
|
109
|
+
pending: number;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Vector distance operators for pgvector */
|
|
113
|
+
export type VectorDistanceOperator =
|
|
114
|
+
| '<->' // L2 distance (Euclidean)
|
|
115
|
+
| '<=>' // Cosine distance
|
|
116
|
+
| '<#>' // Inner product (negative)
|
|
117
|
+
| '<+>'; // L1 distance (Manhattan)
|
|
118
|
+
|
|
119
|
+
/** Vector index types */
|
|
120
|
+
export type VectorIndexType = 'hnsw' | 'ivfflat';
|
|
121
|
+
|
|
122
|
+
/** Vector search options */
|
|
123
|
+
export interface VectorSearchOptions {
|
|
124
|
+
/** Distance operator (default: <->) */
|
|
125
|
+
operator?: VectorDistanceOperator;
|
|
126
|
+
/** Maximum results (default: 10) */
|
|
127
|
+
limit?: number;
|
|
128
|
+
/** Minimum similarity threshold (optional) */
|
|
129
|
+
threshold?: number;
|
|
130
|
+
/** Additional WHERE clause */
|
|
131
|
+
filter?: string;
|
|
132
|
+
/** Filter parameters */
|
|
133
|
+
filterParams?: unknown[];
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Logger interface (matches @singulio/logger) */
|
|
137
|
+
export interface Logger {
|
|
138
|
+
debug(message: string, meta?: Record<string, unknown>): void;
|
|
139
|
+
info(message: string, meta?: Record<string, unknown>): void;
|
|
140
|
+
warn(message: string, meta?: Record<string, unknown>): void;
|
|
141
|
+
error(message: string, meta?: Record<string, unknown>): void;
|
|
142
|
+
}
|