@neetru/sdk 3.4.0 → 3.4.1
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 +18 -0
- package/dist/ai.d.cts +1 -1
- package/dist/ai.d.ts +1 -1
- package/dist/auth.cjs +193 -30
- package/dist/auth.cjs.map +1 -1
- package/dist/auth.d.cts +1 -1
- package/dist/auth.d.ts +1 -1
- package/dist/auth.mjs +193 -30
- package/dist/auth.mjs.map +1 -1
- package/dist/catalog.d.cts +1 -1
- package/dist/catalog.d.ts +1 -1
- package/dist/checkout.d.cts +1 -1
- package/dist/checkout.d.ts +1 -1
- package/dist/db-react.d.cts +1 -1
- package/dist/db-react.d.ts +1 -1
- package/dist/db.cjs +193 -30
- package/dist/db.cjs.map +1 -1
- package/dist/db.d.cts +1 -1
- package/dist/db.d.ts +1 -1
- package/dist/db.mjs +193 -30
- package/dist/db.mjs.map +1 -1
- package/dist/entitlements.d.cts +1 -1
- package/dist/entitlements.d.ts +1 -1
- package/dist/firestore-compat.d.cts +1 -1
- package/dist/firestore-compat.d.ts +1 -1
- package/dist/index.cjs +194 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.mjs +194 -31
- package/dist/index.mjs.map +1 -1
- package/dist/mocks.d.cts +1 -1
- package/dist/mocks.d.ts +1 -1
- package/dist/notifications.d.cts +1 -1
- package/dist/notifications.d.ts +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/support.d.cts +1 -1
- package/dist/support.d.ts +1 -1
- package/dist/telemetry.d.cts +1 -1
- package/dist/telemetry.d.ts +1 -1
- package/dist/{types-DdJyqBy-.d.ts → types-BlOLDMR0.d.ts} +79 -6
- package/dist/{types-BoFSCHF_.d.cts → types-D-QdG502.d.cts} +79 -6
- package/dist/usage.d.cts +1 -1
- package/dist/usage.d.ts +1 -1
- package/dist/webhooks.d.cts +1 -1
- package/dist/webhooks.d.ts +1 -1
- package/package.json +1 -1
package/dist/db.d.cts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { w as ConflictRecord, x as CreateOfflineDocumentsOptions, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, K as DbQuery, L as DbSqlLease, N as DbWhereFilter, P as FieldSentinel, Q as IncrementSentinel, V as NEETRU_SENTINEL_KEY, Y as NeetruDb, Z as NeetruDbDocuments, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a4 as NeetruSqlClient, ad as RealtimeTransport, ai as ServerTimestampSentinel, ak as SqlOptions, ap as SyncState,
|
|
1
|
+
export { w as ConflictRecord, x as CreateOfflineDocumentsOptions, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, K as DbQuery, L as DbSqlLease, N as DbWhereFilter, P as FieldSentinel, Q as IncrementSentinel, V as NEETRU_SENTINEL_KEY, Y as NeetruDb, Z as NeetruDbDocuments, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a4 as NeetruSqlClient, ad as RealtimeTransport, ai as ServerTimestampSentinel, ak as SqlOptions, ap as SyncErrorEvent, aq as SyncState, aH as WithFieldSentinels, aI as __resetDbIdWarningForTests, aL as createNeetruDb, aN as createRestSyncTransport, aP as getWebSocketRealtimeClient, aQ as increment, aR as isFieldSentinel, aS as isIncrementSentinel, aT as isServerTimestampSentinel, aU as serverTimestamp } from './types-D-QdG502.cjs';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.cjs';
|
package/dist/db.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { w as ConflictRecord, x as CreateOfflineDocumentsOptions, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, K as DbQuery, L as DbSqlLease, N as DbWhereFilter, P as FieldSentinel, Q as IncrementSentinel, V as NEETRU_SENTINEL_KEY, Y as NeetruDb, Z as NeetruDbDocuments, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a4 as NeetruSqlClient, ad as RealtimeTransport, ai as ServerTimestampSentinel, ak as SqlOptions, ap as SyncState,
|
|
1
|
+
export { w as ConflictRecord, x as CreateOfflineDocumentsOptions, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, K as DbQuery, L as DbSqlLease, N as DbWhereFilter, P as FieldSentinel, Q as IncrementSentinel, V as NEETRU_SENTINEL_KEY, Y as NeetruDb, Z as NeetruDbDocuments, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a4 as NeetruSqlClient, ad as RealtimeTransport, ai as ServerTimestampSentinel, ak as SqlOptions, ap as SyncErrorEvent, aq as SyncState, aH as WithFieldSentinels, aI as __resetDbIdWarningForTests, aL as createNeetruDb, aN as createRestSyncTransport, aP as getWebSocketRealtimeClient, aQ as increment, aR as isFieldSentinel, aS as isIncrementSentinel, aT as isServerTimestampSentinel, aU as serverTimestamp } from './types-BlOLDMR0.js';
|
|
2
2
|
import 'drizzle-orm/node-postgres';
|
|
3
3
|
import './errors.js';
|
package/dist/db.mjs
CHANGED
|
@@ -1734,8 +1734,10 @@ var MutationQueue = class {
|
|
|
1734
1734
|
await this.store.deleteMutation(mutationId);
|
|
1735
1735
|
}
|
|
1736
1736
|
/**
|
|
1737
|
-
* Marca uma mutação como `failed` e registra o erro.
|
|
1738
|
-
* Incrementa `attempts`. Chamado pelo SyncEngine
|
|
1737
|
+
* Marca uma mutação como `failed` (dead-letter) e registra o erro.
|
|
1738
|
+
* Incrementa `attempts` e carimba `lastAttemptAt`. Chamado pelo SyncEngine
|
|
1739
|
+
* quando a mutação esgota o teto de tentativas (mutação-veneno): sai do
|
|
1740
|
+
* conjunto `queued`, então `drain()` para de devolvê-la e a fila DESTRAVA.
|
|
1739
1741
|
*/
|
|
1740
1742
|
async markFailed(mutationId, error) {
|
|
1741
1743
|
const mut = await this.store.getMutation(mutationId);
|
|
@@ -1744,12 +1746,14 @@ var MutationQueue = class {
|
|
|
1744
1746
|
...mut,
|
|
1745
1747
|
status: "failed",
|
|
1746
1748
|
lastError: error,
|
|
1747
|
-
attempts: mut.attempts + 1
|
|
1749
|
+
attempts: mut.attempts + 1,
|
|
1750
|
+
lastAttemptAt: Date.now()
|
|
1748
1751
|
});
|
|
1749
1752
|
}
|
|
1750
1753
|
/**
|
|
1751
|
-
* Incrementa `attempts` e volta para `queued`
|
|
1752
|
-
* Chamado pelo SyncEngine em falha
|
|
1754
|
+
* Incrementa `attempts`, carimba `lastAttemptAt` e volta para `queued`
|
|
1755
|
+
* (falha transiente com backoff). Chamado pelo SyncEngine em falha
|
|
1756
|
+
* transiente (5xx, timeout) da mutação que REALMENTE foi tentada.
|
|
1753
1757
|
*/
|
|
1754
1758
|
async markRetry(mutationId, error) {
|
|
1755
1759
|
const mut = await this.store.getMutation(mutationId);
|
|
@@ -1758,9 +1762,24 @@ var MutationQueue = class {
|
|
|
1758
1762
|
...mut,
|
|
1759
1763
|
status: "queued",
|
|
1760
1764
|
lastError: error,
|
|
1761
|
-
attempts: mut.attempts + 1
|
|
1765
|
+
attempts: mut.attempts + 1,
|
|
1766
|
+
lastAttemptAt: Date.now()
|
|
1762
1767
|
});
|
|
1763
1768
|
}
|
|
1769
|
+
/**
|
|
1770
|
+
* Reverte uma mutação `inflight` de volta a `queued` SEM tocar em `attempts`
|
|
1771
|
+
* nem `lastAttemptAt`. Usado para mutações que foram marcadas inflight mas
|
|
1772
|
+
* NUNCA chegaram a ser enviadas (estavam atrás da head no FIFO e o push
|
|
1773
|
+
* parou antes delas): contá-las como tentativa as empurraria pro dead-letter
|
|
1774
|
+
* por engano, e adiaria seu envio via backoff sem que tenham falhado.
|
|
1775
|
+
* No-op se a mutação não está mais `inflight`.
|
|
1776
|
+
*/
|
|
1777
|
+
async requeueInflight(mutationId) {
|
|
1778
|
+
const mut = await this.store.getMutation(mutationId);
|
|
1779
|
+
if (!mut) return;
|
|
1780
|
+
if (mut.status !== "inflight") return;
|
|
1781
|
+
await this.store.putMutation({ ...mut, status: "queued" });
|
|
1782
|
+
}
|
|
1764
1783
|
};
|
|
1765
1784
|
|
|
1766
1785
|
// src/db/field-value.ts
|
|
@@ -1816,6 +1835,10 @@ function hasFieldSentinel(data) {
|
|
|
1816
1835
|
}
|
|
1817
1836
|
|
|
1818
1837
|
// src/db/offline/sync-engine.ts
|
|
1838
|
+
var MAX_MUTATION_ATTEMPTS = 8;
|
|
1839
|
+
var BACKOFF_BASE_MS = 1e3;
|
|
1840
|
+
var BACKOFF_CAP_MS = 5 * 60 * 1e3;
|
|
1841
|
+
var TRUNCATED_COLLECTIONS_META_KEY = "truncatedCollections";
|
|
1819
1842
|
function hasSentinels(payload) {
|
|
1820
1843
|
return payload !== null && hasFieldSentinel(payload);
|
|
1821
1844
|
}
|
|
@@ -1850,6 +1873,8 @@ var SyncEngine = class {
|
|
|
1850
1873
|
_state;
|
|
1851
1874
|
/** Listeners de mudança de SyncState. */
|
|
1852
1875
|
_stateListeners = /* @__PURE__ */ new Set();
|
|
1876
|
+
/** Listeners de erro de ciclo de sync (P2-C — não mais engolido em silêncio). */
|
|
1877
|
+
_errorListeners = /* @__PURE__ */ new Set();
|
|
1853
1878
|
/** Cleanup functions dos listeners externos. */
|
|
1854
1879
|
_cleanups = [];
|
|
1855
1880
|
/** Timer de resync periódico. */
|
|
@@ -1908,6 +1933,19 @@ var SyncEngine = class {
|
|
|
1908
1933
|
this._stateListeners.delete(cb);
|
|
1909
1934
|
};
|
|
1910
1935
|
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Registra um hook de erro de sync (P2-C). Disparado quando um ciclo falha
|
|
1938
|
+
* (exceção inesperada nas fases, falha de pull/resync ou dead-letter de uma
|
|
1939
|
+
* mutação-veneno) — o que antes era engolido com "log silencioso".
|
|
1940
|
+
* O mesmo diagnóstico também aparece em `SyncState.lastError`.
|
|
1941
|
+
* Retorna uma função de unsubscribe.
|
|
1942
|
+
*/
|
|
1943
|
+
onError(cb) {
|
|
1944
|
+
this._errorListeners.add(cb);
|
|
1945
|
+
return () => {
|
|
1946
|
+
this._errorListeners.delete(cb);
|
|
1947
|
+
};
|
|
1948
|
+
}
|
|
1911
1949
|
/**
|
|
1912
1950
|
* Força um sync imediato (I3 §10.3 `flush()`).
|
|
1913
1951
|
* Resolve quando a fila esvazia OU rejeita se o engine estiver destruído.
|
|
@@ -1940,8 +1978,10 @@ var SyncEngine = class {
|
|
|
1940
1978
|
}
|
|
1941
1979
|
await this._phase2Pull();
|
|
1942
1980
|
this._phase3Realtime();
|
|
1943
|
-
this._updateState({ status: "idle", lastSyncedAt: Date.now() });
|
|
1981
|
+
this._updateState({ status: "idle", lastSyncedAt: Date.now(), lastError: null });
|
|
1944
1982
|
} catch (err) {
|
|
1983
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1984
|
+
this._recordSyncError("cycle", `ciclo de sync falhou: ${msg}`);
|
|
1945
1985
|
this._updateState({ status: "idle" });
|
|
1946
1986
|
} finally {
|
|
1947
1987
|
this._syncing = false;
|
|
@@ -1976,6 +2016,9 @@ var SyncEngine = class {
|
|
|
1976
2016
|
async _phase1Push() {
|
|
1977
2017
|
const mutations = await this._queue.drain();
|
|
1978
2018
|
if (mutations.length === 0) return true;
|
|
2019
|
+
if (this._isInBackoff(mutations[0])) {
|
|
2020
|
+
return true;
|
|
2021
|
+
}
|
|
1979
2022
|
for (const mut of mutations) {
|
|
1980
2023
|
await this._queue.markInflight(mut.mutationId);
|
|
1981
2024
|
}
|
|
@@ -1983,10 +2026,16 @@ var SyncEngine = class {
|
|
|
1983
2026
|
try {
|
|
1984
2027
|
result = await this._transport.pushMutations(mutations);
|
|
1985
2028
|
} catch (err) {
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2029
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2030
|
+
const busChangesOnThrow = [];
|
|
2031
|
+
const [head, ...behind] = mutations;
|
|
2032
|
+
await this._retryOrDeadLetter(head, errMsg, busChangesOnThrow);
|
|
2033
|
+
for (const mut of behind) {
|
|
2034
|
+
await this._queue.requeueInflight(mut.mutationId);
|
|
1989
2035
|
}
|
|
2036
|
+
if (busChangesOnThrow.length > 0) this._bus.emit(busChangesOnThrow);
|
|
2037
|
+
this._refreshPendingWrites().catch(() => {
|
|
2038
|
+
});
|
|
1990
2039
|
return false;
|
|
1991
2040
|
}
|
|
1992
2041
|
const busChanges = [];
|
|
@@ -2013,8 +2062,16 @@ var SyncEngine = class {
|
|
|
2013
2062
|
}
|
|
2014
2063
|
const processedIds = new Set(result.results.map((r) => r.mutationId));
|
|
2015
2064
|
const unprocessed = mutations.filter((m) => !processedIds.has(m.mutationId));
|
|
2016
|
-
|
|
2017
|
-
|
|
2065
|
+
if (unprocessed.length > 0) {
|
|
2066
|
+
const [boundary, ...behind] = unprocessed;
|
|
2067
|
+
await this._retryOrDeadLetter(
|
|
2068
|
+
boundary,
|
|
2069
|
+
"partial_push: muta\xE7\xE3o n\xE3o processada no lote",
|
|
2070
|
+
busChanges
|
|
2071
|
+
);
|
|
2072
|
+
for (const mut of behind) {
|
|
2073
|
+
await this._queue.requeueInflight(mut.mutationId);
|
|
2074
|
+
}
|
|
2018
2075
|
}
|
|
2019
2076
|
if (busChanges.length > 0) {
|
|
2020
2077
|
this._bus.emit(busChanges);
|
|
@@ -2206,6 +2263,67 @@ var SyncEngine = class {
|
|
|
2206
2263
|
});
|
|
2207
2264
|
}
|
|
2208
2265
|
}
|
|
2266
|
+
// ─── Dead-letter + backoff (P1-C) ────────────────────────────────────────────
|
|
2267
|
+
/**
|
|
2268
|
+
* Trata a falha transiente de UMA mutação que foi realmente tentada.
|
|
2269
|
+
*
|
|
2270
|
+
* - Se ainda há orçamento de tentativas → `markRetry` (volta pra fila com
|
|
2271
|
+
* backoff, `attempts++`).
|
|
2272
|
+
* - Se esgotou (`attempts+1 >= MAX_MUTATION_ATTEMPTS`) → DEAD-LETTER:
|
|
2273
|
+
* `markFailed` (sai do conjunto `queued`, então `drain()` para de devolvê-la
|
|
2274
|
+
* e a fila FIFO DESTRAVA para as demais coleções) + grava um ConflictRecord
|
|
2275
|
+
* `failed_max_attempts` (o produto vê via `getConflicts()`) + limpa o estado
|
|
2276
|
+
* pending do doc no cache + registra `lastError`/dispara `onError`.
|
|
2277
|
+
*/
|
|
2278
|
+
async _retryOrDeadLetter(mut, errMsg, busChanges) {
|
|
2279
|
+
if (mut.attempts + 1 < MAX_MUTATION_ATTEMPTS) {
|
|
2280
|
+
await this._queue.markRetry(mut.mutationId, errMsg);
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
await this._queue.markFailed(mut.mutationId, errMsg);
|
|
2284
|
+
const conflictRecord = {
|
|
2285
|
+
collection: mut.collection,
|
|
2286
|
+
docId: mut.docId,
|
|
2287
|
+
mutationId: mut.mutationId,
|
|
2288
|
+
losingData: mut.payload ?? {},
|
|
2289
|
+
winningData: {},
|
|
2290
|
+
reason: "failed_max_attempts",
|
|
2291
|
+
detectedAt: Date.now(),
|
|
2292
|
+
delivered: false
|
|
2293
|
+
};
|
|
2294
|
+
await this._store.appendConflict(conflictRecord);
|
|
2295
|
+
const existing = await this._store.getDoc(mut.collection, mut.docId);
|
|
2296
|
+
if (existing) {
|
|
2297
|
+
await this._store.putDoc({
|
|
2298
|
+
...existing,
|
|
2299
|
+
meta: { ...existing.meta, state: "synced", pendingMutationIds: [] }
|
|
2300
|
+
});
|
|
2301
|
+
busChanges.push({
|
|
2302
|
+
type: "modified",
|
|
2303
|
+
collection: mut.collection,
|
|
2304
|
+
doc: { id: mut.docId, data: existing.data }
|
|
2305
|
+
});
|
|
2306
|
+
}
|
|
2307
|
+
this._recordSyncError(
|
|
2308
|
+
"push",
|
|
2309
|
+
`muta\xE7\xE3o ${mut.mutationId} (${mut.collection}/${mut.docId}) descartada ap\xF3s ${mut.attempts + 1} tentativas: ${errMsg}`
|
|
2310
|
+
);
|
|
2311
|
+
}
|
|
2312
|
+
/**
|
|
2313
|
+
* `true` se a mutação (head do FIFO) ainda está dentro da janela de backoff
|
|
2314
|
+
* desde a última tentativa falha. Só se aplica a mutações já tentadas
|
|
2315
|
+
* (`attempts > 0` e `lastAttemptAt` carimbado).
|
|
2316
|
+
*/
|
|
2317
|
+
_isInBackoff(mut) {
|
|
2318
|
+
if (mut.attempts <= 0) return false;
|
|
2319
|
+
const last = mut.lastAttemptAt;
|
|
2320
|
+
if (typeof last !== "number") return false;
|
|
2321
|
+
return Date.now() < last + this._backoffDelayMs(mut.attempts);
|
|
2322
|
+
}
|
|
2323
|
+
/** Backoff exponencial keyed em `attempts`, com teto `BACKOFF_CAP_MS`. */
|
|
2324
|
+
_backoffDelayMs(attempts) {
|
|
2325
|
+
return Math.min(BACKOFF_BASE_MS * 2 ** (attempts - 1), BACKOFF_CAP_MS);
|
|
2326
|
+
}
|
|
2209
2327
|
// ─── FASE 2 — pull (reconciliar o cache) ─────────────────────────────────────
|
|
2210
2328
|
/**
|
|
2211
2329
|
* Busca mudanças do servidor e aplica ao cache via ConflictResolver.
|
|
@@ -2245,10 +2363,14 @@ var SyncEngine = class {
|
|
|
2245
2363
|
}
|
|
2246
2364
|
const busChanges = [];
|
|
2247
2365
|
await this._applyServerDocs(resyncResult.docs, busChanges, true);
|
|
2366
|
+
const truncatedCollections = resyncResult.truncatedCollections ?? [];
|
|
2367
|
+
const truncatedSet = new Set(truncatedCollections);
|
|
2368
|
+
await this._store.setMeta(TRUNCATED_COLLECTIONS_META_KEY, truncatedCollections);
|
|
2248
2369
|
const returnedKeys = new Set(
|
|
2249
2370
|
resyncResult.docs.map((d) => `${d.collection}::${d.id}`)
|
|
2250
2371
|
);
|
|
2251
2372
|
for (const col of collections) {
|
|
2373
|
+
if (truncatedSet.has(col)) continue;
|
|
2252
2374
|
const queryResult = await this._store.listDocs(col, {});
|
|
2253
2375
|
for (const { id } of queryResult.docs) {
|
|
2254
2376
|
const key = `${col}::${id}`;
|
|
@@ -2408,7 +2530,8 @@ var SyncEngine = class {
|
|
|
2408
2530
|
status: isOnline ? "idle" : "offline",
|
|
2409
2531
|
pendingWrites: 0,
|
|
2410
2532
|
lastSyncedAt: null,
|
|
2411
|
-
isLeaderTab: this._tabCoordinator.isLeader()
|
|
2533
|
+
isLeaderTab: this._tabCoordinator.isLeader(),
|
|
2534
|
+
lastError: null
|
|
2412
2535
|
};
|
|
2413
2536
|
}
|
|
2414
2537
|
/**
|
|
@@ -2418,7 +2541,7 @@ var SyncEngine = class {
|
|
|
2418
2541
|
_updateState(partial) {
|
|
2419
2542
|
const prev = this._state;
|
|
2420
2543
|
const next = { ...prev, ...partial };
|
|
2421
|
-
const changed = prev.status !== next.status || prev.pendingWrites !== next.pendingWrites || prev.lastSyncedAt !== next.lastSyncedAt || prev.isLeaderTab !== next.isLeaderTab;
|
|
2544
|
+
const changed = prev.status !== next.status || prev.pendingWrites !== next.pendingWrites || prev.lastSyncedAt !== next.lastSyncedAt || prev.isLeaderTab !== next.isLeaderTab || (prev.lastError ?? null) !== (next.lastError ?? null);
|
|
2422
2545
|
this._state = next;
|
|
2423
2546
|
if (changed) {
|
|
2424
2547
|
this._emitState(next);
|
|
@@ -2432,6 +2555,21 @@ var SyncEngine = class {
|
|
|
2432
2555
|
}
|
|
2433
2556
|
}
|
|
2434
2557
|
}
|
|
2558
|
+
/**
|
|
2559
|
+
* P2-C: registra a falha de um ciclo de sync — antes engolida em silêncio.
|
|
2560
|
+
* Publica em `SyncState.lastError` (visível via `onSyncStateChanged`) e
|
|
2561
|
+
* dispara o hook `onError`. Nunca lança (diagnóstico é best-effort).
|
|
2562
|
+
*/
|
|
2563
|
+
_recordSyncError(phase, message) {
|
|
2564
|
+
this._updateState({ lastError: message });
|
|
2565
|
+
const event = { phase, message, at: Date.now() };
|
|
2566
|
+
for (const listener of this._errorListeners) {
|
|
2567
|
+
try {
|
|
2568
|
+
listener({ ...event });
|
|
2569
|
+
} catch {
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
}
|
|
2435
2573
|
/**
|
|
2436
2574
|
* Atualiza `pendingWrites` no estado.
|
|
2437
2575
|
* Chamado de forma assíncrona quando necessário.
|
|
@@ -3241,7 +3379,9 @@ var DbCollectionRefImpl = class {
|
|
|
3241
3379
|
const isOnline = this._isOnline();
|
|
3242
3380
|
const syncState = this._engine.getSyncState();
|
|
3243
3381
|
const nextCursor = buildNextCursor(result.docs, effectiveLimit);
|
|
3244
|
-
const
|
|
3382
|
+
const truncatedRaw = await this._store.getMeta(TRUNCATED_COLLECTIONS_META_KEY);
|
|
3383
|
+
const truncated = Array.isArray(truncatedRaw) && truncatedRaw.includes(this._collection);
|
|
3384
|
+
const stale = !isOnline || syncState.status === "offline" || truncated;
|
|
3245
3385
|
const fromCache = hasPending || syncState.lastSyncedAt === null || stale;
|
|
3246
3386
|
return {
|
|
3247
3387
|
docs: result.docs.map((d) => ({ id: d.id, data: d.data })),
|
|
@@ -3249,7 +3389,8 @@ var DbCollectionRefImpl = class {
|
|
|
3249
3389
|
fromCache,
|
|
3250
3390
|
stale,
|
|
3251
3391
|
hasPendingWrites: hasPending,
|
|
3252
|
-
changes: []
|
|
3392
|
+
changes: [],
|
|
3393
|
+
truncated
|
|
3253
3394
|
};
|
|
3254
3395
|
}
|
|
3255
3396
|
// ── CRUD ────────────────────────────────────────────────────────────────────
|
|
@@ -3695,6 +3836,9 @@ var NeetruDbDocumentsImpl = class {
|
|
|
3695
3836
|
async getConflicts() {
|
|
3696
3837
|
return this._store.listConflicts();
|
|
3697
3838
|
}
|
|
3839
|
+
onError(cb) {
|
|
3840
|
+
return this._engine.onError(cb);
|
|
3841
|
+
}
|
|
3698
3842
|
};
|
|
3699
3843
|
async function createOfflineDocumentsNamespace(opts) {
|
|
3700
3844
|
const store = isIndexedDBUnavailable() ? new MemoryStore() : new LocalStore(opts.dbName);
|
|
@@ -4171,6 +4315,7 @@ var defaultWebSocketFactory = (url) => {
|
|
|
4171
4315
|
init_db_errors();
|
|
4172
4316
|
var DATASTORE_COLLECTION_ENDPOINT = (collection) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}`;
|
|
4173
4317
|
var DATASTORE_DOC_ENDPOINT = (collection, docId) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}/${encodeURIComponent(docId)}`;
|
|
4318
|
+
var DOCUMENTS_RESYNC_PAGE_CAP = 200;
|
|
4174
4319
|
function resolveTransport(transport, opts, config) {
|
|
4175
4320
|
if (opts._transport) {
|
|
4176
4321
|
return opts._transport;
|
|
@@ -4283,6 +4428,7 @@ function createRestSyncTransport(config) {
|
|
|
4283
4428
|
const tenantHeaders = tenantScopeId ? { "x-neetru-tenant": tenantScopeId } : void 0;
|
|
4284
4429
|
const now = Date.now();
|
|
4285
4430
|
const docs = [];
|
|
4431
|
+
const truncatedCollections = [];
|
|
4286
4432
|
for (const collection of collections) {
|
|
4287
4433
|
let raw;
|
|
4288
4434
|
try {
|
|
@@ -4292,11 +4438,11 @@ function createRestSyncTransport(config) {
|
|
|
4292
4438
|
requireAuth: true,
|
|
4293
4439
|
retries: 2,
|
|
4294
4440
|
headers: tenantHeaders,
|
|
4295
|
-
// Teto do Core
|
|
4296
|
-
//
|
|
4297
|
-
// deleção-por-ausência
|
|
4298
|
-
//
|
|
4299
|
-
query: { limit:
|
|
4441
|
+
// Teto do Core (DOCUMENTS_RESYNC_PAGE_CAP). Coleções acima dele vêm
|
|
4442
|
+
// TRUNCADAS — reportadas em `truncatedCollections` p/ o SyncEngine
|
|
4443
|
+
// (a) NÃO rodar deleção-por-ausência e (b) sinalizar
|
|
4444
|
+
// `DbListResult.truncated`. Paginação por cursor é tarefa Core (gated).
|
|
4445
|
+
query: { limit: DOCUMENTS_RESYNC_PAGE_CAP }
|
|
4300
4446
|
});
|
|
4301
4447
|
} catch (err) {
|
|
4302
4448
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -4307,6 +4453,9 @@ function createRestSyncTransport(config) {
|
|
|
4307
4453
|
}
|
|
4308
4454
|
const resp = raw;
|
|
4309
4455
|
const items = Array.isArray(resp?.items) ? resp.items : [];
|
|
4456
|
+
if (items.length >= DOCUMENTS_RESYNC_PAGE_CAP) {
|
|
4457
|
+
truncatedCollections.push(collection);
|
|
4458
|
+
}
|
|
4310
4459
|
for (const item of items) {
|
|
4311
4460
|
if (!item || typeof item !== "object") continue;
|
|
4312
4461
|
const record = item;
|
|
@@ -4334,7 +4483,7 @@ function createRestSyncTransport(config) {
|
|
|
4334
4483
|
});
|
|
4335
4484
|
}
|
|
4336
4485
|
}
|
|
4337
|
-
return { docs, newWatermark: now };
|
|
4486
|
+
return { docs, newWatermark: now, truncatedCollections };
|
|
4338
4487
|
}
|
|
4339
4488
|
};
|
|
4340
4489
|
}
|
|
@@ -4399,23 +4548,23 @@ function buildTicketProvider(config, opts) {
|
|
|
4399
4548
|
function getWebSocketRealtimeClient(gatewayUrl, ticketProvider, dbId, wsFactory) {
|
|
4400
4549
|
return new NeetruRealtimeClient({ gatewayUrl, ticketProvider, dbId, webSocketFactory: wsFactory });
|
|
4401
4550
|
}
|
|
4402
|
-
var
|
|
4551
|
+
var _hasWarnedDbIdIgnored = false;
|
|
4403
4552
|
function __resetDbIdWarningForTests() {
|
|
4404
|
-
|
|
4553
|
+
_hasWarnedDbIdIgnored = false;
|
|
4405
4554
|
}
|
|
4406
|
-
function
|
|
4407
|
-
if (
|
|
4408
|
-
if (dbOpts.dbId) return;
|
|
4555
|
+
function warnDbIdIgnoredOnce(config, dbOpts) {
|
|
4556
|
+
if (_hasWarnedDbIdIgnored) return;
|
|
4557
|
+
if (!dbOpts.dbId) return;
|
|
4409
4558
|
if (config.env === "prod") return;
|
|
4410
4559
|
if (typeof window === "undefined") return;
|
|
4411
4560
|
if (typeof console === "undefined" || typeof console.warn !== "function") return;
|
|
4412
|
-
|
|
4561
|
+
_hasWarnedDbIdIgnored = true;
|
|
4413
4562
|
console.warn(
|
|
4414
|
-
|
|
4563
|
+
'[neetru/sdk] client.db recebeu `dbId` ("' + dbOpts.dbId + '") mas ele N\xC3O roteia nem escopa nada no servidor: as escritas do mundo Documentos (add/set/update/remove/list) SEMPRE caem no datastore gen\xE9rico do Core, escopado s\xF3 por tenant/produto (header x-neetru-tenant). `dbId` apenas nomeia o cache offline LOCAL (IndexedDB). ATEN\xC7\xC3O: usar dbIds distintos no MESMO tenant/produto N\xC3O isola os dados no servidor \u2014 eles se misturam (co-mingling). Veja o JSDoc de NeetruDbOptions.dbId.'
|
|
4415
4564
|
);
|
|
4416
4565
|
}
|
|
4417
4566
|
function createNeetruDb(config, dbOpts = {}) {
|
|
4418
|
-
|
|
4567
|
+
warnDbIdIgnoredOnce(config, dbOpts);
|
|
4419
4568
|
let resolvedTransport;
|
|
4420
4569
|
if (dbOpts.transport !== void 0) {
|
|
4421
4570
|
resolvedTransport = dbOpts.transport;
|
|
@@ -4493,6 +4642,20 @@ function createNeetruDb(config, dbOpts = {}) {
|
|
|
4493
4642
|
async getConflicts() {
|
|
4494
4643
|
const docs = await getDocsPromise();
|
|
4495
4644
|
return docs.getConflicts();
|
|
4645
|
+
},
|
|
4646
|
+
onError(cb) {
|
|
4647
|
+
if (_docsResolved) return _docsResolved.onError(cb);
|
|
4648
|
+
let unsub = null;
|
|
4649
|
+
let cancelled = false;
|
|
4650
|
+
getDocsPromise().then((docs) => {
|
|
4651
|
+
if (!cancelled) {
|
|
4652
|
+
unsub = docs.onError(cb);
|
|
4653
|
+
}
|
|
4654
|
+
});
|
|
4655
|
+
return () => {
|
|
4656
|
+
cancelled = true;
|
|
4657
|
+
unsub?.();
|
|
4658
|
+
};
|
|
4496
4659
|
}
|
|
4497
4660
|
};
|
|
4498
4661
|
}
|