@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/index.cjs CHANGED
@@ -5,12 +5,6 @@ var idb = require('idb');
5
5
 
6
6
  var __defProp = Object.defineProperty;
7
7
  var __getOwnPropNames = Object.getOwnPropertyNames;
8
- var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
9
- get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
10
- }) : x)(function(x) {
11
- if (typeof require !== "undefined") return require.apply(this, arguments);
12
- throw Error('Dynamic require of "' + x + '" is not supported');
13
- });
14
8
  var __esm = (fn, res) => function __init() {
15
9
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
10
  };
@@ -174,6 +168,411 @@ var init_http = __esm({
174
168
  }
175
169
  });
176
170
 
171
+ // src/db-errors.ts
172
+ var RETRYABLE_CODES2; exports.NeetruDbError = void 0;
173
+ var init_db_errors = __esm({
174
+ "src/db-errors.ts"() {
175
+ init_errors();
176
+ RETRYABLE_CODES2 = /* @__PURE__ */ new Set([
177
+ "db_unavailable",
178
+ "db_conflict",
179
+ "db_timeout"
180
+ ]);
181
+ exports.NeetruDbError = class _NeetruDbError extends exports.NeetruError {
182
+ /** Código de erro fechado — específico de DB. */
183
+ code;
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_CODES2.has(code);
199
+ this.dbId = dbId;
200
+ Object.setPrototypeOf(this, _NeetruDbError.prototype);
201
+ }
202
+ };
203
+ }
204
+ });
205
+
206
+ // src/db/sql/lease.ts
207
+ function mapPoolError(err) {
208
+ if (err instanceof exports.NeetruDbError) return err;
209
+ const message = err instanceof Error ? err.message : String(err);
210
+ const code = err instanceof Error ? err.code : void 0;
211
+ if (typeof code === "string" && (code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "ETIMEDOUT")) {
212
+ return new exports.NeetruDbError(
213
+ "db_unavailable",
214
+ `Pool connection failed (${code}): ${message}`
215
+ );
216
+ }
217
+ if (message.includes("statement timeout") || message.includes("lock timeout")) {
218
+ return new exports.NeetruDbError("db_timeout", `Query timeout: ${message}`);
219
+ }
220
+ if (message.includes("40001") || message.includes("40P01") || message.includes("deadlock")) {
221
+ return new exports.NeetruDbError("db_conflict", `Transaction conflict: ${message}`);
222
+ }
223
+ if (message.includes("42501") || message.toLowerCase().includes("permission denied")) {
224
+ return new exports.NeetruDbError("db_permission_denied", `Permission denied: ${message}`);
225
+ }
226
+ return new exports.NeetruDbError("db_unavailable", `Database error: ${message}`);
227
+ }
228
+ var RENEWAL_THRESHOLD, MIN_RENEWAL_DELAY_MS, SqlLeaseManager;
229
+ var init_lease = __esm({
230
+ "src/db/sql/lease.ts"() {
231
+ init_db_errors();
232
+ RENEWAL_THRESHOLD = 0.8;
233
+ MIN_RENEWAL_DELAY_MS = 3e4;
234
+ SqlLeaseManager = class {
235
+ _lease;
236
+ _pool;
237
+ _orm;
238
+ _renewalTimer = null;
239
+ _closed = false;
240
+ _deps;
241
+ constructor(lease, pool, orm, deps) {
242
+ this._lease = lease;
243
+ this._pool = pool;
244
+ this._orm = orm;
245
+ this._deps = {
246
+ ...deps,
247
+ now: deps.now ?? (() => Date.now())
248
+ };
249
+ this._scheduleRenewal();
250
+ }
251
+ // ─── Superfície pública (NeetruSqlClient) ──────────────────────────────────
252
+ get orm() {
253
+ this._assertOpen();
254
+ return this._orm;
255
+ }
256
+ async transaction(fn, opts) {
257
+ this._assertOpen();
258
+ try {
259
+ if (opts?.isolationLevel) {
260
+ return await this._orm.transaction(fn, {
261
+ isolationLevel: opts.isolationLevel
262
+ });
263
+ }
264
+ return await this._orm.transaction(fn);
265
+ } catch (err) {
266
+ throw mapPoolError(err);
267
+ }
268
+ }
269
+ async close() {
270
+ if (this._closed) return;
271
+ this._closed = true;
272
+ this._cancelRenewal();
273
+ await this._pool.end().catch(() => {
274
+ });
275
+ }
276
+ // ─── Renovação proativa ────────────────────────────────────────────────────
277
+ /**
278
+ * Calcula o delay de renovação: momento de ~80% do TTL restante.
279
+ *
280
+ * Fórmula: `renewAt = issuedAt + TTL * RENEWAL_THRESHOLD`
281
+ * = `expiresAt - TTL * (1 - RENEWAL_THRESHOLD)`
282
+ *
283
+ * Se o lease já está além do ponto de renovação (ou TTL não calculável),
284
+ * usa `MIN_RENEWAL_DELAY_MS` como fallback seguro.
285
+ */
286
+ _calcRenewalDelayMs() {
287
+ const expiresAtMs = Date.parse(this._lease.expiresAt);
288
+ if (!Number.isFinite(expiresAtMs)) return MIN_RENEWAL_DELAY_MS;
289
+ const now = this._deps.now();
290
+ const remainingMs = expiresAtMs - now;
291
+ if (remainingMs <= 0) return MIN_RENEWAL_DELAY_MS;
292
+ const renewInMs = remainingMs * (1 - RENEWAL_THRESHOLD);
293
+ return Math.max(MIN_RENEWAL_DELAY_MS, Math.round(renewInMs));
294
+ }
295
+ _scheduleRenewal() {
296
+ if (this._closed) return;
297
+ const delayMs = this._calcRenewalDelayMs();
298
+ this._renewalTimer = setTimeout(() => {
299
+ void this._doRenew();
300
+ }, delayMs);
301
+ }
302
+ _cancelRenewal() {
303
+ if (this._renewalTimer !== null) {
304
+ clearTimeout(this._renewalTimer);
305
+ this._renewalTimer = null;
306
+ }
307
+ }
308
+ async _doRenew() {
309
+ if (this._closed) return;
310
+ try {
311
+ const renewOpts = {
312
+ leaseId: this._lease.leaseId
313
+ };
314
+ if (this._deps.database !== void 0) {
315
+ renewOpts.database = this._deps.database;
316
+ }
317
+ const newLease = await this._deps.fetchLease(renewOpts);
318
+ await this._swapPool(newLease);
319
+ } catch (err) {
320
+ this._renewalTimer = setTimeout(() => {
321
+ void this._doRenew();
322
+ }, MIN_RENEWAL_DELAY_MS);
323
+ if (process.env["NODE_ENV"] !== "production") {
324
+ console.warn("[SqlLeaseManager] Lease renewal failed, will retry:", err);
325
+ }
326
+ return;
327
+ }
328
+ this._scheduleRenewal();
329
+ }
330
+ /**
331
+ * Hot-swap do pool: cria pool novo, atualiza ponteiros, drena pool antigo.
332
+ *
333
+ * Exposto como método protegido para testes.
334
+ */
335
+ async _swapPool(newLease) {
336
+ const credRotated = newLease.credentialVersion !== this._lease.credentialVersion;
337
+ if (!credRotated && newLease.leaseId === this._lease.leaseId) {
338
+ this._lease = newLease;
339
+ return;
340
+ }
341
+ const newPool = this._deps.createPool(newLease);
342
+ const newOrm = this._deps.createDrizzle(newPool, this._deps.schema);
343
+ const oldPool = this._pool;
344
+ this._lease = newLease;
345
+ this._pool = newPool;
346
+ this._orm = newOrm;
347
+ void oldPool.end().catch(() => {
348
+ });
349
+ }
350
+ // ─── Helpers ───────────────────────────────────────────────────────────────
351
+ _assertOpen() {
352
+ if (this._closed) {
353
+ throw new exports.NeetruDbError(
354
+ "db_unavailable",
355
+ "SqlLeaseManager foi fechado \u2014 chame close() apenas no shutdown."
356
+ );
357
+ }
358
+ }
359
+ };
360
+ }
361
+ });
362
+
363
+ // src/db/sql/sql-client.ts
364
+ var sql_client_exports = {};
365
+ __export(sql_client_exports, {
366
+ LEASE_ENDPOINT: () => LEASE_ENDPOINT,
367
+ LEASE_RENEW_ENDPOINT: () => LEASE_RENEW_ENDPOINT,
368
+ createDrizzleHandle: () => createDrizzleHandle,
369
+ createHttpLeaseFetcher: () => createHttpLeaseFetcher,
370
+ createPgPool: () => createPgPool,
371
+ createSqlClientFromConfig: () => createSqlClientFromConfig,
372
+ createSqlClientWithDeps: () => createSqlClientWithDeps
373
+ });
374
+ function mapEnvToCore(sdkEnv) {
375
+ if (sdkEnv === "workspace") return "staging";
376
+ if (sdkEnv === "prod") return "production";
377
+ return "dev";
378
+ }
379
+ function createHttpLeaseFetcher(config) {
380
+ return async (opts) => {
381
+ const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
382
+ const isRenewal = Boolean(opts?.leaseId);
383
+ const path = isRenewal ? LEASE_RENEW_ENDPOINT : LEASE_ENDPOINT;
384
+ let body;
385
+ if (isRenewal) {
386
+ body = { leaseId: opts.leaseId };
387
+ } else {
388
+ body = {
389
+ productId: config.productId ?? "",
390
+ environment: mapEnvToCore(config.env ?? "prod")
391
+ };
392
+ if (opts?.database !== void 0) {
393
+ body["database"] = opts.database;
394
+ }
395
+ }
396
+ let raw;
397
+ try {
398
+ raw = await httpRequest2(config, {
399
+ method: "POST",
400
+ path,
401
+ body,
402
+ requireAuth: true,
403
+ // Lease fetch é idempotente do ponto de vista de segurança — retries OK.
404
+ retries: 2
405
+ });
406
+ } catch (err) {
407
+ const msg = err instanceof Error ? err.message : String(err);
408
+ throw new exports.NeetruDbError(
409
+ "db_unavailable",
410
+ `Falha ao ${isRenewal ? "renovar" : "obter"} lease SQL: ${msg}`
411
+ );
412
+ }
413
+ return parseLease(raw);
414
+ };
415
+ }
416
+ function parseLease(raw) {
417
+ if (!raw || typeof raw !== "object") {
418
+ throw new exports.NeetruDbError("db_unavailable", "Resposta de lease inv\xE1lida (n\xE3o \xE9 objeto).");
419
+ }
420
+ const envelope = raw;
421
+ const leaseObj = envelope["lease"];
422
+ if (!leaseObj || typeof leaseObj !== "object") {
423
+ throw new exports.NeetruDbError(
424
+ "db_unavailable",
425
+ 'Resposta de lease inv\xE1lida: campo "lease" ausente ou n\xE3o \xE9 objeto. O Core retorna { kind, lease: { leaseId, host, ... } }.'
426
+ );
427
+ }
428
+ const r = leaseObj;
429
+ function req(field, type) {
430
+ if (typeof r[field] !== type) {
431
+ throw new exports.NeetruDbError(
432
+ "db_unavailable",
433
+ `Campo obrigat\xF3rio "${field}" ausente ou tipo inv\xE1lido na resposta de lease.`
434
+ );
435
+ }
436
+ return r[field];
437
+ }
438
+ return {
439
+ leaseId: req("leaseId", "string"),
440
+ host: req("host", "string"),
441
+ port: typeof r["port"] === "number" ? r["port"] : 5433,
442
+ dbName: req("dbName", "string"),
443
+ user: req("user", "string"),
444
+ password: req("password", "string"),
445
+ sslca: typeof r["sslca"] === "string" ? r["sslca"] : null,
446
+ clientCert: typeof r["clientCert"] === "string" ? r["clientCert"] : null,
447
+ clientKey: typeof r["clientKey"] === "string" ? r["clientKey"] : null,
448
+ credentialVersion: typeof r["credentialVersion"] === "number" ? r["credentialVersion"] : 1,
449
+ expiresAt: req("expiresAt", "string")
450
+ };
451
+ }
452
+ async function createPgPool(lease) {
453
+ const { Pool } = await import(
454
+ /* webpackIgnore: true */
455
+ 'pg'
456
+ );
457
+ const ssl = lease.sslca ? {
458
+ ca: lease.sslca,
459
+ cert: lease.clientCert ?? void 0,
460
+ key: lease.clientKey ?? void 0,
461
+ rejectUnauthorized: true
462
+ } : false;
463
+ return new Pool({
464
+ host: lease.host,
465
+ port: lease.port,
466
+ database: lease.dbName,
467
+ user: lease.user,
468
+ password: lease.password,
469
+ ssl,
470
+ max: 3,
471
+ idleTimeoutMillis: 3e4,
472
+ connectionTimeoutMillis: 1e4
473
+ });
474
+ }
475
+ async function createDrizzleHandle(pool, schema) {
476
+ const { drizzle } = await import(
477
+ /* webpackIgnore: true */
478
+ 'drizzle-orm/node-postgres'
479
+ );
480
+ return drizzle(pool, { schema });
481
+ }
482
+ async function createSqlClientWithDeps(deps, options) {
483
+ const depsWithDb = options?.database !== void 0 ? { ...deps, database: options.database } : deps;
484
+ let lease;
485
+ try {
486
+ if (options?.database !== void 0) {
487
+ lease = await depsWithDb.fetchLease({ database: options.database });
488
+ } else {
489
+ lease = await depsWithDb.fetchLease();
490
+ }
491
+ } catch (err) {
492
+ if (err instanceof exports.NeetruDbError) throw err;
493
+ const msg = err instanceof Error ? err.message : String(err);
494
+ throw new exports.NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
495
+ }
496
+ let pool;
497
+ try {
498
+ pool = depsWithDb.createPool(lease);
499
+ } catch (err) {
500
+ const msg = err instanceof Error ? err.message : String(err);
501
+ throw new exports.NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
502
+ }
503
+ let orm;
504
+ try {
505
+ orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
506
+ } catch (err) {
507
+ await pool.end().catch(() => {
508
+ });
509
+ const msg = err instanceof Error ? err.message : String(err);
510
+ throw new exports.NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
511
+ }
512
+ return new SqlLeaseManager(lease, pool, orm, depsWithDb);
513
+ }
514
+ async function createSqlClientFromConfig(config, schema, options) {
515
+ const fetchLease = createHttpLeaseFetcher(config);
516
+ let pgMod;
517
+ let drizzlePgMod;
518
+ try {
519
+ pgMod = await import(
520
+ /* webpackIgnore: true */
521
+ 'pg'
522
+ );
523
+ drizzlePgMod = await import(
524
+ /* webpackIgnore: true */
525
+ 'drizzle-orm/node-postgres'
526
+ );
527
+ } catch (err) {
528
+ throw new exports.NeetruDbError(
529
+ "db_unavailable",
530
+ `[client.db.sql] requer pg + drizzle-orm como peerDependencies do servidor. Instale: npm install pg drizzle-orm. (${err.message})`
531
+ );
532
+ }
533
+ const createPool = (lease) => {
534
+ const Pool = pgMod.Pool ?? pgMod.default?.Pool;
535
+ const ssl = lease.sslca ? {
536
+ ca: lease.sslca,
537
+ cert: lease.clientCert ?? void 0,
538
+ key: lease.clientKey ?? void 0,
539
+ rejectUnauthorized: true
540
+ } : false;
541
+ return new Pool({
542
+ host: lease.host,
543
+ port: lease.port,
544
+ database: lease.dbName,
545
+ user: lease.user,
546
+ password: lease.password,
547
+ ssl,
548
+ max: 3,
549
+ idleTimeoutMillis: 3e4,
550
+ connectionTimeoutMillis: 1e4
551
+ });
552
+ };
553
+ const createDrizzle = (pool, s) => {
554
+ return drizzlePgMod.drizzle(pool, { schema: s });
555
+ };
556
+ return createSqlClientWithDeps(
557
+ {
558
+ fetchLease,
559
+ createPool,
560
+ createDrizzle,
561
+ schema
562
+ },
563
+ options
564
+ );
565
+ }
566
+ var LEASE_ENDPOINT, LEASE_RENEW_ENDPOINT;
567
+ var init_sql_client = __esm({
568
+ "src/db/sql/sql-client.ts"() {
569
+ init_db_errors();
570
+ init_lease();
571
+ LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
572
+ LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
573
+ }
574
+ });
575
+
177
576
  // src/auth.ts
