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/dist/client.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - PostgreSQL Client
|
|
3
|
+
* Thin wrapper around bun:postgres native client
|
|
4
|
+
*/
|
|
5
|
+
import type { PostgresConfig, QueryResult, QueryRow, Logger } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL client wrapping Bun's native postgres driver
|
|
8
|
+
*/
|
|
9
|
+
export declare class PostgresClient {
|
|
10
|
+
private connectionString;
|
|
11
|
+
private logger?;
|
|
12
|
+
private connected;
|
|
13
|
+
constructor(config: PostgresConfig | string, logger?: Logger);
|
|
14
|
+
private buildConnectionString;
|
|
15
|
+
/**
|
|
16
|
+
* Execute a SQL query with parameters
|
|
17
|
+
*/
|
|
18
|
+
query<T = QueryRow>(queryText: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
19
|
+
/**
|
|
20
|
+
* Execute a query and return the first row or null
|
|
21
|
+
*/
|
|
22
|
+
queryOne<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T | null>;
|
|
23
|
+
/**
|
|
24
|
+
* Execute a query and return all rows
|
|
25
|
+
*/
|
|
26
|
+
queryAll<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T[]>;
|
|
27
|
+
/**
|
|
28
|
+
* Execute a command (INSERT/UPDATE/DELETE) and return affected row count
|
|
29
|
+
*/
|
|
30
|
+
execute(queryText: string, params?: unknown[]): Promise<number>;
|
|
31
|
+
/**
|
|
32
|
+
* Check if connected and working
|
|
33
|
+
*/
|
|
34
|
+
ping(): Promise<boolean>;
|
|
35
|
+
/**
|
|
36
|
+
* Get server version
|
|
37
|
+
*/
|
|
38
|
+
version(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* Close all connections (for bun:postgres this is mostly a no-op as it manages connections)
|
|
41
|
+
*/
|
|
42
|
+
close(): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Get connection string (for debugging, sensitive parts masked)
|
|
45
|
+
*/
|
|
46
|
+
get maskedConnectionString(): string;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create a new PostgresClient instance
|
|
50
|
+
*/
|
|
51
|
+
export declare function createClient(config: PostgresConfig | string, logger?: Logger): PostgresClient;
|
|
52
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEhF;;GAEG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,SAAS,CAAS;gBAEd,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;IAM5D,OAAO,CAAC,qBAAqB;IAgC7B;;OAEG;IACG,KAAK,CAAC,CAAC,GAAG,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IA8BzF;;OAEG;IACG,QAAQ,CAAC,CAAC,GAAG,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAKtF;;OAEG;IACG,QAAQ,CAAC,CAAC,GAAG,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,EAAE,CAAC;IAKjF;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC;IAKrE;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,OAAO,CAAC;IAS9B;;OAEG;IACG,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;IAKhC;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAK5B;;OAEG;IACH,IAAI,sBAAsB,IAAI,MAAM,CAEnC;CACF;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,cAAc,GAAG,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,cAAc,CAE7F"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - PostgreSQL Client
|
|
3
|
+
* Thin wrapper around bun:postgres native client
|
|
4
|
+
*/
|
|
5
|
+
import { sql } from 'bun';
|
|
6
|
+
/**
|
|
7
|
+
* PostgreSQL client wrapping Bun's native postgres driver
|
|
8
|
+
*/
|
|
9
|
+
export class PostgresClient {
|
|
10
|
+
connectionString;
|
|
11
|
+
logger;
|
|
12
|
+
connected = false;
|
|
13
|
+
constructor(config, logger) {
|
|
14
|
+
this.connectionString =
|
|
15
|
+
typeof config === 'string' ? config : this.buildConnectionString(config);
|
|
16
|
+
this.logger = logger;
|
|
17
|
+
}
|
|
18
|
+
buildConnectionString(config) {
|
|
19
|
+
if (config.connectionString)
|
|
20
|
+
return config.connectionString;
|
|
21
|
+
const { host = 'localhost', port = 5432, database = 'postgres', user = 'postgres', password = '', ssl, applicationName, } = config;
|
|
22
|
+
let connStr = `postgres://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
|
|
23
|
+
const params = [];
|
|
24
|
+
if (ssl === true || ssl === 'require') {
|
|
25
|
+
params.push('sslmode=require');
|
|
26
|
+
}
|
|
27
|
+
else if (ssl === 'prefer') {
|
|
28
|
+
params.push('sslmode=prefer');
|
|
29
|
+
}
|
|
30
|
+
if (applicationName) {
|
|
31
|
+
params.push(`application_name=${encodeURIComponent(applicationName)}`);
|
|
32
|
+
}
|
|
33
|
+
if (params.length > 0) {
|
|
34
|
+
connStr += '?' + params.join('&');
|
|
35
|
+
}
|
|
36
|
+
return connStr;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute a SQL query with parameters
|
|
40
|
+
*/
|
|
41
|
+
async query(queryText, params) {
|
|
42
|
+
const start = performance.now();
|
|
43
|
+
try {
|
|
44
|
+
// Use Bun's sql tagged template for parameterized queries
|
|
45
|
+
const result = await sql.unsafe(queryText, params ?? []);
|
|
46
|
+
const latency = performance.now() - start;
|
|
47
|
+
this.logger?.debug('Query executed', {
|
|
48
|
+
query: queryText.substring(0, 100),
|
|
49
|
+
params: params?.length ?? 0,
|
|
50
|
+
rows: result.length,
|
|
51
|
+
latencyMs: latency.toFixed(2),
|
|
52
|
+
});
|
|
53
|
+
return {
|
|
54
|
+
rows: result,
|
|
55
|
+
rowCount: result.length,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
const latency = performance.now() - start;
|
|
60
|
+
this.logger?.error('Query failed', {
|
|
61
|
+
query: queryText.substring(0, 100),
|
|
62
|
+
error: error instanceof Error ? error.message : String(error),
|
|
63
|
+
latencyMs: latency.toFixed(2),
|
|
64
|
+
});
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Execute a query and return the first row or null
|
|
70
|
+
*/
|
|
71
|
+
async queryOne(queryText, params) {
|
|
72
|
+
const result = await this.query(queryText, params);
|
|
73
|
+
return result.rows[0] ?? null;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Execute a query and return all rows
|
|
77
|
+
*/
|
|
78
|
+
async queryAll(queryText, params) {
|
|
79
|
+
const result = await this.query(queryText, params);
|
|
80
|
+
return result.rows;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Execute a command (INSERT/UPDATE/DELETE) and return affected row count
|
|
84
|
+
*/
|
|
85
|
+
async execute(queryText, params) {
|
|
86
|
+
const result = await this.query(queryText, params);
|
|
87
|
+
return result.rowCount;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Check if connected and working
|
|
91
|
+
*/
|
|
92
|
+
async ping() {
|
|
93
|
+
try {
|
|
94
|
+
const result = await sql `SELECT 1 as ok`;
|
|
95
|
+
return result.length > 0;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Get server version
|
|
103
|
+
*/
|
|
104
|
+
async version() {
|
|
105
|
+
const result = await sql `SELECT version()`;
|
|
106
|
+
return result[0]?.version ?? 'unknown';
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Close all connections (for bun:postgres this is mostly a no-op as it manages connections)
|
|
110
|
+
*/
|
|
111
|
+
async close() {
|
|
112
|
+
this.connected = false;
|
|
113
|
+
this.logger?.info('PostgresClient closed');
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get connection string (for debugging, sensitive parts masked)
|
|
117
|
+
*/
|
|
118
|
+
get maskedConnectionString() {
|
|
119
|
+
return this.connectionString.replace(/:([^@]+)@/, ':***@');
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Create a new PostgresClient instance
|
|
124
|
+
*/
|
|
125
|
+
export function createClient(config, logger) {
|
|
126
|
+
return new PostgresClient(config, logger);
|
|
127
|
+
}
|
package/dist/health.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - Health Check Utilities
|
|
3
|
+
* Kubernetes readiness/liveness probes for PostgreSQL
|
|
4
|
+
*/
|
|
5
|
+
import type { HealthCheckResult, Logger } from './types.js';
|
|
6
|
+
import type { PostgresPool } from './pool.js';
|
|
7
|
+
/**
|
|
8
|
+
* Simple health check - verifies database connectivity
|
|
9
|
+
*/
|
|
10
|
+
export declare function healthCheck(logger?: Logger): Promise<HealthCheckResult>;
|
|
11
|
+
/**
|
|
12
|
+
* Extended health check with pool statistics
|
|
13
|
+
*/
|
|
14
|
+
export declare function healthCheckWithPool(pool: PostgresPool, logger?: Logger): Promise<HealthCheckResult>;
|
|
15
|
+
/**
|
|
16
|
+
* Deep health check - verifies database is accepting writes
|
|
17
|
+
*/
|
|
18
|
+
export declare function deepHealthCheck(logger?: Logger): Promise<HealthCheckResult>;
|
|
19
|
+
/**
|
|
20
|
+
* Check PostgreSQL version
|
|
21
|
+
*/
|
|
22
|
+
export declare function getVersion(): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* Check if database is in recovery mode (replica)
|
|
25
|
+
*/
|
|
26
|
+
export declare function isInRecovery(): Promise<boolean>;
|
|
27
|
+
/**
|
|
28
|
+
* Get database size
|
|
29
|
+
*/
|
|
30
|
+
export declare function getDatabaseSize(dbName?: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* Get connection count
|
|
33
|
+
*/
|
|
34
|
+
export declare function getConnectionCount(): Promise<{
|
|
35
|
+
active: number;
|
|
36
|
+
idle: number;
|
|
37
|
+
total: number;
|
|
38
|
+
maxConnections: number;
|
|
39
|
+
}>;
|
|
40
|
+
/**
|
|
41
|
+
* Create an HTTP health check handler for Bun.serve
|
|
42
|
+
*/
|
|
43
|
+
export declare function createHealthHandler(pool?: PostgresPool): (req: Request) => Promise<Response>;
|
|
44
|
+
//# sourceMappingURL=health.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../src/health.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAE9C;;GAEG;AACH,wBAAsB,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CA2B7E;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,YAAY,EAClB,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,CAAC,CAwC5B;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAkDjF;AAED;;GAEG;AACH,wBAAsB,UAAU,IAAI,OAAO,CAAC,MAAM,CAAC,CAGlD;AAED;;GAEG;AACH,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CAGrD;AAED;;GAEG;AACH,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAMtE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,IAAI,OAAO,CAAC;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,cAAc,EAAE,MAAM,CAAC;CACxB,CAAC,CAiBD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,CAAC,EAAE,YAAY,IACvC,KAAK,OAAO,KAAG,OAAO,CAAC,QAAQ,CAAC,CAuB/C"}
|
package/dist/health.js
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - Health Check Utilities
|
|
3
|
+
* Kubernetes readiness/liveness probes for PostgreSQL
|
|
4
|
+
*/
|
|
5
|
+
import { sql } from 'bun';
|
|
6
|
+
/**
|
|
7
|
+
* Simple health check - verifies database connectivity
|
|
8
|
+
*/
|
|
9
|
+
export async function healthCheck(logger) {
|
|
10
|
+
const start = performance.now();
|
|
11
|
+
try {
|
|
12
|
+
const result = await sql `SELECT 1 as ok`;
|
|
13
|
+
const latencyMs = performance.now() - start;
|
|
14
|
+
const healthy = result.length > 0 && result[0].ok === 1;
|
|
15
|
+
logger?.debug('Health check completed', { healthy, latencyMs: latencyMs.toFixed(2) });
|
|
16
|
+
return {
|
|
17
|
+
healthy,
|
|
18
|
+
latencyMs,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
const latencyMs = performance.now() - start;
|
|
23
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
24
|
+
logger?.error('Health check failed', { error: errorMessage, latencyMs: latencyMs.toFixed(2) });
|
|
25
|
+
return {
|
|
26
|
+
healthy: false,
|
|
27
|
+
latencyMs,
|
|
28
|
+
error: errorMessage,
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Extended health check with pool statistics
|
|
34
|
+
*/
|
|
35
|
+
export async function healthCheckWithPool(pool, logger) {
|
|
36
|
+
const start = performance.now();
|
|
37
|
+
try {
|
|
38
|
+
// Query through pool
|
|
39
|
+
const result = await pool.query('SELECT 1 as ok');
|
|
40
|
+
const latencyMs = performance.now() - start;
|
|
41
|
+
const healthy = result.rows.length > 0 && result.rows[0].ok === 1;
|
|
42
|
+
const poolStats = pool.stats();
|
|
43
|
+
logger?.debug('Pool health check completed', {
|
|
44
|
+
healthy,
|
|
45
|
+
latencyMs: latencyMs.toFixed(2),
|
|
46
|
+
...poolStats,
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
healthy,
|
|
50
|
+
latencyMs,
|
|
51
|
+
poolStats,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
const latencyMs = performance.now() - start;
|
|
56
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
57
|
+
const poolStats = pool.stats();
|
|
58
|
+
logger?.error('Pool health check failed', {
|
|
59
|
+
error: errorMessage,
|
|
60
|
+
latencyMs: latencyMs.toFixed(2),
|
|
61
|
+
...poolStats,
|
|
62
|
+
});
|
|
63
|
+
return {
|
|
64
|
+
healthy: false,
|
|
65
|
+
latencyMs,
|
|
66
|
+
poolStats,
|
|
67
|
+
error: errorMessage,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Deep health check - verifies database is accepting writes
|
|
73
|
+
*/
|
|
74
|
+
export async function deepHealthCheck(logger) {
|
|
75
|
+
const start = performance.now();
|
|
76
|
+
try {
|
|
77
|
+
// Check we can read and write
|
|
78
|
+
await sql `
|
|
79
|
+
CREATE TEMP TABLE IF NOT EXISTS _health_check (
|
|
80
|
+
id serial PRIMARY KEY,
|
|
81
|
+
ts timestamptz DEFAULT NOW()
|
|
82
|
+
)
|
|
83
|
+
`;
|
|
84
|
+
await sql `INSERT INTO _health_check DEFAULT VALUES`;
|
|
85
|
+
const result = await sql `SELECT COUNT(*) as count FROM _health_check`;
|
|
86
|
+
await sql `DROP TABLE IF EXISTS _health_check`;
|
|
87
|
+
const latencyMs = performance.now() - start;
|
|
88
|
+
const healthy = result.length > 0 && result[0].count > 0;
|
|
89
|
+
logger?.debug('Deep health check completed', {
|
|
90
|
+
healthy,
|
|
91
|
+
latencyMs: latencyMs.toFixed(2),
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
healthy,
|
|
95
|
+
latencyMs,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
const latencyMs = performance.now() - start;
|
|
100
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
101
|
+
// Cleanup on error
|
|
102
|
+
try {
|
|
103
|
+
await sql `DROP TABLE IF EXISTS _health_check`;
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Ignore cleanup errors
|
|
107
|
+
}
|
|
108
|
+
logger?.error('Deep health check failed', {
|
|
109
|
+
error: errorMessage,
|
|
110
|
+
latencyMs: latencyMs.toFixed(2),
|
|
111
|
+
});
|
|
112
|
+
return {
|
|
113
|
+
healthy: false,
|
|
114
|
+
latencyMs,
|
|
115
|
+
error: errorMessage,
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Check PostgreSQL version
|
|
121
|
+
*/
|
|
122
|
+
export async function getVersion() {
|
|
123
|
+
const result = await sql `SELECT version()`;
|
|
124
|
+
return result[0]?.version ?? 'unknown';
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Check if database is in recovery mode (replica)
|
|
128
|
+
*/
|
|
129
|
+
export async function isInRecovery() {
|
|
130
|
+
const result = await sql `SELECT pg_is_in_recovery() as in_recovery`;
|
|
131
|
+
return result[0]?.in_recovery === true;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Get database size
|
|
135
|
+
*/
|
|
136
|
+
export async function getDatabaseSize(dbName) {
|
|
137
|
+
const result = await sql.unsafe(`SELECT pg_size_pretty(pg_database_size(${dbName ? '$1' : 'current_database()'}))::text as size`, dbName ? [dbName] : []);
|
|
138
|
+
return result[0]?.size ?? 'unknown';
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Get connection count
|
|
142
|
+
*/
|
|
143
|
+
export async function getConnectionCount() {
|
|
144
|
+
const result = await sql `
|
|
145
|
+
SELECT
|
|
146
|
+
COUNT(*) FILTER (WHERE state = 'active') as active,
|
|
147
|
+
COUNT(*) FILTER (WHERE state = 'idle') as idle,
|
|
148
|
+
COUNT(*) as total,
|
|
149
|
+
(SELECT setting::int FROM pg_settings WHERE name = 'max_connections') as max_connections
|
|
150
|
+
FROM pg_stat_activity
|
|
151
|
+
WHERE datname = current_database()
|
|
152
|
+
`;
|
|
153
|
+
return {
|
|
154
|
+
active: Number(result[0]?.active) || 0,
|
|
155
|
+
idle: Number(result[0]?.idle) || 0,
|
|
156
|
+
total: Number(result[0]?.total) || 0,
|
|
157
|
+
maxConnections: Number(result[0]?.max_connections) || 100,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Create an HTTP health check handler for Bun.serve
|
|
162
|
+
*/
|
|
163
|
+
export function createHealthHandler(pool) {
|
|
164
|
+
return async (req) => {
|
|
165
|
+
const url = new URL(req.url);
|
|
166
|
+
if (url.pathname === '/health' || url.pathname === '/healthz') {
|
|
167
|
+
const result = pool ? await healthCheckWithPool(pool) : await healthCheck();
|
|
168
|
+
return new Response(JSON.stringify(result), {
|
|
169
|
+
status: result.healthy ? 200 : 503,
|
|
170
|
+
headers: { 'Content-Type': 'application/json' },
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
if (url.pathname === '/ready' || url.pathname === '/readyz') {
|
|
174
|
+
const result = await deepHealthCheck();
|
|
175
|
+
return new Response(JSON.stringify(result), {
|
|
176
|
+
status: result.healthy ? 200 : 503,
|
|
177
|
+
headers: { 'Content-Type': 'application/json' },
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
return new Response('Not Found', { status: 404 });
|
|
181
|
+
};
|
|
182
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres
|
|
3
|
+
* PostgreSQL 18.x client for Bun with RLS, pooling, transactions, and pgvector support
|
|
4
|
+
*
|
|
5
|
+
* A thin wrapper around bun:postgres native client providing:
|
|
6
|
+
* - Connection pooling with configurable limits
|
|
7
|
+
* - Row-Level Security (RLS) helpers for multi-tenant isolation
|
|
8
|
+
* - Transaction support with auto-rollback
|
|
9
|
+
* - pgvector extension for AI/ML embeddings
|
|
10
|
+
* - Kubernetes health check utilities
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createClient, createPool, withTenant, vectorSearch } from '@singulio/postgres';
|
|
15
|
+
*
|
|
16
|
+
* // Simple client
|
|
17
|
+
* const client = createClient(process.env.DATABASE_URL);
|
|
18
|
+
* const users = await client.queryAll('SELECT * FROM users');
|
|
19
|
+
*
|
|
20
|
+
* // With RLS tenant context
|
|
21
|
+
* const tenantData = await withTenant({ tenantId: 'tenant-123' }, async () => {
|
|
22
|
+
* return client.queryAll('SELECT * FROM contacts'); // RLS filters by tenant
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Vector similarity search
|
|
26
|
+
* const similar = await vectorSearch('documents', 'embedding', queryVector, {
|
|
27
|
+
* operator: '<=>', // Cosine similarity
|
|
28
|
+
* limit: 10,
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export type { PostgresConfig, PoolConfig, RLSContext, QueryResult, QueryRow, FieldInfo, IsolationLevel, TransactionOptions, HealthCheckResult, PoolStats, VectorDistanceOperator, VectorIndexType, VectorSearchOptions, Logger, } from './types.js';
|
|
33
|
+
export { PostgresClient, createClient } from './client.js';
|
|
34
|
+
export { PostgresPool, createPool } from './pool.js';
|
|
35
|
+
export { setRLSContext, clearRLSContext, getRLSContext, withTenant, queryWithTenant, RLSClient, createRLSClient, } from './rls.js';
|
|
36
|
+
export { transaction, serializableTransaction, readOnlyTransaction, savepoint, rollbackToSavepoint, releaseSavepoint, type TransactionQuery, } from './transaction.js';
|
|
37
|
+
export { formatVector, parseVector, vectorDimension, normalizeVector, getDistanceOperator, getDistanceOperatorName, ensureVectorExtension, createVectorColumn, createVectorIndex, vectorSearch, insertWithVector, updateVector, batchInsertWithVectors, type Vector, } from './vector.js';
|
|
38
|
+
export { healthCheck, healthCheckWithPool, deepHealthCheck, getVersion, isInRecovery, getDatabaseSize, getConnectionCount, createHealthHandler, } from './health.js';
|
|
39
|
+
export { sql } from 'bun';
|
|
40
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,YAAY,EACV,cAAc,EACd,UAAU,EACV,UAAU,EACV,WAAW,EACX,QAAQ,EACR,SAAS,EACT,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,SAAS,EACT,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,MAAM,GACP,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAG3D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAGrD,OAAO,EACL,aAAa,EACb,eAAe,EACf,aAAa,EACb,UAAU,EACV,eAAe,EACf,SAAS,EACT,eAAe,GAChB,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,mBAAmB,EACnB,SAAS,EACT,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,gBAAgB,GACtB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,YAAY,EACZ,WAAW,EACX,eAAe,EACf,eAAe,EACf,mBAAmB,EACnB,uBAAuB,EACvB,qBAAqB,EACrB,kBAAkB,EAClB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,sBAAsB,EACtB,KAAK,MAAM,GACZ,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,WAAW,EACX,mBAAmB,EACnB,eAAe,EACf,UAAU,EACV,YAAY,EACZ,eAAe,EACf,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,aAAa,CAAC;AAGrB,OAAO,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres
|
|
3
|
+
* PostgreSQL 18.x client for Bun with RLS, pooling, transactions, and pgvector support
|
|
4
|
+
*
|
|
5
|
+
* A thin wrapper around bun:postgres native client providing:
|
|
6
|
+
* - Connection pooling with configurable limits
|
|
7
|
+
* - Row-Level Security (RLS) helpers for multi-tenant isolation
|
|
8
|
+
* - Transaction support with auto-rollback
|
|
9
|
+
* - pgvector extension for AI/ML embeddings
|
|
10
|
+
* - Kubernetes health check utilities
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { createClient, createPool, withTenant, vectorSearch } from '@singulio/postgres';
|
|
15
|
+
*
|
|
16
|
+
* // Simple client
|
|
17
|
+
* const client = createClient(process.env.DATABASE_URL);
|
|
18
|
+
* const users = await client.queryAll('SELECT * FROM users');
|
|
19
|
+
*
|
|
20
|
+
* // With RLS tenant context
|
|
21
|
+
* const tenantData = await withTenant({ tenantId: 'tenant-123' }, async () => {
|
|
22
|
+
* return client.queryAll('SELECT * FROM contacts'); // RLS filters by tenant
|
|
23
|
+
* });
|
|
24
|
+
*
|
|
25
|
+
* // Vector similarity search
|
|
26
|
+
* const similar = await vectorSearch('documents', 'embedding', queryVector, {
|
|
27
|
+
* operator: '<=>', // Cosine similarity
|
|
28
|
+
* limit: 10,
|
|
29
|
+
* });
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
// Client
|
|
33
|
+
export { PostgresClient, createClient } from './client.js';
|
|
34
|
+
// Pool
|
|
35
|
+
export { PostgresPool, createPool } from './pool.js';
|
|
36
|
+
// RLS
|
|
37
|
+
export { setRLSContext, clearRLSContext, getRLSContext, withTenant, queryWithTenant, RLSClient, createRLSClient, } from './rls.js';
|
|
38
|
+
// Transactions
|
|
39
|
+
export { transaction, serializableTransaction, readOnlyTransaction, savepoint, rollbackToSavepoint, releaseSavepoint, } from './transaction.js';
|
|
40
|
+
// Vector (pgvector)
|
|
41
|
+
export { formatVector, parseVector, vectorDimension, normalizeVector, getDistanceOperator, getDistanceOperatorName, ensureVectorExtension, createVectorColumn, createVectorIndex, vectorSearch, insertWithVector, updateVector, batchInsertWithVectors, } from './vector.js';
|
|
42
|
+
// Health
|
|
43
|
+
export { healthCheck, healthCheckWithPool, deepHealthCheck, getVersion, isInRecovery, getDatabaseSize, getConnectionCount, createHealthHandler, } from './health.js';
|
|
44
|
+
// Re-export bun:postgres sql for direct usage
|
|
45
|
+
export { sql } from 'bun';
|
package/dist/pool.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @singulio/postgres - Connection Pool Manager
|
|
3
|
+
* Manages PostgreSQL connections with configurable pooling
|
|
4
|
+
*
|
|
5
|
+
* Note: Bun's native sql driver handles connection pooling internally.
|
|
6
|
+
* This module provides a higher-level API with explicit pool management
|
|
7
|
+
* for scenarios requiring fine-grained control.
|
|
8
|
+
*/
|
|
9
|
+
import type { PoolConfig, PoolStats, QueryResult, QueryRow, Logger } from './types.js';
|
|
10
|
+
/**
|
|
11
|
+
* Connection pool manager for PostgreSQL
|
|
12
|
+
*/
|
|
13
|
+
export declare class PostgresPool {
|
|
14
|
+
private config;
|
|
15
|
+
private logger?;
|
|
16
|
+
private connections;
|
|
17
|
+
private nextId;
|
|
18
|
+
private waitQueue;
|
|
19
|
+
private closed;
|
|
20
|
+
private idleCheckTimer?;
|
|
21
|
+
constructor(config: PoolConfig, logger?: Logger);
|
|
22
|
+
/**
|
|
23
|
+
* Initialize the pool with minimum connections
|
|
24
|
+
*/
|
|
25
|
+
initialize(): Promise<void>;
|
|
26
|
+
private createConnection;
|
|
27
|
+
private acquireConnection;
|
|
28
|
+
private releaseConnection;
|
|
29
|
+
private destroyConnection;
|
|
30
|
+
private checkIdleConnections;
|
|
31
|
+
/**
|
|
32
|
+
* Execute a query using a pooled connection
|
|
33
|
+
*/
|
|
34
|
+
query<T = QueryRow>(queryText: string, params?: unknown[]): Promise<QueryResult<T>>;
|
|
35
|
+
/**
|
|
36
|
+
* Execute with exclusive connection for multi-statement operations
|
|
37
|
+
*/
|
|
38
|
+
withConnection<T>(fn: (query: (sql: string, params?: unknown[]) => Promise<QueryResult>) => Promise<T>): Promise<T>;
|
|
39
|
+
/**
|
|
40
|
+
* Get current pool statistics
|
|
41
|
+
*/
|
|
42
|
+
stats(): PoolStats;
|
|
43
|
+
/**
|
|
44
|
+
* Close the pool and all connections
|
|
45
|
+
*/
|
|
46
|
+
close(): Promise<void>;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Create and initialize a connection pool
|
|
50
|
+
*/
|
|
51
|
+
export declare function createPool(config: PoolConfig, logger?: Logger): Promise<PostgresPool>;
|
|
52
|
+
//# sourceMappingURL=pool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pool.d.ts","sourceRoot":"","sources":["../src/pool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAkBvF;;GAEG;AACH,qBAAa,YAAY;IACvB,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,MAAM,CAAC,CAAS;IACxB,OAAO,CAAC,WAAW,CAA4C;IAC/D,OAAO,CAAC,MAAM,CAAK;IACnB,OAAO,CAAC,SAAS,CAIT;IACR,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,cAAc,CAAC,CAAQ;gBAEnB,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM;IAe/C;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;YAgBnB,gBAAgB;YAWhB,iBAAiB;IAkC/B,OAAO,CAAC,iBAAiB;YAaX,iBAAiB;YAKjB,oBAAoB;IAclC;;OAEG;IACG,KAAK,CAAC,CAAC,GAAG,QAAQ,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IA4BzF;;OAEG;IACG,cAAc,CAAC,CAAC,EACpB,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,KAAK,OAAO,CAAC,WAAW,CAAC,KAAK,OAAO,CAAC,CAAC,CAAC,GACnF,OAAO,CAAC,CAAC,CAAC;IAoBb;;OAEG;IACH,KAAK,IAAI,SAAS;IAoBlB;;OAEG;IACG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAqB7B;AAED;;GAEG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,UAAU,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAI3F"}
|