@neetru/sdk 3.2.2 → 3.3.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/CHANGELOG.md +36 -0
- package/dist/ai.cjs +229 -7
- package/dist/ai.cjs.map +1 -1
- package/dist/ai.d.cts +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/ai.mjs +229 -7
- package/dist/ai.mjs.map +1 -1
- package/dist/auth.cjs +556 -20
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +556 -20
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.cjs +19 -3
- package/dist/catalog.cjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/catalog.mjs +19 -3
- package/dist/catalog.mjs.map +1 -1
- package/dist/checkout.cjs +19 -3
- package/dist/checkout.cjs.map +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/checkout.mjs +19 -3
- package/dist/checkout.mjs.map +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.cjs +330 -12
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +330 -12
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.cjs +19 -3
- package/dist/entitlements.cjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/entitlements.mjs +19 -3
- package/dist/entitlements.mjs.map +1 -1
- package/dist/errors.cjs +11 -1
- package/dist/errors.cjs.map +1 -1
- package/dist/errors.d.cts +10 -1
- package/dist/errors.d.ts +10 -1
- package/dist/errors.mjs +11 -1
- package/dist/errors.mjs.map +1 -1
- package/dist/firestore-compat.d.cts +1 -1
- package/dist/firestore-compat.d.ts +1 -1
- package/dist/index.cjs +557 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +557 -21
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +19 -3
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +3 -1
- package/dist/mocks.d.ts +3 -1
- package/dist/mocks.mjs +19 -3
- package/dist/mocks.mjs.map +1 -1
- package/dist/notifications.cjs +19 -3
- package/dist/notifications.cjs.map +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/notifications.mjs +19 -3
- package/dist/notifications.mjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.cjs +19 -3
- package/dist/support.cjs.map +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/support.mjs +19 -3
- package/dist/support.mjs.map +1 -1
- package/dist/telemetry.cjs +19 -3
- package/dist/telemetry.cjs.map +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.mjs +19 -3
- package/dist/telemetry.mjs.map +1 -1
- package/dist/{types-PzHvWMbV.d.cts → types-DQplLXIk.d.cts} +69 -5
- package/dist/{types-iLDjiqqk.d.ts → types-DslphKIM.d.ts} +69 -5
- package/dist/usage.cjs +19 -3
- package/dist/usage.cjs.map +1 -1
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/usage.mjs +19 -3
- package/dist/usage.mjs.map +1 -1
- package/dist/webhooks.cjs +19 -3
- package/dist/webhooks.cjs.map +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/dist/webhooks.mjs +19 -3
- package/dist/webhooks.mjs.map +1 -1
- package/package.json +1 -1
package/dist/db.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { w as ConflictRecord, x as CreateOfflineDocumentsOptions, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, K as DbQuery, L as DbSqlLease, N as DbWhereFilter, P as FieldSentinel, Q as IncrementSentinel, V as NEETRU_SENTINEL_KEY, Y as NeetruDb, Z as NeetruDbDocuments, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a4 as NeetruSqlClient, ad as RealtimeTransport, ai as ServerTimestampSentinel, ak as SqlOptions, ap as SyncState, aG as WithFieldSentinels, aJ as createNeetruDb, aL as createRestSyncTransport, aN as getWebSocketRealtimeClient, aO as increment, aP as isFieldSentinel, aQ as isIncrementSentinel, aR as isServerTimestampSentinel, aS as serverTimestamp } from './types-DQplLXIk.cjs';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.cjs';
|
package/dist/db.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export {
|
|
1
|
+
export { w as ConflictRecord, x as CreateOfflineDocumentsOptions, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, K as DbQuery, L as DbSqlLease, N as DbWhereFilter, P as FieldSentinel, Q as IncrementSentinel, V as NEETRU_SENTINEL_KEY, Y as NeetruDb, Z as NeetruDbDocuments, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a4 as NeetruSqlClient, ad as RealtimeTransport, ai as ServerTimestampSentinel, ak as SqlOptions, ap as SyncState, aG as WithFieldSentinels, aJ as createNeetruDb, aL as createRestSyncTransport, aN as getWebSocketRealtimeClient, aO as increment, aP as isFieldSentinel, aQ as isIncrementSentinel, aR as isServerTimestampSentinel, aS as serverTimestamp } from './types-DslphKIM.js';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.js';
|
package/dist/db.mjs
CHANGED
|
@@ -18,12 +18,22 @@ var init_errors = __esm({
|
|
|
18
18
|
code;
|
|
19
19
|
status;
|
|
20
20
|
requestId;
|
|
21
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Motivo semântico opcional propagado pelo Core no corpo do erro (campo
|
|
23
|
+
* `reason` no TOPO do body, fora de `error`). Usado principalmente pelo 503
|
|
24
|
+
* de lease de DB, onde `reason` discrimina o estado do ambiente
|
|
25
|
+
* (`suspended` | `archived` | `revoked` | `provisioning` | `degraded` |
|
|
26
|
+
* `no_route` | `expired`). Camadas superiores (ex.: `db/sql`) mapeiam isso
|
|
27
|
+
* para um `NeetruDbErrorCode` mais específico (recuperável vs não).
|
|
28
|
+
*/
|
|
29
|
+
reason;
|
|
30
|
+
constructor(code, message, status, requestId, reason) {
|
|
22
31
|
super(message);
|
|
23
32
|
this.name = "NeetruError";
|
|
24
33
|
this.code = code;
|
|
25
34
|
this.status = status;
|
|
26
35
|
this.requestId = requestId;
|
|
36
|
+
this.reason = reason;
|
|
27
37
|
Object.setPrototypeOf(this, _NeetruError.prototype);
|
|
28
38
|
}
|
|
29
39
|
};
|
|
@@ -229,16 +239,22 @@ async function httpRequest(config, opts) {
|
|
|
229
239
|
const body = await safeJson(res);
|
|
230
240
|
let code = statusToCode(res.status);
|
|
231
241
|
let message = `HTTP ${res.status}`;
|
|
232
|
-
|
|
242
|
+
let reason;
|
|
243
|
+
if (body && typeof body === "object") {
|
|
233
244
|
const errField = body.error;
|
|
234
245
|
if (typeof errField === "string") {
|
|
235
246
|
message = errField;
|
|
236
247
|
} else if (errField && typeof errField === "object") {
|
|
237
248
|
if (typeof errField.code === "string") code = errField.code;
|
|
238
249
|
if (typeof errField.message === "string") message = errField.message;
|
|
250
|
+
if (typeof errField.reason === "string") reason = errField.reason;
|
|
251
|
+
} else {
|
|
252
|
+
if (typeof body.code === "string") code = body.code;
|
|
253
|
+
if (typeof body.message === "string") message = body.message;
|
|
239
254
|
}
|
|
255
|
+
if (typeof body.reason === "string") reason = body.reason;
|
|
240
256
|
}
|
|
241
|
-
const err = new NeetruError(code, message, res.status, requestId);
|
|
257
|
+
const err = new NeetruError(code, message, res.status, requestId, reason);
|
|
242
258
|
lastError = err;
|
|
243
259
|
const isRetryable = RETRYABLE_CODES2.has(code);
|
|
244
260
|
if (isRetryable && attempt < maxRetries) {
|
|
@@ -274,8 +290,98 @@ var init_http = __esm({
|
|
|
274
290
|
}
|
|
275
291
|
});
|
|
276
292
|
|
|
293
|
+
// src/db/sql/resilient-pool.ts
|
|
294
|
+
function isResilientPool(x) {
|
|
295
|
+
return typeof x === "object" && x !== null && x[RESILIENT_POOL_BRAND] === true;
|
|
296
|
+
}
|
|
297
|
+
function isConnectionEstablishmentError(err) {
|
|
298
|
+
const code = err && typeof err === "object" && "code" in err ? err.code : void 0;
|
|
299
|
+
if (code === "ECONNREFUSED" || code === "ETIMEDOUT" || code === "ENOTFOUND" || code === "EHOSTUNREACH" || code === "EAI_AGAIN") {
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
if (typeof code === "string") {
|
|
303
|
+
const upper = code.toUpperCase();
|
|
304
|
+
if (upper === "CERT_HAS_EXPIRED" || upper.startsWith("ERR_SSL") || upper.startsWith("ERR_TLS") || upper.includes("CERTIFICATE_EXPIRED")) {
|
|
305
|
+
return true;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
const rawMessage = err instanceof Error ? err.message : typeof err === "string" ? err : "";
|
|
309
|
+
const message = rawMessage.toLowerCase();
|
|
310
|
+
if (message.length === 0) return false;
|
|
311
|
+
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")) {
|
|
312
|
+
return true;
|
|
313
|
+
}
|
|
314
|
+
if (message.includes("econnrefused") || message.includes("etimedout") || message.includes("enotfound") || message.includes("ehostunreach") || message.includes("getaddrinfo")) {
|
|
315
|
+
return true;
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
function createResilientPool(initialInner) {
|
|
320
|
+
const state = { current: initialInner };
|
|
321
|
+
let reLease = null;
|
|
322
|
+
const controls = {
|
|
323
|
+
__neetruSwapInner(newInner) {
|
|
324
|
+
const old = state.current;
|
|
325
|
+
state.current = newInner;
|
|
326
|
+
return old;
|
|
327
|
+
},
|
|
328
|
+
__neetruGetInner() {
|
|
329
|
+
return state.current;
|
|
330
|
+
},
|
|
331
|
+
__neetruSetReLease(fn) {
|
|
332
|
+
reLease = fn;
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
const invokeQuery = (pool, args) => pool.query(...args);
|
|
336
|
+
const wrappedQuery = (...args) => {
|
|
337
|
+
if (args.length > 0 && typeof args[args.length - 1] === "function") {
|
|
338
|
+
return invokeQuery(state.current, args);
|
|
339
|
+
}
|
|
340
|
+
return (async () => {
|
|
341
|
+
try {
|
|
342
|
+
return await invokeQuery(state.current, args);
|
|
343
|
+
} catch (err) {
|
|
344
|
+
const fn = reLease;
|
|
345
|
+
if (fn === null || !isConnectionEstablishmentError(err)) {
|
|
346
|
+
throw err;
|
|
347
|
+
}
|
|
348
|
+
try {
|
|
349
|
+
await fn();
|
|
350
|
+
} catch {
|
|
351
|
+
throw err;
|
|
352
|
+
}
|
|
353
|
+
return await invokeQuery(state.current, args);
|
|
354
|
+
}
|
|
355
|
+
})();
|
|
356
|
+
};
|
|
357
|
+
const target = Object.create(Object.getPrototypeOf(initialInner));
|
|
358
|
+
const proxy = new Proxy(target, {
|
|
359
|
+
get(_t, prop) {
|
|
360
|
+
if (prop === RESILIENT_POOL_BRAND) return true;
|
|
361
|
+
if (prop === "__neetruSwapInner") return controls.__neetruSwapInner;
|
|
362
|
+
if (prop === "__neetruGetInner") return controls.__neetruGetInner;
|
|
363
|
+
if (prop === "__neetruSetReLease") return controls.__neetruSetReLease;
|
|
364
|
+
if (prop === "query") return wrappedQuery;
|
|
365
|
+
if (prop === "constructor") {
|
|
366
|
+
return state.current.constructor;
|
|
367
|
+
}
|
|
368
|
+
const current = state.current;
|
|
369
|
+
const value = Reflect.get(current, prop, current);
|
|
370
|
+
return typeof value === "function" ? value.bind(current) : value;
|
|
371
|
+
}
|
|
372
|
+
});
|
|
373
|
+
return proxy;
|
|
374
|
+
}
|
|
375
|
+
var RESILIENT_POOL_BRAND;
|
|
376
|
+
var init_resilient_pool = __esm({
|
|
377
|
+
"src/db/sql/resilient-pool.ts"() {
|
|
378
|
+
RESILIENT_POOL_BRAND = /* @__PURE__ */ Symbol.for("neetru.sdk.resilientPool");
|
|
379
|
+
}
|
|
380
|
+
});
|
|
381
|
+
|
|
277
382
|
// src/db/sql/lease.ts
|
|
278
383
|
function hasConnectionChanged(oldLease, newLease) {
|
|
384
|
+
if (!oldLease) return true;
|
|
279
385
|
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;
|
|
280
386
|
}
|
|
281
387
|
function mapPoolError(err) {
|
|
@@ -303,14 +409,31 @@ var RENEWAL_THRESHOLD, MIN_RENEWAL_DELAY_MS, SqlLeaseManager;
|
|
|
303
409
|
var init_lease = __esm({
|
|
304
410
|
"src/db/sql/lease.ts"() {
|
|
305
411
|
init_db_errors();
|
|
412
|
+
init_resilient_pool();
|
|
306
413
|
RENEWAL_THRESHOLD = 0.8;
|
|
307
414
|
MIN_RENEWAL_DELAY_MS = 3e4;
|
|
308
415
|
SqlLeaseManager = class {
|
|
416
|
+
/**
|
|
417
|
+
* Lease atual. `null` no modo LAZY antes da 1ª query (o 1º lease é adiado —
|
|
418
|
+
* ver `_ensureInitialLease`). Após a 1ª aquisição, sempre não-nulo.
|
|
419
|
+
*/
|
|
309
420
|
_lease;
|
|
310
421
|
_pool;
|
|
311
422
|
_orm;
|
|
312
423
|
_renewalTimer = null;
|
|
313
424
|
_closed = false;
|
|
425
|
+
/**
|
|
426
|
+
* Promessa de re-lease REATIVO em voo — coalesce chamadas concorrentes
|
|
427
|
+
* (N queries falhando ao mesmo tempo disparam UM único re-lease). Ver
|
|
428
|
+
* `_reactiveReLease`.
|
|
429
|
+
*/
|
|
430
|
+
_reLeaseInFlight = null;
|
|
431
|
+
/**
|
|
432
|
+
* Promessa da aquisição do LEASE INICIAL em voo (modo lazy) — coalesce as
|
|
433
|
+
* N primeiras queries concorrentes num único fetch. `null` quando não há
|
|
434
|
+
* aquisição inicial pendente. Ver `_ensureInitialLease`.
|
|
435
|
+
*/
|
|
436
|
+
_initInFlight = null;
|
|
314
437
|
_deps;
|
|
315
438
|
constructor(lease, pool, orm, deps) {
|
|
316
439
|
this._lease = lease;
|
|
@@ -320,7 +443,7 @@ var init_lease = __esm({
|
|
|
320
443
|
...deps,
|
|
321
444
|
now: deps.now ?? (() => Date.now())
|
|
322
445
|
};
|
|
323
|
-
this._scheduleRenewal();
|
|
446
|
+
if (this._lease) this._scheduleRenewal();
|
|
324
447
|
}
|
|
325
448
|
// ─── Superfície pública (NeetruSqlClient) ──────────────────────────────────
|
|
326
449
|
get orm() {
|
|
@@ -358,6 +481,7 @@ var init_lease = __esm({
|
|
|
358
481
|
* usa `MIN_RENEWAL_DELAY_MS` como fallback seguro.
|
|
359
482
|
*/
|
|
360
483
|
_calcRenewalDelayMs() {
|
|
484
|
+
if (!this._lease) return MIN_RENEWAL_DELAY_MS;
|
|
361
485
|
const expiresAtMs = Date.parse(this._lease.expiresAt);
|
|
362
486
|
if (!Number.isFinite(expiresAtMs)) return MIN_RENEWAL_DELAY_MS;
|
|
363
487
|
const now = this._deps.now();
|
|
@@ -381,6 +505,8 @@ var init_lease = __esm({
|
|
|
381
505
|
}
|
|
382
506
|
async _doRenew() {
|
|
383
507
|
if (this._closed) return;
|
|
508
|
+
const currentLease = this._lease;
|
|
509
|
+
if (!currentLease) return;
|
|
384
510
|
try {
|
|
385
511
|
const withDb = (o) => {
|
|
386
512
|
if (this._deps.database !== void 0) o.database = this._deps.database;
|
|
@@ -388,7 +514,7 @@ var init_lease = __esm({
|
|
|
388
514
|
};
|
|
389
515
|
let newLease;
|
|
390
516
|
try {
|
|
391
|
-
newLease = await this._deps.fetchLease(withDb({ leaseId:
|
|
517
|
+
newLease = await this._deps.fetchLease(withDb({ leaseId: currentLease.leaseId }));
|
|
392
518
|
} catch (renewErr) {
|
|
393
519
|
newLease = await this._deps.fetchLease(withDb({}));
|
|
394
520
|
}
|
|
@@ -405,6 +531,114 @@ var init_lease = __esm({
|
|
|
405
531
|
}
|
|
406
532
|
this._scheduleRenewal();
|
|
407
533
|
}
|
|
534
|
+
// ─── Re-lease REATIVO (P1.5) ───────────────────────────────────────────────
|
|
535
|
+
/**
|
|
536
|
+
* Recuperação REATIVA: chamado pelo wrapper resiliente (`resilient-pool.ts`)
|
|
537
|
+
* quando uma query plana falha num erro de ESTABELECIMENTO de conexão (cert
|
|
538
|
+
* mTLS expirado no meio de uma request, ou timer de renovação que não
|
|
539
|
+
* disparou). Emite um lease NOVO e faz o swap do pool interno do wrapper com
|
|
540
|
+
* o certificado fresco; o wrapper então re-tenta a query 1x no pool novo.
|
|
541
|
+
*
|
|
542
|
+
* Vai DIRETO ao lease novo (sem `leaseId`) — a conexão falhou porque o
|
|
543
|
+
* certificado do lease atual está ruim, então `renew` (que depende do lease
|
|
544
|
+
* antigo estar vivo) não é o caminho mais robusto. Emitir do zero é o que já
|
|
545
|
+
* cura o death-spiral no caminho proativo (ver `_doRenew`).
|
|
546
|
+
*
|
|
547
|
+
* Coalesce concorrência: N queries falhando ao mesmo tempo compartilham UM
|
|
548
|
+
* único re-lease (não N leases). Após o swap, cada query re-tenta no pool
|
|
549
|
+
* vigente.
|
|
550
|
+
*
|
|
551
|
+
* NÃO engole erro: se o re-lease falhar, propaga — o wrapper então relança o
|
|
552
|
+
* erro ORIGINAL da query pro consumer.
|
|
553
|
+
*/
|
|
554
|
+
async _reactiveReLease() {
|
|
555
|
+
if (this._closed) {
|
|
556
|
+
throw new NeetruDbError(
|
|
557
|
+
"db_unavailable",
|
|
558
|
+
"SqlLeaseManager foi fechado \u2014 re-lease reativo abortado."
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
if (this._reLeaseInFlight !== null) return this._reLeaseInFlight;
|
|
562
|
+
const run = (async () => {
|
|
563
|
+
const withDb = (o) => {
|
|
564
|
+
if (this._deps.database !== void 0) o.database = this._deps.database;
|
|
565
|
+
return o;
|
|
566
|
+
};
|
|
567
|
+
const fresh = await this._deps.fetchLease(withDb({}));
|
|
568
|
+
await this._swapPool(fresh);
|
|
569
|
+
})();
|
|
570
|
+
this._reLeaseInFlight = run;
|
|
571
|
+
try {
|
|
572
|
+
await run;
|
|
573
|
+
} finally {
|
|
574
|
+
this._reLeaseInFlight = null;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
// ─── Lease INICIAL lazy (motor de ambientes suspenso) ──────────────────────
|
|
578
|
+
/**
|
|
579
|
+
* Adquire o LEASE INICIAL sob demanda (modo lazy). Chamado pelo pool "adiado"
|
|
580
|
+
* (`createDeferredPool`) na 1ª query/transação — nunca no boot do módulo.
|
|
581
|
+
*
|
|
582
|
+
* Fluxo:
|
|
583
|
+
* 1. Se já há lease (`_lease != null`), devolve o pool REAL interno vigente
|
|
584
|
+
* (idempotente — chamadas após a 1ª aquisição são no-op de fetch).
|
|
585
|
+
* 2. Busca o 1º lease no Core (com `database` se houver — contrato C-H).
|
|
586
|
+
* 3. Cria o pool REAL e faz o swap no wrapper resiliente (`__neetruSwapInner`)
|
|
587
|
+
* pra que as queries seguintes passem direto pelo pool real.
|
|
588
|
+
* 4. Agenda a renovação proativa (só a partir daqui — antes não havia lease).
|
|
589
|
+
*
|
|
590
|
+
* Coalesce concorrência: N primeiras queries compartilham UM único fetch
|
|
591
|
+
* (`_initInFlight`). Se a aquisição FALHA (ex.: ambiente suspenso →
|
|
592
|
+
* `db_suspended`, ou Core fora → `db_unavailable`), o erro propaga pra query
|
|
593
|
+
* que a disparou e `_initInFlight` é limpo — a PRÓXIMA query re-tenta (o
|
|
594
|
+
* ambiente pode ter voltado). NÃO derruba o processo nem o import do módulo.
|
|
595
|
+
*/
|
|
596
|
+
async _ensureInitialLease() {
|
|
597
|
+
if (this._closed) {
|
|
598
|
+
throw new NeetruDbError(
|
|
599
|
+
"db_unavailable",
|
|
600
|
+
"SqlLeaseManager foi fechado \u2014 aquisi\xE7\xE3o de lease inicial abortada."
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
if (this._lease) {
|
|
604
|
+
return isResilientPool(this._pool) ? this._pool.__neetruGetInner() : this._pool;
|
|
605
|
+
}
|
|
606
|
+
if (this._initInFlight !== null) return this._initInFlight;
|
|
607
|
+
const run = (async () => {
|
|
608
|
+
let lease;
|
|
609
|
+
try {
|
|
610
|
+
lease = this._deps.database !== void 0 ? await this._deps.fetchLease({ database: this._deps.database }) : await this._deps.fetchLease();
|
|
611
|
+
} catch (err) {
|
|
612
|
+
if (err instanceof NeetruDbError) throw err;
|
|
613
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
614
|
+
throw new NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
|
|
615
|
+
}
|
|
616
|
+
let realPool;
|
|
617
|
+
try {
|
|
618
|
+
realPool = this._deps.createPool(lease);
|
|
619
|
+
} catch (err) {
|
|
620
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
621
|
+
throw new NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
|
|
622
|
+
}
|
|
623
|
+
if (isResilientPool(this._pool)) {
|
|
624
|
+
const old = this._pool.__neetruSwapInner(realPool);
|
|
625
|
+
void old.end().catch(() => {
|
|
626
|
+
});
|
|
627
|
+
} else {
|
|
628
|
+
this._pool = realPool;
|
|
629
|
+
}
|
|
630
|
+
this._lease = lease;
|
|
631
|
+
this._scheduleRenewal();
|
|
632
|
+
return realPool;
|
|
633
|
+
})();
|
|
634
|
+
this._initInFlight = run;
|
|
635
|
+
try {
|
|
636
|
+
return await run;
|
|
637
|
+
} catch (err) {
|
|
638
|
+
this._initInFlight = null;
|
|
639
|
+
throw err;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
408
642
|
/**
|
|
409
643
|
* Hot-swap do pool: cria pool novo, atualiza ponteiros, drena pool antigo.
|
|
410
644
|
*
|
|
@@ -416,6 +650,13 @@ var init_lease = __esm({
|
|
|
416
650
|
return;
|
|
417
651
|
}
|
|
418
652
|
const newPool = this._deps.createPool(newLease);
|
|
653
|
+
if (isResilientPool(this._pool)) {
|
|
654
|
+
const oldInner = this._pool.__neetruSwapInner(newPool);
|
|
655
|
+
this._lease = newLease;
|
|
656
|
+
void oldInner.end().catch(() => {
|
|
657
|
+
});
|
|
658
|
+
return;
|
|
659
|
+
}
|
|
419
660
|
const newOrm = this._deps.createDrizzle(newPool, this._deps.schema);
|
|
420
661
|
const oldPool = this._pool;
|
|
421
662
|
this._lease = newLease;
|
|
@@ -481,10 +722,19 @@ function createHttpLeaseFetcher(config) {
|
|
|
481
722
|
retries: 2
|
|
482
723
|
});
|
|
483
724
|
} catch (err) {
|
|
725
|
+
const verb = isRenewal ? "renovar" : "obter";
|
|
484
726
|
const msg = err instanceof Error ? err.message : String(err);
|
|
727
|
+
const reason = err instanceof NeetruError ? err.reason : void 0;
|
|
728
|
+
if (reason === "suspended" || reason === "archived" || reason === "revoked") {
|
|
729
|
+
const human = reason === "suspended" ? "ambiente suspenso \u2014 a m\xE1quina que o servia parou; ser\xE1 religado quando o ambiente voltar" : reason === "archived" ? "ambiente arquivado \u2014 este banco foi arquivado e n\xE3o aceita mais conex\xF5es" : "lease revogado \u2014 as credenciais deste ambiente foram revogadas pelo Core";
|
|
730
|
+
throw new NeetruDbError(
|
|
731
|
+
"db_suspended",
|
|
732
|
+
`Falha ao ${verb} lease SQL: ${human} (reason=${reason}).`
|
|
733
|
+
);
|
|
734
|
+
}
|
|
485
735
|
throw new NeetruDbError(
|
|
486
736
|
"db_unavailable",
|
|
487
|
-
`Falha ao ${
|
|
737
|
+
`Falha ao ${verb} lease SQL: ${msg}`
|
|
488
738
|
);
|
|
489
739
|
}
|
|
490
740
|
return parseLease(raw);
|
|
@@ -556,8 +806,64 @@ async function createDrizzleHandle(pool, schema) {
|
|
|
556
806
|
);
|
|
557
807
|
return drizzle(pool, { schema });
|
|
558
808
|
}
|
|
809
|
+
function createDeferredPool(poolPrototype, ensureRealPool) {
|
|
810
|
+
let real = null;
|
|
811
|
+
let inflight = null;
|
|
812
|
+
const getReal = () => {
|
|
813
|
+
if (real) return Promise.resolve(real);
|
|
814
|
+
if (!inflight) {
|
|
815
|
+
inflight = ensureRealPool().then((p) => {
|
|
816
|
+
real = p;
|
|
817
|
+
inflight = null;
|
|
818
|
+
return p;
|
|
819
|
+
}).catch((e) => {
|
|
820
|
+
inflight = null;
|
|
821
|
+
throw e;
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
return inflight;
|
|
825
|
+
};
|
|
826
|
+
const deferred = {
|
|
827
|
+
async query(...args) {
|
|
828
|
+
const p = await getReal();
|
|
829
|
+
return p.query(...args);
|
|
830
|
+
},
|
|
831
|
+
async connect(...args) {
|
|
832
|
+
const p = await getReal();
|
|
833
|
+
return p.connect(...args);
|
|
834
|
+
},
|
|
835
|
+
async end() {
|
|
836
|
+
if (real) await real.end();
|
|
837
|
+
}
|
|
838
|
+
};
|
|
839
|
+
if (poolPrototype) {
|
|
840
|
+
Object.setPrototypeOf(deferred, poolPrototype);
|
|
841
|
+
}
|
|
842
|
+
return deferred;
|
|
843
|
+
}
|
|
559
844
|
async function createSqlClientWithDeps(deps, options) {
|
|
560
845
|
const depsWithDb = options?.database !== void 0 ? { ...deps, database: options.database } : deps;
|
|
846
|
+
if (depsWithDb.lazy) {
|
|
847
|
+
let managerRef = null;
|
|
848
|
+
const deferred = createDeferredPool(depsWithDb.poolPrototype ?? null, () => {
|
|
849
|
+
if (!managerRef) {
|
|
850
|
+
throw new NeetruDbError("db_unavailable", "SQL client n\xE3o inicializado (manager ausente).");
|
|
851
|
+
}
|
|
852
|
+
return managerRef._ensureInitialLease();
|
|
853
|
+
});
|
|
854
|
+
const pool2 = createResilientPool(deferred);
|
|
855
|
+
let orm2;
|
|
856
|
+
try {
|
|
857
|
+
orm2 = depsWithDb.createDrizzle(pool2, depsWithDb.schema);
|
|
858
|
+
} catch (err) {
|
|
859
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
860
|
+
throw new NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
|
|
861
|
+
}
|
|
862
|
+
const manager2 = new SqlLeaseManager(null, pool2, orm2, depsWithDb);
|
|
863
|
+
managerRef = manager2;
|
|
864
|
+
pool2.__neetruSetReLease(() => manager2._reactiveReLease());
|
|
865
|
+
return manager2;
|
|
866
|
+
}
|
|
561
867
|
let lease;
|
|
562
868
|
try {
|
|
563
869
|
if (options?.database !== void 0) {
|
|
@@ -570,23 +876,26 @@ async function createSqlClientWithDeps(deps, options) {
|
|
|
570
876
|
const msg = err instanceof Error ? err.message : String(err);
|
|
571
877
|
throw new NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
|
|
572
878
|
}
|
|
573
|
-
let
|
|
879
|
+
let innerPool;
|
|
574
880
|
try {
|
|
575
|
-
|
|
881
|
+
innerPool = depsWithDb.createPool(lease);
|
|
576
882
|
} catch (err) {
|
|
577
883
|
const msg = err instanceof Error ? err.message : String(err);
|
|
578
884
|
throw new NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
|
|
579
885
|
}
|
|
886
|
+
const pool = createResilientPool(innerPool);
|
|
580
887
|
let orm;
|
|
581
888
|
try {
|
|
582
889
|
orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
|
|
583
890
|
} catch (err) {
|
|
584
|
-
await
|
|
891
|
+
await innerPool.end().catch(() => {
|
|
585
892
|
});
|
|
586
893
|
const msg = err instanceof Error ? err.message : String(err);
|
|
587
894
|
throw new NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
|
|
588
895
|
}
|
|
589
|
-
|
|
896
|
+
const manager = new SqlLeaseManager(lease, pool, orm, depsWithDb);
|
|
897
|
+
pool.__neetruSetReLease(() => manager._reactiveReLease());
|
|
898
|
+
return manager;
|
|
590
899
|
}
|
|
591
900
|
async function createSqlClientFromConfig(config, schema, options) {
|
|
592
901
|
const fetchLease = createHttpLeaseFetcher(config);
|
|
@@ -607,8 +916,8 @@ async function createSqlClientFromConfig(config, schema, options) {
|
|
|
607
916
|
`[client.db.sql] requer pg + drizzle-orm como peerDependencies do servidor. Instale: npm install pg drizzle-orm. (${err.message})`
|
|
608
917
|
);
|
|
609
918
|
}
|
|
919
|
+
const Pool = pgMod.Pool ?? pgMod.default?.Pool;
|
|
610
920
|
const createPool = (lease) => {
|
|
611
|
-
const Pool = pgMod.Pool ?? pgMod.default?.Pool;
|
|
612
921
|
const ssl = lease.sslca ? {
|
|
613
922
|
ca: lease.sslca,
|
|
614
923
|
cert: lease.clientCert ?? void 0,
|
|
@@ -635,7 +944,14 @@ async function createSqlClientFromConfig(config, schema, options) {
|
|
|
635
944
|
fetchLease,
|
|
636
945
|
createPool,
|
|
637
946
|
createDrizzle,
|
|
638
|
-
schema
|
|
947
|
+
schema,
|
|
948
|
+
// Lease inicial LAZY (motor de ambientes suspenso): `client.db.sql()` não
|
|
949
|
+
// pode explodir o `import` do módulo do produto quando o ambiente está
|
|
950
|
+
// suspenso/arquivado no boot. O 1º lease é adiado pra 1ª query.
|
|
951
|
+
lazy: true,
|
|
952
|
+
// Placeholder pré-1ª-query herda `pg.Pool.prototype` pra `instanceof Pool`
|
|
953
|
+
// valer no wrapper resiliente (transações Drizzle PASS-THROUGH seguras).
|
|
954
|
+
poolPrototype: Pool?.prototype ?? null
|
|
639
955
|
},
|
|
640
956
|
options
|
|
641
957
|
);
|
|
@@ -644,7 +960,9 @@ var LEASE_ENDPOINT, LEASE_RENEW_ENDPOINT;
|
|
|
644
960
|
var init_sql_client = __esm({
|
|
645
961
|
"src/db/sql/sql-client.ts"() {
|
|
646
962
|
init_db_errors();
|
|
963
|
+
init_errors();
|
|
647
964
|
init_lease();
|
|
965
|
+
init_resilient_pool();
|
|
648
966
|
LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
|
|
649
967
|
LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
|
|
650
968
|
}
|