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.
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @singulio/postgres - Type Definitions
3
+ * PostgreSQL 18.x types for Bun native client
4
+ */
5
+ /** Connection configuration */
6
+ export interface PostgresConfig {
7
+ /** Connection string (postgres://user:pass@host:port/db) */
8
+ connectionString?: string;
9
+ /** Host (default: localhost) */
10
+ host?: string;
11
+ /** Port (default: 5432) */
12
+ port?: number;
13
+ /** Database name */
14
+ database?: string;
15
+ /** Username */
16
+ user?: string;
17
+ /** Password */
18
+ password?: string;
19
+ /** SSL mode */
20
+ ssl?: boolean | 'require' | 'prefer' | 'disable';
21
+ /** Application name for pg_stat_activity */
22
+ applicationName?: string;
23
+ }
24
+ /** Pool configuration */
25
+ export interface PoolConfig extends PostgresConfig {
26
+ /** Minimum connections (default: 2) */
27
+ min?: number;
28
+ /** Maximum connections (default: 20) */
29
+ max?: number;
30
+ /** Idle timeout in ms (default: 30000) */
31
+ idleTimeout?: number;
32
+ /** Connection timeout in ms (default: 10000) */
33
+ connectionTimeout?: number;
34
+ /** Max lifetime per connection in ms (default: 3600000 = 1 hour) */
35
+ maxLifetime?: number;
36
+ }
37
+ /** RLS context for multi-tenant isolation */
38
+ export interface RLSContext {
39
+ /** Tenant reference ID */
40
+ tenantId: string;
41
+ /** User ID (optional) */
42
+ userId?: string;
43
+ /** Is admin user (bypasses some RLS) */
44
+ isAdmin?: boolean;
45
+ /** Additional session GUCs */
46
+ extraGUCs?: Record<string, string>;
47
+ }
48
+ /** Query result row */
49
+ export type QueryRow = Record<string, unknown>;
50
+ /** Query result */
51
+ export interface QueryResult<T = QueryRow> {
52
+ /** Result rows */
53
+ rows: T[];
54
+ /** Number of affected rows */
55
+ rowCount: number;
56
+ /** Field metadata */
57
+ fields?: FieldInfo[];
58
+ }
59
+ /** Field metadata */
60
+ export interface FieldInfo {
61
+ name: string;
62
+ dataTypeID: number;
63
+ tableID?: number;
64
+ columnID?: number;
65
+ }
66
+ /** Transaction isolation levels */
67
+ export type IsolationLevel = 'READ UNCOMMITTED' | 'READ COMMITTED' | 'REPEATABLE READ' | 'SERIALIZABLE';
68
+ /** Transaction options */
69
+ export interface TransactionOptions {
70
+ /** Isolation level (default: READ COMMITTED) */
71
+ isolationLevel?: IsolationLevel;
72
+ /** Read-only transaction */
73
+ readOnly?: boolean;
74
+ /** Deferrable (only with SERIALIZABLE + readOnly) */
75
+ deferrable?: boolean;
76
+ /** RLS context to apply */
77
+ rlsContext?: RLSContext;
78
+ }
79
+ /** Health check result */
80
+ export interface HealthCheckResult {
81
+ healthy: boolean;
82
+ latencyMs: number;
83
+ poolStats?: PoolStats;
84
+ error?: string;
85
+ }
86
+ /** Pool statistics */
87
+ export interface PoolStats {
88
+ /** Total connections in pool */
89
+ total: number;
90
+ /** Idle connections */
91
+ idle: number;
92
+ /** Active connections */
93
+ active: number;
94
+ /** Pending connection requests */
95
+ pending: number;
96
+ }
97
+ /** Vector distance operators for pgvector */
98
+ export type VectorDistanceOperator = '<->' | '<=>' | '<#>' | '<+>';
99
+ /** Vector index types */
100
+ export type VectorIndexType = 'hnsw' | 'ivfflat';
101
+ /** Vector search options */
102
+ export interface VectorSearchOptions {
103
+ /** Distance operator (default: <->) */
104
+ operator?: VectorDistanceOperator;
105
+ /** Maximum results (default: 10) */
106
+ limit?: number;
107
+ /** Minimum similarity threshold (optional) */
108
+ threshold?: number;
109
+ /** Additional WHERE clause */
110
+ filter?: string;
111
+ /** Filter parameters */
112
+ filterParams?: unknown[];
113
+ }
114
+ /** Logger interface (matches @singulio/logger) */
115
+ export interface Logger {
116
+ debug(message: string, meta?: Record<string, unknown>): void;
117
+ info(message: string, meta?: Record<string, unknown>): void;
118
+ warn(message: string, meta?: Record<string, unknown>): void;
119
+ error(message: string, meta?: Record<string, unknown>): void;
120
+ }
121
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * @singulio/postgres - Type Definitions
3
+ * PostgreSQL 18.x types for Bun native client
4
+ */
5
+ export {};
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @singulio/postgres - pgvector Extension Support
3
+ * Vector operations for AI/ML embeddings
4
+ */
5
+ import type { VectorDistanceOperator, VectorIndexType, VectorSearchOptions, QueryResult, Logger } from './types.js';
6
+ /** Vector type (array of numbers) */
7
+ export type Vector = number[] | Float32Array | Float64Array;
8
+ /**
9
+ * Format a vector for PostgreSQL insertion
10
+ * Converts array to '[1,2,3]' format expected by pgvector
11
+ */
12
+ export declare function formatVector(vector: Vector): string;
13
+ /**
14
+ * Parse a vector from PostgreSQL result
15
+ * Converts '[1,2,3]' string back to number array
16
+ */
17
+ export declare function parseVector(value: string | number[] | null): number[] | null;
18
+ /**
19
+ * Calculate vector dimension
20
+ */
21
+ export declare function vectorDimension(vector: Vector): number;
22
+ /**
23
+ * Normalize a vector to unit length (for cosine similarity)
24
+ */
25
+ export declare function normalizeVector(vector: Vector): number[];
26
+ /**
27
+ * Get the distance operator SQL string
28
+ */
29
+ export declare function getDistanceOperator(op: VectorDistanceOperator): string;
30
+ /**
31
+ * Get human-readable name for distance operator
32
+ */
33
+ export declare function getDistanceOperatorName(op: VectorDistanceOperator): string;
34
+ /**
35
+ * Ensure pgvector extension is installed
36
+ */
37
+ export declare function ensureVectorExtension(): Promise<void>;
38
+ /**
39
+ * Create a vector column on a table
40
+ */
41
+ export declare function createVectorColumn(table: string, column: string, dimensions: number): Promise<void>;
42
+ /**
43
+ * Create a vector index for similarity search
44
+ */
45
+ export declare function createVectorIndex(table: string, column: string, indexType?: VectorIndexType, operator?: VectorDistanceOperator, options?: {
46
+ /** HNSW: max connections per layer (default: 16) */
47
+ m?: number;
48
+ /** HNSW: size of dynamic candidate list (default: 64) */
49
+ efConstruction?: number;
50
+ /** IVFFlat: number of lists (default: 100) */
51
+ lists?: number;
52
+ }): Promise<void>;
53
+ /**
54
+ * Search for similar vectors
55
+ */
56
+ export declare function vectorSearch<T = Record<string, unknown>>(table: string, column: string, queryVector: Vector, options?: VectorSearchOptions, logger?: Logger): Promise<QueryResult<T & {
57
+ distance: number;
58
+ }>>;
59
+ /**
60
+ * Insert a row with vector data
61
+ */
62
+ export declare function insertWithVector(table: string, data: Record<string, unknown>, vectorColumn: string, vector: Vector): Promise<QueryResult>;
63
+ /**
64
+ * Update vector data for a row
65
+ */
66
+ export declare function updateVector(table: string, idColumn: string, idValue: unknown, vectorColumn: string, vector: Vector): Promise<QueryResult>;
67
+ /**
68
+ * Batch insert rows with vectors
69
+ */
70
+ export declare function batchInsertWithVectors(table: string, columns: string[], vectorColumn: string, rows: Array<{
71
+ data: unknown[];
72
+ vector: Vector;
73
+ }>): Promise<QueryResult>;
74
+ //# sourceMappingURL=vector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vector.d.ts","sourceRoot":"","sources":["../src/vector.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,KAAK,EACV,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,WAAW,EACX,MAAM,EACP,MAAM,YAAY,CAAC;AAEpB,qCAAqC;AACrC,MAAM,MAAM,MAAM,GAAG,MAAM,EAAE,GAAG,YAAY,GAAG,YAAY,CAAC;AAE5D;;;GAGG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAGnD;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,IAAI,GAAG,MAAM,EAAE,GAAG,IAAI,CAc5E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAEtD;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAKxD;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,sBAAsB,GAAG,MAAM,CAEtE;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,sBAAsB,GAAG,MAAM,CAa1E;AAED;;GAEG;AACH,wBAAsB,qBAAqB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3D;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,IAAI,CAAC,CAEf;AAED;;GAEG;AACH,wBAAsB,iBAAiB,CACrC,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,eAAwB,EACnC,QAAQ,GAAE,sBAA8B,EACxC,OAAO,CAAC,EAAE;IACR,oDAAoD;IACpD,CAAC,CAAC,EAAE,MAAM,CAAC;IACX,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,8CAA8C;IAC9C,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,GACA,OAAO,CAAC,IAAI,CAAC,CAmCf;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC5D,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,MAAM,EACnB,OAAO,GAAE,mBAAwB,EACjC,MAAM,CAAC,EAAE,MAAM,GACd,OAAO,CAAC,WAAW,CAAC,CAAC,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CAyChD;AAED;;GAEG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC7B,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,CAAC,CAsBtB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,YAAY,EAAE,MAAM,EACpB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,WAAW,CAAC,CAetB;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,EAAE,EACjB,YAAY,EAAE,MAAM,EACpB,IAAI,EAAE,KAAK,CAAC;IAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC,GAC/C,OAAO,CAAC,WAAW,CAAC,CAkCtB"}
package/dist/vector.js ADDED
@@ -0,0 +1,223 @@
1
+ /**
2
+ * @singulio/postgres - pgvector Extension Support
3
+ * Vector operations for AI/ML embeddings
4
+ */
5
+ import { sql } from 'bun';
6
+ /**
7
+ * Format a vector for PostgreSQL insertion
8
+ * Converts array to '[1,2,3]' format expected by pgvector
9
+ */
10
+ export function formatVector(vector) {
11
+ const arr = Array.isArray(vector) ? vector : Array.from(vector);
12
+ return `[${arr.join(',')}]`;
13
+ }
14
+ /**
15
+ * Parse a vector from PostgreSQL result
16
+ * Converts '[1,2,3]' string back to number array
17
+ */
18
+ export function parseVector(value) {
19
+ if (value === null)
20
+ return null;
21
+ // Already an array (some drivers return it parsed)
22
+ if (Array.isArray(value))
23
+ return value;
24
+ // Parse string format '[1,2,3]'
25
+ if (typeof value === 'string') {
26
+ const trimmed = value.replace(/^\[|\]$/g, '');
27
+ if (!trimmed)
28
+ return [];
29
+ return trimmed.split(',').map(Number);
30
+ }
31
+ return null;
32
+ }
33
+ /**
34
+ * Calculate vector dimension
35
+ */
36
+ export function vectorDimension(vector) {
37
+ return Array.isArray(vector) ? vector.length : vector.length;
38
+ }
39
+ /**
40
+ * Normalize a vector to unit length (for cosine similarity)
41
+ */
42
+ export function normalizeVector(vector) {
43
+ const arr = Array.isArray(vector) ? vector : Array.from(vector);
44
+ const magnitude = Math.sqrt(arr.reduce((sum, val) => sum + val * val, 0));
45
+ if (magnitude === 0)
46
+ return arr;
47
+ return arr.map(val => val / magnitude);
48
+ }
49
+ /**
50
+ * Get the distance operator SQL string
51
+ */
52
+ export function getDistanceOperator(op) {
53
+ return op;
54
+ }
55
+ /**
56
+ * Get human-readable name for distance operator
57
+ */
58
+ export function getDistanceOperatorName(op) {
59
+ switch (op) {
60
+ case '<->':
61
+ return 'L2 (Euclidean)';
62
+ case '<=>':
63
+ return 'Cosine';
64
+ case '<#>':
65
+ return 'Inner Product';
66
+ case '<+>':
67
+ return 'L1 (Manhattan)';
68
+ default:
69
+ return 'Unknown';
70
+ }
71
+ }
72
+ /**
73
+ * Ensure pgvector extension is installed
74
+ */
75
+ export async function ensureVectorExtension() {
76
+ await sql `CREATE EXTENSION IF NOT EXISTS vector`;
77
+ }
78
+ /**
79
+ * Create a vector column on a table
80
+ */
81
+ export async function createVectorColumn(table, column, dimensions) {
82
+ await sql.unsafe(`ALTER TABLE ${table} ADD COLUMN IF NOT EXISTS ${column} vector(${dimensions})`);
83
+ }
84
+ /**
85
+ * Create a vector index for similarity search
86
+ */
87
+ export async function createVectorIndex(table, column, indexType = 'hnsw', operator = '<->', options) {
88
+ const indexName = `idx_${table}_${column}_${indexType}`;
89
+ // Determine operator class
90
+ let opClass;
91
+ switch (operator) {
92
+ case '<=>':
93
+ opClass = 'vector_cosine_ops';
94
+ break;
95
+ case '<#>':
96
+ opClass = 'vector_ip_ops';
97
+ break;
98
+ case '<+>':
99
+ opClass = 'vector_l1_ops';
100
+ break;
101
+ default:
102
+ opClass = 'vector_l2_ops';
103
+ }
104
+ if (indexType === 'hnsw') {
105
+ const m = options?.m ?? 16;
106
+ const efConstruction = options?.efConstruction ?? 64;
107
+ await sql.unsafe(`
108
+ CREATE INDEX IF NOT EXISTS ${indexName}
109
+ ON ${table} USING hnsw (${column} ${opClass})
110
+ WITH (m = ${m}, ef_construction = ${efConstruction})
111
+ `);
112
+ }
113
+ else {
114
+ const lists = options?.lists ?? 100;
115
+ await sql.unsafe(`
116
+ CREATE INDEX IF NOT EXISTS ${indexName}
117
+ ON ${table} USING ivfflat (${column} ${opClass})
118
+ WITH (lists = ${lists})
119
+ `);
120
+ }
121
+ }
122
+ /**
123
+ * Search for similar vectors
124
+ */
125
+ export async function vectorSearch(table, column, queryVector, options = {}, logger) {
126
+ const { operator = '<->', limit = 10, threshold, filter, filterParams = [] } = options;
127
+ const vectorStr = formatVector(queryVector);
128
+ const params = [vectorStr, ...filterParams, limit];
129
+ let whereClause = '';
130
+ if (filter) {
131
+ whereClause = `WHERE ${filter}`;
132
+ }
133
+ if (threshold !== undefined) {
134
+ const thresholdClause = `${column} ${operator} $1 < ${threshold}`;
135
+ whereClause = whereClause
136
+ ? `${whereClause} AND ${thresholdClause}`
137
+ : `WHERE ${thresholdClause}`;
138
+ }
139
+ const start = performance.now();
140
+ const result = await sql.unsafe(`
141
+ SELECT *, ${column} ${operator} $1 AS distance
142
+ FROM ${table}
143
+ ${whereClause}
144
+ ORDER BY ${column} ${operator} $1
145
+ LIMIT $${params.length}
146
+ `, params);
147
+ logger?.debug('Vector search completed', {
148
+ table,
149
+ column,
150
+ operator: getDistanceOperatorName(operator),
151
+ results: result.length,
152
+ latencyMs: (performance.now() - start).toFixed(2),
153
+ });
154
+ return {
155
+ rows: result,
156
+ rowCount: result.length,
157
+ };
158
+ }
159
+ /**
160
+ * Insert a row with vector data
161
+ */
162
+ export async function insertWithVector(table, data, vectorColumn, vector) {
163
+ const columns = Object.keys(data);
164
+ const values = Object.values(data);
165
+ columns.push(vectorColumn);
166
+ values.push(formatVector(vector));
167
+ const placeholders = values.map((_, i) => `$${i + 1}`).join(', ');
168
+ const result = await sql.unsafe(`
169
+ INSERT INTO ${table} (${columns.join(', ')})
170
+ VALUES (${placeholders})
171
+ RETURNING *
172
+ `, values);
173
+ return {
174
+ rows: result,
175
+ rowCount: result.length,
176
+ };
177
+ }
178
+ /**
179
+ * Update vector data for a row
180
+ */
181
+ export async function updateVector(table, idColumn, idValue, vectorColumn, vector) {
182
+ const result = await sql.unsafe(`
183
+ UPDATE ${table}
184
+ SET ${vectorColumn} = $1
185
+ WHERE ${idColumn} = $2
186
+ RETURNING *
187
+ `, [formatVector(vector), idValue]);
188
+ return {
189
+ rows: result,
190
+ rowCount: result.length,
191
+ };
192
+ }
193
+ /**
194
+ * Batch insert rows with vectors
195
+ */
196
+ export async function batchInsertWithVectors(table, columns, vectorColumn, rows) {
197
+ if (rows.length === 0) {
198
+ return { rows: [], rowCount: 0 };
199
+ }
200
+ const allColumns = [...columns, vectorColumn];
201
+ const valueSets = [];
202
+ const params = [];
203
+ let paramIndex = 1;
204
+ for (const row of rows) {
205
+ const placeholders = [];
206
+ for (const value of row.data) {
207
+ placeholders.push(`$${paramIndex++}`);
208
+ params.push(value);
209
+ }
210
+ placeholders.push(`$${paramIndex++}`);
211
+ params.push(formatVector(row.vector));
212
+ valueSets.push(`(${placeholders.join(', ')})`);
213
+ }
214
+ const result = await sql.unsafe(`
215
+ INSERT INTO ${table} (${allColumns.join(', ')})
216
+ VALUES ${valueSets.join(', ')}
217
+ RETURNING *
218
+ `, params);
219
+ return {
220
+ rows: result,
221
+ rowCount: result.length,
222
+ };
223
+ }
package/package.json ADDED
@@ -0,0 +1,97 @@
1
+ {
2
+ "name": "singulio-postgres",
3
+ "version": "1.1.0",
4
+ "description": "PostgreSQL client wrapper with RLS, pooling, transactions, and pgvector support",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./pool": {
15
+ "types": "./dist/pool.d.ts",
16
+ "import": "./dist/pool.js"
17
+ },
18
+ "./rls": {
19
+ "types": "./dist/rls.d.ts",
20
+ "import": "./dist/rls.js"
21
+ },
22
+ "./vector": {
23
+ "types": "./dist/vector.d.ts",
24
+ "import": "./dist/vector.js"
25
+ },
26
+ "./transaction": {
27
+ "types": "./dist/transaction.d.ts",
28
+ "import": "./dist/transaction.js"
29
+ },
30
+ "./health": {
31
+ "types": "./dist/health.d.ts",
32
+ "import": "./dist/health.js"
33
+ },
34
+ "./types": {
35
+ "types": "./dist/types.d.ts",
36
+ "import": "./dist/types.js"
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "src"
42
+ ],
43
+ "scripts": {
44
+ "build": "tsc",
45
+ "clean": "node -e \"require('fs').rmSync('dist', {recursive: true, force: true})\"",
46
+ "test": "node --test",
47
+ "typecheck": "tsc --noEmit"
48
+ },
49
+ "keywords": [
50
+ "postgres",
51
+ "postgresql",
52
+ "rls",
53
+ "pgvector",
54
+ "multi-tenant",
55
+ "database"
56
+ ],
57
+ "author": "Singulio",
58
+ "license": "MIT",
59
+ "publishConfig": {
60
+ "registry": "https://registry.npmjs.org",
61
+ "access": "public"
62
+ },
63
+ "repository": {
64
+ "type": "git",
65
+ "url": "git+https://github.com/singuliodev/postgres.git"
66
+ },
67
+ "bugs": {
68
+ "url": "https://github.com/singuliodev/postgres/issues"
69
+ },
70
+ "homepage": "https://github.com/singuliodev/postgres#readme",
71
+ "dependencies": {},
72
+ "devDependencies": {
73
+ "@semantic-release/changelog": "^6.0.3",
74
+ "@semantic-release/commit-analyzer": "^13.0.1",
75
+ "@semantic-release/git": "^10.0.1",
76
+ "@semantic-release/github": "^11.0.1",
77
+ "@semantic-release/npm": "^12.0.1",
78
+ "@semantic-release/release-notes-generator": "^14.0.3",
79
+ "conventional-changelog-conventionalcommits": "^8.0.0",
80
+ "typescript": "^5.9.3"
81
+ },
82
+ "engines": {
83
+ "node": ">=18.0.0",
84
+ "bun": ">=1.0.0"
85
+ },
86
+ "os": [
87
+ "linux",
88
+ "darwin",
89
+ "win32"
90
+ ],
91
+ "cpu": [
92
+ "x64",
93
+ "ia32",
94
+ "arm64",
95
+ "arm"
96
+ ]
97
+ }
package/src/client.ts ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * @singulio/postgres - PostgreSQL Client
3
+ * Thin wrapper around bun:postgres native client
4
+ */
5
+
6
+ import { sql } from 'bun';
7
+ import type { PostgresConfig, QueryResult, QueryRow, Logger } from './types.js';
8
+
9
+ /**
10
+ * PostgreSQL client wrapping Bun's native postgres driver
11
+ */
12
+ export class PostgresClient {
13
+ private connectionString: string;
14
+ private logger?: Logger;
15
+ private connected = false;
16
+
17
+ constructor(config: PostgresConfig | string, logger?: Logger) {
18
+ this.connectionString =
19
+ typeof config === 'string' ? config : this.buildConnectionString(config);
20
+ this.logger = logger;
21
+ }
22
+
23
+ private buildConnectionString(config: PostgresConfig): string {
24
+ if (config.connectionString) return config.connectionString;
25
+
26
+ const {
27
+ host = 'localhost',
28
+ port = 5432,
29
+ database = 'postgres',
30
+ user = 'postgres',
31
+ password = '',
32
+ ssl,
33
+ applicationName,
34
+ } = config;
35
+
36
+ let connStr = `postgres://${encodeURIComponent(user)}:${encodeURIComponent(password)}@${host}:${port}/${database}`;
37
+
38
+ const params: string[] = [];
39
+ if (ssl === true || ssl === 'require') {
40
+ params.push('sslmode=require');
41
+ } else if (ssl === 'prefer') {
42
+ params.push('sslmode=prefer');
43
+ }
44
+ if (applicationName) {
45
+ params.push(`application_name=${encodeURIComponent(applicationName)}`);
46
+ }
47
+
48
+ if (params.length > 0) {
49
+ connStr += '?' + params.join('&');
50
+ }
51
+
52
+ return connStr;
53
+ }
54
+
55
+ /**
56
+ * Execute a SQL query with parameters
57
+ */
58
+ async query<T = QueryRow>(queryText: string, params?: unknown[]): Promise<QueryResult<T>> {
59
+ const start = performance.now();
60
+
61
+ try {
62
+ // Use Bun's sql tagged template for parameterized queries
63
+ const result = await sql.unsafe(queryText, params ?? []);
64
+
65
+ const latency = performance.now() - start;
66
+ this.logger?.debug('Query executed', {
67
+ query: queryText.substring(0, 100),
68
+ params: params?.length ?? 0,
69
+ rows: result.length,
70
+ latencyMs: latency.toFixed(2),
71
+ });
72
+
73
+ return {
74
+ rows: result as T[],
75
+ rowCount: result.length,
76
+ };
77
+ } catch (error) {
78
+ const latency = performance.now() - start;
79
+ this.logger?.error('Query failed', {
80
+ query: queryText.substring(0, 100),
81
+ error: error instanceof Error ? error.message : String(error),
82
+ latencyMs: latency.toFixed(2),
83
+ });
84
+ throw error;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Execute a query and return the first row or null
90
+ */
91
+ async queryOne<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T | null> {
92
+ const result = await this.query<T>(queryText, params);
93
+ return result.rows[0] ?? null;
94
+ }
95
+
96
+ /**
97
+ * Execute a query and return all rows
98
+ */
99
+ async queryAll<T = QueryRow>(queryText: string, params?: unknown[]): Promise<T[]> {
100
+ const result = await this.query<T>(queryText, params);
101
+ return result.rows;
102
+ }
103
+
104
+ /**
105
+ * Execute a command (INSERT/UPDATE/DELETE) and return affected row count
106
+ */
107
+ async execute(queryText: string, params?: unknown[]): Promise<number> {
108
+ const result = await this.query(queryText, params);
109
+ return result.rowCount;
110
+ }
111
+
112
+ /**
113
+ * Check if connected and working
114
+ */
115
+ async ping(): Promise<boolean> {
116
+ try {
117
+ const result = await sql`SELECT 1 as ok`;
118
+ return result.length > 0;
119
+ } catch {
120
+ return false;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Get server version
126
+ */
127
+ async version(): Promise<string> {
128
+ const result = await sql`SELECT version()`;
129
+ return result[0]?.version ?? 'unknown';
130
+ }
131
+
132
+ /**
133
+ * Close all connections (for bun:postgres this is mostly a no-op as it manages connections)
134
+ */
135
+ async close(): Promise<void> {
136
+ this.connected = false;
137
+ this.logger?.info('PostgresClient closed');
138
+ }
139
+
140
+ /**
141
+ * Get connection string (for debugging, sensitive parts masked)
142
+ */
143
+ get maskedConnectionString(): string {
144
+ return this.connectionString.replace(/:([^@]+)@/, ':***@');
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Create a new PostgresClient instance
150
+ */
151
+ export function createClient(config: PostgresConfig | string, logger?: Logger): PostgresClient {
152
+ return new PostgresClient(config, logger);
153
+ }