178
577
  init_errors();
179
578
 
@@ -753,35 +1152,8 @@ function createSupportNamespace(config) {
753
1152
  };
754
1153
  }
755
1154
 
756
- // src/db-errors.ts
757
- init_errors();
758
- var RETRYABLE_CODES2 = /* @__PURE__ */ new Set([
759
- "db_unavailable",
760
- "db_conflict",
761
- "db_timeout"
762
- ]);
763
- var NeetruDbError = class _NeetruDbError extends exports.NeetruError {
764
- /** Código de erro fechado — específico de DB. */
765
- code;
766
- /**
767
- * `true` para erros transientes que o produto pode tentar novamente.
768
- * São retryable: `db_unavailable`, `db_conflict`, `db_timeout`.
769
- */
770
- retryable;
771
- /**
772
- * ID opaco do banco lógico — só para correlação com logs do Core.
773
- * Nunca deve ser exibido ao usuário final.
774
- */
775
- dbId;
776
- constructor(code, message, dbId) {
777
- super(code, message);
778
- this.name = "NeetruDbError";
779
- this.code = code;
780
- this.retryable = RETRYABLE_CODES2.has(code);
781
- this.dbId = dbId;
782
- Object.setPrototypeOf(this, _NeetruDbError.prototype);
783
- }
784
- };
1155
+ // src/db/client-db.ts
1156
+ init_db_errors();
785
1157
 
