@neetru/sdk 2.3.3 → 2.3.5
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 +295 -483
- package/dist/auth.cjs +404 -337
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.mjs +404 -337
- package/dist/auth.mjs.map +1 -1
- package/dist/db.cjs +409 -342
- package/dist/db.cjs.map +1 -1
- package/dist/db.mjs +404 -336
- package/dist/db.mjs.map +1 -1
- package/dist/index.cjs +460 -363
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +454 -356
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.cjs +48 -19
- package/dist/mocks.cjs.map +1 -1
- package/dist/mocks.d.cts +14 -10
- package/dist/mocks.d.ts +14 -10
- package/dist/mocks.mjs +48 -19
- package/dist/mocks.mjs.map +1 -1
- package/package.json +6 -1
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,405 @@ 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('pg');
|
|
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('drizzle-orm/node-postgres');
|
|
474
|
+
return drizzle(pool, { schema });
|
|
475
|
+
}
|
|
476
|
+
async function createSqlClientWithDeps(deps, options) {
|
|
477
|
+
const depsWithDb = options?.database !== void 0 ? { ...deps, database: options.database } : deps;
|
|
478
|
+
let lease;
|
|
479
|
+
try {
|
|
480
|
+
if (options?.database !== void 0) {
|
|
481
|
+
lease = await depsWithDb.fetchLease({ database: options.database });
|
|
482
|
+
} else {
|
|
483
|
+
lease = await depsWithDb.fetchLease();
|
|
484
|
+
}
|
|
485
|
+
} catch (err) {
|
|
486
|
+
if (err instanceof exports.NeetruDbError) throw err;
|
|
487
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
488
|
+
throw new exports.NeetruDbError("db_unavailable", `Falha ao obter lease inicial: ${msg}`);
|
|
489
|
+
}
|
|
490
|
+
let pool;
|
|
491
|
+
try {
|
|
492
|
+
pool = depsWithDb.createPool(lease);
|
|
493
|
+
} catch (err) {
|
|
494
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
495
|
+
throw new exports.NeetruDbError("db_unavailable", `Falha ao abrir pool: ${msg}`);
|
|
496
|
+
}
|
|
497
|
+
let orm;
|
|
498
|
+
try {
|
|
499
|
+
orm = depsWithDb.createDrizzle(pool, depsWithDb.schema);
|
|
500
|
+
} catch (err) {
|
|
501
|
+
await pool.end().catch(() => {
|
|
502
|
+
});
|
|
503
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
504
|
+
throw new exports.NeetruDbError("db_unavailable", `Falha ao criar handle Drizzle: ${msg}`);
|
|
505
|
+
}
|
|
506
|
+
return new SqlLeaseManager(lease, pool, orm, depsWithDb);
|
|
507
|
+
}
|
|
508
|
+
async function createSqlClientFromConfig(config, schema, options) {
|
|
509
|
+
const fetchLease = createHttpLeaseFetcher(config);
|
|
510
|
+
let pgMod;
|
|
511
|
+
let drizzlePgMod;
|
|
512
|
+
try {
|
|
513
|
+
pgMod = await import(
|
|
514
|
+
/* webpackIgnore: true */
|
|
515
|
+
'pg'
|
|
516
|
+
);
|
|
517
|
+
drizzlePgMod = await import(
|
|
518
|
+
/* webpackIgnore: true */
|
|
519
|
+
'drizzle-orm/node-postgres'
|
|
520
|
+
);
|
|
521
|
+
} catch (err) {
|
|
522
|
+
throw new exports.NeetruDbError(
|
|
523
|
+
"db_unavailable",
|
|
524
|
+
`[client.db.sql] requer pg + drizzle-orm como peerDependencies do servidor. Instale: npm install pg drizzle-orm. (${err.message})`
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
const createPool = (lease) => {
|
|
528
|
+
const Pool = pgMod.Pool ?? pgMod.default?.Pool;
|
|
529
|
+
const ssl = lease.sslca ? {
|
|
530
|
+
ca: lease.sslca,
|
|
531
|
+
cert: lease.clientCert ?? void 0,
|
|
532
|
+
key: lease.clientKey ?? void 0,
|
|
533
|
+
rejectUnauthorized: true
|
|
534
|
+
} : false;
|
|
535
|
+
return new Pool({
|
|
536
|
+
host: lease.host,
|
|
537
|
+
port: lease.port,
|
|
538
|
+
database: lease.dbName,
|
|
539
|
+
user: lease.user,
|
|
540
|
+
password: lease.password,
|
|
541
|
+
ssl,
|
|
542
|
+
max: 3,
|
|
543
|
+
idleTimeoutMillis: 3e4,
|
|
544
|
+
connectionTimeoutMillis: 1e4
|
|
545
|
+
});
|
|
546
|
+
};
|
|
547
|
+
const createDrizzle = (pool, s) => {
|
|
548
|
+
return drizzlePgMod.drizzle(pool, { schema: s });
|
|
549
|
+
};
|
|
550
|
+
return createSqlClientWithDeps(
|
|
551
|
+
{
|
|
552
|
+
fetchLease,
|
|
553
|
+
createPool,
|
|
554
|
+
createDrizzle,
|
|
555
|
+
schema
|
|
556
|
+
},
|
|
557
|
+
options
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
var LEASE_ENDPOINT, LEASE_RENEW_ENDPOINT;
|
|
561
|
+
var init_sql_client = __esm({
|
|
562
|
+
"src/db/sql/sql-client.ts"() {
|
|
563
|
+
init_db_errors();
|
|
564
|
+
init_lease();
|
|
565
|
+
LEASE_ENDPOINT = "/api/sdk/v1/db/lease";
|
|
566
|
+
LEASE_RENEW_ENDPOINT = "/api/sdk/v1/db/lease/renew";
|
|
567
|
+
}
|
|
568
|
+
});
|
|
569
|
+
|
|
177
570
|
// src/auth.ts
|
|
178
571
|
init_errors();
|
|
179
572
|
|
|
@@ -753,35 +1146,8 @@ function createSupportNamespace(config) {
|
|
|
753
1146
|
};
|
|
754
1147
|
}
|
|
755
1148
|
|
|
756
|
-
// src/db-
|
|
757
|
-
|
|
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
|
-
};
|
|
1149
|
+
// src/db/client-db.ts
|
|
1150
|
+
init_db_errors();
|
|
785
1151
|
|
|
786
1152
|
// src/db/offline/query-engine.ts
|
|
787
1153
|
function typeRank(v) {
|
|
@@ -2828,13 +3194,14 @@ var TabCoordinator = class {
|
|
|
2828
3194
|
};
|
|
2829
3195
|
|
|
2830
3196
|
// src/db/collection-ref.ts
|
|
3197
|
+
init_db_errors();
|
|
2831
3198
|
function isIndexedDBUnavailable() {
|
|
2832
3199
|
return typeof indexedDB === "undefined" || typeof window === "undefined";
|
|
2833
3200
|
}
|
|
2834
3201
|
var COLL_RE = /^[a-z0-9][a-z0-9_-]{0,62}$/;
|
|
2835
3202
|
function assertValidCollection(name) {
|
|
2836
3203
|
if (!COLL_RE.test(name)) {
|
|
2837
|
-
throw new NeetruDbError(
|
|
3204
|
+
throw new exports.NeetruDbError(
|
|
2838
3205
|
"db_invalid_query",
|
|
2839
3206
|
`Nome de cole\xE7\xE3o inv\xE1lido: "${name}". Deve seguir o padr\xE3o ${COLL_RE}.`
|
|
2840
3207
|
);
|
|
@@ -2842,7 +3209,7 @@ function assertValidCollection(name) {
|
|
|
2842
3209
|
}
|
|
2843
3210
|
function assertValidId(id, label = "id") {
|
|
2844
3211
|
if (!id || typeof id !== "string" || id.trim() === "") {
|
|
2845
|
-
throw new NeetruDbError(
|
|
3212
|
+
throw new exports.NeetruDbError(
|
|
2846
3213
|
"db_invalid_query",
|
|
2847
3214
|
`${label} \xE9 obrigat\xF3rio e deve ser uma string n\xE3o-vazia.`
|
|
2848
3215
|
);
|
|
@@ -3413,307 +3780,6 @@ async function createOfflineDocumentsNamespace(opts) {
|
|
|
3413
3780
|
});
|
|
3414
3781
|
}
|
|
3415
3782
|
|
|
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
3783
|
// src/db/realtime/realtime-client.ts
|
|
3718
3784
|
var WS_OPEN = 1;
|
|
3719
3785
|
var WS_CLOSED = 3;
|
|
@@ -4160,7 +4226,7 @@ function createRestSyncTransport(config) {
|
|
|
4160
4226
|
return {
|
|
4161
4227
|
async pushMutations(mutations) {
|
|
4162
4228
|
if (!config) {
|
|
4163
|
-
throw new NeetruDbError(
|
|
4229
|
+
throw new exports.NeetruDbError(
|
|
4164
4230
|
"db_unavailable",
|
|
4165
4231
|
"[RestSyncTransport] config n\xE3o dispon\xEDvel \u2014 n\xE3o \xE9 poss\xEDvel enviar ao Core. Inicialize o transporte via createNeetruDb."
|
|
4166
4232
|
);
|
|
@@ -4217,7 +4283,7 @@ function createRestSyncTransport(config) {
|
|
|
4217
4283
|
});
|
|
4218
4284
|
} catch (err) {
|
|
4219
4285
|
const msg = err instanceof Error ? err.message : String(err);
|
|
4220
|
-
throw new NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
|
|
4286
|
+
throw new exports.NeetruDbError("db_unavailable", `pushMutations falhou: ${msg}`);
|
|
4221
4287
|
}
|
|
4222
4288
|
}
|
|
4223
4289
|
return { results };
|
|
@@ -4331,12 +4397,13 @@ function createNeetruDb(config, dbOpts = {}) {
|
|
|
4331
4397
|
},
|
|
4332
4398
|
async sql(schema, options) {
|
|
4333
4399
|
if (config.env === "dev") {
|
|
4334
|
-
throw new NeetruDbError(
|
|
4400
|
+
throw new exports.NeetruDbError(
|
|
4335
4401
|
"db_unavailable",
|
|
4336
4402
|
"[SDK] db.sql() n\xE3o dispon\xEDvel em NEETRU_ENV=dev. Use `neetru dev` para subir o container Postgres local."
|
|
4337
4403
|
);
|
|
4338
4404
|
}
|
|
4339
|
-
|
|
4405
|
+
const sqlClientMod = await Promise.resolve().then(() => (init_sql_client(), sql_client_exports));
|
|
4406
|
+
return sqlClientMod.createSqlClientFromConfig(config, schema, options);
|
|
4340
4407
|
},
|
|
4341
4408
|
get syncState() {
|
|
4342
4409
|
return _docsResolved?.syncState ?? {
|
|
@@ -4941,6 +5008,7 @@ var MockNotifications = class {
|
|
|
4941
5008
|
};
|
|
4942
5009
|
|
|
4943
5010
|
// src/mocks.ts
|
|
5011
|
+
init_db_errors();
|
|
4944
5012
|
var DEV_FIXTURE_USER = Object.freeze({
|
|
4945
5013
|
uid: "dev-fixture-uid-0001",
|
|
4946
5014
|
email: "dev@neetru.local",
|
|
@@ -5370,35 +5438,59 @@ var MockDb = class {
|
|
|
5370
5438
|
return ref;
|
|
5371
5439
|
}
|
|
5372
5440
|
/**
|
|
5373
|
-
* Implementa SQL in-memory
|
|
5441
|
+
* Implementa SQL in-memory para testes — preferência: `@electric-sql/pglite`
|
|
5442
|
+
* + `drizzle-orm/pglite`; fallback: `pg-mem` + `drizzle-orm/node-postgres`
|
|
5443
|
+
* com strip interceptor.
|
|
5374
5444
|
*
|
|
5375
|
-
*
|
|
5376
|
-
*
|
|
5377
|
-
*
|
|
5378
|
-
*
|
|
5445
|
+
* bug_6bae8674461f4600809e2f9de13c6043 (HIGH 26/05): fix anterior (2.3.3)
|
|
5446
|
+
* teve efeito INVERSO. Injetar `pool.types.getTypeParser` fez drizzle-orm
|
|
5447
|
+
* passar `query.types.getTypeParser` pro pg-mem, que tem guard interno
|
|
5448
|
+
* `if (query.types?.getTypeParser) throw NotSupported`. Ou seja: presença
|
|
5449
|
+
* de getTypeParser é o gatilho do erro — não ausência.
|
|
5379
5450
|
*
|
|
5380
|
-
*
|
|
5381
|
-
*
|
|
5382
|
-
*
|
|
5451
|
+
* Solução A (pglite, preferida): `@electric-sql/pglite` é WASM-Postgres
|
|
5452
|
+
* nativo — sem adapter pg-mem, sem esse conflito de tipos.
|
|
5453
|
+
* Solução B (fallback pg-mem): strip interceptor no pool.query que deleta
|
|
5454
|
+
* `types.getTypeParser` do objeto de query ANTES do pg-mem ver. Compat com
|
|
5455
|
+
* consumers que só têm pg-mem nas devDeps.
|
|
5383
5456
|
*
|
|
5384
|
-
* Use `options.initSql` pra
|
|
5385
|
-
* funciona mas qualquer SELECT/INSERT em tabela inexistente vai falhar.
|
|
5457
|
+
* Use `options.initSql` pra rodar DDL no setup (CREATE TABLE etc).
|
|
5386
5458
|
*/
|
|
5387
5459
|
async sql(schema, options) {
|
|
5460
|
+
const ddls = options?.initSql ? Array.isArray(options.initSql) ? options.initSql : [options.initSql] : [];
|
|
5461
|
+
try {
|
|
5462
|
+
const pglitePkg = await import('@electric-sql/pglite');
|
|
5463
|
+
const drizzlePglitePkg = await import('drizzle-orm/pglite');
|
|
5464
|
+
const pglite = await pglitePkg.PGlite.create();
|
|
5465
|
+
for (const ddl of ddls) {
|
|
5466
|
+
await pglite.query(ddl);
|
|
5467
|
+
}
|
|
5468
|
+
const pgliteOrm = drizzlePglitePkg.drizzle(pglite, { schema });
|
|
5469
|
+
const ormAsNode = pgliteOrm;
|
|
5470
|
+
return {
|
|
5471
|
+
orm: ormAsNode,
|
|
5472
|
+
async transaction(fn, _opts) {
|
|
5473
|
+
return await pgliteOrm.transaction(fn);
|
|
5474
|
+
},
|
|
5475
|
+
async close() {
|
|
5476
|
+
if (!pglite.closed) await pglite.close();
|
|
5477
|
+
}
|
|
5478
|
+
};
|
|
5479
|
+
} catch (_pgliteErr) {
|
|
5480
|
+
}
|
|
5388
5481
|
let pgMemMod;
|
|
5389
5482
|
let drizzlePg;
|
|
5390
5483
|
try {
|
|
5391
5484
|
pgMemMod = await import('pg-mem');
|
|
5392
5485
|
drizzlePg = await import('drizzle-orm/node-postgres');
|
|
5393
5486
|
} catch {
|
|
5394
|
-
throw new NeetruDbError(
|
|
5487
|
+
throw new exports.NeetruDbError(
|
|
5395
5488
|
"db_unavailable",
|
|
5396
|
-
"[MockDb] sql() requer pg-mem + drizzle-orm + pg como devDependencies
|
|
5489
|
+
"[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."
|
|
5397
5490
|
);
|
|
5398
5491
|
}
|
|
5399
5492
|
const memDb = pgMemMod.newDb({ noAstCoverageCheck: true });
|
|
5400
|
-
if (
|
|
5401
|
-
const ddls = Array.isArray(options.initSql) ? options.initSql : [options.initSql];
|
|
5493
|
+
if (ddls.length > 0) {
|
|
5402
5494
|
for (const ddl of ddls) {
|
|
5403
5495
|
memDb.public.none(ddl);
|
|
5404
5496
|
}
|
|
@@ -5406,12 +5498,17 @@ var MockDb = class {
|
|
|
5406
5498
|
const adapters = memDb.adapters.createPg();
|
|
5407
5499
|
const PoolCtor = adapters.Pool;
|
|
5408
5500
|
const pool = new PoolCtor();
|
|
5409
|
-
const
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5501
|
+
const origQuery = pool.query.bind(pool);
|
|
5502
|
+
pool.query = (q, ...rest) => {
|
|
5503
|
+
if (q !== null && typeof q === "object") {
|
|
5504
|
+
const queryObj = q;
|
|
5505
|
+
const types = queryObj["types"];
|
|
5506
|
+
if (types !== null && typeof types === "object" && "getTypeParser" in types) {
|
|
5507
|
+
delete types["getTypeParser"];
|
|
5508
|
+
}
|
|
5509
|
+
}
|
|
5510
|
+
return origQuery(q, ...rest);
|
|
5511
|
+
};
|
|
5415
5512
|
const orm = drizzlePg.drizzle(pool, { schema });
|
|
5416
5513
|
return {
|
|
5417
5514
|
orm,
|
|
@@ -5701,6 +5798,7 @@ function createNeetruClient(config = {}) {
|
|
|
5701
5798
|
|
|
5702
5799
|
// src/index.ts
|
|
5703
5800
|
init_errors();
|
|
5801
|
+
init_db_errors();
|
|
5704
5802
|
var VERSION = "2.0.0";
|
|
5705
5803
|
function initNeetru(config) {
|
|
5706
5804
|
const { apiUrl: _apiUrl, baseUrl, ...rest } = config;
|
|
@@ -5717,7 +5815,6 @@ exports.MockNotifications = MockNotifications;
|
|
|
5717
5815
|
exports.MockSupport = MockSupport;
|
|
5718
5816
|
exports.MockUsage = MockUsage;
|
|
5719
5817
|
exports.MockWebhooks = MockWebhooks;
|
|
5720
|
-
exports.NeetruDbError = NeetruDbError;
|
|
5721
5818
|
exports.VERSION = VERSION;
|
|
5722
5819
|
exports.createCheckoutNamespace = createCheckoutNamespace;
|
|
5723
5820
|
exports.createNeetruClient = createNeetruClient;
|