@bzync/nextsql 0.0.1 → 0.0.3
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/client-encryption.js +1 -1
- package/nextsql.d.ts +36 -6
- package/nextsql.js +211 -0
- package/package.json +1 -1
package/client-encryption.js
CHANGED
|
@@ -379,7 +379,7 @@ module.exports = function createClientEncryption(deps) {
|
|
|
379
379
|
}
|
|
380
380
|
|
|
381
381
|
function aesBlock(key, block) {
|
|
382
|
-
const cipher = createCipheriv('aes-128-
|
|
382
|
+
const cipher = createCipheriv('aes-128-cbc', key, Buffer.alloc(16));
|
|
383
383
|
cipher.setAutoPadding(false);
|
|
384
384
|
return Buffer.concat([cipher.update(block), cipher.final()]);
|
|
385
385
|
}
|
package/nextsql.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
//
|
|
1
|
+
// Shared TypeScript surface for the official Node.js and Bun NextSQL drivers.
|
|
2
|
+
// Keys and passwords are never accepted in a URL.
|
|
2
3
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
// parity test in the driver test suite fails if this drifts from the canonical
|
|
7
|
-
// shared file.
|
|
4
|
+
// drivers/node/nextsql.d.ts and drivers/bun/nextsql.d.ts are published copies
|
|
5
|
+
// of this file (npm packs only in-package files); their parity tests fail if
|
|
6
|
+
// they drift from this canonical source.
|
|
8
7
|
|
|
9
8
|
export interface TLSOptions {
|
|
10
9
|
/** PEM or DER trust material. */
|
|
@@ -52,6 +51,8 @@ export interface Config {
|
|
|
52
51
|
readConsistency?: ReadConsistencyMode;
|
|
53
52
|
/** Bounded-read staleness window in milliseconds. 0 selects the server default. */
|
|
54
53
|
maxStalenessMs?: number;
|
|
54
|
+
/** Maximum number of prepared statements to cache per connection LRU. Default: 0 (disabled on raw connect; 50 in pool). */
|
|
55
|
+
statementCacheSize?: number;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
export interface FieldKey {
|
|
@@ -330,6 +331,35 @@ export declare const Type: {
|
|
|
330
331
|
|
|
331
332
|
export declare function connect(cfg: Config): Promise<Conn>;
|
|
332
333
|
export declare function connectCluster(cfg: Config): Promise<Cluster>;
|
|
334
|
+
|
|
335
|
+
export interface PoolOptions {
|
|
336
|
+
/** Minimum number of idle connections to maintain. Default: 1 */
|
|
337
|
+
min?: number;
|
|
338
|
+
/** Maximum number of connections in the pool. Default: 10 */
|
|
339
|
+
max?: number;
|
|
340
|
+
/** Maximum milliseconds to wait for a connection before timing out. Default: 30000 (30s) */
|
|
341
|
+
timeoutMs?: number;
|
|
342
|
+
/** Maximum idle milliseconds before an unused connection is retired. Default: 300000 (5m) */
|
|
343
|
+
idleTimeoutMs?: number;
|
|
344
|
+
/** Maximum number of prepared statements to cache per connection LRU. Default: 50. 0 disables. */
|
|
345
|
+
statementCacheSize?: number;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export declare class Pool {
|
|
349
|
+
constructor(config: Config, options?: PoolOptions);
|
|
350
|
+
readonly size: number;
|
|
351
|
+
readonly idleCount: number;
|
|
352
|
+
readonly inUseCount: number;
|
|
353
|
+
readonly isClosed: boolean;
|
|
354
|
+
acquire(): Promise<Conn>;
|
|
355
|
+
release(conn: Conn): Promise<void>;
|
|
356
|
+
withConnection<T>(fn: (conn: Conn) => Promise<T>): Promise<T>;
|
|
357
|
+
exec(sql: string, params?: unknown[]): Promise<Result>;
|
|
358
|
+
query(sql: string, params?: unknown[]): Promise<Result>;
|
|
359
|
+
close(): Promise<void>;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export declare function createPool(config: Config, options?: PoolOptions): Pool;
|
|
333
363
|
export declare function generateFieldKey(id: string): FieldKey;
|
|
334
364
|
export declare function inspectField(ciphertext: string): { keyID: string; type: FieldTypeDescriptor };
|
|
335
365
|
export declare function encryptField(provider: FieldKeyProvider, database: string, table: string, column: string, type: FieldTypeDescriptor, value: unknown): Promise<string | null>;
|
package/nextsql.js
CHANGED
|
@@ -1759,6 +1759,8 @@ class Conn {
|
|
|
1759
1759
|
this.secret = 0n;
|
|
1760
1760
|
this.busy = false;
|
|
1761
1761
|
this.publicErrorCodes = false;
|
|
1762
|
+
this.statementCacheSize = cfg?.statementCacheSize ?? 0;
|
|
1763
|
+
this._stmtCache = this.statementCacheSize > 0 ? new Map() : null;
|
|
1762
1764
|
}
|
|
1763
1765
|
|
|
1764
1766
|
async handshake() {
|
|
@@ -1904,6 +1906,30 @@ class Conn {
|
|
|
1904
1906
|
if (this.busy) {
|
|
1905
1907
|
throw new NextSQLError('conflict', 'connection is busy');
|
|
1906
1908
|
}
|
|
1909
|
+
if (this._stmtCache && params && params.length > 0) {
|
|
1910
|
+
let stmt = this._stmtCache.get(sql);
|
|
1911
|
+
if (stmt) {
|
|
1912
|
+
this._stmtCache.delete(sql);
|
|
1913
|
+
this._stmtCache.set(sql, stmt);
|
|
1914
|
+
} else {
|
|
1915
|
+
stmt = await this.prepare(sql);
|
|
1916
|
+
if (this._stmtCache.size >= this.statementCacheSize) {
|
|
1917
|
+
const oldestKey = this._stmtCache.keys().next().value;
|
|
1918
|
+
const oldestStmt = this._stmtCache.get(oldestKey);
|
|
1919
|
+
this._stmtCache.delete(oldestKey);
|
|
1920
|
+
oldestStmt.close().catch(() => {});
|
|
1921
|
+
}
|
|
1922
|
+
this._stmtCache.set(sql, stmt);
|
|
1923
|
+
}
|
|
1924
|
+
try {
|
|
1925
|
+
return await this._execute(stmt.id, params);
|
|
1926
|
+
} catch (err) {
|
|
1927
|
+
this._stmtCache.delete(sql);
|
|
1928
|
+
stmt.close().catch(() => {});
|
|
1929
|
+
throw err;
|
|
1930
|
+
}
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1907
1933
|
this.busy = true;
|
|
1908
1934
|
try {
|
|
1909
1935
|
await this.wire.writeFrame(Type.Query, encodeQuery(sql, params || []));
|
|
@@ -1996,6 +2022,11 @@ class Conn {
|
|
|
1996
2022
|
}
|
|
1997
2023
|
|
|
1998
2024
|
async close() {
|
|
2025
|
+
if (this._stmtCache) {
|
|
2026
|
+
const stmts = [...this._stmtCache.values()];
|
|
2027
|
+
this._stmtCache.clear();
|
|
2028
|
+
await Promise.allSettled(stmts.map(s => s.close()));
|
|
2029
|
+
}
|
|
1999
2030
|
if (!this.wire) {
|
|
2000
2031
|
return;
|
|
2001
2032
|
}
|
|
@@ -2197,8 +2228,188 @@ async function connectCluster(cfg) {
|
|
|
2197
2228
|
return new Cluster(cfg, conns);
|
|
2198
2229
|
}
|
|
2199
2230
|
|
|
2231
|
+
class Pool {
|
|
2232
|
+
constructor(cfg, options = {}) {
|
|
2233
|
+
validateConfig(cfg);
|
|
2234
|
+
const min = options.min ?? 1;
|
|
2235
|
+
const max = options.max ?? 10;
|
|
2236
|
+
const timeoutMs = options.timeoutMs ?? 30000;
|
|
2237
|
+
const idleTimeoutMs = options.idleTimeoutMs ?? 300000;
|
|
2238
|
+
const statementCacheSize = options.statementCacheSize ?? 50;
|
|
2239
|
+
|
|
2240
|
+
if (min < 0) throw new NextSQLError('invalid_argument', 'min must be >= 0');
|
|
2241
|
+
if (max < 1) throw new NextSQLError('invalid_argument', 'max must be >= 1');
|
|
2242
|
+
if (max < min) throw new NextSQLError('invalid_argument', 'max must be >= min');
|
|
2243
|
+
if (statementCacheSize < 0) throw new NextSQLError('invalid_argument', 'statementCacheSize must be >= 0');
|
|
2244
|
+
|
|
2245
|
+
this._cfg = { ...cfg, statementCacheSize };
|
|
2246
|
+
this._min = min;
|
|
2247
|
+
this._max = max;
|
|
2248
|
+
this._timeoutMs = timeoutMs;
|
|
2249
|
+
this._idleTimeoutMs = idleTimeoutMs;
|
|
2250
|
+
this._statementCacheSize = statementCacheSize;
|
|
2251
|
+
|
|
2252
|
+
this._idle = []; // [{ conn, lastUsed }]
|
|
2253
|
+
this._inUse = new Set();
|
|
2254
|
+
this._waiters = []; // [{ resolve, reject, timer }]
|
|
2255
|
+
this._closed = false;
|
|
2256
|
+
}
|
|
2257
|
+
|
|
2258
|
+
get size() {
|
|
2259
|
+
return this._idle.length + this._inUse.size;
|
|
2260
|
+
}
|
|
2261
|
+
|
|
2262
|
+
get idleCount() {
|
|
2263
|
+
return this._idle.length;
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
get inUseCount() {
|
|
2267
|
+
return this._inUse.size;
|
|
2268
|
+
}
|
|
2269
|
+
|
|
2270
|
+
get isClosed() {
|
|
2271
|
+
return this._closed;
|
|
2272
|
+
}
|
|
2273
|
+
|
|
2274
|
+
async acquire() {
|
|
2275
|
+
if (this._closed) {
|
|
2276
|
+
throw new NextSQLError('unavailable', 'connection pool is closed');
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
const now = Date.now();
|
|
2280
|
+
while (this._idle.length > 0) {
|
|
2281
|
+
const item = this._idle.pop();
|
|
2282
|
+
if (this._idleTimeoutMs > 0 && (now - item.lastUsed) > this._idleTimeoutMs) {
|
|
2283
|
+
item.conn.close().catch(() => {});
|
|
2284
|
+
continue;
|
|
2285
|
+
}
|
|
2286
|
+
if (item.conn.wire && item.conn.wire.closed) {
|
|
2287
|
+
continue;
|
|
2288
|
+
}
|
|
2289
|
+
this._inUse.add(item.conn);
|
|
2290
|
+
return item.conn;
|
|
2291
|
+
}
|
|
2292
|
+
|
|
2293
|
+
if (this.size < this._max) {
|
|
2294
|
+
const conn = await connect(this._cfg);
|
|
2295
|
+
this._inUse.add(conn);
|
|
2296
|
+
return conn;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
return new Promise((resolve, reject) => {
|
|
2300
|
+
let timer = null;
|
|
2301
|
+
if (this._timeoutMs > 0) {
|
|
2302
|
+
timer = setTimeout(() => {
|
|
2303
|
+
const idx = this._waiters.findIndex(w => w.resolve === resolve);
|
|
2304
|
+
if (idx >= 0) {
|
|
2305
|
+
this._waiters.splice(idx, 1);
|
|
2306
|
+
reject(new NextSQLError('resource_exhausted', 'connection pool exhausted, wait timeout exceeded'));
|
|
2307
|
+
}
|
|
2308
|
+
}, this._timeoutMs);
|
|
2309
|
+
}
|
|
2310
|
+
this._waiters.push({ resolve, reject, timer });
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
|
|
2314
|
+
async release(conn) {
|
|
2315
|
+
if (!this._inUse.has(conn)) {
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
this._inUse.delete(conn);
|
|
2319
|
+
|
|
2320
|
+
if (this._closed || (conn.wire && conn.wire.closed)) {
|
|
2321
|
+
conn.close().catch(() => {});
|
|
2322
|
+
this._drainWaiters();
|
|
2323
|
+
return;
|
|
2324
|
+
}
|
|
2325
|
+
|
|
2326
|
+
// Clean connection state: roll back uncommitted transaction
|
|
2327
|
+
try {
|
|
2328
|
+
await conn.exec('ROLLBACK');
|
|
2329
|
+
} catch {
|
|
2330
|
+
conn.close().catch(() => {});
|
|
2331
|
+
this._drainWaiters();
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
while (this._waiters.length > 0) {
|
|
2336
|
+
const waiter = this._waiters.shift();
|
|
2337
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
2338
|
+
this._inUse.add(conn);
|
|
2339
|
+
waiter.resolve(conn);
|
|
2340
|
+
return;
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
this._idle.push({ conn, lastUsed: Date.now() });
|
|
2344
|
+
}
|
|
2345
|
+
|
|
2346
|
+
_drainWaiters() {
|
|
2347
|
+
if (this._closed) {
|
|
2348
|
+
while (this._waiters.length > 0) {
|
|
2349
|
+
const w = this._waiters.shift();
|
|
2350
|
+
if (w.timer) clearTimeout(w.timer);
|
|
2351
|
+
w.reject(new NextSQLError('unavailable', 'connection pool is closed'));
|
|
2352
|
+
}
|
|
2353
|
+
return;
|
|
2354
|
+
}
|
|
2355
|
+
if (this.size < this._max && this._waiters.length > 0) {
|
|
2356
|
+
const waiter = this._waiters.shift();
|
|
2357
|
+
if (waiter.timer) clearTimeout(waiter.timer);
|
|
2358
|
+
connect(this._cfg).then(conn => {
|
|
2359
|
+
this._inUse.add(conn);
|
|
2360
|
+
waiter.resolve(conn);
|
|
2361
|
+
}).catch(err => {
|
|
2362
|
+
waiter.reject(err);
|
|
2363
|
+
});
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
async withConnection(fn) {
|
|
2368
|
+
const conn = await this.acquire();
|
|
2369
|
+
try {
|
|
2370
|
+
return await fn(conn);
|
|
2371
|
+
} finally {
|
|
2372
|
+
await this.release(conn);
|
|
2373
|
+
}
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
async exec(sql, params) {
|
|
2377
|
+
return this.withConnection(conn => conn.exec(sql, params));
|
|
2378
|
+
}
|
|
2379
|
+
|
|
2380
|
+
async query(sql, params) {
|
|
2381
|
+
return this.withConnection(async (conn) => {
|
|
2382
|
+
const rows = await conn.query(sql, params);
|
|
2383
|
+
return rows.collect();
|
|
2384
|
+
});
|
|
2385
|
+
}
|
|
2386
|
+
|
|
2387
|
+
async close() {
|
|
2388
|
+
if (this._closed) return;
|
|
2389
|
+
this._closed = true;
|
|
2390
|
+
|
|
2391
|
+
while (this._waiters.length > 0) {
|
|
2392
|
+
const w = this._waiters.shift();
|
|
2393
|
+
if (w.timer) clearTimeout(w.timer);
|
|
2394
|
+
w.reject(new NextSQLError('unavailable', 'connection pool is closed'));
|
|
2395
|
+
}
|
|
2396
|
+
|
|
2397
|
+
const conns = [...this._idle.map(item => item.conn), ...this._inUse];
|
|
2398
|
+
this._idle = [];
|
|
2399
|
+
this._inUse.clear();
|
|
2400
|
+
|
|
2401
|
+
await Promise.allSettled(conns.map(c => c.close()));
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
2404
|
+
|
|
2405
|
+
function createPool(cfg, options) {
|
|
2406
|
+
return new Pool(cfg, options);
|
|
2407
|
+
}
|
|
2408
|
+
|
|
2200
2409
|
module.exports = {
|
|
2201
2410
|
connect,
|
|
2411
|
+
createPool,
|
|
2412
|
+
Pool,
|
|
2202
2413
|
FlagCancel,
|
|
2203
2414
|
FlagPublicErrorCodes,
|
|
2204
2415
|
connectCluster,
|