@agent-native/core 0.145.6 → 0.145.7
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/changelog/2026-08-07-analytics-no-longer-stalls-under-bursts-of-event-ingest-key-.md +6 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +65 -10
- package/corpus/templates/analytics/server/lib/session-replay.ts +5 -5
- package/dist/client/use-db-sync.js +4 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
package/corpus/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getDbExec } from "@agent-native/core/db";
|
|
2
2
|
import { runWithRequestContext } from "@agent-native/core/server";
|
|
3
|
-
import { and, eq, isNull, or } from "drizzle-orm";
|
|
3
|
+
import { and, eq, isNull, lt, or } from "drizzle-orm";
|
|
4
4
|
|
|
5
5
|
import { FIRST_PARTY_ANALYTICS_QUERY_TIMEOUT_MS } from "../../shared/dashboard-report-timeouts.js";
|
|
6
6
|
import { getDb, schema } from "../db/index.js";
|
|
@@ -181,6 +181,68 @@ export async function listAnalyticsPublicKeys(
|
|
|
181
181
|
}));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
+
/** How stale the last-used stamp must be before a request pays to refresh it. */
|
|
185
|
+
const LAST_USED_AT_REFRESH_MS = 60_000;
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Refresh a public key's last-used stamp without serializing ingest behind it.
|
|
189
|
+
*
|
|
190
|
+
* This UPDATE used to sit inside the ingest transaction, so every concurrent
|
|
191
|
+
* request for one public key took an exclusive row lock on that key's row and
|
|
192
|
+
* held it until the transaction committed — which meant through the rollup
|
|
193
|
+
* upsert. Production stacked 36 writers on three hot rows waiting 38-57s each;
|
|
194
|
+
* that exhausted the connection pool, and the whole app stopped loading while
|
|
195
|
+
* Postgres reported "no server connection available, client being queued". The
|
|
196
|
+
* stamp is bookkeeping: it needs neither atomicity with the events nor
|
|
197
|
+
* second-precision, and losing one is harmless.
|
|
198
|
+
*
|
|
199
|
+
* The staleness predicate throttles in SQL rather than in the caller, because
|
|
200
|
+
* Postgres only locks rows an UPDATE actually matches — a request whose key was
|
|
201
|
+
* stamped seconds ago matches nothing and takes no lock at all. Doing the same
|
|
202
|
+
* check in JS would reintroduce the convoy, since every racing request would
|
|
203
|
+
* still issue its own unconditional write.
|
|
204
|
+
*
|
|
205
|
+
* `last_used_at` is TEXT holding ISO-8601 UTC (always `Z`-suffixed), so `lt` is
|
|
206
|
+
* a lexicographic comparison that happens to be chronological. Storing a local
|
|
207
|
+
* or offset-bearing timestamp here would silently break this ordering.
|
|
208
|
+
*/
|
|
209
|
+
export async function touchPublicKeyLastUsedAt(
|
|
210
|
+
keyId: string,
|
|
211
|
+
receivedAt: string,
|
|
212
|
+
): Promise<void> {
|
|
213
|
+
const parsed = Date.parse(receivedAt);
|
|
214
|
+
if (!Number.isFinite(parsed)) {
|
|
215
|
+
console.warn(
|
|
216
|
+
"[first-party-analytics] Skipping last-used stamp: unparseable receivedAt",
|
|
217
|
+
receivedAt,
|
|
218
|
+
);
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
const staleBefore = new Date(parsed - LAST_USED_AT_REFRESH_MS).toISOString();
|
|
222
|
+
try {
|
|
223
|
+
const db = await getDb();
|
|
224
|
+
await db
|
|
225
|
+
.update(schema.analyticsPublicKeys)
|
|
226
|
+
.set({ lastUsedAt: receivedAt })
|
|
227
|
+
.where(
|
|
228
|
+
and(
|
|
229
|
+
eq(schema.analyticsPublicKeys.id, keyId),
|
|
230
|
+
or(
|
|
231
|
+
isNull(schema.analyticsPublicKeys.lastUsedAt),
|
|
232
|
+
lt(schema.analyticsPublicKeys.lastUsedAt, staleBefore),
|
|
233
|
+
),
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
// Best-effort by design: a failed stamp must not reject an ingest whose
|
|
238
|
+
// events already committed. Loud enough to see if it starts failing always.
|
|
239
|
+
console.warn(
|
|
240
|
+
"[first-party-analytics] Failed to refresh key last-used stamp:",
|
|
241
|
+
error,
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
|
|
184
246
|
function parseReplayAllowedOrigins(value: unknown): string[] {
|
|
185
247
|
if (Array.isArray(value)) {
|
|
186
248
|
return value.filter((item): item is string => typeof item === "string");
|
|
@@ -539,18 +601,11 @@ export async function recordAnalyticsEvents(
|
|
|
539
601
|
if (rows.length && backend.sink !== "bigquery") {
|
|
540
602
|
await db.transaction(async (tx: any) => {
|
|
541
603
|
await tx.insert(schema.analyticsEvents).values(rows);
|
|
542
|
-
await tx
|
|
543
|
-
.update(schema.analyticsPublicKeys)
|
|
544
|
-
.set({ lastUsedAt: receivedAt })
|
|
545
|
-
.where(eq(schema.analyticsPublicKeys.id, key.id));
|
|
546
604
|
await upsertFirstPartyAnalyticsRollups(rows, tx);
|
|
547
605
|
});
|
|
548
606
|
}
|
|
549
|
-
if (rows.length
|
|
550
|
-
await
|
|
551
|
-
.update(schema.analyticsPublicKeys)
|
|
552
|
-
.set({ lastUsedAt: receivedAt })
|
|
553
|
-
.where(eq(schema.analyticsPublicKeys.id, key.id));
|
|
607
|
+
if (rows.length) {
|
|
608
|
+
await touchPublicKeyLastUsedAt(key.id, receivedAt);
|
|
554
609
|
}
|
|
555
610
|
|
|
556
611
|
// Fork captured exceptions into the dedicated error-capture tables. This is
|
|
@@ -39,7 +39,10 @@ import {
|
|
|
39
39
|
SESSION_REPLAY_NETWORK_EVENT_TAG,
|
|
40
40
|
} from "../../shared/session-replay-diagnostics.js";
|
|
41
41
|
import { getDb, schema } from "../db/index.js";
|
|
42
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
resolveAnalyticsEventDimensions,
|
|
44
|
+
touchPublicKeyLastUsedAt,
|
|
45
|
+
} from "./first-party-analytics.js";
|
|
43
46
|
|
|
44
47
|
export type ReplayRange = "24h" | "7d" | "30d" | "90d" | "all";
|
|
45
48
|
|
|
@@ -1654,10 +1657,7 @@ export async function recordSessionReplayChunks(
|
|
|
1654
1657
|
})
|
|
1655
1658
|
.where(eq(schema.sessionRecordings.id, recording.id));
|
|
1656
1659
|
|
|
1657
|
-
await
|
|
1658
|
-
.update(schema.analyticsPublicKeys)
|
|
1659
|
-
.set({ lastUsedAt: ingestedAt })
|
|
1660
|
-
.where(eq(schema.analyticsPublicKeys.id, key.id));
|
|
1660
|
+
await touchPublicKeyLastUsedAt(key.id, ingestedAt);
|
|
1661
1661
|
|
|
1662
1662
|
recordChange({
|
|
1663
1663
|
source: "session-recordings",
|
|
@@ -335,6 +335,10 @@ class SyncTransport {
|
|
|
335
335
|
// collab on its fast presence cadence against the local stream. Subscribers
|
|
336
336
|
// are re-notified via the close/connect cycle below.
|
|
337
337
|
this.capabilities = [];
|
|
338
|
+
// The failure count was earned against the gateway. Carrying it over would
|
|
339
|
+
// back the local endpoint off by 2^failures (already 8min at the threshold)
|
|
340
|
+
// before its first poll, even though it just served this page.
|
|
341
|
+
this.consecutiveFailures = 0;
|
|
338
342
|
if (this.gatewayReconnectTimer) {
|
|
339
343
|
clearTimeout(this.gatewayReconnectTimer);
|
|
340
344
|
this.gatewayReconnectTimer = null;
|
package/dist/collab/routes.d.ts
CHANGED
|
@@ -26,8 +26,8 @@ export declare const getCollabState: import("h3").EventHandlerWithFetch<import("
|
|
|
26
26
|
* Body: { update: string (base64), requestSource?: string }
|
|
27
27
|
*/
|
|
28
28
|
export declare const postCollabUpdate: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
29
|
-
error: string;
|
|
30
29
|
ok?: undefined;
|
|
30
|
+
error: string;
|
|
31
31
|
} | {
|
|
32
32
|
error?: undefined;
|
|
33
33
|
ok: boolean;
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
ok?: undefined;
|
|
17
16
|
error: string;
|
|
17
|
+
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -17,11 +17,11 @@ declare const _default: import("../../action.js").ActionDefinition<{
|
|
|
17
17
|
id?: undefined;
|
|
18
18
|
provider?: undefined;
|
|
19
19
|
} | {
|
|
20
|
-
error?: undefined;
|
|
21
20
|
configured?: undefined;
|
|
22
21
|
connectPath?: undefined;
|
|
23
22
|
url: string;
|
|
24
23
|
id: string;
|
|
25
24
|
provider: string;
|
|
25
|
+
error?: undefined;
|
|
26
26
|
}>;
|
|
27
27
|
export default _default;
|
|
@@ -41,16 +41,16 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
41
41
|
thumbsUpRate: number;
|
|
42
42
|
avgEvalScore: number;
|
|
43
43
|
} | {
|
|
44
|
-
error?: undefined;
|
|
45
44
|
summary: import("./types.js").TraceSummary;
|
|
46
45
|
spans: import("./types.js").TraceSpan[];
|
|
47
46
|
id?: undefined;
|
|
47
|
+
error?: undefined;
|
|
48
48
|
ok?: undefined;
|
|
49
49
|
} | {
|
|
50
|
-
error?: undefined;
|
|
51
50
|
summary?: undefined;
|
|
52
51
|
spans?: undefined;
|
|
53
52
|
id: string;
|
|
53
|
+
error?: undefined;
|
|
54
54
|
ok?: undefined;
|
|
55
55
|
} | {
|
|
56
56
|
summary?: undefined;
|
|
@@ -59,9 +59,9 @@ export declare function createObservabilityHandler(): import("h3").EventHandlerW
|
|
|
59
59
|
error: any;
|
|
60
60
|
ok?: undefined;
|
|
61
61
|
} | {
|
|
62
|
-
error?: undefined;
|
|
63
62
|
summary?: undefined;
|
|
64
63
|
spans?: undefined;
|
|
65
64
|
id?: undefined;
|
|
66
65
|
ok: boolean;
|
|
66
|
+
error?: undefined;
|
|
67
67
|
}>>;
|
|
@@ -48,8 +48,8 @@ export declare function handleUpdateResource(event: any): Promise<import("./stor
|
|
|
48
48
|
}>;
|
|
49
49
|
/** DELETE /_agent-native/resources/:id — delete a resource */
|
|
50
50
|
export declare function handleDeleteResource(event: any): Promise<{
|
|
51
|
-
ok?: undefined;
|
|
52
51
|
error: string;
|
|
52
|
+
ok?: undefined;
|
|
53
53
|
} | {
|
|
54
54
|
error?: undefined;
|
|
55
55
|
ok: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.145.
|
|
3
|
+
"version": "0.145.7",
|
|
4
4
|
"description": "Framework for agent-native application development — where AI agents and UI share SQL state, actions, and context",
|
|
5
5
|
"homepage": "https://github.com/BuilderIO/agent-native#readme",
|
|
6
6
|
"bugs": {
|