@jami-studio/core 0.92.18 → 0.92.20
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/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/db/client.ts +17 -2
- package/corpus/core/src/db/create-get-db.ts +67 -23
- package/corpus/core/src/server/better-auth-instance.ts +15 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/db/client.d.ts.map +1 -1
- package/dist/db/client.js +15 -2
- package/dist/db/client.js.map +1 -1
- package/dist/db/create-get-db.d.ts +9 -0
- package/dist/db/create-get-db.d.ts.map +1 -1
- package/dist/db/create-get-db.js +24 -3
- package/dist/db/create-get-db.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +15 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/package.json +1 -1
package/corpus/core/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @agent-native/core
|
|
2
2
|
|
|
3
|
+
## 0.92.20
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 7a019a3: Complete the Neon-on-workerd fix: the better-auth Neon pool (third pool site) now also routes over stateless HTTP with single-use WebSocket clients on Cloudflare Workers, and buildResilientNeonPool gains an httpPerQuery mode so the Drizzle path actually goes through pool.query() (its manual connect()/client.query() checkout was bypassing poolQueryViaFetch and pinning queries to per-request WebSocket clients). Fixes worker hangs on the request following an authenticated write under wrangler pages dev.
|
|
8
|
+
|
|
9
|
+
## 0.92.19
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 33aeaec: Fix Neon on Cloudflare Workers: workerd forbids reusing I/O objects across requests, so pooled Neon WebSocket clients created by one request and handed to a later one threw "Cannot perform I/O on behalf of a different request", hung the worker, and returned 500s (first seen on auth get-session under wrangler pages dev). Both Neon paths (raw DbExec in client.ts and the Drizzle pool in create-get-db.ts) now route plain queries over Neon's stateless HTTP transport (poolQueryViaFetch) on workerd and cap WebSocket clients (still used for transactions) to a single use so they are never reused across requests.
|
|
14
|
+
|
|
3
15
|
## 0.92.18
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/corpus/core/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agent-native/core",
|
|
3
|
-
"version": "0.92.
|
|
3
|
+
"version": "0.92.20",
|
|
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/studio-jami/jami-studio#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -10,6 +10,8 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import path from "path";
|
|
12
12
|
|
|
13
|
+
import { isCloudflareRuntime } from "../shared/runtime.js";
|
|
14
|
+
|
|
13
15
|
const recyclingPostgresPools = new WeakSet<object>();
|
|
14
16
|
const loggedNeonPools = new WeakSet<object>();
|
|
15
17
|
|
|
@@ -1079,12 +1081,25 @@ async function createDbExecInternal(
|
|
|
1079
1081
|
// analytics worker stalls right after model resolution and never claims.
|
|
1080
1082
|
// HTTP-per-query (poolQueryViaFetch) has no persistent socket to die; the
|
|
1081
1083
|
// foreground keeps the WebSocket pool. See the bg-fn execute branch below.
|
|
1082
|
-
|
|
1084
|
+
// Cloudflare Workers (workerd) additionally forbids reusing I/O objects
|
|
1085
|
+
// across requests — a pooled WebSocket created in request A and handed
|
|
1086
|
+
// to request B throws "Cannot perform I/O on behalf of a different
|
|
1087
|
+
// request" and hangs the worker — so workerd always takes the stateless
|
|
1088
|
+
// HTTP path too.
|
|
1089
|
+
const bgHttp =
|
|
1090
|
+
isBackgroundFunctionPoolContext() || isCloudflareRuntime();
|
|
1083
1091
|
if (bgHttp) {
|
|
1084
1092
|
(neonConfig as { poolQueryViaFetch?: boolean }).poolQueryViaFetch =
|
|
1085
1093
|
true;
|
|
1086
1094
|
}
|
|
1087
|
-
|
|
1095
|
+
// On workerd, transactions still use pool.connect() WebSocket clients —
|
|
1096
|
+
// cap each client to a single use so a socket created in one request is
|
|
1097
|
+
// destroyed on release instead of being handed to a later request.
|
|
1098
|
+
const pool = new Pool({
|
|
1099
|
+
connectionString: url,
|
|
1100
|
+
max: neonPoolMax(),
|
|
1101
|
+
...(isCloudflareRuntime() ? { maxUses: 1 } : {}),
|
|
1102
|
+
});
|
|
1088
1103
|
attachNeonPoolErrorLogger(pool);
|
|
1089
1104
|
if (trackSingletonResources) _neonPool = pool;
|
|
1090
1105
|
async function queryNeonClient(
|
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
retryOnConnectionError,
|
|
23
23
|
dbOpTimeoutMs,
|
|
24
24
|
} from "./client.js";
|
|
25
|
+
import { isCloudflareRuntime } from "../shared/runtime.js";
|
|
25
26
|
|
|
26
27
|
// Lazy driver loaders — cached promises so dynamic import only runs once.
|
|
27
28
|
let _pgDrizzle: Promise<{ drizzle: any; postgres: any }> | undefined;
|
|
@@ -38,7 +39,9 @@ function getPgDrizzle() {
|
|
|
38
39
|
return _pgDrizzle;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
|
-
let _neonServerlessDrizzle:
|
|
42
|
+
let _neonServerlessDrizzle:
|
|
43
|
+
| Promise<{ drizzle: any; Pool: any; neonConfig: any }>
|
|
44
|
+
| undefined;
|
|
42
45
|
function getNeonServerlessDrizzle() {
|
|
43
46
|
if (!_neonServerlessDrizzle) {
|
|
44
47
|
_neonServerlessDrizzle = Promise.all([
|
|
@@ -47,6 +50,7 @@ function getNeonServerlessDrizzle() {
|
|
|
47
50
|
]).then(([drizzleMod, neonMod]) => ({
|
|
48
51
|
drizzle: drizzleMod.drizzle,
|
|
49
52
|
Pool: neonMod.Pool,
|
|
53
|
+
neonConfig: neonMod.neonConfig,
|
|
50
54
|
}));
|
|
51
55
|
}
|
|
52
56
|
return _neonServerlessDrizzle;
|
|
@@ -84,15 +88,28 @@ export function isSqlRead(sql: string): boolean {
|
|
|
84
88
|
* connection the transaction needs. The pool-level error logger still fires
|
|
85
89
|
* on idle-client drops inside transactions.
|
|
86
90
|
*/
|
|
87
|
-
export function buildResilientNeonPool(
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
export function buildResilientNeonPool(
|
|
92
|
+
pool: {
|
|
93
|
+
connect(): Promise<any>;
|
|
94
|
+
query(
|
|
95
|
+
sql: string,
|
|
96
|
+
args?: any[],
|
|
97
|
+
): Promise<{ rows: unknown[]; rowCount?: number }>;
|
|
98
|
+
end(): Promise<void>;
|
|
99
|
+
on(event: string, listener: (...args: any[]) => void): unknown;
|
|
100
|
+
},
|
|
101
|
+
options?: {
|
|
102
|
+
/**
|
|
103
|
+
* Route plain queries through pool.query() instead of a manual
|
|
104
|
+
* connect()/client.query()/release() checkout. Required on Cloudflare
|
|
105
|
+
* Workers where neonConfig.poolQueryViaFetch routes pool.query() over
|
|
106
|
+
* Neon's stateless HTTP transport — the manual checkout would bypass
|
|
107
|
+
* that and pin queries to per-request WebSocket clients.
|
|
108
|
+
*/
|
|
109
|
+
httpPerQuery?: boolean;
|
|
110
|
+
},
|
|
111
|
+
): typeof pool {
|
|
112
|
+
const httpPerQuery = options?.httpPerQuery === true;
|
|
96
113
|
// Preserve all original pool methods and properties; only override `connect`
|
|
97
114
|
// and `query` at the Pool level (used by drizzle's neon-serverless adapter
|
|
98
115
|
// when it calls pool.query() directly, e.g. outside a transaction).
|
|
@@ -106,6 +123,19 @@ export function buildResilientNeonPool(pool: {
|
|
|
106
123
|
rows: unknown[];
|
|
107
124
|
rowCount?: number;
|
|
108
125
|
}> => {
|
|
126
|
+
if (httpPerQuery) {
|
|
127
|
+
// Stateless HTTP transport (poolQueryViaFetch): no connection
|
|
128
|
+
// checkout, no persistent socket — just bound the query itself.
|
|
129
|
+
return withDbTimeout(
|
|
130
|
+
"query",
|
|
131
|
+
() =>
|
|
132
|
+
pool.query(sql, args ?? []) as Promise<{
|
|
133
|
+
rows: unknown[];
|
|
134
|
+
rowCount?: number;
|
|
135
|
+
}>,
|
|
136
|
+
dbOpTimeoutMs(),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
109
139
|
// Bound the pool.connect() acquire — a frozen Neon WebSocket stalls here
|
|
110
140
|
// before the query ever starts, so a query-level timeout alone won't help.
|
|
111
141
|
let acquireTimedOut = false;
|
|
@@ -465,19 +495,33 @@ export function createGetDb<T extends Record<string, unknown>>(schema: T) {
|
|
|
465
495
|
|
|
466
496
|
if (dialect === "postgres") {
|
|
467
497
|
if (isNeonUrl(url)) {
|
|
468
|
-
_dbReady = getNeonServerlessDrizzle().then(
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
498
|
+
_dbReady = getNeonServerlessDrizzle().then(
|
|
499
|
+
({ drizzle, Pool, neonConfig }) => {
|
|
500
|
+
// Cloudflare Workers (workerd) forbids reusing I/O objects across
|
|
501
|
+
// requests — a WebSocket client cached in the pool by request A and
|
|
502
|
+
// handed to request B throws "Cannot perform I/O on behalf of a
|
|
503
|
+
// different request" and hangs the worker. Route plain queries over
|
|
504
|
+
// Neon's stateless HTTP transport (poolQueryViaFetch) and cap each
|
|
505
|
+
// WebSocket client (used for transactions) to a single use so it is
|
|
506
|
+
// never reused across requests.
|
|
507
|
+
const cfWorker = isCloudflareRuntime();
|
|
508
|
+
if (cfWorker) neonConfig.poolQueryViaFetch = true;
|
|
509
|
+
const rawPool = new Pool({
|
|
510
|
+
connectionString: url,
|
|
511
|
+
max: neonPoolMax(),
|
|
512
|
+
...(cfWorker ? { maxUses: 1 } : {}),
|
|
513
|
+
});
|
|
514
|
+
attachNeonPoolErrorLogger(rawPool);
|
|
515
|
+
// Wrap the pool with the resilience layer so Drizzle queries get the
|
|
516
|
+
// same withDbTimeout + retryOnConnectionError protection as the raw
|
|
517
|
+
// DbExec path in client.ts. Reads retry freely; writes only retry on
|
|
518
|
+
// acquire-timeout (pre-send) errors to avoid double-execution.
|
|
519
|
+
const pool = buildResilientNeonPool(rawPool, {
|
|
520
|
+
httpPerQuery: cfWorker,
|
|
521
|
+
});
|
|
522
|
+
_db = drizzle(pool, { schema });
|
|
523
|
+
},
|
|
524
|
+
);
|
|
481
525
|
} else {
|
|
482
526
|
_dbReady = getPgDrizzle().then(({ drizzle, postgres }) => {
|
|
483
527
|
// pgPoolOptions caps the pool to a small size on serverless so
|
|
@@ -48,6 +48,7 @@ import { getAppProductionUrl } from "./app-url.js";
|
|
|
48
48
|
import { signupAttributionFromCookieHeader } from "./attribution.js";
|
|
49
49
|
import { resolveAuthCookieNamespace } from "./cookie-namespace.js";
|
|
50
50
|
import { getWorkspaceA2ADerivedSecret } from "./derived-secret.js";
|
|
51
|
+
import { isCloudflareRuntime } from "../shared/runtime.js";
|
|
51
52
|
import {
|
|
52
53
|
renderResetPasswordEmail,
|
|
53
54
|
renderVerifySignupEmail,
|
|
@@ -1122,14 +1123,27 @@ async function buildDatabaseConfig(
|
|
|
1122
1123
|
// opens a raw TCP connection on port 5432 which frequently times out on
|
|
1123
1124
|
// Netlify Functions / Vercel / CF Workers when Neon's pooler is cold.
|
|
1124
1125
|
if (isNeonUrl(url)) {
|
|
1125
|
-
const { Pool } = await import("@neondatabase/serverless");
|
|
1126
|
+
const { Pool, neonConfig } = await import("@neondatabase/serverless");
|
|
1126
1127
|
// Cap the auth pool the same way as the app pool. Better Auth runs a
|
|
1127
1128
|
// session lookup on essentially every authenticated request, so an
|
|
1128
1129
|
// un-capped pool here is a primary contributor to "Max client
|
|
1129
1130
|
// connections reached" across concurrent serverless instances.
|
|
1131
|
+
//
|
|
1132
|
+
// Cloudflare Workers (workerd) forbids reusing I/O objects across
|
|
1133
|
+
// requests — a pooled WebSocket client created by one request and
|
|
1134
|
+
// handed to a later one hangs the worker ("Cannot perform I/O on
|
|
1135
|
+
// behalf of a different request"). Route plain queries over Neon's
|
|
1136
|
+
// stateless HTTP transport and cap WebSocket clients to a single use,
|
|
1137
|
+
// mirroring the app pools in db/client.ts and db/create-get-db.ts.
|
|
1138
|
+
const cfWorker = isCloudflareRuntime();
|
|
1139
|
+
if (cfWorker) {
|
|
1140
|
+
(neonConfig as { poolQueryViaFetch?: boolean }).poolQueryViaFetch =
|
|
1141
|
+
true;
|
|
1142
|
+
}
|
|
1130
1143
|
_neonAuthPool = new Pool({
|
|
1131
1144
|
connectionString: url,
|
|
1132
1145
|
max: neonPoolMax(),
|
|
1146
|
+
...(cfWorker ? { maxUses: 1 } : {}),
|
|
1133
1147
|
});
|
|
1134
1148
|
attachNeonPoolErrorLogger(_neonAuthPool, "db/neon-auth");
|
|
1135
1149
|
const { drizzle } = await import("drizzle-orm/neon-serverless");
|
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
|
-
ok?: undefined;
|
|
30
29
|
error: string;
|
|
30
|
+
ok?: undefined;
|
|
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;
|
package/dist/db/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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"}
|
package/dist/db/client.js
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* Cloudflare Workers, edge) without failing on missing native deps.
|
|
10
10
|
*/
|
|
11
11
|
import path from "path";
|
|
12
|
+
import { isCloudflareRuntime } from "../shared/runtime.js";
|
|
12
13
|
const recyclingPostgresPools = new WeakSet();
|
|
13
14
|
const loggedNeonPools = new WeakSet();
|
|
14
15
|
/** Read the request-scoped Cloudflare binding without requiring every
|
|
@@ -909,12 +910,24 @@ async function createDbExecInternal(config = {}, trackSingletonResources = false
|
|
|
909
910
|
// analytics worker stalls right after model resolution and never claims.
|
|
910
911
|
// HTTP-per-query (poolQueryViaFetch) has no persistent socket to die; the
|
|
911
912
|
// foreground keeps the WebSocket pool. See the bg-fn execute branch below.
|
|
912
|
-
|
|
913
|
+
// Cloudflare Workers (workerd) additionally forbids reusing I/O objects
|
|
914
|
+
// across requests — a pooled WebSocket created in request A and handed
|
|
915
|
+
// to request B throws "Cannot perform I/O on behalf of a different
|
|
916
|
+
// request" and hangs the worker — so workerd always takes the stateless
|
|
917
|
+
// HTTP path too.
|
|
918
|
+
const bgHttp = isBackgroundFunctionPoolContext() || isCloudflareRuntime();
|
|
913
919
|
if (bgHttp) {
|
|
914
920
|
neonConfig.poolQueryViaFetch =
|
|
915
921
|
true;
|
|
916
922
|
}
|
|
917
|
-
|
|
923
|
+
// On workerd, transactions still use pool.connect() WebSocket clients —
|
|
924
|
+
// cap each client to a single use so a socket created in one request is
|
|
925
|
+
// destroyed on release instead of being handed to a later request.
|
|
926
|
+
const pool = new Pool({
|
|
927
|
+
connectionString: url,
|
|
928
|
+
max: neonPoolMax(),
|
|
929
|
+
...(isCloudflareRuntime() ? { maxUses: 1 } : {}),
|
|
930
|
+
});
|
|
918
931
|
attachNeonPoolErrorLogger(pool);
|
|
919
932
|
if (trackSingletonResources)
|
|
920
933
|
_neonPool = pool;
|