@neetru/sdk 2.3.4 → 2.3.6
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 +25 -0
- package/dist/auth.cjs +410 -337
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.mjs +410 -337
- package/dist/auth.mjs.map +1 -1
- package/dist/db.cjs +415 -342
- package/dist/db.cjs.map +1 -1
- package/dist/db.mjs +410 -336
- package/dist/db.mjs.map +1 -1
- package/dist/index.cjs +434 -348
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +428 -341
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +16 -4
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.mjs +16 -4
- package/dist/mocks.mjs.map +1 -1
- package/package.json +1 -1
package/dist/db.mjs
CHANGED
|
@@ -2,12 +2,6 @@ import { openDB } from 'idb';
|
|
|
2
2
|
|
|
3
3
|
var __defProp = Object.defineProperty;
|
|
4
4
|
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
-
}) : x)(function(x) {
|
|
8
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
-
});
|
|
11
5
|
var __esm = (fn, res) => function __init() {
|
|
12
6
|
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
13
7
|
};
|
|
@@ -36,6 +30,41 @@ var init_errors = __esm({
|
|
|
36
30
|
}
|
|
37
31
|
});
|
|
38
32
|
|
|
33
|
+
// src/db-errors.ts
|
|
34
|
+
var RETRYABLE_CODES, NeetruDbError;
|
|
35
|
+
var init_db_errors = __esm({
|
|
36
|
+
"src/db-errors.ts"() {
|
|
37
|
+
init_errors();
|
|
38
|
+
RETRYABLE_CODES = /* @__PURE__ */ new Set([
|
|
39
|
+
"db_unavailable",
|
|
40
|
+
"db_conflict",
|
|
41
|
+
"db_timeout"
|
|
42
|
+
]);
|
|
43
|
+
NeetruDbError = class _NeetruDbError extends NeetruError {
|
|
44
|
+
/** Código de erro fechado — específico de DB. */
|
|
45
|
+
code;
|
|
46
|
+
/**
|
|
47
|
+
* `true` para erros transientes que o produto pode tentar novamente.
|
|
48
|
+
* São retryable: `db_unavailable`, `db_conflict`, `db_timeout`.
|
|
49
|
+
*/
|
|
50
|
+
retryable;
|
|
51
|
+
/**
|
|
52
|
+
* ID opaco do banco lógico — só para correlação com logs do Core.
|
|
53
|
+
* Nunca deve ser exibido ao usuário final.
|
|
54
|
+
*/
|
|
55
|
+
dbId;
|
|
56
|
+
constructor(code, message, dbId) {
|
|
57
|
+
super(code, message);
|
|
58
|
+
this.name = "NeetruDbError";
|
|
59
|
+
this.code = code;
|
|
60
|
+
this.retryable = RETRYABLE_CODES.has(code);
|
|
61
|
+
this.dbId = dbId;
|
|
62
|
+
Object.setPrototypeOf(this, _NeetruDbError.prototype);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
39
68
|
// src/http.ts
|
|
40
69
|
var http_exports = {};
|
|
41
70
|
__export(http_exports, {
|
|
@@ -171,35 +200,378 @@ var init_http = __esm({
|
|
|
171
200
|
}
|
|
172
201
|
});
|
|
173
202
|
|
|
174
|
-
// src/db
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
/**
|
|
185
|
-
* `true` para erros transientes que o produto pode tentar novamente.
|
|
186
|
-
* São retryable: `db_unavailable`, `db_conflict`, `db_timeout`.
|
|
187
|
-
*/
|
|
188
|
-
retryable;
|
|
189
|
-
/**
|
|
190
|
-
* ID opaco do banco lógico — só para correlação com logs do Core.
|
|
191
|
-
* Nunca deve ser exibido ao usuário final.
|
|
192
|
-
*/
|
|
193
|
-
dbId;
|
|
194
|
-
constructor(code, message, dbId) {
|
|
195
|
-
super(code, message);
|
|
196
|
-
this.name = "NeetruDbError";
|
|
197
|
-
this.code = code;
|
|
198
|
-
this.retryable = RETRYABLE_CODES.has(code);
|
|
199
|
-
this.dbId = dbId;
|
|
200
|
-
Object.setPrototypeOf(this, _NeetruDbError.prototype);
|
|
203
|
+
// src/db/sql/lease.ts
|
|
204
|
+
function mapPoolError(err) {
|
|
205
|
+
if (err instanceof NeetruDbError) return err;
|
|
206
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
207
|
+
const code = err instanceof Error ? err.code : void 0;
|
|
208
|
+
if (typeof code === "string" && (code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "ETIMEDOUT")) {
|
|
209
|
+
return new NeetruDbError(
|
|
210
|
+
"db_unavailable",
|
|
211
|
+
`Pool connection failed (${code}): ${message}`
|
|
212
|
+
);
|
|
201
213
|
}
|
|
202
|
-
|
|
214
|
+
if (message.includes("statement timeout") || message.includes("lock timeout")) {
|
|
215
|
+
return new NeetruDbError("db_timeout", `Query timeout: ${message}`);
|
|
216
|
+
}
|
|
217
|
+
if (message.includes("40001") || message.includes("40P01") || message.includes("deadlock")) {
|
|
218
|
+
return new NeetruDbError("db_conflict", `Transaction conflict: ${message}`);
|
|
219
|
+
}
|
|
220
|
+
if (message.includes("42501") || message.toLowerCase().includes("permission denied")) {
|
|
221
|
+
return new NeetruDbError("db_permission_denied", `Permission denied: ${message}`);
|
|
222
|
+
}
|
|
223
|
+
return new NeetruDbError("db_unavailable", `Database error: ${message}`);
|
|
224
|
+
}
|
|
225
|
+
var RENEWAL_THRESHOLD, MIN_RENEWAL_DELAY_MS, SqlLeaseManager;
|
|
226
|
+
var init_lease = __esm({
|
|
227
|
+
"src/db/sql/lease.ts"() {
|
|
228
|
+
init_db_errors();
|
|
229
|
+
RENEWAL_THRESHOLD = 0.8;
|
|
230
|
+
MIN_RENEWAL_DELAY_MS = 3e4;
|
|
231
|
+
SqlLeaseManager = class {
|
|
232
|
+
_lease;
|
|
233
|
+
_pool;
|
|
234
|
+
_orm;
|
|
235
|
+
_renewalTimer = null;
|
|
236
|
+
_closed = false;
|
|
237
|
+
_deps;
|
|
238
|
+
constructor(lease, pool, orm, deps) {
|
|
239
|
+
this._lease = lease;
|
|
240
|
+
this._pool = pool;
|
|
241
|
+
this._orm = orm;
|
|
242
|
+
this._deps = {
|
|
243
|
+
...deps,
|
|
244
|
+
now: deps.now ?? (() => Date.now())
|
|
245
|
+
};
|
|
246
|
+
this._scheduleRenewal();
|
|
247
|
+
}
|
|
248
|
+
// ─── Superfície pública (NeetruSqlClient) ──────────────────────────────────
|
|
249
|
+
get orm() {
|
|
250
|
+
this._assertOpen();
|
|
251
|
+
return this._orm;
|
|
252
|
+
}
|
|
253
|
+
async transaction(fn, opts) {
|
|
254
|
+
this._assertOpen();
|
|
255
|
+
try {
|
|
256
|
+
if (opts?.isolationLevel) {
|
|
257
|
+
return await this._orm.transaction(fn, {
|
|
258
|
+
isolationLevel: opts.isolationLevel
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
return await this._orm.transaction(fn);
|
|
262
|
+
} catch (err) {
|
|
263
|
+
throw mapPoolError(err);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async close() {
|
|
267
|
+
if (this._closed) return;
|
|
268
|
+
this._closed = true;
|
|
269
|
+
this._cancelRenewal();
|
|
270
|
+
await this._pool.end().catch(() => {
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
// ─── Renovação proativa ────────────────────────────────────────────────────
|
|
274
|
+
/**
|
|
275
|
+
* Calcula o delay de renovação: momento de ~80% do TTL restante.
|
|
276
|
+
*
|
|
277
|
+
* Fórmula: `renewAt = issuedAt + TTL * RENEWAL_THRESHOLD`
|
|
278
|
+
* = `expiresAt - TTL * (1 - RENEWAL_THRESHOLD)`
|
|
279
|
+
*
|
|
280
|
+
* Se o lease já está além do ponto de renovação (ou TTL não calculável),
|
|
281
|
+
* usa `MIN_RENEWAL_DELAY_MS` como fallback seguro.
|
|
282
|
+
*/
|
|
283
|
+
_calcRenewalDelayMs() {
|
|
284
|
+
const expiresAtMs = Date.parse(this._lease.expiresAt);
|
|
285
|
+
if (!Number.isFinite(expiresAtMs)) return MIN_RENEWAL_DELAY_MS;
|
|
286
|
+
const now = this._deps.now();
|
|
287
|
+
const remainingMs = expiresAtMs - now;
|
|
288
|
+
if (remainingMs <= 0) return MIN_RENEWAL_DELAY_MS;
|
|
289
|
+
const renewInMs = remainingMs * (1 - RENEWAL_THRESHOLD);
|
|
290
|
+
return Math.max(MIN_RENEWAL_DELAY_MS, Math.round(renewInMs));
|
|
291
|
+
}
|
|
292
|
+
_scheduleRenewal() {
|
|
293
|
+
if (this._closed) return;
|
|
294
|
+
const delayMs = this._calcRenewalDelayMs();
|
|
295
|
+
this._renewalTimer = setTimeout(() => {
|
|
296
|
+
void this._doRenew();
|
|
297
|
+
}, delayMs);
|
|
298
|
+
}
|
|
299
|
+
_cancelRenewal() {
|
|
300
|
+
if (this._renewalTimer !== null) {
|
|
301
|
+
clearTimeout(this._renewalTimer);
|
|
302
|
+
this._renewalTimer = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
async _doRenew() {
|
|
306
|
+
if (this._closed) return;
|
|
307
|
+
try {
|
|
308
|
+
const renewOpts = {
|
|
309
|
+
leaseId: this._lease.leaseId
|
|
310
|
+
};
|
|
311
|
+
if (this._deps.database !== void 0) {
|
|
312
|
+
renewOpts.database = this._deps.database;
|
|
313
|
+
}
|
|
314
|
+
const newLease = await this._deps.fetchLease(renewOpts);
|
|
315
|
+
await this._swapPool(newLease);
|
|
316
|
+
} catch (err) {
|
|
317
|
+
this._renewalTimer = setTimeout(() => {
|
|
318
|
+
void this._doRenew();
|
|
319
|
+
}, MIN_RENEWAL_DELAY_MS);
|
|
320
|
+
if (process.env["NODE_ENV"] !== "production") {
|
|
321
|
+
console.warn("[SqlLeaseManager] Lease renewal failed, will retry:", err);
|
|
322
|
+
}
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
this._scheduleRenewal();
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Hot-swap do pool: cria pool novo, atualiza ponteiros, drena pool antigo.
|
|
329
|
+
*
|
|
330
|
+
* Exposto como método protegido para testes.
|
|
331
|
+
*/
|
|
332
|
+
async _swapPool(newLease) {
|
|
333
|
+
const credRotated = newLease.credentialVersion !== this._lease.credentialVersion;
|
|
334
|
+
if (!credRotated && newLease.leaseId === this._lease.leaseId) {
|
|
335
|
+
this._lease = newLease;
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const newPool = this._deps.createPool(newLease);
|
|
339
|
+
const newOrm = this._deps.createDrizzle(newPool, this._deps.schema);
|
|
340
|
+
const oldPool = this._pool;
|
|
341
|
+
this._lease = newLease;
|
|
342
|
+
this._pool = newPool;
|
|
343
|
+
this._orm = newOrm;
|
|
344
|
+
void oldPool.end().catch(() => {
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
348
|
+
_assertOpen() {
|
|
349
|
+
if (this._closed) {
|
|
350
|
+
throw new NeetruDbError(
|
|
351
|
+
"db_unavailable",
|
|
352
|
+
"SqlLeaseManager foi fechado \u2014 chame close() apenas no shutdown."
|
|
353
|
+
);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// src/db/sql/sql-client.ts
|
|
361
|
+
var sql_client_exports = {};
|
|
362
|
+
__export(sql_client_exports, {
|
|
363
|
+
LEASE_ENDPOINT: () => LEASE_ENDPOINT,
|
|
364
|
+
LEASE_RENEW_ENDPOINT: () => LEASE_RENEW_ENDPOINT,
|
|
365
|
+
createDrizzleHandle: () => createDrizzleHandle,
|
|
366
|
+
createHttpLeaseFetcher: () => createHttpLeaseFetcher,
|
|
367
|
+
createPgPool: () => createPgPool,
|
|
368
|
+
createSqlClientFromConfig: () => createSqlClientFromConfig,
|
|
369
|
+
createSqlClientWithDeps: () => createSqlClientWithDeps
|
|
370
|
+
});
|
|
371
|
+
function mapEnvToCore(sdkEnv) {
|
|
372
|
+
if (sdkEnv === "workspace") return "staging";
|
|
373
|
+
if (sdkEnv === "prod") return "production";
|
|
374
|
+
return "dev";
|
|
375
|
+
}
|
|
376
|
+
function createHttpLeaseFetcher(config) {
|
|
377
|
+
return async (opts) => {
|
|
378
|
+
const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
379
|
+
const isRenewal = Boolean(opts?.leaseId);
|
|
380
|
+
const path = isRenewal ? LEASE_RENEW_ENDPOINT : LEASE_ENDPOINT;
|
|
381
|
+
let body;
|
|
382
|
+
if (isRenewal) {
|
|
383
|
+
body = { leaseId: opts.leaseId };
|
|
384
|
+
} else {
|
|
385
|
+
body = {
|
|
386
|
+
productId: config.productId ?? "",
|
|
387
|
+
environment: mapEnvToCore(config.env ?? "prod")
|
|
388
|
+
};
|
|
389
|
+
if (opts?.database !== void 0) {
|
|
390
|
+
body["database"] = opts.database;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
let raw;
|
|
394
|
+
try {
|
|
395
|
+
raw = await httpRequest2(config, {
|
|
396
|
+
method: "POST",
|
|
397
|
+
path,
|
|
398
|
+
body,
|
|
399
|
+
requireAuth: true,
|
|
400
|
+
// Lease fetch é idempotente do ponto de vista de segurança — retries OK.
|
|
401
|
+
retries: 2
|
|
402
|
+
});
|
|
403
|
+
} catch (err) {
|
|
404
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
405
|
+
throw new NeetruDbError(
|
|
406
|
+
"db_unavailable",
|
|
407
|
+
`Falha ao ${isRenewal ? "renovar" : "obter"} lease SQL: ${msg}`
|
|
408
|
+
);
|
|
409
|
+
}
|
|
410
|
+
return parseLease(raw);
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
function parseLease(raw) {
|
|
414
|
+
if (!raw || typeof raw !== "object") {
|
|
415
|
+
throw new NeetruDbError("db_unavailable", "Resposta de lease inv\xE1lida (n\xE3o \xE9 objeto).");
|
|
416
|
+
}
|
|
417
|
+
const envelope = raw;
|
|
418
|
+
const leaseObj = envelope["lease"];
|
|
419
|
+
if (!leaseObj || typeof leaseObj !== "object") {
|
|
420
|
+
throw new NeetruDbError(
|
|
421
|
+
"db_unavailable",
|
|
422
|
+
'Resposta de lease inv\xE1lida: campo "lease" ausente ou n\xE3o \xE9 objeto. O Core retorna { kind, lease: { leaseId, host, ... } }.'
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
const r = leaseObj;
|
|
426
|
+
function req(field, type) {
|
|
427
|
+
if (typeof r[field] !== type) {
|
|
428
|
+
throw new NeetruDbError(
|
|
429
|
+
"db_unavailable",
|
|
430
|
+
`Campo obrigat\xF3rio "${field}" ausente ou tipo inv\xE1lido na resposta de lease.`
|
|
431
|
+
);
|
|
432
|
+
}
|
|
433
|
+
return r[field];
|
|
434
|
+
}
|
|
435
|
+
return {
|
|
436
|
+
leaseId: req("leaseId", "string"),
|
|
437
|
+
host: req("host", "string"),
|
|
438
|
+
port: typeof r["port"] === "number" ? r["port"] : 5433,
|
|
439
|
+
dbName: req("dbName", "string"),
|
|
440
|
+
user: req("user", "string"),
|
|
441
|
+
password: req("password", "string"),
|
|
442
|
+
sslca: typeof r["sslca"] === "string" ? r["sslca"] : null,
|
|
443
|
+
clientCert: typeof r["clientCert"] === "string" ? r["clientCert"] : null,
|
|
444
|
+
clientKey: typeof r["clientKey"] === "string" ? r["clientKey"] : null,
|
|
445
|
+
credentialVersion: typeof r["credentialVersion"] === "number" ? r["credentialVersion"] : 1,
|
|
446
|
+
expiresAt: req("expiresAt", "string")
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
async function createPgPool(lease) {
|
|
450
|
+
const { Pool } = await import(
|
|
451
|
+
/* webpackIgnore: true */
|
|
452
|
+
'pg'
|
|
453
|
+
);
|
|
454
|
+
const ssl = lease.sslca ? {
|
|
455
|
+
ca: lease.sslca,
|
|
456
|
+
cert: lease.clientCert ?? void 0,
|
|
457
|
+
key: lease.clientKey ?? void 0,
|
|
458
|
+
rejectUnauthorized: true
|
|
459
|
+
} : false;
|
|
460
|
+
return new Pool({
|
|
461
|
+
host: lease.host,
|
|
462
|
+
port: lease.port,
|
|
463
|
+
database: lease.dbName,
|
|
464
|
+
user: lease.user,
|
|
465
|
+
password: lease.password,
|
|
466
|
+
ssl,
|
|
467
|
+
max: 3,
|
|
468
|
+
idleTimeoutMillis: 3e4,
|
|
469
|
+
connectionTimeoutMillis: 1e4
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
async function createDrizzleHandle(pool, schema) {
|
|
473
|
+
const { drizzle } = await import(
|
|
474
|
+
/* webpackIgnore: true */
|
|
475
|
+
'drizzle-orm/node-postgres'
|
|
476
|
+
);
|
|
477
|
+
return drizzle(pool, { schema });
|
|
478
|
+
}
|
|
479
|
+
async function createSqlClientWithDeps(deps, options) {
|
|
480
|
+
const depsWithDb = options?.database !== void 0 ? { ...deps, database: options.database } : deps;
|
|
481
|
+
let lease;
|
|
482
|
+
try {
|
|
483
|
+
if (options?.database !== void 0) {
|
|
484
|
+
lease = await depsWithDb.fetchLease({ database: options.database });
|
|
485
|
+
} else {
|
|
486
|
+
lease = await depsWithDb.fetchLease();
|
|
487
|
+
}
|
|
488
|
+
} catch (err) {
|
|
489
|
+
if (err instanceof NeetruDbError) throw err;
|
|
490
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
491
|
+
throw new NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
|
|
492
|
+
}
|
|
493
|
+
let pool;
|
|
494
|
+
try {
|
|
495
|
+
pool = depsWithDb.createPool(lease);
|
|
496
|
+
} catch (err) {
|
|
497
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
498
|
+
throw new NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
|
|
499
|
+
}
|
|
500
|
+
let orm;
|
|
501
|
+
try {
|
|
502
|
+
orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
|
|
503
|
+
} catch (err) {
|
|
504
|
+
await pool.end().catch(() => {
|
|
505
|
+
});
|
|
506
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
507
|
+
throw new NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
|
|
508
|
+
}
|
|
509
|
+
return new SqlLeaseManager(lease, pool, orm, depsWithDb);
|
|
510
|
+
}
|
|
511
|
+
async function createSqlClientFromConfig(config, schema, options) {
|
|
512
|
+
const fetchLease = createHttpLeaseFetcher(config);
|
|
513
|
+
let pgMod;
|
|
514
|
+
let drizzlePgMod;
|
|
515
|
+
try {
|
|
516
|
+
pgMod = await import(
|
|
517
|
+
/* webpackIgnore: true */
|
|
518
|
+
'pg'
|
|
519
|
+
);
|
|
520
|
+
drizzlePgMod = await import(
|
|
521
|
+
/* webpackIgnore: true */
|
|
522
|
+
'drizzle-orm/node-postgres'
|
|
523
|
+
);
|
|
524
|
+
} catch (err) {
|
|
525
|
+
throw new NeetruDbError(
|
|
526
|
+
"db_unavailable",
|
|
527
|
+
`[client.db.sql] requer pg + drizzle-orm como peerDependencies do servidor. Instale: npm install pg drizzle-orm. (${err.message})`
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
const createPool = (lease) => {
|
|
531
|
+
const Pool = pgMod.Pool ?? pgMod.default?.Pool;
|
|
532
|
+
const ssl = lease.sslca ? {
|
|
533
|
+
ca: lease.sslca,
|
|
534
|
+
cert: lease.clientCert ?? void 0,
|
|
535
|
+
key: lease.clientKey ?? void 0,
|
|
536
|
+
rejectUnauthorized: true
|
|
537
|
+
} : false;
|
|
538
|
+
return new Pool({
|
|
539
|
+
host: lease.host,
|
|
540
|
+
port: lease.port,
|
|
541
|
+
database: lease.dbName,
|
|
542
|
+
user: lease.user,
|
|
543
|
+
password: lease.password,
|
|
544
|
+
ssl,
|
|
545
|
+
max: 3,
|
|
546
|
+
idleTimeoutMillis: 3e4,
|
|
547
|
+
connectionTimeoutMillis: 1e4
|
|
548
|
+
});
|
|
549
|
+
};
|
|
550
|
+
const createDrizzle = (pool, s) => {
|
|
551
|
+
return drizzlePgMod.drizzle(pool, { schema: s });
|
|
552
|
+
};
|
|
553
|
+
return createSqlClientWithDeps(
|
|
554
|
+
{
|
|
555
|
+
fetchLease,
|
|
556
|
+
createPool,
|
|
557
|
+
createDrizzle,
|
|
558
|
+
schema
|
|
559
|
+
},
|
|
560
|
+
options
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
var LEASE_ENDPOINT, LEASE_RENEW_ENDPOINT;
|
|
564
|
+
var init_sql_client = __esm({
|
|
565
|
+
"src/db/sql/sql-client.ts"() {
|
|
566
|
+
init_db_errors();
|
|
567
|
+
init_lease();
|
|
568
|
+
LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
|
|
569
|
+
LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
|
|
570
|
+
}
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// src/db/client-db.ts
|
|
574
|
+
init_db_errors();
|
|
203
575
|
|
|
204
576
|
// src/db/offline/query-engine.ts
|
|
205
577
|
function typeRank(v) {
|
|
@@ -2246,6 +2618,7 @@ var TabCoordinator = class {
|
|
|
2246
2618
|
};
|
|
2247
2619
|
|
|
2248
2620
|
// src/db/collection-ref.ts
|
|
2621
|
+
init_db_errors();
|
|
2249
2622
|
function isIndexedDBUnavailable() {
|
|
2250
2623
|
return typeof indexedDB === "undefined" || typeof window === "undefined";
|
|
2251
2624
|
}
|
|
@@ -2831,307 +3204,6 @@ async function createOfflineDocumentsNamespace(opts) {
|
|
|
2831
3204
|
});
|
|
2832
3205
|
}
|
|
2833
3206
|
|
|
2834
|
-
// src/db/sql/lease.ts
|
|
2835
|
-
var RENEWAL_THRESHOLD = 0.8;
|
|
2836
|
-
var MIN_RENEWAL_DELAY_MS = 3e4;
|
|
2837
|
-
var SqlLeaseManager = class {
|
|
2838
|
-
_lease;
|
|
2839
|
-
_pool;
|
|
2840
|
-
_orm;
|
|
2841
|
-
_renewalTimer = null;
|
|
2842
|
-
_closed = false;
|
|
2843
|
-
_deps;
|
|
2844
|
-
constructor(lease, pool, orm, deps) {
|
|
2845
|
-
this._lease = lease;
|
|
2846
|
-
this._pool = pool;
|
|
2847
|
-
this._orm = orm;
|
|
2848
|
-
this._deps = {
|
|
2849
|
-
...deps,
|
|
2850
|
-
now: deps.now ?? (() => Date.now())
|
|
2851
|
-
};
|
|
2852
|
-
this._scheduleRenewal();
|
|
2853
|
-
}
|
|
2854
|
-
// ─── Superfície pública (NeetruSqlClient) ──────────────────────────────────
|
|
2855
|
-
get orm() {
|
|
2856
|
-
this._assertOpen();
|
|
2857
|
-
return this._orm;
|
|
2858
|
-
}
|
|
2859
|
-
async transaction(fn, opts) {
|
|
2860
|
-
this._assertOpen();
|
|
2861
|
-
try {
|
|
2862
|
-
if (opts?.isolationLevel) {
|
|
2863
|
-
return await this._orm.transaction(fn, {
|
|
2864
|
-
isolationLevel: opts.isolationLevel
|
|
2865
|
-
});
|
|
2866
|
-
}
|
|
2867
|
-
return await this._orm.transaction(fn);
|
|
2868
|
-
} catch (err) {
|
|
2869
|
-
throw mapPoolError(err);
|
|
2870
|
-
}
|
|
2871
|
-
}
|
|
2872
|
-
async close() {
|
|
2873
|
-
if (this._closed) return;
|
|
2874
|
-
this._closed = true;
|
|
2875
|
-
this._cancelRenewal();
|
|
2876
|
-
await this._pool.end().catch(() => {
|
|
2877
|
-
});
|
|
2878
|
-
}
|
|
2879
|
-
// ─── Renovação proativa ────────────────────────────────────────────────────
|
|
2880
|
-
/**
|
|
2881
|
-
* Calcula o delay de renovação: momento de ~80% do TTL restante.
|
|
2882
|
-
*
|
|
2883
|
-
* Fórmula: `renewAt = issuedAt + TTL * RENEWAL_THRESHOLD`
|
|
2884
|
-
* = `expiresAt - TTL * (1 - RENEWAL_THRESHOLD)`
|
|
2885
|
-
*
|
|
2886
|
-
* Se o lease já está além do ponto de renovação (ou TTL não calculável),
|
|
2887
|
-
* usa `MIN_RENEWAL_DELAY_MS` como fallback seguro.
|
|
2888
|
-
*/
|
|
2889
|
-
_calcRenewalDelayMs() {
|
|
2890
|
-
const expiresAtMs = Date.parse(this._lease.expiresAt);
|
|
2891
|
-
if (!Number.isFinite(expiresAtMs)) return MIN_RENEWAL_DELAY_MS;
|
|
2892
|
-
const now = this._deps.now();
|
|
2893
|
-
const remainingMs = expiresAtMs - now;
|
|
2894
|
-
if (remainingMs <= 0) return MIN_RENEWAL_DELAY_MS;
|
|
2895
|
-
const renewInMs = remainingMs * (1 - RENEWAL_THRESHOLD);
|
|
2896
|
-
return Math.max(MIN_RENEWAL_DELAY_MS, Math.round(renewInMs));
|
|
2897
|
-
}
|
|
2898
|
-
_scheduleRenewal() {
|
|
2899
|
-
if (this._closed) return;
|
|
2900
|
-
const delayMs = this._calcRenewalDelayMs();
|
|
2901
|
-
this._renewalTimer = setTimeout(() => {
|
|
2902
|
-
void this._doRenew();
|
|
2903
|
-
}, delayMs);
|
|
2904
|
-
}
|
|
2905
|
-
_cancelRenewal() {
|
|
2906
|
-
if (this._renewalTimer !== null) {
|
|
2907
|
-
clearTimeout(this._renewalTimer);
|
|
2908
|
-
this._renewalTimer = null;
|
|
2909
|
-
}
|
|
2910
|
-
}
|
|
2911
|
-
async _doRenew() {
|
|
2912
|
-
if (this._closed) return;
|
|
2913
|
-
try {
|
|
2914
|
-
const renewOpts = {
|
|
2915
|
-
leaseId: this._lease.leaseId
|
|
2916
|
-
};
|
|
2917
|
-
if (this._deps.database !== void 0) {
|
|
2918
|
-
renewOpts.database = this._deps.database;
|
|
2919
|
-
}
|
|
2920
|
-
const newLease = await this._deps.fetchLease(renewOpts);
|
|
2921
|
-
await this._swapPool(newLease);
|
|
2922
|
-
} catch (err) {
|
|
2923
|
-
this._renewalTimer = setTimeout(() => {
|
|
2924
|
-
void this._doRenew();
|
|
2925
|
-
}, MIN_RENEWAL_DELAY_MS);
|
|
2926
|
-
if (process.env["NODE_ENV"] !== "production") {
|
|
2927
|
-
console.warn("[SqlLeaseManager] Lease renewal failed, will retry:", err);
|
|
2928
|
-
}
|
|
2929
|
-
return;
|
|
2930
|
-
}
|
|
2931
|
-
this._scheduleRenewal();
|
|
2932
|
-
}
|
|
2933
|
-
/**
|
|
2934
|
-
* Hot-swap do pool: cria pool novo, atualiza ponteiros, drena pool antigo.
|
|
2935
|
-
*
|
|
2936
|
-
* Exposto como método protegido para testes.
|
|
2937
|
-
*/
|
|
2938
|
-
async _swapPool(newLease) {
|
|
2939
|
-
const credRotated = newLease.credentialVersion !== this._lease.credentialVersion;
|
|
2940
|
-
if (!credRotated && newLease.leaseId === this._lease.leaseId) {
|
|
2941
|
-
this._lease = newLease;
|
|
2942
|
-
return;
|
|
2943
|
-
}
|
|
2944
|
-
const newPool = this._deps.createPool(newLease);
|
|
2945
|
-
const newOrm = this._deps.createDrizzle(newPool, this._deps.schema);
|
|
2946
|
-
const oldPool = this._pool;
|
|
2947
|
-
this._lease = newLease;
|
|
2948
|
-
this._pool = newPool;
|
|
2949
|
-
this._orm = newOrm;
|
|
2950
|
-
void oldPool.end().catch(() => {
|
|
2951
|
-
});
|
|
2952
|
-
}
|
|
2953
|
-
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
2954
|
-
_assertOpen() {
|
|
2955
|
-
if (this._closed) {
|
|
2956
|
-
throw new NeetruDbError(
|
|
2957
|
-
"db_unavailable",
|
|
2958
|
-
"SqlLeaseManager foi fechado \u2014 chame close() apenas no shutdown."
|
|
2959
|
-
);
|
|
2960
|
-
}
|
|
2961
|
-
}
|
|
2962
|
-
};
|
|
2963
|
-
function mapPoolError(err) {
|
|
2964
|
-
if (err instanceof NeetruDbError) return err;
|
|
2965
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
2966
|
-
const code = err instanceof Error ? err.code : void 0;
|
|
2967
|
-
if (typeof code === "string" && (code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "ETIMEDOUT")) {
|
|
2968
|
-
return new NeetruDbError(
|
|
2969
|
-
"db_unavailable",
|
|
2970
|
-
`Pool connection failed (${code}): ${message}`
|
|
2971
|
-
);
|
|
2972
|
-
}
|
|
2973
|
-
if (message.includes("statement timeout") || message.includes("lock timeout")) {
|
|
2974
|
-
return new NeetruDbError("db_timeout", `Query timeout: ${message}`);
|
|
2975
|
-
}
|
|
2976
|
-
if (message.includes("40001") || message.includes("40P01") || message.includes("deadlock")) {
|
|
2977
|
-
return new NeetruDbError("db_conflict", `Transaction conflict: ${message}`);
|
|
2978
|
-
}
|
|
2979
|
-
if (message.includes("42501") || message.toLowerCase().includes("permission denied")) {
|
|
2980
|
-
return new NeetruDbError("db_permission_denied", `Permission denied: ${message}`);
|
|
2981
|
-
}
|
|
2982
|
-
return new NeetruDbError("db_unavailable", `Database error: ${message}`);
|
|
2983
|
-
}
|
|
2984
|
-
|
|
2985
|
-
// src/db/sql/sql-client.ts
|
|
2986
|
-
var LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
|
|
2987
|
-
var LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
|
|
2988
|
-
function mapEnvToCore(sdkEnv) {
|
|
2989
|
-
if (sdkEnv === "workspace") return "staging";
|
|
2990
|
-
if (sdkEnv === "prod") return "production";
|
|
2991
|
-
return "dev";
|
|
2992
|
-
}
|
|
2993
|
-
function createHttpLeaseFetcher(config) {
|
|
2994
|
-
return async (opts) => {
|
|
2995
|
-
const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
2996
|
-
const isRenewal = Boolean(opts?.leaseId);
|
|
2997
|
-
const path = isRenewal ? LEASE_RENEW_ENDPOINT : LEASE_ENDPOINT;
|
|
2998
|
-
let body;
|
|
2999
|
-
if (isRenewal) {
|
|
3000
|
-
body = { leaseId: opts.leaseId };
|
|
3001
|
-
} else {
|
|
3002
|
-
body = {
|
|
3003
|
-
productId: config.productId ?? "",
|
|
3004
|
-
environment: mapEnvToCore(config.env ?? "prod")
|
|
3005
|
-
};
|
|
3006
|
-
if (opts?.database !== void 0) {
|
|
3007
|
-
body["database"] = opts.database;
|
|
3008
|
-
}
|
|
3009
|
-
}
|
|
3010
|
-
let raw;
|
|
3011
|
-
try {
|
|
3012
|
-
raw = await httpRequest2(config, {
|
|
3013
|
-
method: "POST",
|
|
3014
|
-
path,
|
|
3015
|
-
body,
|
|
3016
|
-
requireAuth: true,
|
|
3017
|
-
// Lease fetch é idempotente do ponto de vista de segurança — retries OK.
|
|
3018
|
-
retries: 2
|
|
3019
|
-
});
|
|
3020
|
-
} catch (err) {
|
|
3021
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3022
|
-
throw new NeetruDbError(
|
|
3023
|
-
"db_unavailable",
|
|
3024
|
-
`Falha ao ${isRenewal ? "renovar" : "obter"} lease SQL: ${msg}`
|
|
3025
|
-
);
|
|
3026
|
-
}
|
|
3027
|
-
return parseLease(raw);
|
|
3028
|
-
};
|
|
3029
|
-
}
|
|
3030
|
-
function parseLease(raw) {
|
|
3031
|
-
if (!raw || typeof raw !== "object") {
|
|
3032
|
-
throw new NeetruDbError("db_unavailable", "Resposta de lease inv\xE1lida (n\xE3o \xE9 objeto).");
|
|
3033
|
-
}
|
|
3034
|
-
const envelope = raw;
|
|
3035
|
-
const leaseObj = envelope["lease"];
|
|
3036
|
-
if (!leaseObj || typeof leaseObj !== "object") {
|
|
3037
|
-
throw new NeetruDbError(
|
|
3038
|
-
"db_unavailable",
|
|
3039
|
-
'Resposta de lease inv\xE1lida: campo "lease" ausente ou n\xE3o \xE9 objeto. O Core retorna { kind, lease: { leaseId, host, ... } }.'
|
|
3040
|
-
);
|
|
3041
|
-
}
|
|
3042
|
-
const r = leaseObj;
|
|
3043
|
-
function req(field, type) {
|
|
3044
|
-
if (typeof r[field] !== type) {
|
|
3045
|
-
throw new NeetruDbError(
|
|
3046
|
-
"db_unavailable",
|
|
3047
|
-
`Campo obrigat\xF3rio "${field}" ausente ou tipo inv\xE1lido na resposta de lease.`
|
|
3048
|
-
);
|
|
3049
|
-
}
|
|
3050
|
-
return r[field];
|
|
3051
|
-
}
|
|
3052
|
-
return {
|
|
3053
|
-
leaseId: req("leaseId", "string"),
|
|
3054
|
-
host: req("host", "string"),
|
|
3055
|
-
port: typeof r["port"] === "number" ? r["port"] : 5433,
|
|
3056
|
-
dbName: req("dbName", "string"),
|
|
3057
|
-
user: req("user", "string"),
|
|
3058
|
-
password: req("password", "string"),
|
|
3059
|
-
sslca: typeof r["sslca"] === "string" ? r["sslca"] : null,
|
|
3060
|
-
clientCert: typeof r["clientCert"] === "string" ? r["clientCert"] : null,
|
|
3061
|
-
clientKey: typeof r["clientKey"] === "string" ? r["clientKey"] : null,
|
|
3062
|
-
credentialVersion: typeof r["credentialVersion"] === "number" ? r["credentialVersion"] : 1,
|
|
3063
|
-
expiresAt: req("expiresAt", "string")
|
|
3064
|
-
};
|
|
3065
|
-
}
|
|
3066
|
-
async function createSqlClientWithDeps(deps, options) {
|
|
3067
|
-
const depsWithDb = options?.database !== void 0 ? { ...deps, database: options.database } : deps;
|
|
3068
|
-
let lease;
|
|
3069
|
-
try {
|
|
3070
|
-
if (options?.database !== void 0) {
|
|
3071
|
-
lease = await depsWithDb.fetchLease({ database: options.database });
|
|
3072
|
-
} else {
|
|
3073
|
-
lease = await depsWithDb.fetchLease();
|
|
3074
|
-
}
|
|
3075
|
-
} catch (err) {
|
|
3076
|
-
if (err instanceof NeetruDbError) throw err;
|
|
3077
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3078
|
-
throw new NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
|
|
3079
|
-
}
|
|
3080
|
-
let pool;
|
|
3081
|
-
try {
|
|
3082
|
-
pool = depsWithDb.createPool(lease);
|
|
3083
|
-
} catch (err) {
|
|
3084
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3085
|
-
throw new NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
|
|
3086
|
-
}
|
|
3087
|
-
let orm;
|
|
3088
|
-
try {
|
|
3089
|
-
orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
|
|
3090
|
-
} catch (err) {
|
|
3091
|
-
await pool.end().catch(() => {
|
|
3092
|
-
});
|
|
3093
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
3094
|
-
throw new NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
|
|
3095
|
-
}
|
|
3096
|
-
return new SqlLeaseManager(lease, pool, orm, depsWithDb);
|
|
3097
|
-
}
|
|
3098
|
-
async function createSqlClientFromConfig(config, schema, options) {
|
|
3099
|
-
const fetchLease = createHttpLeaseFetcher(config);
|
|
3100
|
-
const createPool = (lease) => {
|
|
3101
|
-
const { Pool } = __require("pg");
|
|
3102
|
-
const ssl = lease.sslca ? {
|
|
3103
|
-
ca: lease.sslca,
|
|
3104
|
-
cert: lease.clientCert ?? void 0,
|
|
3105
|
-
key: lease.clientKey ?? void 0,
|
|
3106
|
-
rejectUnauthorized: true
|
|
3107
|
-
} : false;
|
|
3108
|
-
return new Pool({
|
|
3109
|
-
host: lease.host,
|
|
3110
|
-
port: lease.port,
|
|
3111
|
-
database: lease.dbName,
|
|
3112
|
-
user: lease.user,
|
|
3113
|
-
password: lease.password,
|
|
3114
|
-
ssl,
|
|
3115
|
-
max: 3,
|
|
3116
|
-
idleTimeoutMillis: 3e4,
|
|
3117
|
-
connectionTimeoutMillis: 1e4
|
|
3118
|
-
});
|
|
3119
|
-
};
|
|
3120
|
-
const createDrizzle = (pool, s) => {
|
|
3121
|
-
const { drizzle } = __require("drizzle-orm/node-postgres");
|
|
3122
|
-
return drizzle(pool, { schema: s });
|
|
3123
|
-
};
|
|
3124
|
-
return createSqlClientWithDeps(
|
|
3125
|
-
{
|
|
3126
|
-
fetchLease,
|
|
3127
|
-
createPool,
|
|
3128
|
-
createDrizzle,
|
|
3129
|
-
schema
|
|
3130
|
-
},
|
|
3131
|
-
options
|
|
3132
|
-
);
|
|
3133
|
-
}
|
|
3134
|
-
|
|
3135
3207
|
// src/db/realtime/realtime-client.ts
|
|
3136
3208
|
var WS_OPEN = 1;
|
|
3137
3209
|
var WS_CLOSED = 3;
|
|
@@ -3558,6 +3630,7 @@ var defaultWebSocketFactory = (url) => {
|
|
|
3558
3630
|
};
|
|
3559
3631
|
|
|
3560
3632
|
// src/db/client-db.ts
|
|
3633
|
+
init_db_errors();
|
|
3561
3634
|
var DATASTORE_COLLECTION_ENDPOINT = (collection) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}`;
|
|
3562
3635
|
var DATASTORE_DOC_ENDPOINT = (collection, docId) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}/${encodeURIComponent(docId)}`;
|
|
3563
3636
|
function resolveTransport(transport, opts, config) {
|
|
@@ -3757,7 +3830,8 @@ function createNeetruDb(config, dbOpts = {}) {
|
|
|
3757
3830
|
"[SDK] db.sql() n\xE3o dispon\xEDvel em NEETRU_ENV=dev. Use `neetru dev` para subir o container Postgres local."
|
|
3758
3831
|
);
|
|
3759
3832
|
}
|
|
3760
|
-
|
|
3833
|
+
const sqlClientMod = await Promise.resolve().then(() => (init_sql_client(), sql_client_exports));
|
|
3834
|
+
return sqlClientMod.createSqlClientFromConfig(config, schema, options);
|
|
3761
3835
|
},
|
|
3762
3836
|
get syncState() {
|
|
3763
3837
|
return _docsResolved?.syncState ?? {
|