graphile-llm 1.11.2 → 1.11.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.
@@ -7,6 +7,11 @@
7
7
  *
8
8
  * Results are cached per-database with a TTL so the REST middleware
9
9
  * doesn't hit the database on every request.
10
+ *
11
+ * Discovery is keyed by `database_id`, as every other module lookup is: one
12
+ * serving database holds several tenants' schemas, so an unkeyed lookup does
13
+ * not fail — it resolves a neighbouring tenant's agent tables, and the cache
14
+ * then serves that for its whole TTL.
10
15
  */
11
16
  import { Pool } from 'pg';
12
17
  export interface AgentTableInfo {
@@ -24,6 +29,6 @@ export interface AgentDiscovery {
24
29
  export declare function clearAgentDiscoveryCache(): void;
25
30
  /**
26
31
  * Look up agent table info for a database, querying the module config table.
27
- * Results are cached per-database with a 60s TTL.
32
+ * Results are cached per database id with a 60s TTL.
28
33
  */
29
- export declare function getAgentDiscovery(pool: Pool, dbname: string): Promise<AgentDiscovery | null>;
34
+ export declare function getAgentDiscovery(pool: Pool, databaseId: string): Promise<AgentDiscovery | null>;
@@ -7,6 +7,11 @@
7
7
  *
8
8
  * Results are cached per-database with a TTL so the REST middleware
9
9
  * doesn't hit the database on every request.
10
+ *
11
+ * Discovery is keyed by `database_id`, as every other module lookup is: one
12
+ * serving database holds several tenants' schemas, so an unkeyed lookup does
13
+ * not fail — it resolves a neighbouring tenant's agent tables, and the cache
14
+ * then serves that for its whole TTL.
10
15
  */
11
16
  import { ModuleConfigCache } from 'graphile-cache';
12
17
  // ─── Cache ──────────────────────────────────────────────────────────────────
@@ -27,20 +32,34 @@ const DISCOVERY_SQL = `
27
32
  acm.task_table_name
28
33
  FROM metaschema_modules_public.agent_chat_module acm
29
34
  JOIN metaschema_public.schema s ON s.id = acm.schema_id
35
+ WHERE acm.database_id = $1
30
36
  LIMIT 1
31
37
  `;
38
+ /** The module (or the whole metaschema) is simply absent from this database. */
39
+ const NOT_PROVISIONED = new Set([
40
+ '42P01', // undefined_table
41
+ '3F000' // invalid_schema_name
42
+ ]);
43
+ const isNotProvisioned = (err) => typeof err === 'object' &&
44
+ err !== null &&
45
+ 'code' in err &&
46
+ typeof err.code === 'string' &&
47
+ NOT_PROVISIONED.has(err.code);
32
48
  /**
33
49
  * Look up agent table info for a database, querying the module config table.
34
- * Results are cached per-database with a 60s TTL.
50
+ * Results are cached per database id with a 60s TTL.
35
51
  */
36
- export async function getAgentDiscovery(pool, dbname) {
37
- const cached = agentDiscoveryCache.get(dbname);
52
+ export async function getAgentDiscovery(pool, databaseId) {
53
+ if (!databaseId) {
54
+ throw new Error('getAgentDiscovery: databaseId is required');
55
+ }
56
+ const cached = agentDiscoveryCache.get(databaseId);
38
57
  if (cached !== undefined) {
39
58
  return cached;
40
59
  }
41
60
  let discovery = null;
42
61
  try {
43
- const { rows } = await pool.query(DISCOVERY_SQL);
62
+ const { rows } = await pool.query(DISCOVERY_SQL, [databaseId]);
44
63
  if (rows.length > 0) {
45
64
  const row = rows[0];
46
65
  const schemaName = row.schema_name;
@@ -57,9 +76,12 @@ export async function getAgentDiscovery(pool, dbname) {
57
76
  };
58
77
  }
59
78
  }
60
- catch {
61
- // Module table doesn't exist in this database — not provisioned
79
+ catch (err) {
80
+ // Only the absence being probed for is swallowed. A dead pool or a bad
81
+ // databaseId reported as "not provisioned" is a silently agent-less API.
82
+ if (!isNotProvisioned(err))
83
+ throw err;
62
84
  }
63
- agentDiscoveryCache.set(dbname, discovery);
85
+ agentDiscoveryCache.set(databaseId, discovery);
64
86
  return discovery;
65
87
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "graphile-llm",
3
- "version": "1.11.2",
3
+ "version": "1.11.3",
4
4
  "description": "LLM integration plugin for PostGraphile v5 — server-side text-to-vector embedding and text companion fields for pgvector columns",
5
5
  "author": "Constructive <developers@constructive.io>",
6
6
  "homepage": "https://github.com/constructive-io/constructive",
@@ -30,9 +30,9 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "@agentic-kit/ollama": "2.13.0",
33
- "@constructive-io/express-context": "^0.24.1",
33
+ "@constructive-io/express-context": "^0.24.2",
34
34
  "@constructive-io/llm-env": "^0.13.0",
35
- "graphile-cache": "^4.10.1"
35
+ "graphile-cache": "^4.10.2"
36
36
  },
37
37
  "peerDependencies": {
38
38
  "@dataplan/pg": "1.0.3",
@@ -56,11 +56,11 @@
56
56
  },
57
57
  "devDependencies": {
58
58
  "@types/node": "^22.19.11",
59
- "graphile-connection-filter": "^2.11.2",
60
- "graphile-search": "^2.11.2",
61
- "graphile-test": "^5.11.2",
59
+ "graphile-connection-filter": "^2.11.3",
60
+ "graphile-search": "^2.11.3",
61
+ "graphile-test": "^5.11.3",
62
62
  "makage": "^0.3.0",
63
- "pgsql-test": "^5.10.2"
63
+ "pgsql-test": "^5.10.3"
64
64
  },
65
65
  "keywords": [
66
66
  "postgraphile",
@@ -75,5 +75,5 @@
75
75
  "ollama",
76
76
  "openai"
77
77
  ],
78
- "gitHead": "c9dcb0c96174dfb0bc6fd63e46cc7776ea7af173"
78
+ "gitHead": "43121004a8bedd2c5fcf7bf33208079d55bad01f"
79
79
  }
@@ -7,6 +7,11 @@
7
7
  *
8
8
  * Results are cached per-database with a TTL so the REST middleware
9
9
  * doesn't hit the database on every request.
10
+ *
11
+ * Discovery is keyed by `database_id`, as every other module lookup is: one
12
+ * serving database holds several tenants' schemas, so an unkeyed lookup does
13
+ * not fail — it resolves a neighbouring tenant's agent tables, and the cache
14
+ * then serves that for its whole TTL.
10
15
  */
11
16
  import { Pool } from 'pg';
12
17
  export interface AgentTableInfo {
@@ -24,6 +29,6 @@ export interface AgentDiscovery {
24
29
  export declare function clearAgentDiscoveryCache(): void;
25
30
  /**
26
31
  * Look up agent table info for a database, querying the module config table.
27
- * Results are cached per-database with a 60s TTL.
32
+ * Results are cached per database id with a 60s TTL.
28
33
  */
29
- export declare function getAgentDiscovery(pool: Pool, dbname: string): Promise<AgentDiscovery | null>;
34
+ export declare function getAgentDiscovery(pool: Pool, databaseId: string): Promise<AgentDiscovery | null>;
@@ -8,6 +8,11 @@
8
8
  *
9
9
  * Results are cached per-database with a TTL so the REST middleware
10
10
  * doesn't hit the database on every request.
11
+ *
12
+ * Discovery is keyed by `database_id`, as every other module lookup is: one
13
+ * serving database holds several tenants' schemas, so an unkeyed lookup does
14
+ * not fail — it resolves a neighbouring tenant's agent tables, and the cache
15
+ * then serves that for its whole TTL.
11
16
  */
12
17
  Object.defineProperty(exports, "__esModule", { value: true });
13
18
  exports.clearAgentDiscoveryCache = clearAgentDiscoveryCache;
@@ -31,20 +36,34 @@ const DISCOVERY_SQL = `
31
36
  acm.task_table_name
32
37
  FROM metaschema_modules_public.agent_chat_module acm
33
38
  JOIN metaschema_public.schema s ON s.id = acm.schema_id
39
+ WHERE acm.database_id = $1
34
40
  LIMIT 1
35
41
  `;
42
+ /** The module (or the whole metaschema) is simply absent from this database. */
43
+ const NOT_PROVISIONED = new Set([
44
+ '42P01', // undefined_table
45
+ '3F000' // invalid_schema_name
46
+ ]);
47
+ const isNotProvisioned = (err) => typeof err === 'object' &&
48
+ err !== null &&
49
+ 'code' in err &&
50
+ typeof err.code === 'string' &&
51
+ NOT_PROVISIONED.has(err.code);
36
52
  /**
37
53
  * Look up agent table info for a database, querying the module config table.
38
- * Results are cached per-database with a 60s TTL.
54
+ * Results are cached per database id with a 60s TTL.
39
55
  */
40
- async function getAgentDiscovery(pool, dbname) {
41
- const cached = agentDiscoveryCache.get(dbname);
56
+ async function getAgentDiscovery(pool, databaseId) {
57
+ if (!databaseId) {
58
+ throw new Error('getAgentDiscovery: databaseId is required');
59
+ }
60
+ const cached = agentDiscoveryCache.get(databaseId);
42
61
  if (cached !== undefined) {
43
62
  return cached;
44
63
  }
45
64
  let discovery = null;
46
65
  try {
47
- const { rows } = await pool.query(DISCOVERY_SQL);
66
+ const { rows } = await pool.query(DISCOVERY_SQL, [databaseId]);
48
67
  if (rows.length > 0) {
49
68
  const row = rows[0];
50
69
  const schemaName = row.schema_name;
@@ -61,9 +80,12 @@ async function getAgentDiscovery(pool, dbname) {
61
80
  };
62
81
  }
63
82
  }
64
- catch {
65
- // Module table doesn't exist in this database — not provisioned
83
+ catch (err) {
84
+ // Only the absence being probed for is swallowed. A dead pool or a bad
85
+ // databaseId reported as "not provisioned" is a silently agent-less API.
86
+ if (!isNotProvisioned(err))
87
+ throw err;
66
88
  }
67
- agentDiscoveryCache.set(dbname, discovery);
89
+ agentDiscoveryCache.set(databaseId, discovery);
68
90
  return discovery;
69
91
  }