@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/index.d.cts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createNeetruClient } from './auth.cjs';
|
|
2
2
|
export { NeetruError, NeetruErrorCode } from './errors.cjs';
|
|
3
|
-
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, l as AuthNamespace, m as AuthStateListener, C as CatalogListOptions, n as CatalogListResponse, p as CheckoutIntentInfo, q as CheckoutIntentStatus, r as CheckoutNamespace, s as CheckoutStartInput, t as CheckoutStartResult, u as CheckoutTenantType, v as CompleteSignInResult, y as CreateTicketInput, D as DEFAULT_BASE_URL, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, J as DbNamespace, K as DbQuery, L as DbSqlLease, M as DbWhereFilter, O as EntitlementCheck, P as FieldSentinel, Q as IncrementSentinel, R as ListNotificationsOptions, S as MockCheckout, T as MockNotifications, U as MockWebhooks, V as NEETRU_SENTINEL_KEY, W as NeetruClient, X as NeetruClientConfig, Y as NeetruDb, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a3 as NeetruEnv, a4 as NeetruSqlClient, a5 as NeetruUser, a6 as NotificationScopeOptions, a7 as NotificationsNamespace, a8 as Product, a9 as ProductNotification, aa as ProductNotificationSeverity, ab as ProductPlan, ac as ProductStatus, ae as RedirectCallbackResult, af as RegisterWebhookInput, ag as ResolvedConfig, ah as SendNotificationInput, ai as ServerTimestampSentinel, aj as SignInOptions, al as SupportNamespace, am as SupportSeverity, an as SupportStatus, ao as SupportTicket,
|
|
3
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, l as AuthNamespace, m as AuthStateListener, C as CatalogListOptions, n as CatalogListResponse, p as CheckoutIntentInfo, q as CheckoutIntentStatus, r as CheckoutNamespace, s as CheckoutStartInput, t as CheckoutStartResult, u as CheckoutTenantType, v as CompleteSignInResult, y as CreateTicketInput, D as DEFAULT_BASE_URL, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, J as DbNamespace, K as DbQuery, L as DbSqlLease, M as DbWhereFilter, O as EntitlementCheck, P as FieldSentinel, Q as IncrementSentinel, R as ListNotificationsOptions, S as MockCheckout, T as MockNotifications, U as MockWebhooks, V as NEETRU_SENTINEL_KEY, W as NeetruClient, X as NeetruClientConfig, Y as NeetruDb, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a3 as NeetruEnv, a4 as NeetruSqlClient, a5 as NeetruUser, a6 as NotificationScopeOptions, a7 as NotificationsNamespace, a8 as Product, a9 as ProductNotification, aa as ProductNotificationSeverity, ab as ProductPlan, ac as ProductStatus, ae as RedirectCallbackResult, af as RegisterWebhookInput, ag as ResolvedConfig, ah as SendNotificationInput, ai as ServerTimestampSentinel, aj as SignInOptions, al as SupportNamespace, am as SupportSeverity, an as SupportStatus, ao as SupportTicket, ar as TelemetryEventAck, as as TelemetryEventInput, aw as UsageEventInput, ax as UsageNamespace, ay as UsageQuota, az as VerifyTokenOptions, aA as VerifyWebhookRequestOptions, aB as VerifyWebhookRequestResult, aC as WebhookEndpoint, aD as WebhookEvent, aE as WebhookScopeOptions, aF as WebhookTestResult, aG as WebhooksNamespace, aH as WithFieldSentinels, aJ as createAiNamespace, aK as createCheckoutNamespace, aL as createNeetruDb, aM as createNotificationsNamespace, aO as createWebhooksNamespace, aQ as increment, aR as isFieldSentinel, aS as isIncrementSentinel, aT as isServerTimestampSentinel, aU as serverTimestamp, aV as verifyWebhookRequest, aW as verifyWebhookSignature } from './types-D-QdG502.cjs';
|
|
4
4
|
export { DEV_FIXTURE_USER, MockAi, MockAuth, MockAuthOptions, MockDb, MockEntitlements, MockSupport, MockUsage } from './mocks.cjs';
|
|
5
5
|
import 'drizzle-orm/node-postgres';
|
|
6
6
|
|
|
@@ -42,6 +42,6 @@ declare function checkCoreVersionCompat(coreVersion: string): void;
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
/** Versão do SDK — exportada explicitamente pra detecção em runtime. */
|
|
45
|
-
declare const VERSION: "3.4.
|
|
45
|
+
declare const VERSION: "3.4.1";
|
|
46
46
|
|
|
47
47
|
export { VERSION, checkCoreVersionCompat };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { createNeetruClient } from './auth.js';
|
|
2
2
|
export { NeetruError, NeetruErrorCode } from './errors.js';
|
|
3
|
-
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, l as AuthNamespace, m as AuthStateListener, C as CatalogListOptions, n as CatalogListResponse, p as CheckoutIntentInfo, q as CheckoutIntentStatus, r as CheckoutNamespace, s as CheckoutStartInput, t as CheckoutStartResult, u as CheckoutTenantType, v as CompleteSignInResult, y as CreateTicketInput, D as DEFAULT_BASE_URL, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, J as DbNamespace, K as DbQuery, L as DbSqlLease, M as DbWhereFilter, O as EntitlementCheck, P as FieldSentinel, Q as IncrementSentinel, R as ListNotificationsOptions, S as MockCheckout, T as MockNotifications, U as MockWebhooks, V as NEETRU_SENTINEL_KEY, W as NeetruClient, X as NeetruClientConfig, Y as NeetruDb, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a3 as NeetruEnv, a4 as NeetruSqlClient, a5 as NeetruUser, a6 as NotificationScopeOptions, a7 as NotificationsNamespace, a8 as Product, a9 as ProductNotification, aa as ProductNotificationSeverity, ab as ProductPlan, ac as ProductStatus, ae as RedirectCallbackResult, af as RegisterWebhookInput, ag as ResolvedConfig, ah as SendNotificationInput, ai as ServerTimestampSentinel, aj as SignInOptions, al as SupportNamespace, am as SupportSeverity, an as SupportStatus, ao as SupportTicket,
|
|
3
|
+
export { A as AiChatChoice, a as AiChatMessage, b as AiChatOptions, c as AiChatResult, d as AiChatUsage, e as AiContentPart, f as AiImageUrlPart, g as AiNamespace, h as AiResponseFormat, i as AiStreamHandle, j as AiStreamMeta, k as AiTextPart, l as AuthNamespace, m as AuthStateListener, C as CatalogListOptions, n as CatalogListResponse, p as CheckoutIntentInfo, q as CheckoutIntentStatus, r as CheckoutNamespace, s as CheckoutStartInput, t as CheckoutStartResult, u as CheckoutTenantType, v as CompleteSignInResult, y as CreateTicketInput, D as DEFAULT_BASE_URL, z as DbBatchOp, B as DbChangeType, E as DbCollectionRef, F as DbDoc, G as DbDocRef, H as DbGetResult, I as DbListResult, J as DbNamespace, K as DbQuery, L as DbSqlLease, M as DbWhereFilter, O as EntitlementCheck, P as FieldSentinel, Q as IncrementSentinel, R as ListNotificationsOptions, S as MockCheckout, T as MockNotifications, U as MockWebhooks, V as NEETRU_SENTINEL_KEY, W as NeetruClient, X as NeetruClientConfig, Y as NeetruDb, _ as NeetruDbEngine, $ as NeetruDbError, a0 as NeetruDbErrorCode, a1 as NeetruDbOptions, a2 as NeetruDbTransport, a3 as NeetruEnv, a4 as NeetruSqlClient, a5 as NeetruUser, a6 as NotificationScopeOptions, a7 as NotificationsNamespace, a8 as Product, a9 as ProductNotification, aa as ProductNotificationSeverity, ab as ProductPlan, ac as ProductStatus, ae as RedirectCallbackResult, af as RegisterWebhookInput, ag as ResolvedConfig, ah as SendNotificationInput, ai as ServerTimestampSentinel, aj as SignInOptions, al as SupportNamespace, am as SupportSeverity, an as SupportStatus, ao as SupportTicket, ar as TelemetryEventAck, as as TelemetryEventInput, aw as UsageEventInput, ax as UsageNamespace, ay as UsageQuota, az as VerifyTokenOptions, aA as VerifyWebhookRequestOptions, aB as VerifyWebhookRequestResult, aC as WebhookEndpoint, aD as WebhookEvent, aE as WebhookScopeOptions, aF as WebhookTestResult, aG as WebhooksNamespace, aH as WithFieldSentinels, aJ as createAiNamespace, aK as createCheckoutNamespace, aL as createNeetruDb, aM as createNotificationsNamespace, aO as createWebhooksNamespace, aQ as increment, aR as isFieldSentinel, aS as isIncrementSentinel, aT as isServerTimestampSentinel, aU as serverTimestamp, aV as verifyWebhookRequest, aW as verifyWebhookSignature } from './types-BlOLDMR0.js';
|
|
4
4
|
export { DEV_FIXTURE_USER, MockAi, MockAuth, MockAuthOptions, MockDb, MockEntitlements, MockSupport, MockUsage } from './mocks.js';
|
|
5
5
|
import 'drizzle-orm/node-postgres';
|
|
6
6
|
|
|
@@ -42,6 +42,6 @@ declare function checkCoreVersionCompat(coreVersion: string): void;
|
|
|
42
42
|
*/
|
|
43
43
|
|
|
44
44
|
/** Versão do SDK — exportada explicitamente pra detecção em runtime. */
|
|
45
|
-
declare const VERSION: "3.4.
|
|
45
|
+
declare const VERSION: "3.4.1";
|
|
46
46
|
|
|
47
47
|
export { VERSION, checkCoreVersionCompat };
|
package/dist/index.mjs
CHANGED
|
@@ -2541,8 +2541,10 @@ var MutationQueue = class {
|
|
|
2541
2541
|
await this.store.deleteMutation(mutationId);
|
|
2542
2542
|
}
|
|
2543
2543
|
/**
|
|
2544
|
-
* Marca uma mutação como `failed` e registra o erro.
|
|
2545
|
-
* Incrementa `attempts`. Chamado pelo SyncEngine
|
|
2544
|
+
* Marca uma mutação como `failed` (dead-letter) e registra o erro.
|
|
2545
|
+
* Incrementa `attempts` e carimba `lastAttemptAt`. Chamado pelo SyncEngine
|
|
2546
|
+
* quando a mutação esgota o teto de tentativas (mutação-veneno): sai do
|
|
2547
|
+
* conjunto `queued`, então `drain()` para de devolvê-la e a fila DESTRAVA.
|
|
2546
2548
|
*/
|
|
2547
2549
|
async markFailed(mutationId, error) {
|
|
2548
2550
|
const mut = await this.store.getMutation(mutationId);
|
|
@@ -2551,12 +2553,14 @@ var MutationQueue = class {
|
|
|
2551
2553
|
...mut,
|
|
2552
2554
|
status: "failed",
|
|
2553
2555
|
lastError: error,
|
|
2554
|
-
attempts: mut.attempts + 1
|
|
2556
|
+
attempts: mut.attempts + 1,
|
|
2557
|
+
lastAttemptAt: Date.now()
|
|
2555
2558
|
});
|
|
2556
2559
|
}
|
|
2557
2560
|
/**
|
|
2558
|
-
* Incrementa `attempts` e volta para `queued`
|
|
2559
|
-
* Chamado pelo SyncEngine em falha
|
|
2561
|
+
* Incrementa `attempts`, carimba `lastAttemptAt` e volta para `queued`
|
|
2562
|
+
* (falha transiente com backoff). Chamado pelo SyncEngine em falha
|
|
2563
|
+
* transiente (5xx, timeout) da mutação que REALMENTE foi tentada.
|
|
2560
2564
|
*/
|
|
2561
2565
|
async markRetry(mutationId, error) {
|
|
2562
2566
|
const mut = await this.store.getMutation(mutationId);
|
|
@@ -2565,9 +2569,24 @@ var MutationQueue = class {
|
|
|
2565
2569
|
...mut,
|
|
2566
2570
|
status: "queued",
|
|
2567
2571
|
lastError: error,
|
|
2568
|
-
attempts: mut.attempts + 1
|
|
2572
|
+
attempts: mut.attempts + 1,
|
|
2573
|
+
lastAttemptAt: Date.now()
|
|
2569
2574
|
});
|
|
2570
2575
|
}
|
|
2576
|
+
/**
|
|
2577
|
+
* Reverte uma mutação `inflight` de volta a `queued` SEM tocar em `attempts`
|
|
2578
|
+
* nem `lastAttemptAt`. Usado para mutações que foram marcadas inflight mas
|
|
2579
|
+
* NUNCA chegaram a ser enviadas (estavam atrás da head no FIFO e o push
|
|
2580
|
+
* parou antes delas): contá-las como tentativa as empurraria pro dead-letter
|
|
2581
|
+
* por engano, e adiaria seu envio via backoff sem que tenham falhado.
|
|
2582
|
+
* No-op se a mutação não está mais `inflight`.
|
|
2583
|
+
*/
|
|
2584
|
+
async requeueInflight(mutationId) {
|
|
2585
|
+
const mut = await this.store.getMutation(mutationId);
|
|
2586
|
+
if (!mut) return;
|
|
2587
|
+
if (mut.status !== "inflight") return;
|
|
2588
|
+
await this.store.putMutation({ ...mut, status: "queued" });
|
|
2589
|
+
}
|
|
2571
2590
|
};
|
|
2572
2591
|
|
|
2573
2592
|
// src/db/field-value.ts
|
|
@@ -2623,6 +2642,10 @@ function hasFieldSentinel(data) {
|
|
|
2623
2642
|
}
|
|
2624
2643
|
|
|
2625
2644
|
// src/db/offline/sync-engine.ts
|
|
2645
|
+
var MAX_MUTATION_ATTEMPTS = 8;
|
|
2646
|
+
var BACKOFF_BASE_MS = 1e3;
|
|
2647
|
+
var BACKOFF_CAP_MS = 5 * 60 * 1e3;
|
|
2648
|
+
var TRUNCATED_COLLECTIONS_META_KEY = "truncatedCollections";
|
|
2626
2649
|
function hasSentinels(payload) {
|
|
2627
2650
|
return payload !== null && hasFieldSentinel(payload);
|
|
2628
2651
|
}
|
|
@@ -2657,6 +2680,8 @@ var SyncEngine = class {
|
|
|
2657
2680
|
_state;
|
|
2658
2681
|
/** Listeners de mudança de SyncState. */
|
|
2659
2682
|
_stateListeners = /* @__PURE__ */ new Set();
|
|
2683
|
+
/** Listeners de erro de ciclo de sync (P2-C — não mais engolido em silêncio). */
|
|
2684
|
+
_errorListeners = /* @__PURE__ */ new Set();
|
|
2660
2685
|
/** Cleanup functions dos listeners externos. */
|
|
2661
2686
|
_cleanups = [];
|
|
2662
2687
|
/** Timer de resync periódico. */
|
|
@@ -2715,6 +2740,19 @@ var SyncEngine = class {
|
|
|
2715
2740
|
this._stateListeners.delete(cb);
|
|
2716
2741
|
};
|
|
2717
2742
|
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Registra um hook de erro de sync (P2-C). Disparado quando um ciclo falha
|
|
2745
|
+
* (exceção inesperada nas fases, falha de pull/resync ou dead-letter de uma
|
|
2746
|
+
* mutação-veneno) — o que antes era engolido com "log silencioso".
|
|
2747
|
+
* O mesmo diagnóstico também aparece em `SyncState.lastError`.
|
|
2748
|
+
* Retorna uma função de unsubscribe.
|
|
2749
|
+
*/
|
|
2750
|
+
onError(cb) {
|
|
2751
|
+
this._errorListeners.add(cb);
|
|
2752
|
+
return () => {
|
|
2753
|
+
this._errorListeners.delete(cb);
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2718
2756
|
/**
|
|
2719
2757
|
* Força um sync imediato (I3 §10.3 `flush()`).
|
|
2720
2758
|
* Resolve quando a fila esvazia OU rejeita se o engine estiver destruído.
|
|
@@ -2747,8 +2785,10 @@ var SyncEngine = class {
|
|
|
2747
2785
|
}
|
|
2748
2786
|
await this._phase2Pull();
|
|
2749
2787
|
this._phase3Realtime();
|
|
2750
|
-
this._updateState({ status: "idle", lastSyncedAt: Date.now() });
|
|
2788
|
+
this._updateState({ status: "idle", lastSyncedAt: Date.now(), lastError: null });
|
|
2751
2789
|
} catch (err) {
|
|
2790
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
2791
|
+
this._recordSyncError("cycle", `ciclo de sync falhou: ${msg}`);
|
|
2752
2792
|
this._updateState({ status: "idle" });
|
|
2753
2793
|
} finally {
|
|
2754
2794
|
this._syncing = false;
|
|
@@ -2783,6 +2823,9 @@ var SyncEngine = class {
|
|
|
2783
2823
|
async _phase1Push() {
|
|
2784
2824
|
const mutations = await this._queue.drain();
|
|
2785
2825
|
if (mutations.length === 0) return true;
|
|
2826
|
+
if (this._isInBackoff(mutations[0])) {
|
|
2827
|
+
return true;
|
|
2828
|
+
}
|
|
2786
2829
|
for (const mut of mutations) {
|
|
2787
2830
|
await this._queue.markInflight(mut.mutationId);
|
|
2788
2831
|
}
|
|
@@ -2790,10 +2833,16 @@ var SyncEngine = class {
|
|
|
2790
2833
|
try {
|
|
2791
2834
|
result = await this._transport.pushMutations(mutations);
|
|
2792
2835
|
} catch (err) {
|
|
2793
|
-
|
|
2794
|
-
|
|
2795
|
-
|
|
2796
|
-
|
|
2836
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
2837
|
+
const busChangesOnThrow = [];
|
|
2838
|
+
const [head, ...behind] = mutations;
|
|
2839
|
+
await this._retryOrDeadLetter(head, errMsg, busChangesOnThrow);
|
|
2840
|
+
for (const mut of behind) {
|
|
2841
|
+
await this._queue.requeueInflight(mut.mutationId);
|
|
2842
|
+
}
|
|
2843
|
+
if (busChangesOnThrow.length > 0) this._bus.emit(busChangesOnThrow);
|
|
2844
|
+
this._refreshPendingWrites().catch(() => {
|
|
2845
|
+
});
|
|
2797
2846
|
return false;
|
|
2798
2847
|
}
|
|
2799
2848
|
const busChanges = [];
|
|
@@ -2820,8 +2869,16 @@ var SyncEngine = class {
|
|
|
2820
2869
|
}
|
|
2821
2870
|
const processedIds = new Set(result.results.map((r) => r.mutationId));
|
|
2822
2871
|
const unprocessed = mutations.filter((m) => !processedIds.has(m.mutationId));
|
|
2823
|
-
|
|
2824
|
-
|
|
2872
|
+
if (unprocessed.length > 0) {
|
|
2873
|
+
const [boundary, ...behind] = unprocessed;
|
|
2874
|
+
await this._retryOrDeadLetter(
|
|
2875
|
+
boundary,
|
|
2876
|
+
"partial_push: muta\xE7\xE3o n\xE3o processada no lote",
|
|
2877
|
+
busChanges
|
|
2878
|
+
);
|
|
2879
|
+
for (const mut of behind) {
|
|
2880
|
+
await this._queue.requeueInflight(mut.mutationId);
|
|
2881
|
+
}
|
|
2825
2882
|
}
|
|
2826
2883
|
if (busChanges.length > 0) {
|
|
2827
2884
|
this._bus.emit(busChanges);
|
|
@@ -3013,6 +3070,67 @@ var SyncEngine = class {
|
|
|
3013
3070
|
});
|
|
3014
3071
|
}
|
|
3015
3072
|
}
|
|
3073
|
+
// ─── Dead-letter + backoff (P1-C) ────────────────────────────────────────────
|
|
3074
|
+
/**
|
|
3075
|
+
* Trata a falha transiente de UMA mutação que foi realmente tentada.
|
|
3076
|
+
*
|
|
3077
|
+
* - Se ainda há orçamento de tentativas → `markRetry` (volta pra fila com
|
|
3078
|
+
* backoff, `attempts++`).
|
|
3079
|
+
* - Se esgotou (`attempts+1 >= MAX_MUTATION_ATTEMPTS`) → DEAD-LETTER:
|
|
3080
|
+
* `markFailed` (sai do conjunto `queued`, então `drain()` para de devolvê-la
|
|
3081
|
+
* e a fila FIFO DESTRAVA para as demais coleções) + grava um ConflictRecord
|
|
3082
|
+
* `failed_max_attempts` (o produto vê via `getConflicts()`) + limpa o estado
|
|
3083
|
+
* pending do doc no cache + registra `lastError`/dispara `onError`.
|
|
3084
|
+
*/
|
|
3085
|
+
async _retryOrDeadLetter(mut, errMsg, busChanges) {
|
|
3086
|
+
if (mut.attempts + 1 < MAX_MUTATION_ATTEMPTS) {
|
|
3087
|
+
await this._queue.markRetry(mut.mutationId, errMsg);
|
|
3088
|
+
return;
|
|
3089
|
+
}
|
|
3090
|
+
await this._queue.markFailed(mut.mutationId, errMsg);
|
|
3091
|
+
const conflictRecord = {
|
|
3092
|
+
collection: mut.collection,
|
|
3093
|
+
docId: mut.docId,
|
|
3094
|
+
mutationId: mut.mutationId,
|
|
3095
|
+
losingData: mut.payload ?? {},
|
|
3096
|
+
winningData: {},
|
|
3097
|
+
reason: "failed_max_attempts",
|
|
3098
|
+
detectedAt: Date.now(),
|
|
3099
|
+
delivered: false
|
|
3100
|
+
};
|
|
3101
|
+
await this._store.appendConflict(conflictRecord);
|
|
3102
|
+
const existing = await this._store.getDoc(mut.collection, mut.docId);
|
|
3103
|
+
if (existing) {
|
|
3104
|
+
await this._store.putDoc({
|
|
3105
|
+
...existing,
|
|
3106
|
+
meta: { ...existing.meta, state: "synced", pendingMutationIds: [] }
|
|
3107
|
+
});
|
|
3108
|
+
busChanges.push({
|
|
3109
|
+
type: "modified",
|
|
3110
|
+
collection: mut.collection,
|
|
3111
|
+
doc: { id: mut.docId, data: existing.data }
|
|
3112
|
+
});
|
|
3113
|
+
}
|
|
3114
|
+
this._recordSyncError(
|
|
3115
|
+
"push",
|
|
3116
|
+
`muta\xE7\xE3o ${mut.mutationId} (${mut.collection}/${mut.docId}) descartada ap\xF3s ${mut.attempts + 1} tentativas: ${errMsg}`
|
|
3117
|
+
);
|
|
3118
|
+
}
|
|
3119
|
+
/**
|
|
3120
|
+
* `true` se a mutação (head do FIFO) ainda está dentro da janela de backoff
|
|
3121
|
+
* desde a última tentativa falha. Só se aplica a mutações já tentadas
|
|
3122
|
+
* (`attempts > 0` e `lastAttemptAt` carimbado).
|
|
3123
|
+
*/
|
|
3124
|
+
_isInBackoff(mut) {
|
|
3125
|
+
if (mut.attempts <= 0) return false;
|
|
3126
|
+
const last = mut.lastAttemptAt;
|
|
3127
|
+
if (typeof last !== "number") return false;
|
|
3128
|
+
return Date.now() < last + this._backoffDelayMs(mut.attempts);
|
|
3129
|
+
}
|
|
3130
|
+
/** Backoff exponencial keyed em `attempts`, com teto `BACKOFF_CAP_MS`. */
|
|
3131
|
+
_backoffDelayMs(attempts) {
|
|
3132
|
+
return Math.min(BACKOFF_BASE_MS * 2 ** (attempts - 1), BACKOFF_CAP_MS);
|
|
3133
|
+
}
|
|
3016
3134
|
// ─── FASE 2 — pull (reconciliar o cache) ─────────────────────────────────────
|
|
3017
3135
|
/**
|
|
3018
3136
|
* Busca mudanças do servidor e aplica ao cache via ConflictResolver.
|
|
@@ -3052,10 +3170,14 @@ var SyncEngine = class {
|
|
|
3052
3170
|
}
|
|
3053
3171
|
const busChanges = [];
|
|
3054
3172
|
await this._applyServerDocs(resyncResult.docs, busChanges, true);
|
|
3173
|
+
const truncatedCollections = resyncResult.truncatedCollections ?? [];
|
|
3174
|
+
const truncatedSet = new Set(truncatedCollections);
|
|
3175
|
+
await this._store.setMeta(TRUNCATED_COLLECTIONS_META_KEY, truncatedCollections);
|
|
3055
3176
|
const returnedKeys = new Set(
|
|
3056
3177
|
resyncResult.docs.map((d) => `${d.collection}::${d.id}`)
|
|
3057
3178
|
);
|
|
3058
3179
|
for (const col of collections) {
|
|
3180
|
+
if (truncatedSet.has(col)) continue;
|
|
3059
3181
|
const queryResult = await this._store.listDocs(col, {});
|
|
3060
3182
|
for (const { id } of queryResult.docs) {
|
|
3061
3183
|
const key = `${col}::${id}`;
|
|
@@ -3215,7 +3337,8 @@ var SyncEngine = class {
|
|
|
3215
3337
|
status: isOnline ? "idle" : "offline",
|
|
3216
3338
|
pendingWrites: 0,
|
|
3217
3339
|
lastSyncedAt: null,
|
|
3218
|
-
isLeaderTab: this._tabCoordinator.isLeader()
|
|
3340
|
+
isLeaderTab: this._tabCoordinator.isLeader(),
|
|
3341
|
+
lastError: null
|
|
3219
3342
|
};
|
|
3220
3343
|
}
|
|
3221
3344
|
/**
|
|
@@ -3225,7 +3348,7 @@ var SyncEngine = class {
|
|
|
3225
3348
|
_updateState(partial) {
|
|
3226
3349
|
const prev = this._state;
|
|
3227
3350
|
const next = { ...prev, ...partial };
|
|
3228
|
-
const changed = prev.status !== next.status || prev.pendingWrites !== next.pendingWrites || prev.lastSyncedAt !== next.lastSyncedAt || prev.isLeaderTab !== next.isLeaderTab;
|
|
3351
|
+
const changed = prev.status !== next.status || prev.pendingWrites !== next.pendingWrites || prev.lastSyncedAt !== next.lastSyncedAt || prev.isLeaderTab !== next.isLeaderTab || (prev.lastError ?? null) !== (next.lastError ?? null);
|
|
3229
3352
|
this._state = next;
|
|
3230
3353
|
if (changed) {
|
|
3231
3354
|
this._emitState(next);
|
|
@@ -3239,6 +3362,21 @@ var SyncEngine = class {
|
|
|
3239
3362
|
}
|
|
3240
3363
|
}
|
|
3241
3364
|
}
|
|
3365
|
+
/**
|
|
3366
|
+
* P2-C: registra a falha de um ciclo de sync — antes engolida em silêncio.
|
|
3367
|
+
* Publica em `SyncState.lastError` (visível via `onSyncStateChanged`) e
|
|
3368
|
+
* dispara o hook `onError`. Nunca lança (diagnóstico é best-effort).
|
|
3369
|
+
*/
|
|
3370
|
+
_recordSyncError(phase, message) {
|
|
3371
|
+
this._updateState({ lastError: message });
|
|
3372
|
+
const event = { phase, message, at: Date.now() };
|
|
3373
|
+
for (const listener of this._errorListeners) {
|
|
3374
|
+
try {
|
|
3375
|
+
listener({ ...event });
|
|
3376
|
+
} catch {
|
|
3377
|
+
}
|
|
3378
|
+
}
|
|
3379
|
+
}
|
|
3242
3380
|
/**
|
|
3243
3381
|
* Atualiza `pendingWrites` no estado.
|
|
3244
3382
|
* Chamado de forma assíncrona quando necessário.
|
|
@@ -4048,7 +4186,9 @@ var DbCollectionRefImpl = class {
|
|
|
4048
4186
|
const isOnline = this._isOnline();
|
|
4049
4187
|
const syncState = this._engine.getSyncState();
|
|
4050
4188
|
const nextCursor = buildNextCursor(result.docs, effectiveLimit);
|
|
4051
|
-
const
|
|
4189
|
+
const truncatedRaw = await this._store.getMeta(TRUNCATED_COLLECTIONS_META_KEY);
|
|
4190
|
+
const truncated = Array.isArray(truncatedRaw) && truncatedRaw.includes(this._collection);
|
|
4191
|
+
const stale = !isOnline || syncState.status === "offline" || truncated;
|
|
4052
4192
|
const fromCache = hasPending || syncState.lastSyncedAt === null || stale;
|
|
4053
4193
|
return {
|
|
4054
4194
|
docs: result.docs.map((d) => ({ id: d.id, data: d.data })),
|
|
@@ -4056,7 +4196,8 @@ var DbCollectionRefImpl = class {
|
|
|
4056
4196
|
fromCache,
|
|
4057
4197
|
stale,
|
|
4058
4198
|
hasPendingWrites: hasPending,
|
|
4059
|
-
changes: []
|
|
4199
|
+
changes: [],
|
|
4200
|
+
truncated
|
|
4060
4201
|
};
|
|
4061
4202
|
}
|
|
4062
4203
|
// ── CRUD ────────────────────────────────────────────────────────────────────
|
|
@@ -4502,6 +4643,9 @@ var NeetruDbDocumentsImpl = class {
|
|
|
4502
4643
|
async getConflicts() {
|
|
4503
4644
|
return this._store.listConflicts();
|
|
4504
4645
|
}
|
|
4646
|
+
onError(cb) {
|
|
4647
|
+
return this._engine.onError(cb);
|
|
4648
|
+
}
|
|
4505
4649
|
};
|
|
4506
4650
|
async function createOfflineDocumentsNamespace(opts) {
|
|
4507
4651
|
const store = isIndexedDBUnavailable() ? new MemoryStore() : new LocalStore(opts.dbName);
|
|
@@ -4977,6 +5121,7 @@ var defaultWebSocketFactory = (url) => {
|
|
|
4977
5121
|
// src/db/client-db.ts
|
|
4978
5122
|
var DATASTORE_COLLECTION_ENDPOINT = (collection) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}`;
|
|
4979
5123
|
var DATASTORE_DOC_ENDPOINT = (collection, docId) => `/api/sdk/v1/datastore/${encodeURIComponent(collection)}/${encodeURIComponent(docId)}`;
|
|
5124
|
+
var DOCUMENTS_RESYNC_PAGE_CAP = 200;
|
|
4980
5125
|
function resolveTransport(transport, opts, config) {
|
|
4981
5126
|
if (opts._transport) {
|
|
4982
5127
|
return opts._transport;
|
|
@@ -5089,6 +5234,7 @@ function createRestSyncTransport(config) {
|
|
|
5089
5234
|
const tenantHeaders = tenantScopeId ? { "x-neetru-tenant": tenantScopeId } : void 0;
|
|
5090
5235
|
const now = Date.now();
|
|
5091
5236
|
const docs = [];
|
|
5237
|
+
const truncatedCollections = [];
|
|
5092
5238
|
for (const collection of collections) {
|
|
5093
5239
|
let raw;
|
|
5094
5240
|
try {
|
|
@@ -5098,11 +5244,11 @@ function createRestSyncTransport(config) {
|
|
|
5098
5244
|
requireAuth: true,
|
|
5099
5245
|
retries: 2,
|
|
5100
5246
|
headers: tenantHeaders,
|
|
5101
|
-
// Teto do Core
|
|
5102
|
-
//
|
|
5103
|
-
// deleção-por-ausência
|
|
5104
|
-
//
|
|
5105
|
-
query: { limit:
|
|
5247
|
+
// Teto do Core (DOCUMENTS_RESYNC_PAGE_CAP). Coleções acima dele vêm
|
|
5248
|
+
// TRUNCADAS — reportadas em `truncatedCollections` p/ o SyncEngine
|
|
5249
|
+
// (a) NÃO rodar deleção-por-ausência e (b) sinalizar
|
|
5250
|
+
// `DbListResult.truncated`. Paginação por cursor é tarefa Core (gated).
|
|
5251
|
+
query: { limit: DOCUMENTS_RESYNC_PAGE_CAP }
|
|
5106
5252
|
});
|
|
5107
5253
|
} catch (err) {
|
|
5108
5254
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -5113,6 +5259,9 @@ function createRestSyncTransport(config) {
|
|
|
5113
5259
|
}
|
|
5114
5260
|
const resp = raw;
|
|
5115
5261
|
const items = Array.isArray(resp?.items) ? resp.items : [];
|
|
5262
|
+
if (items.length >= DOCUMENTS_RESYNC_PAGE_CAP) {
|
|
5263
|
+
truncatedCollections.push(collection);
|
|
5264
|
+
}
|
|
5116
5265
|
for (const item of items) {
|
|
5117
5266
|
if (!item || typeof item !== "object") continue;
|
|
5118
5267
|
const record = item;
|
|
@@ -5140,7 +5289,7 @@ function createRestSyncTransport(config) {
|
|
|
5140
5289
|
});
|
|
5141
5290
|
}
|
|
5142
5291
|
}
|
|
5143
|
-
return { docs, newWatermark: now };
|
|
5292
|
+
return { docs, newWatermark: now, truncatedCollections };
|
|
5144
5293
|
}
|
|
5145
5294
|
};
|
|
5146
5295
|
}
|
|
@@ -5202,20 +5351,20 @@ function buildTicketProvider(config, opts) {
|
|
|
5202
5351
|
return ticket;
|
|
5203
5352
|
};
|
|
5204
5353
|
}
|
|
5205
|
-
var
|
|
5206
|
-
function
|
|
5207
|
-
if (
|
|
5208
|
-
if (dbOpts.dbId) return;
|
|
5354
|
+
var _hasWarnedDbIdIgnored = false;
|
|
5355
|
+
function warnDbIdIgnoredOnce(config, dbOpts) {
|
|
5356
|
+
if (_hasWarnedDbIdIgnored) return;
|
|
5357
|
+
if (!dbOpts.dbId) return;
|
|
5209
5358
|
if (config.env === "prod") return;
|
|
5210
5359
|
if (typeof window === "undefined") return;
|
|
5211
5360
|
if (typeof console === "undefined" || typeof console.warn !== "function") return;
|
|
5212
|
-
|
|
5361
|
+
_hasWarnedDbIdIgnored = true;
|
|
5213
5362
|
console.warn(
|
|
5214
|
-
|
|
5363
|
+
'[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.'
|
|
5215
5364
|
);
|
|
5216
5365
|
}
|
|
5217
5366
|
function createNeetruDb(config, dbOpts = {}) {
|
|
5218
|
-
|
|
5367
|
+
warnDbIdIgnoredOnce(config, dbOpts);
|
|
5219
5368
|
let resolvedTransport;
|
|
5220
5369
|
if (dbOpts.transport !== void 0) {
|
|
5221
5370
|
resolvedTransport = dbOpts.transport;
|
|
@@ -5293,6 +5442,20 @@ function createNeetruDb(config, dbOpts = {}) {
|
|
|
5293
5442
|
async getConflicts() {
|
|
5294
5443
|
const docs = await getDocsPromise();
|
|
5295
5444
|
return docs.getConflicts();
|
|
5445
|
+
},
|
|
5446
|
+
onError(cb) {
|
|
5447
|
+
if (_docsResolved) return _docsResolved.onError(cb);
|
|
5448
|
+
let unsub = null;
|
|
5449
|
+
let cancelled = false;
|
|
5450
|
+
getDocsPromise().then((docs) => {
|
|
5451
|
+
if (!cancelled) {
|
|
5452
|
+
unsub = docs.onError(cb);
|
|
5453
|
+
}
|
|
5454
|
+
});
|
|
5455
|
+
return () => {
|
|
5456
|
+
cancelled = true;
|
|
5457
|
+
unsub?.();
|
|
5458
|
+
};
|
|
5296
5459
|
}
|
|
5297
5460
|
};
|
|
5298
5461
|
}
|
|
@@ -7839,7 +8002,7 @@ function createNeetruClient(config = {}) {
|
|
|
7839
8002
|
init_errors();
|
|
7840
8003
|
init_db_errors();
|
|
7841
8004
|
init_http();
|
|
7842
|
-
var VERSION = "3.4.
|
|
8005
|
+
var VERSION = "3.4.1";
|
|
7843
8006
|
|
|
7844
8007
|
export { DEFAULT_BASE_URL, DEV_FIXTURE_USER, MockAi, MockAuth, MockCheckout, MockDb, MockEntitlements, MockNotifications, MockSupport, MockUsage, MockWebhooks, NEETRU_SENTINEL_KEY, NeetruDbError, NeetruError, VERSION, checkCoreVersionCompat, createAiNamespace, createCheckoutNamespace, createNeetruClient, createNeetruDb, createNotificationsNamespace, createWebhooksNamespace, increment, isFieldSentinel, isIncrementSentinel, isServerTimestampSentinel, serverTimestamp, verifyWebhookRequest, verifyWebhookSignature };
|
|
7845
8008
|
//# sourceMappingURL=index.mjs.map
|