@neetru/sdk 3.2.1 → 3.2.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/CHANGELOG.md +35 -0
- package/dist/auth.cjs +170 -14
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.mjs +170 -14
- package/dist/auth.mjs.map +1 -1
- package/dist/db.cjs +170 -14
- package/dist/db.cjs.map +1 -1
- package/dist/db.mjs +170 -14
- package/dist/db.mjs.map +1 -1
- package/dist/index.cjs +171 -15
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +171 -15
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/db.mjs
CHANGED
|
@@ -274,7 +274,99 @@ var init_http = __esm({
|
|
|
274
274
|
}
|
|
275
275
|
});
|
|
276
276
|
|
|
277
|
+
// src/db/sql/resilient-pool.ts
|
|
278
|
+
function isResilientPool(x) {
|
|
279
|
+
return typeof x === "object" && x !== null && x[RESILIENT_POOL_BRAND] === true;
|
|
280
|
+
}
|
|
281
|
+
function isConnectionEstablishmentError(err) {
|
|
282
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
283
|
+
if (code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "EHOSTUNREACH" || code === "EAI_AGAIN") {
|
|
284
|
+
return true;
|
|
285
|
+
}
|
|
286
|
+
if (typeof code === "string") {
|
|
287
|
+
const upper = code.toUpperCase();
|
|
288
|
+
if (upper === "CERT_HAS_EXPIRED" || upper.startsWith("ERR_SSL") || upper.startsWith("ERR_TLS") || upper.includes("CERTIFICATE_EXPIRED")) {
|
|
289
|
+
return true;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
const rawMessage = err instanceof Error ? err.message : typeof err === "string" ? err : "";
|
|
293
|
+
const message = rawMessage.toLowerCase();
|
|
294
|
+
if (message.length === 0) return false;
|
|
295
|
+
if (message.includes("certificate expired") || message.includes("certificate has expired") || message.includes("cert_has_expired") || message.includes("alert certificate expired") || message.includes("err_ssl") || message.includes("sslv3 alert") || message.includes("tlsv1 alert")) {
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
if (message.includes("econnrefused") || message.includes("etimedout") || message.includes("enotfound") || message.includes("ehostunreach") || message.includes("getaddrinfo")) {
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function createResilientPool(initialInner) {
|
|
304
|
+
const state = { current: initialInner };
|
|
305
|
+
let reLease = null;
|
|
306
|
+
const controls = {
|
|
307
|
+
__neetruSwapInner(newInner) {
|
|
308
|
+
const old = state.current;
|
|
309
|
+
state.current = newInner;
|
|
310
|
+
return old;
|
|
311
|
+
},
|
|
312
|
+
__neetruGetInner() {
|
|
313
|
+
return state.current;
|
|
314
|
+
},
|
|
315
|
+
__neetruSetReLease(fn) {
|
|
316
|
+
reLease = fn;
|
|
317
|
+
}
|
|
318
|
+
};
|
|
319
|
+
const invokeQuery = (pool, args) => pool.query(...args);
|
|
320
|
+
const wrappedQuery = (...args) => {
|
|
321
|
+
if (args.length > 0 && typeof args[args.length - 1] === "function") {
|
|
322
|
+
return invokeQuery(state.current, args);
|
|
323
|
+
}
|
|
324
|
+
return (async () => {
|
|
325
|
+
try {
|
|
326
|
+
return await invokeQuery(state.current, args);
|
|
327
|
+
} catch (err) {
|
|
328
|
+
const fn = reLease;
|
|
329
|
+
if (fn === null || !isConnectionEstablishmentError(err)) {
|
|
330
|
+
throw err;
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
await fn();
|
|
334
|
+
} catch {
|
|
335
|
+
throw err;
|
|
336
|
+
}
|
|
337
|
+
return await invokeQuery(state.current, args);
|
|
338
|
+
}
|
|
339
|
+
})();
|
|
340
|
+
};
|
|
341
|
+
const target = Object.create(Object.getPrototypeOf(initialInner));
|
|
342
|
+
const proxy = new Proxy(target, {
|
|
343
|
+
get(_t, prop) {
|
|
344
|
+
if (prop === RESILIENT_POOL_BRAND) return true;
|
|
345
|
+
if (prop === "__neetruSwapInner") return controls.__neetruSwapInner;
|
|
346
|
+
if (prop === "__neetruGetInner") return controls.__neetruGetInner;
|
|
347
|
+
if (prop === "__neetruSetReLease") return controls.__neetruSetReLease;
|
|
348
|
+
if (prop === "query") return wrappedQuery;
|
|
349
|
+
if (prop === "constructor") {
|
|
350
|
+
return state.current.constructor;
|
|
351
|
+
}
|
|
352
|
+
const current = state.current;
|
|
353
|
+
const value = Reflect.get(current, prop, current);
|
|
354
|
+
return typeof value === "function" ? value.bind(current) : value;
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
return proxy;
|
|
358
|
+
}
|
|
359
|
+
var RESILIENT_POOL_BRAND;
|
|
360
|
+
var init_resilient_pool = __esm({
|
|
361
|
+
"src/db/sql/resilient-pool.ts"() {
|
|
362
|
+
RESILIENT_POOL_BRAND = /* @__PURE__ */ Symbol.for("neetru.sdk.resilientPool");
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
|
|
277
366
|
// src/db/sql/lease.ts
|
|
367
|
+
function hasConnectionChanged(oldLease, newLease) {
|
|
368
|
+
return oldLease.leaseId !== newLease.leaseId || oldLease.credentialVersion !== newLease.credentialVersion || oldLease.host !== newLease.host || oldLease.port !== newLease.port || oldLease.dbName !== newLease.dbName || oldLease.user !== newLease.user || oldLease.password !== newLease.password || oldLease.sslca !== newLease.sslca || oldLease.clientCert !== newLease.clientCert || oldLease.clientKey !== newLease.clientKey;
|
|
369
|
+
}
|
|
278
370
|
function mapPoolError(err) {
|
|
279
371
|
if (err instanceof NeetruDbError) return err;
|
|
280
372
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -300,6 +392,7 @@ var RENEWAL_THRESHOLD, MIN_RENEWAL_DELAY_MS, SqlLeaseManager;
|
|
|
300
392
|
var init_lease = __esm({
|
|
301
393
|
"src/db/sql/lease.ts"() {
|
|
302
394
|
init_db_errors();
|
|
395
|
+
init_resilient_pool();
|
|
303
396
|
RENEWAL_THRESHOLD = 0.8;
|
|
304
397
|
MIN_RENEWAL_DELAY_MS = 3e4;
|
|
305
398
|
SqlLeaseManager = class {
|
|
@@ -308,6 +401,12 @@ var init_lease = __esm({
|
|
|
308
401
|
_orm;
|
|
309
402
|
_renewalTimer = null;
|
|
310
403
|
_closed = false;
|
|
404
|
+
/**
|
|
405
|
+
* Promessa de re-lease REATIVO em voo — coalesce chamadas concorrentes
|
|
406
|
+
* (N queries falhando ao mesmo tempo disparam UM único re-lease). Ver
|
|
407
|
+
* `_reactiveReLease`.
|
|
408
|
+
*/
|
|
409
|
+
_reLeaseInFlight = null;
|
|
311
410
|
_deps;
|
|
312
411
|
constructor(lease, pool, orm, deps) {
|
|
313
412
|
this._lease = lease;
|
|
@@ -379,37 +478,90 @@ var init_lease = __esm({
|
|
|
379
478
|
async _doRenew() {
|
|
380
479
|
if (this._closed) return;
|
|
381
480
|
try {
|
|
382
|
-
const
|
|
383
|
-
|
|
481
|
+
const withDb = (o) => {
|
|
482
|
+
if (this._deps.database !== void 0) o.database = this._deps.database;
|
|
483
|
+
return o;
|
|
384
484
|
};
|
|
385
|
-
|
|
386
|
-
|
|
485
|
+
let newLease;
|
|
486
|
+
try {
|
|
487
|
+
newLease = await this._deps.fetchLease(withDb({ leaseId: this._lease.leaseId }));
|
|
488
|
+
} catch (renewErr) {
|
|
489
|
+
newLease = await this._deps.fetchLease(withDb({}));
|
|
387
490
|
}
|
|
388
|
-
const newLease = await this._deps.fetchLease(renewOpts);
|
|
389
491
|
await this._swapPool(newLease);
|
|
390
492
|
} catch (err) {
|
|
391
493
|
this._renewalTimer = setTimeout(() => {
|
|
392
494
|
void this._doRenew();
|
|
393
495
|
}, MIN_RENEWAL_DELAY_MS);
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
496
|
+
console.warn(
|
|
497
|
+
`[SqlLeaseManager] renova\xE7\xE3o E emiss\xE3o de lease novo falharam; re-tentando em ${MIN_RENEWAL_DELAY_MS}ms (cert mTLS expira ~15min ap\xF3s o \xFAltimo swap):`,
|
|
498
|
+
err instanceof Error ? err.message : err
|
|
499
|
+
);
|
|
397
500
|
return;
|
|
398
501
|
}
|
|
399
502
|
this._scheduleRenewal();
|
|
400
503
|
}
|
|
504
|
+
// ─── Re-lease REATIVO (P1.5) ───────────────────────────────────────────────
|
|
505
|
+
/**
|
|
506
|
+
* Recuperação REATIVA: chamado pelo wrapper resiliente (`resilient-pool.ts`)
|
|
507
|
+
* quando uma query plana falha num erro de ESTABELECIMENTO de conexão (cert
|
|
508
|
+
* mTLS expirado no meio de uma request, ou timer de renovação que não
|
|
509
|
+
* disparou). Emite um lease NOVO e faz o swap do pool interno do wrapper com
|
|
510
|
+
* o certificado fresco; o wrapper então re-tenta a query 1x no pool novo.
|
|
511
|
+
*
|
|
512
|
+
* Vai DIRETO ao lease novo (sem `leaseId`) — a conexão falhou porque o
|
|
513
|
+
* certificado do lease atual está ruim, então `renew` (que depende do lease
|
|
514
|
+
* antigo estar vivo) não é o caminho mais robusto. Emitir do zero é o que já
|
|
515
|
+
* cura o death-spiral no caminho proativo (ver `_doRenew`).
|
|
516
|
+
*
|
|
517
|
+
* Coalesce concorrência: N queries falhando ao mesmo tempo compartilham UM
|
|
518
|
+
* único re-lease (não N leases). Após o swap, cada query re-tenta no pool
|
|
519
|
+
* vigente.
|
|
520
|
+
*
|
|
521
|
+
* NÃO engole erro: se o re-lease falhar, propaga — o wrapper então relança o
|
|
522
|
+
* erro ORIGINAL da query pro consumer.
|
|
523
|
+
*/
|
|
524
|
+
async _reactiveReLease() {
|
|
525
|
+
if (this._closed) {
|
|
526
|
+
throw new NeetruDbError(
|
|
527
|
+
"db_unavailable",
|
|
528
|
+
"SqlLeaseManager foi fechado \u2014 re-lease reativo abortado."
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
if (this._reLeaseInFlight !== null) return this._reLeaseInFlight;
|
|
532
|
+
const run = (async () => {
|
|
533
|
+
const withDb = (o) => {
|
|
534
|
+
if (this._deps.database !== void 0) o.database = this._deps.database;
|
|
535
|
+
return o;
|
|
536
|
+
};
|
|
537
|
+
const fresh = await this._deps.fetchLease(withDb({}));
|
|
538
|
+
await this._swapPool(fresh);
|
|
539
|
+
})();
|
|
540
|
+
this._reLeaseInFlight = run;
|
|
541
|
+
try {
|
|
542
|
+
await run;
|
|
543
|
+
} finally {
|
|
544
|
+
this._reLeaseInFlight = null;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
401
547
|
/**
|
|
402
548
|
* Hot-swap do pool: cria pool novo, atualiza ponteiros, drena pool antigo.
|
|
403
549
|
*
|
|
404
550
|
* Exposto como método protegido para testes.
|
|
405
551
|
*/
|
|
406
552
|
async _swapPool(newLease) {
|
|
407
|
-
|
|
408
|
-
if (!credRotated && newLease.leaseId === this._lease.leaseId) {
|
|
553
|
+
if (!hasConnectionChanged(this._lease, newLease)) {
|
|
409
554
|
this._lease = newLease;
|
|
410
555
|
return;
|
|
411
556
|
}
|
|
412
557
|
const newPool = this._deps.createPool(newLease);
|
|
558
|
+
if (isResilientPool(this._pool)) {
|
|
559
|
+
const oldInner = this._pool.__neetruSwapInner(newPool);
|
|
560
|
+
this._lease = newLease;
|
|
561
|
+
void oldInner.end().catch(() => {
|
|
562
|
+
});
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
413
565
|
const newOrm = this._deps.createDrizzle(newPool, this._deps.schema);
|
|
414
566
|
const oldPool = this._pool;
|
|
415
567
|
this._lease = newLease;
|
|
@@ -564,23 +716,26 @@ async function createSqlClientWithDeps(deps, options) {
|
|
|
564
716
|
const msg = err instanceof Error ? err.message : String(err);
|
|
565
717
|
throw new NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
|
|
566
718
|
}
|
|
567
|
-
let
|
|
719
|
+
let innerPool;
|
|
568
720
|
try {
|
|
569
|
-
|
|
721
|
+
innerPool = depsWithDb.createPool(lease);
|
|
570
722
|
} catch (err) {
|
|
571
723
|
const msg = err instanceof Error ? err.message : String(err);
|
|
572
724
|
throw new NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
|
|
573
725
|
}
|
|
726
|
+
const pool = createResilientPool(innerPool);
|
|
574
727
|
let orm;
|
|
575
728
|
try {
|
|
576
729
|
orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
|
|
577
730
|
} catch (err) {
|
|
578
|
-
await
|
|
731
|
+
await innerPool.end().catch(() => {
|
|
579
732
|
});
|
|
580
733
|
const msg = err instanceof Error ? err.message : String(err);
|
|
581
734
|
throw new NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
|
|
582
735
|
}
|
|
583
|
-
|
|
736
|
+
const manager = new SqlLeaseManager(lease, pool, orm, depsWithDb);
|
|
737
|
+
pool.__neetruSetReLease(() => manager._reactiveReLease());
|
|
738
|
+
return manager;
|
|
584
739
|
}
|
|
585
740
|
async function createSqlClientFromConfig(config, schema, options) {
|
|
586
741
|
const fetchLease = createHttpLeaseFetcher(config);
|
|
@@ -639,6 +794,7 @@ var init_sql_client = __esm({
|
|
|
639
794
|
"src/db/sql/sql-client.ts"() {
|
|
640
795
|
init_db_errors();
|
|
641
796
|
init_lease();
|
|
797
|
+
init_resilient_pool();
|
|
642
798
|
LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
|
|
643
799
|
LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
|
|
644
800
|
}
|