@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.
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-native/core",
3
- "version": "0.92.18",
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
- const bgHttp = isBackgroundFunctionPoolContext();
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
- const pool = new Pool({ connectionString: url, max: neonPoolMax() });
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: Promise<{ drizzle: any; Pool: any }> | undefined;
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(pool: {
88
- connect(): Promise<any>;
89
- query(
90
- sql: string,
91
- args?: any[],
92
- ): Promise<{ rows: unknown[]; rowCount?: number }>;
93
- end(): Promise<void>;
94
- on(event: string, listener: (...args: any[]) => void): unknown;
95
- }): typeof pool {
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(({ drizzle, Pool }) => {
469
- const rawPool = new Pool({
470
- connectionString: url,
471
- max: neonPoolMax(),
472
- });
473
- attachNeonPoolErrorLogger(rawPool);
474
- // Wrap the pool with the resilience layer so Drizzle queries get the
475
- // same withDbTimeout + retryOnConnectionError protection as the raw
476
- // DbExec path in client.ts. Reads retry freely; writes only retry on
477
- // acquire-timeout (pre-send) errors to avoid double-execution.
478
- const pool = buildResilientNeonPool(rawPool);
479
- _db = drizzle(pool, { schema });
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");
@@ -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;
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/db/client.ts"],"names":[],"mappings":"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"}
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
- const bgHttp = isBackgroundFunctionPoolContext();
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
- const pool = new Pool({ connectionString: url, max: neonPoolMax() });
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;