786
1158
  // src/db/offline/query-engine.ts
787
1159
  function typeRank(v) {
@@ -2828,13 +3200,14 @@ var TabCoordinator = class {
2828
3200
  };
2829
3201
 
2830
3202
  // src/db/collection-ref.ts
3203
+ init_db_errors();
2831
3204
  function isIndexedDBUnavailable() {
2832
3205
  return typeof indexedDB === "undefined" || typeof window === "undefined";
2833
3206
  }
2834
3207
  var COLL_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
2835
3208
  function assertValidCollection(name) {
2836
3209
  if (!COLL_RE.test(name)) {
2837
- throw new NeetruDbError(
3210
+ throw new exports.NeetruDbError(
2838
3211
  "db_invalid_query",
2839
3212
  `Nome de cole\xE7\xE3o inv\xE1lido: "${name}". Deve seguir o padr\xE3o ${COLL_RE}.`
2840
3213
  );
@@ -2842,7 +3215,7 @@ function assertValidCollection(name) {
2842
3215
  }
2843
3216
  function assertValidId(id, label = "id") {
2844
3217
  if (!id || typeof id !== "string" || id.trim() === "") {
2845
- throw new NeetruDbError(
3218
+ throw new exports.NeetruDbError(
2846
3219
  "db_invalid_query",
2847
3220
  `${label} \xE9 obrigat\xF3rio e deve ser uma string n\xE3o-vazia.`
2848
3221
  );
@@ -3413,307 +3786,6 @@ async function createOfflineDocumentsNamespace(opts) {
3413
3786
  });
3414
3787
  }
3415
3788
 
3416
- // src/db/sql/lease.ts
3417
- var RENEWAL_THRESHOLD = 0.8;
3418
- var MIN_RENEWAL_DELAY_MS = 3e4;
3419
- var SqlLeaseManager = class {
3420
- _lease;
3421
- _pool;
3422
- _orm;
3423
- _renewalTimer = null;
3424
- _closed = false;
3425
- _deps;
3426
- constructor(lease, pool, orm, deps) {
3427
- this._lease = lease;
3428
- this._pool = pool;
3429
- this._orm = orm;
3430
- this._deps = {
3431
- ...deps,
3432
- now: deps.now ?? (() => Date.now())
3433
- };
3434
- this._scheduleRenewal();
3435
- }
3436
- // ─── Superfície pública (NeetruSqlClient) ──────────────────────────────────
3437
- get orm() {
3438
- this._assertOpen();
3439
- return this._orm;
3440
- }
3441
- async transaction(fn, opts) {
3442
- this._assertOpen();
3443
- try {
3444
- if (opts?.isolationLevel) {
3445
- return await this._orm.transaction(fn, {
3446
- isolationLevel: opts.isolationLevel
3447
- });
3448
- }
3449
- return await this._orm.transaction(fn);
3450
- } catch (err) {
3451
- throw mapPoolError(err);
3452
- }
3453
- }
3454
- async close() {
3455
- if (this._closed) return;
3456
- this._closed = true;
3457
- this._cancelRenewal();
3458
- await this._pool.end().catch(() => {
3459
- });
3460
- }
3461
- // ─── Renovação proativa ────────────────────────────────────────────────────
3462
- /**
3463
- * Calcula o delay de renovação: momento de ~80% do TTL restante.
3464
- *
3465
- * Fórmula: `renewAt = issuedAt + TTL * RENEWAL_THRESHOLD`
3466
- * = `expiresAt - TTL * (1 - RENEWAL_THRESHOLD)`
3467
- *
3468
- * Se o lease já está além do ponto de renovação (ou TTL não calculável),
3469
- * usa `MIN_RENEWAL_DELAY_MS` como fallback seguro.
3470
- */
3471
- _calcRenewalDelayMs() {
3472
- const expiresAtMs = Date.parse(this._lease.expiresAt);
3473
- if (!Number.isFinite(expiresAtMs)) return MIN_RENEWAL_DELAY_MS;
3474
- const now = this._deps.now();
3475
- const remainingMs = expiresAtMs - now;
3476
- if (remainingMs <= 0) return MIN_RENEWAL_DELAY_MS;
3477
- const renewInMs = remainingMs * (1 - RENEWAL_THRESHOLD);
3478
- return Math.max(MIN_RENEWAL_DELAY_MS, Math.round(renewInMs));
3479
- }
3480
- _scheduleRenewal() {
3481
- if (this._closed) return;
3482
- const delayMs = this._calcRenewalDelayMs();
3483
- this._renewalTimer = setTimeout(() => {
3484
- void this._doRenew();
3485
- }, delayMs);
3486
- }
3487
- _cancelRenewal() {
3488
- if (this._renewalTimer !== null) {
3489
- clearTimeout(this._renewalTimer);
3490
- this._renewalTimer = null;
3491
- }
3492
- }
3493
- async _doRenew() {
3494
- if (this._closed) return;
3495
- try {
3496
- const renewOpts = {
3497
- leaseId: this._lease.leaseId
3498
- };
3499
- if (this._deps.database !== void 0) {
3500
- renewOpts.database = this._deps.database;
3501
- }
3502
- const newLease = await this._deps.fetchLease(renewOpts);
3503
- await this._swapPool(newLease);
3504
- } catch (err) {
3505
- this._renewalTimer = setTimeout(() => {
3506
- void this._doRenew();
3507
- }, MIN_RENEWAL_DELAY_MS);
3508
- if (process.env["NODE_ENV"] !== "production") {
3509
- console.warn("[SqlLeaseManager] Lease renewal failed, will retry:", err);
3510
- }
3511
- return;
3512
- }
3513
- this._scheduleRenewal();
3514
- }
3515
- /**
3516
- * Hot-swap do pool: cria pool novo, atualiza ponteiros, drena pool antigo.
3517
- *
3518
- * Exposto como método protegido para testes.
3519
- */
3520
- async _swapPool(newLease) {
3521
- const credRotated = newLease.credentialVersion !== this._lease.credentialVersion;
3522
- if (!credRotated && newLease.leaseId === this._lease.leaseId) {
3523
- this._lease = newLease;
3524
- return;
3525
- }
3526
- const newPool = this._deps.createPool(newLease);
3527
- const newOrm = this._deps.createDrizzle(newPool, this._deps.schema);
3528
- const oldPool = this._pool;
3529
- this._lease = newLease;
3530
- this._pool = newPool;
3531
- this._orm = newOrm;
3532
- void oldPool.end().catch(() => {
3533
- });
3534
- }
3535
- // ─── Helpers ───────────────────────────────────────────────────────────────
3536
- _assertOpen() {
3537
- if (this._closed) {
3538
- throw new NeetruDbError(
3539
- "db_unavailable",
3540
- "SqlLeaseManager foi fechado \u2014 chame close() apenas no shutdown."
3541
- );
3542
- }
3543
- }
3544
- };
3545
- function mapPoolError(err) {
3546
- if (err instanceof NeetruDbError) return err;
3547
- const message = err instanceof Error ? err.message : String(err);
3548
- const code = err instanceof Error ? err.code : void 0;
3549
- if (typeof code === "string" && (code === "ECONNREFUSED" || code === "ENOTFOUND" || code === "ETIMEDOUT")) {
3550
- return new NeetruDbError(
3551
- "db_unavailable",
3552
- `Pool connection failed (${code}): ${message}`
3553
- );
3554
- }
3555
- if (message.includes("statement timeout") || message.includes("lock timeout")) {
3556
- return new NeetruDbError("db_timeout", `Query timeout: ${message}`);
3557
- }
3558
- if (message.includes("40001") || message.includes("40P01") || message.includes("deadlock")) {
3559
- return new NeetruDbError("db_conflict", `Transaction conflict: ${message}`);
3560
- }
3561
- if (message.includes("42501") || message.toLowerCase().includes("permission denied")) {
3562
- return new NeetruDbError("db_permission_denied", `Permission denied: ${message}`);
3563
- }
3564
- return new NeetruDbError("db_unavailable", `Database error: ${message}`);
3565
- }
3566
-
3567
- // src/db/sql/sql-client.ts
3568
- var LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
3569
- var LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
3570
- function mapEnvToCore(sdkEnv) {
3571
- if (sdkEnv === "workspace") return "staging";
3572
- if (sdkEnv === "prod") return "production";
3573
- return "dev";
3574
- }
3575
- function createHttpLeaseFetcher(config) {
3576
- return async (opts) => {
3577
- const { httpRequest: httpRequest2 } = await Promise.resolve().then(() => (init_http(), http_exports));
3578
- const isRenewal = Boolean(opts?.leaseId);
3579
- const path = isRenewal ? LEASE_RENEW_ENDPOINT : LEASE_ENDPOINT;
3580
- let body;
3581
- if (isRenewal) {
3582
- body = { leaseId: opts.leaseId };
3583
- } else {
3584
- body = {
3585
- productId: config.productId ?? "",
3586
- environment: mapEnvToCore(config.env ?? "prod")
3587
- };
3588
- if (opts?.database !== void 0) {
3589
- body["database"] = opts.database;
3590
- }
3591
- }
3592
- let raw;
3593
- try {
3594
- raw = await httpRequest2(config, {
3595
- method: "POST",
3596
- path,
3597
- body,
3598
- requireAuth: true,
3599
- // Lease fetch é idempotente do ponto de vista de segurança — retries OK.
3600
- retries: 2
3601
- });
3602
- } catch (err) {
3603
- const msg = err instanceof Error ? err.message : String(err);
3604
- throw new NeetruDbError(
3605
- "db_unavailable",
3606
- `Falha ao ${isRenewal ? "renovar" : "obter"} lease SQL: ${msg}`
3607
- );
3608
- }
3609
- return parseLease(raw);
3610
- };
3611
- }
3612
- function parseLease(raw) {
3613
- if (!raw || typeof raw !== "object") {
3614
- throw new NeetruDbError("db_unavailable", "Resposta de lease inv\xE1lida (n\xE3o \xE9 objeto).");
3615
- }
3616
- const envelope = raw;
3617
- const leaseObj = envelope["lease"];
3618
- if (!leaseObj || typeof leaseObj !== "object") {
3619
- throw new NeetruDbError(
3620
- "db_unavailable",
3621
- 'Resposta de lease inv\xE1lida: campo "lease" ausente ou n\xE3o \xE9 objeto. O Core retorna { kind, lease: { leaseId, host, ... } }.'
3622
- );
3623
- }
3624
- const r = leaseObj;
3625
- function req(field, type) {
3626
- if (typeof r[field] !== type) {
3627
- throw new NeetruDbError(
3628
- "db_unavailable",
3629
- `Campo obrigat\xF3rio "${field}" ausente ou tipo inv\xE1lido na resposta de lease.`
3630
- );
3631
- }
3632
- return r[field];
3633
- }
3634
- return {
3635
- leaseId: req("leaseId", "string"),
3636
- host: req("host", "string"),
3637
- port: typeof r["port"] === "number" ? r["port"] : 5433,
3638
- dbName: req("dbName", "string"),
3639
- user: req("user", "string"),
3640
- password: req("password", "string"),
3641
- sslca: typeof r["sslca"] === "string" ? r["sslca"] : null,
3642
- clientCert: typeof r["clientCert"] === "string" ? r["clientCert"] : null,
3643
- clientKey: typeof r["clientKey"] === "string" ? r["clientKey"] : null,
3644
- credentialVersion: typeof r["credentialVersion"] === "number" ? r["credentialVersion"] : 1,
3645
- expiresAt: req("expiresAt", "string")
3646
- };
3647
- }
3648
- async function createSqlClientWithDeps(deps, options) {
3649
- const depsWithDb = options?.database !== void 0 ? { ...deps, database: options.database } : deps;
3650
- let lease;
3651
- try {
3652
- if (options?.database !== void 0) {
3653
- lease = await depsWithDb.fetchLease({ database: options.database });
3654
- } else {
3655
- lease = await depsWithDb.fetchLease();
3656
- }
3657
- } catch (err) {
3658
- if (err instanceof NeetruDbError) throw err;
3659
- const msg = err instanceof Error ? err.message : String(err);
3660
- throw new NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
3661
- }
3662
- let pool;
3663
- try {
3664
- pool = depsWithDb.createPool(lease);
3665
- } catch (err) {
3666
- const msg = err instanceof Error ? err.message : String(err);
3667
- throw new NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
3668
- }
3669
- let orm;
3670
- try {
3671
- orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
3672
- } catch (err) {
3673
- await pool.end().catch(() => {
3674
- });
3675
- const msg = err instanceof Error ? err.message : String(err);
3676
- throw new NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
3677
- }
3678
- return new SqlLeaseManager(lease, pool, orm, depsWithDb);
3679
- }
3680
- async function createSqlClientFromConfig(config, schema, options) {
3681
- const fetchLease = createHttpLeaseFetcher(config);
3682
- const createPool = (lease) => {
3683
- const { Pool } = __require("pg");
3684
- const ssl = lease.sslca ? {
3685
- ca: lease.sslca,
3686
- cert: lease.clientCert ?? void 0,
3687
- key: lease.clientKey ?? void 0,
3688
- rejectUnauthorized: true
3689
- } : false;
3690
- return new Pool({
3691
- host: lease.host,
3692
- port: lease.port,
3693
- database: lease.dbName,
3694
- user: lease.user,
3695
- password: lease.password,
3696
- ssl,
3697
- max: 3,
3698
- idleTimeoutMillis: 3e4,
3699
- connectionTimeoutMillis: 1e4
3700
- });
3701
- };
3702
- const createDrizzle = (pool, s) => {
3703
- const { drizzle } = __require("drizzle-orm/node-postgres");
3704
- return drizzle(pool, { schema: s });
3705
- };
3706
- return createSqlClientWithDeps(
3707
- {
3708
- fetchLease,
3709
- createPool,
3710
- createDrizzle,
3711
- schema
3712
- },
3713
- options
3714
- );
3715
- }
3716
-
3717
3789
  // src/db/realtime/realtime-client.ts
3718
3790
  var WS_OPEN = 1;
3719
3791
  var WS_CLOSED = 3;
@@ -4160,7 +4232,7 @@ function createRestSyncTransport(config) {
4160
4232
  return {
4161
4233
  async pushMutations(mutations) {
4162
4234
  if (!config) {
4163
- throw new NeetruDbError(
4235
+ throw new exports.NeetruDbError(
4164
4236
  "db_unavailable",
4165
4237
  "[RestSyncTransport] config n\xE3o dispon\xEDvel \u2014 n\xE3o \xE9 poss\xEDvel enviar ao Core. Inicialize o transporte via createNeetruDb."
4166
4238
  );
@@ -4217,7 +4289,7 @@ function createRestSyncTransport(config) {
4217
4289
  });
4218
4290
  } catch (err) {
4219
4291
  const msg = err instanceof Error ? err.message : String(err);
4220
- throw new NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
4292
+ throw new exports.NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
4221
4293
  }
4222
4294
  }
4223
4295
  return { results };
@@ -4331,12 +4403,13 @@ function createNeetruDb(config, dbOpts = {}) {
4331
4403
  },
4332
4404
  async sql(schema, options) {
4333
4405
  if (config.env === "dev") {
4334
- throw new NeetruDbError(
4406
+ throw new exports.NeetruDbError(
4335
4407
  "db_unavailable",
4336
4408
  "[SDK] db.sql() n\xE3o dispon\xEDvel em NEETRU_ENV=dev. Use `neetru dev` para subir o container Postgres local."
4337
4409
  );
4338
4410
  }
4339
- return createSqlClientFromConfig(config, schema, options);
4411
+ const sqlClientMod = await Promise.resolve().then(() => (init_sql_client(), sql_client_exports));
4412
+ return sqlClientMod.createSqlClientFromConfig(config, schema, options);
4340
4413
  },
4341
4414
  get syncState() {
4342
4415
  return _docsResolved?.syncState ?? {
@@ -4941,6 +5014,7 @@ var MockNotifications = class {
4941
5014
  };
4942
5015
 
4943
5016
  // src/mocks.ts
5017
+ init_db_errors();
4944
5018
  var DEV_FIXTURE_USER = Object.freeze({
4945
5019
  uid: "dev-fixture-uid-0001",
4946
5020
  email: "dev@neetru.local",
@@ -5391,8 +5465,14 @@ var MockDb = class {
5391
5465
  async sql(schema, options) {
5392
5466
  const ddls = options?.initSql ? Array.isArray(options.initSql) ? options.initSql : [options.initSql] : [];
5393
5467
  try {
5394
- const pglitePkg = await import('@electric-sql/pglite');
5395
- const drizzlePglitePkg = await import('drizzle-orm/pglite');
5468
+ const pglitePkg = await import(
5469
+ /* webpackIgnore: true */
5470
+ '@electric-sql/pglite'
5471
+ );
5472
+ const drizzlePglitePkg = await import(
5473
+ /* webpackIgnore: true */
5474
+ 'drizzle-orm/pglite'
5475
+ );
5396
5476
  const pglite = await pglitePkg.PGlite.create();
5397
5477
  for (const ddl of ddls) {
5398
5478
  await pglite.query(ddl);
@@ -5413,10 +5493,16 @@ var MockDb = class {
5413
5493
  let pgMemMod;
5414
5494
  let drizzlePg;
5415
5495
  try {
5416
- pgMemMod = await import('pg-mem');
5417
- drizzlePg = await import('drizzle-orm/node-postgres');
5496
+ pgMemMod = await import(
5497
+ /* webpackIgnore: true */
5498
+ 'pg-mem'
5499
+ );
5500
+ drizzlePg = await import(
5501
+ /* webpackIgnore: true */
5502
+ 'drizzle-orm/node-postgres'
5503
+ );
5418
5504
  } catch {
5419
- throw new NeetruDbError(
5505
+ throw new exports.NeetruDbError(
5420
5506
  "db_unavailable",
5421
5507
  "[MockDb] sql() requer @electric-sql/pglite OU pg-mem + drizzle-orm + pg como devDependencies. Preferido: npm install -D @electric-sql/pglite drizzle-orm. Alternativo: npm install -D pg-mem drizzle-orm pg."
5422
5508
  );
@@ -5730,6 +5816,7 @@ function createNeetruClient(config = {}) {
5730
5816
 
5731
5817
  // src/index.ts
5732
5818
  init_errors();
5819
+ init_db_errors();
5733
5820
  var VERSION = "2.0.0";
5734
5821
  function initNeetru(config) {
5735
5822
  const { apiUrl: _apiUrl, baseUrl, ...rest } = config;
@@ -5746,7 +5833,6 @@ exports.MockNotifications = MockNotifications;
5746
5833
  exports.MockSupport = MockSupport;
5747
5834
  exports.MockUsage = MockUsage;
5748
5835
  exports.MockWebhooks = MockWebhooks;
5749
- exports.NeetruDbError = NeetruDbError;
5750
5836
  exports.VERSION = VERSION;
5751
5837
  exports.createCheckoutNamespace = createCheckoutNamespace;
5752
5838
  exports.createNeetruClient = createNeetruClient;