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