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