@nebulr-group/bridge-svelte 0.8.2 → 0.8.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/bridge-runtime.js +92 -1
- package/package.json +1 -1
|
@@ -263,6 +263,91 @@ export function startBridgeRuntime(options = {}) {
|
|
|
263
263
|
// Best-effort: the next reconnect or push repairs it.
|
|
264
264
|
}
|
|
265
265
|
};
|
|
266
|
+
// TBP-686 — the same repair, for the one live payload the session snapshot
|
|
267
|
+
// does not carry: `quota.updated`.
|
|
268
|
+
//
|
|
269
|
+
// `GET /session/init` has no quota slice (nothing in bridge-api's
|
|
270
|
+
// session-snapshot types mentions it), and auth-core's QuotaStore fills
|
|
271
|
+
// itself from exactly two places: a one-shot lazy `GET /usage/quota/:metric`
|
|
272
|
+
// on the FIRST read of a metric, and live `quota.updated` pushes. It never
|
|
273
|
+
// re-reads — `ensureHydrated()` returns the cached snapshot on every
|
|
274
|
+
// subsequent call. So a push lost across a socket swap leaves `used` frozen
|
|
275
|
+
// at whatever it was when the metric was first read, for the rest of the
|
|
276
|
+
// session, with no error anywhere: the ingest returns 201, the server
|
|
277
|
+
// computes correctly and AppSync accepts the publish.
|
|
278
|
+
//
|
|
279
|
+
// That is what `metered-display` and `metered-plan-switch` hit on stage: a
|
|
280
|
+
// token refresh reauthorized mid-test, and the server published `used`
|
|
281
|
+
// 5-160ms before the replacement subscription was acknowledged.
|
|
282
|
+
//
|
|
283
|
+
// The ordering guarantee is the one stated above for the session snapshot,
|
|
284
|
+
// and it is what makes this deterministic rather than lucky: setOnOpen fires
|
|
285
|
+
// from markOpen(), which AppSync's `subscribe_success` drives — so anything
|
|
286
|
+
// published before this point is already durable and the REST read sees it,
|
|
287
|
+
// and anything published after arrives on the live socket.
|
|
288
|
+
//
|
|
289
|
+
// Scope: only metrics the store has already hydrated. A page that never
|
|
290
|
+
// asked about a metric has nothing stale to repair, so this costs one GET
|
|
291
|
+
// per watched metric per open and nothing at all for apps that do not use
|
|
292
|
+
// quotas.
|
|
293
|
+
const catchUpQuotaSnapshots = async () => {
|
|
294
|
+
if (_realtime !== rt)
|
|
295
|
+
return;
|
|
296
|
+
const token = _currentAuthToken;
|
|
297
|
+
const doFetch = _originalFetch ?? globalThis.fetch;
|
|
298
|
+
if (!token || typeof doFetch !== 'function')
|
|
299
|
+
return;
|
|
300
|
+
// One try over BOTH the lookup and the read: `requestCatchUp` fires this
|
|
301
|
+
// without a `.catch()`, so anything thrown out here becomes an unhandled
|
|
302
|
+
// rejection in the host app rather than a swallowed best-effort repair.
|
|
303
|
+
let quotas;
|
|
304
|
+
let metrics = [];
|
|
305
|
+
try {
|
|
306
|
+
quotas = useBridge().quotas;
|
|
307
|
+
metrics = [...quotas.getAll().keys()];
|
|
308
|
+
}
|
|
309
|
+
catch {
|
|
310
|
+
return; // useBridge() not constructed, or no quota surface — nothing to repair.
|
|
311
|
+
}
|
|
312
|
+
if (metrics.length === 0)
|
|
313
|
+
return;
|
|
314
|
+
let appId = config.appId;
|
|
315
|
+
try {
|
|
316
|
+
appId = getBridgeAuth().getApiContext().appId ?? appId;
|
|
317
|
+
}
|
|
318
|
+
catch {
|
|
319
|
+
// BridgeAuth not constructed — config.appId it is.
|
|
320
|
+
}
|
|
321
|
+
const base = (config.apiBaseUrl ?? 'https://api.thebridge.dev').replace(/\/+$/, '');
|
|
322
|
+
await Promise.all(metrics.map(async (metric) => {
|
|
323
|
+
// The value this repair is replacing. If a live push lands while the
|
|
324
|
+
// GET is in flight, the push is NEWER than the answer coming back and
|
|
325
|
+
// must win — otherwise the repair itself would reintroduce a stale
|
|
326
|
+
// `used`, which is the very bug it exists to fix.
|
|
327
|
+
const before = quotas.getAll().get(metric);
|
|
328
|
+
try {
|
|
329
|
+
const res = await doFetch(`${base}/usage/quota/${encodeURIComponent(metric)}`, {
|
|
330
|
+
method: 'GET',
|
|
331
|
+
headers: { Authorization: `Bearer ${token}`, 'x-app-id': appId ?? '' },
|
|
332
|
+
});
|
|
333
|
+
if (!res.ok)
|
|
334
|
+
return;
|
|
335
|
+
const data = (await res.json());
|
|
336
|
+
// Stopped, or the session changed while this was in flight: the
|
|
337
|
+
// answer describes a workspace we no longer have.
|
|
338
|
+
if (_realtime !== rt || _currentAuthToken !== token)
|
|
339
|
+
return;
|
|
340
|
+
if (quotas.getAll().get(metric) !== before)
|
|
341
|
+
return; // a push won the race
|
|
342
|
+
// `null` is a real answer — "no quota configured for this metric" —
|
|
343
|
+
// and applyInitialSnapshot drops the cached entry for it.
|
|
344
|
+
quotas.applyInitialSnapshot(metric, data ?? null);
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// Best-effort, per metric: one failure must not skip the others.
|
|
348
|
+
}
|
|
349
|
+
}));
|
|
350
|
+
};
|
|
266
351
|
const requestCatchUp = () => {
|
|
267
352
|
if (!_currentAuthToken)
|
|
268
353
|
return; // signed out: no tenant or user scope to fetch
|
|
@@ -271,7 +356,13 @@ export function startBridgeRuntime(options = {}) {
|
|
|
271
356
|
return;
|
|
272
357
|
}
|
|
273
358
|
_catchUpInFlight = true;
|
|
274
|
-
|
|
359
|
+
// Both repairs ride the one in-flight/queued gate, so an open storm still
|
|
360
|
+
// costs one round of requests, and neither can starve the other.
|
|
361
|
+
void Promise.all([catchUpSessionSnapshot(), catchUpQuotaSnapshots()]).catch(() => {
|
|
362
|
+
// Both halves are already best-effort internally; this is the backstop
|
|
363
|
+
// that keeps a repair failure from surfacing as an unhandled rejection
|
|
364
|
+
// in the consuming app. `.finally` below still drains the queue.
|
|
365
|
+
}).finally(() => {
|
|
275
366
|
_catchUpInFlight = false;
|
|
276
367
|
if (_catchUpQueued) {
|
|
277
368
|
_catchUpQueued = false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebulr-group/bridge-svelte",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.3",
|
|
4
4
|
"description": "Bridge Svelte library, This library helps you to add bridge authentication and feature flags, and payments to your svelte application.",
|
|
5
5
|
"author": "Iman Pouya",
|
|
6
6
|
"license": "MIT",
|