@exposify/supabase 0.1.0 → 0.3.1

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/index.cjs CHANGED
@@ -21,11 +21,11 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
23
  MANIFEST_VERSION: () => MANIFEST_VERSION,
24
- MissingAgentifyMetaError: () => MissingAgentifyMetaError,
24
+ MissingExposifyMetaError: () => MissingExposifyMetaError,
25
25
  aiReady: () => aiReady,
26
26
  buildManifest: () => buildManifest,
27
27
  columnsFromOpenApi: () => columnsFromOpenApi,
28
- fetchAgentifyMeta: () => fetchAgentifyMeta,
28
+ fetchExposifyMeta: () => fetchExposifyMeta,
29
29
  fetchOpenApi: () => fetchOpenApi,
30
30
  introspectTables: () => introspectTables,
31
31
  loadManifest: () => loadManifest,
@@ -103,7 +103,7 @@ function warnOnce(message) {
103
103
  if (warned.has(message)) return;
104
104
  warned.add(message);
105
105
  try {
106
- console.warn(`[agentify-supabase] ${message}`);
106
+ console.warn(`[exposify-supabase] ${message}`);
107
107
  } catch {
108
108
  }
109
109
  }
@@ -129,6 +129,7 @@ function buildPostgrestHeaders(incoming, anonKey) {
129
129
  if (value) headers.set(name, value);
130
130
  }
131
131
  if (!headers.has("apikey")) headers.set("apikey", anonKey);
132
+ if (!headers.has("authorization")) headers.set("authorization", `Bearer ${anonKey}`);
132
133
  return headers;
133
134
  }
134
135
  function createPostgrestClient(postgrestUrl, headers, fetchImpl) {
@@ -161,42 +162,49 @@ function createPostgrestClient(postgrestUrl, headers, fetchImpl) {
161
162
  }
162
163
 
163
164
  // src/fetch-openapi.ts
165
+ var WRONG_KEY_TIER_RE = /service_role|secret api key/i;
164
166
  async function fetchOpenApi(postgrestUrl, headers, fetchImpl) {
165
167
  const res = await fetchImpl(postgrestUrl, {
166
168
  headers: { ...Object.fromEntries(headers), accept: "application/openapi+json" }
167
169
  });
168
170
  if (!res.ok) {
169
- throw new Error(`PostgREST OpenAPI fetch failed: HTTP ${res.status}`);
171
+ const body = await res.text().catch(() => "");
172
+ if (res.status === 401 && WRONG_KEY_TIER_RE.test(body)) {
173
+ throw new Error(
174
+ `PostgREST OpenAPI fetch failed: HTTP 401 -- Supabase rejected this request because it wasn't made with the service role key (response: "${body.trim()}"). Schema introspection always needs SUPABASE_SERVICE_ROLE_KEY, never the anon key -- see this package's README, "Why introspection needs the service role key". In a deployed function this is auto-injected automatically; for local dev/testing pass { serviceRoleKey } explicitly (or --key for the CLI).`
175
+ );
176
+ }
177
+ throw new Error(`PostgREST OpenAPI fetch failed: HTTP ${res.status}${body ? `: ${body}` : ""}`);
170
178
  }
171
179
  return await res.json();
172
180
  }
173
181
 
174
182
  // src/meta.ts
175
- var MissingAgentifyMetaError = class extends Error {
183
+ var MissingExposifyMetaError = class extends Error {
176
184
  constructor(cause) {
177
185
  super(cause);
178
- this.name = "MissingAgentifyMetaError";
186
+ this.name = "MissingExposifyMetaError";
179
187
  }
180
188
  };
181
- async function fetchAgentifyMeta(postgrestUrl, headers, fetchImpl) {
182
- const res = await fetchImpl(`${postgrestUrl}/rpc/agentify_meta`, {
189
+ async function fetchExposifyMeta(postgrestUrl, headers, fetchImpl) {
190
+ const res = await fetchImpl(`${postgrestUrl}/rpc/exposify_meta`, {
183
191
  method: "POST",
184
192
  headers: { ...Object.fromEntries(headers), "content-type": "application/json" },
185
193
  body: "{}"
186
194
  });
187
195
  if (res.status === 404) {
188
- throw new MissingAgentifyMetaError(
189
- "agentify_meta() is not reachable at /rpc/agentify_meta (404). Run `npx agentify-supabase init` (or apply sql/agentify_meta.sql directly) and `supabase db push`, then redeploy the function."
196
+ throw new MissingExposifyMetaError(
197
+ "exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx exposify-supabase init` (or apply sql/exposify_meta.sql directly) and `supabase db push`, then redeploy the function."
190
198
  );
191
199
  }
192
200
  if (!res.ok) {
193
201
  const body = await res.text().catch(() => "");
194
202
  if (/PGRST202|function .* does not exist/i.test(body)) {
195
- throw new MissingAgentifyMetaError(
196
- "agentify_meta() is missing from the database (PostgREST error PGRST202). Apply the migration in sql/agentify_meta.sql via `supabase db push`, then redeploy the function."
203
+ throw new MissingExposifyMetaError(
204
+ "exposify_meta() is missing from the database (PostgREST error PGRST202). Apply the migration in sql/exposify_meta.sql via `supabase db push`, then redeploy the function."
197
205
  );
198
206
  }
199
- throw new Error(`agentify_meta() call failed: HTTP ${res.status} ${body}`.trim());
207
+ throw new Error(`exposify_meta() call failed: HTTP ${res.status} ${body}`.trim());
200
208
  }
201
209
  return await res.json();
202
210
  }
@@ -227,7 +235,7 @@ function columnsFromOpenApi(doc, table) {
227
235
  // ON COLUMN` into `description`, then appends its own "Note:\nThis is a
228
236
  // Primary/Foreign Key...<pk/|fk/>" boilerplate) -- strip that boilerplate
229
237
  // so a column with no real comment ends up `undefined`, not the
230
- // boilerplate text. agentify_meta()'s copy wins when both exist anyway
238
+ // boilerplate text. exposify_meta()'s copy wins when both exist anyway
231
239
  // -- see introspect.ts's merge.
232
240
  description: prop.description?.replace(/\n*Note:[\s\S]*$/, "").trim() || void 0
233
241
  }));
@@ -262,7 +270,7 @@ function introspectTables(openapi, metaRows) {
262
270
  if (!fromOpenApi) continue;
263
271
  columns.push({
264
272
  ...fromOpenApi,
265
- // agentify_meta()'s comment wins over OpenAPI's mirrored copy (spec:
273
+ // exposify_meta()'s comment wins over OpenAPI's mirrored copy (spec:
266
274
  // "Column comments → param descriptions" sourced from the migration).
267
275
  description: row.column_comment ?? fromOpenApi.description
268
276
  });
@@ -305,7 +313,7 @@ async function resolveTableSchemas(deps) {
305
313
  try {
306
314
  const [openapi, metaRows] = await Promise.all([
307
315
  fetchOpenApi(deps.postgrestUrl, deps.headers, deps.fetchImpl),
308
- fetchAgentifyMeta(deps.postgrestUrl, deps.headers, deps.fetchImpl)
316
+ fetchExposifyMeta(deps.postgrestUrl, deps.headers, deps.fetchImpl)
309
317
  ]);
310
318
  return { tables: introspectTables(openapi, metaRows), mode: "runtime", warnings, openapi };
311
319
  } catch (err) {
@@ -315,7 +323,7 @@ async function resolveTableSchemas(deps) {
315
323
  if (manifest) {
316
324
  warnings.push({
317
325
  rung: "manifest",
318
- reason: "runtime introspection failed; serving tools from the bundled agentify.manifest.json instead"
326
+ reason: "runtime introspection failed; serving tools from the bundled exposify.manifest.json instead"
319
327
  });
320
328
  return { tables: manifest.tables, mode: "manifest", warnings };
321
329
  }
@@ -602,9 +610,9 @@ ${h.chunk.text}`).join("\n\n---\n\n") : "No matching documentation found.";
602
610
  function registerDiagnosticsResource(server, warnings) {
603
611
  if (warnings.length === 0) return;
604
612
  server.registerResource(
605
- "agentify-diagnostics",
606
- "agentify://diagnostics",
607
- { title: "agentify-supabase diagnostics", mimeType: "application/json" },
613
+ "exposify-diagnostics",
614
+ "exposify://diagnostics",
615
+ { title: "exposify-supabase diagnostics", mimeType: "application/json" },
608
616
  async (uri) => ({ contents: [{ uri: uri.href, text: JSON.stringify(warnings, null, 2) }] })
609
617
  );
610
618
  }
@@ -637,6 +645,7 @@ function aiReady(options = {}) {
637
645
  const env = denoEnv();
638
646
  const supabaseUrl = options.supabaseUrl ?? env?.get("SUPABASE_URL");
639
647
  const anonKey = options.anonKey ?? env?.get("SUPABASE_ANON_KEY") ?? "";
648
+ const serviceRoleKey = options.serviceRoleKey ?? env?.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
640
649
  let kbPromise = null;
641
650
  const getKb = () => {
642
651
  kbPromise ??= (async () => {
@@ -654,12 +663,12 @@ function aiReady(options = {}) {
654
663
  const getLadder = (postgrestUrl) => {
655
664
  ladderPromise ??= resolveTableSchemas({
656
665
  postgrestUrl,
657
- headers: buildPostgrestHeaders(new Headers(), anonKey),
666
+ headers: buildPostgrestHeaders(new Headers(), serviceRoleKey),
658
667
  fetchImpl,
659
668
  manifest: options.manifest
660
669
  }).then((ladder) => {
661
670
  for (const w of ladder.warnings) {
662
- console.error(`[agentify-supabase] degradation ladder: ${w.rung}: ${w.reason}`);
671
+ console.error(`[exposify-supabase] degradation ladder: ${w.rung}: ${w.reason}`);
663
672
  }
664
673
  return ladder;
665
674
  });
@@ -687,7 +696,7 @@ function aiReady(options = {}) {
687
696
  return { status: res.status, bodyText: await res.text() };
688
697
  };
689
698
  return buildSupabaseMcpServer({
690
- appName: "agentify-supabase",
699
+ appName: "exposify-supabase",
691
700
  appVersion: "0.0.0",
692
701
  tables: ladder.tables,
693
702
  cfg,
@@ -706,11 +715,11 @@ function aiReady(options = {}) {
706
715
  // Annotate the CommonJS export names for ESM import in node:
707
716
  0 && (module.exports = {
708
717
  MANIFEST_VERSION,
709
- MissingAgentifyMetaError,
718
+ MissingExposifyMetaError,
710
719
  aiReady,
711
720
  buildManifest,
712
721
  columnsFromOpenApi,
713
- fetchAgentifyMeta,
722
+ fetchExposifyMeta,
714
723
  fetchOpenApi,
715
724
  introspectTables,
716
725
  loadManifest,
package/dist/index.d.cts CHANGED
@@ -7,18 +7,18 @@ interface ColumnSchema {
7
7
  /** OpenAPI `format`, e.g. "uuid", "timestamp with time zone", "bigint". */
8
8
  format?: string;
9
9
  required: boolean;
10
- /** From `agentify_meta()`'s `column_comment` (a `COMMENT ON COLUMN`), not OpenAPI. */
10
+ /** From `exposify_meta()`'s `column_comment` (a `COMMENT ON COLUMN`), not OpenAPI. */
11
11
  description?: string;
12
12
  isPrimaryKey?: boolean;
13
13
  }
14
14
  interface TableSchema {
15
15
  name: string;
16
16
  rlsEnabled: boolean;
17
- /** From `agentify_meta()`'s `table_comment` (a `COMMENT ON TABLE`). */
17
+ /** From `exposify_meta()`'s `table_comment` (a `COMMENT ON TABLE`). */
18
18
  description?: string;
19
19
  columns: ColumnSchema[];
20
20
  }
21
- /** One row of `agentify_meta()` -- the migration returns one row per column. */
21
+ /** One row of `exposify_meta()` -- the migration returns one row per column. */
22
22
  interface MetaRow {
23
23
  table_name: string;
24
24
  rls_enabled: boolean;
@@ -60,9 +60,21 @@ interface AgentifySupabaseOptions {
60
60
  /** Overrides for local/CI use -- production reads these from the Deno env automatically. */
61
61
  supabaseUrl?: string;
62
62
  anonKey?: string;
63
+ /**
64
+ * Used ONLY for schema introspection (fetching PostgREST's OpenAPI document
65
+ * + calling `exposify_meta()`) -- never for per-request data access, which
66
+ * always uses the caller's forwarded JWT (or the anon key as its fallback).
67
+ * Required because Supabase's gateway rejects the OpenAPI-discovery route
68
+ * for anon/publishable-tier keys outright (`Invalid API key` / `Only the
69
+ * service_role API key can be used for this endpoint`), regardless of key
70
+ * format -- discovered via a real deployment, not documented anywhere at
71
+ * design time. Auto-injected as `SUPABASE_SERVICE_ROLE_KEY` in every
72
+ * deployed edge function; this option exists for local dev/testing only.
73
+ */
74
+ serviceRoleKey?: string;
63
75
  /** Injectable for tests; defaults to global `fetch`. */
64
76
  fetchImpl?: typeof fetch;
65
- /** Bundled fallback manifest (from `agentify-supabase generate`), used when runtime introspection fails. */
77
+ /** Bundled fallback manifest (from `exposify-supabase generate`), used when runtime introspection fails. */
66
78
  manifest?: unknown;
67
79
  rateLimit?: number;
68
80
  rateWindowMs?: number;
@@ -98,7 +110,7 @@ interface PostgrestColumnProperty {
98
110
  /**
99
111
  * Extracts column schemas for one table from a PostgREST OpenAPI document.
100
112
  * Returns `[]` for a table PostgREST never exposed (not in `definitions` --
101
- * e.g. it exists at the catalog level per `agentify_meta()` but PostgREST's
113
+ * e.g. it exists at the catalog level per `exposify_meta()` but PostgREST's
102
114
  * role lacks USAGE/SELECT grants on it), which the caller treats the same as
103
115
  * "RLS-less": never shown to the agent.
104
116
  */
@@ -113,27 +125,27 @@ declare function columnsFromOpenApi(doc: PostgrestOpenApiDoc, table: string): Co
113
125
  declare function rpcArgSchema(doc: PostgrestOpenApiDoc, fn: string): JsonSchemaObject | null;
114
126
 
115
127
  declare const MANIFEST_VERSION = 1;
116
- interface AgentifyManifest {
128
+ interface ExposifyManifest {
117
129
  version: typeof MANIFEST_VERSION;
118
130
  generatedAt: string;
119
131
  tables: TableSchema[];
120
132
  }
121
- /** Built by `npx agentify-supabase generate` against a local/linked project. */
122
- declare function buildManifest(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[], generatedAt: string): AgentifyManifest;
133
+ /** Built by `npx exposify-supabase generate` against a local/linked project. */
134
+ declare function buildManifest(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[], generatedAt: string): ExposifyManifest;
123
135
  /**
124
136
  * Defensive load: a manifest is bundled JSON shipped alongside the function
125
137
  * and can go stale relative to the deployed schema, or simply be malformed
126
138
  * (hand-edited, wrong version). Returns `null` -- never throws -- so the
127
139
  * degradation ladder can fall through to the next rung instead of crashing.
128
140
  */
129
- declare function loadManifest(raw: unknown): AgentifyManifest | null;
141
+ declare function loadManifest(raw: unknown): ExposifyManifest | null;
130
142
 
131
143
  /**
132
- * Merges PostgREST's OpenAPI document (column types) with `agentify_meta()`
144
+ * Merges PostgREST's OpenAPI document (column types) with `exposify_meta()`
133
145
  * rows (RLS status + comments) into the DB tool surface.
134
146
  *
135
147
  * DB exposure policy (spec, locked): RLS-gated, reads auto. A table only
136
- * appears here if `agentify_meta()` reports `rls_enabled: true` for it AND
148
+ * appears here if `exposify_meta()` reports `rls_enabled: true` for it AND
137
149
  * PostgREST actually exposes it in its OpenAPI `definitions` (the role
138
150
  * calling this has grants on it) -- a table failing either check never
139
151
  * appears, silently, exactly like a table PostgREST doesn't know about at
@@ -142,25 +154,34 @@ declare function loadManifest(raw: unknown): AgentifyManifest | null;
142
154
  */
143
155
  declare function introspectTables(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[]): TableSchema[];
144
156
 
145
- declare class MissingAgentifyMetaError extends Error {
157
+ declare class MissingExposifyMetaError extends Error {
146
158
  constructor(cause: string);
147
159
  }
148
160
  /**
149
- * Calls the `agentify_meta()` Postgres function via PostgREST's RPC
150
- * endpoint. Requires the migration from `sql/agentify_meta.sql` to have been
161
+ * Calls the `exposify_meta()` Postgres function via PostgREST's RPC
162
+ * endpoint. Requires the migration from `sql/exposify_meta.sql` to have been
151
163
  * applied; a 404 (unknown route) or PostgREST's `PGRST202` (function not
152
164
  * found in schema cache) both mean "migration missing" and are translated
153
165
  * into a self-diagnosing error (spec: "a missing `agentify_meta()` migration
154
- * produces a self-diagnosing message naming the exact fix").
166
+ * produces a self-diagnosing message naming the exact fix" -- function
167
+ * renamed to `exposify_meta()` post-spec, see the decisions journal).
155
168
  */
156
- declare function fetchAgentifyMeta(postgrestUrl: string, headers: Headers, fetchImpl: typeof fetch): Promise<MetaRow[]>;
169
+ declare function fetchExposifyMeta(postgrestUrl: string, headers: Headers, fetchImpl: typeof fetch): Promise<MetaRow[]>;
157
170
 
158
- /** PostgREST serves its OpenAPI document at the API root when asked for it explicitly. */
171
+ /**
172
+ * PostgREST serves its OpenAPI document at the API root when asked for it
173
+ * explicitly. This is the one call in the whole SDK most likely to fail for
174
+ * a specific, fixable reason (the wrong key tier -- see decisions journal
175
+ * §9), so unlike a generic fetch wrapper this one reads the body on failure
176
+ * and turns that one known failure mode into a self-diagnosing message
177
+ * naming the exact fix, the same pattern `meta.ts` already uses for a
178
+ * missing `exposify_meta()` migration.
179
+ */
159
180
  declare function fetchOpenApi(postgrestUrl: string, headers: Headers, fetchImpl: typeof fetch): Promise<PostgrestOpenApiDoc>;
160
181
 
161
182
  /**
162
183
  * Builds the `Deno.serve`-compatible MCP handler. Everything expensive
163
- * (OpenAPI fetch, `agentify_meta()` call, doc loading/chunking) happens
184
+ * (OpenAPI fetch, `exposify_meta()` call, doc loading/chunking) happens
164
185
  * lazily on first request and is cached in-memory for the life of the edge
165
186
  * instance -- "cold start" in the spec's sense, not literally module load,
166
187
  * since env vars and the incoming JWT aren't available before the first
@@ -168,4 +189,4 @@ declare function fetchOpenApi(postgrestUrl: string, headers: Headers, fetchImpl:
168
189
  */
169
190
  declare function aiReady(options?: AgentifySupabaseOptions): WebMcpHandler;
170
191
 
171
- export { type AgentifyManifest, type AgentifySupabaseOptions, MANIFEST_VERSION, MissingAgentifyMetaError, type PostgrestOpenApiDoc, aiReady, buildManifest, columnsFromOpenApi, fetchAgentifyMeta, fetchOpenApi, introspectTables, loadManifest, rpcArgSchema };
192
+ export { type AgentifySupabaseOptions, type ExposifyManifest, MANIFEST_VERSION, MissingExposifyMetaError, type PostgrestOpenApiDoc, aiReady, buildManifest, columnsFromOpenApi, fetchExposifyMeta, fetchOpenApi, introspectTables, loadManifest, rpcArgSchema };
package/dist/index.d.ts CHANGED
@@ -7,18 +7,18 @@ interface ColumnSchema {
7
7
  /** OpenAPI `format`, e.g. "uuid", "timestamp with time zone", "bigint". */
8
8
  format?: string;
9
9
  required: boolean;
10
- /** From `agentify_meta()`'s `column_comment` (a `COMMENT ON COLUMN`), not OpenAPI. */
10
+ /** From `exposify_meta()`'s `column_comment` (a `COMMENT ON COLUMN`), not OpenAPI. */
11
11
  description?: string;
12
12
  isPrimaryKey?: boolean;
13
13
  }
14
14
  interface TableSchema {
15
15
  name: string;
16
16
  rlsEnabled: boolean;
17
- /** From `agentify_meta()`'s `table_comment` (a `COMMENT ON TABLE`). */
17
+ /** From `exposify_meta()`'s `table_comment` (a `COMMENT ON TABLE`). */
18
18
  description?: string;
19
19
  columns: ColumnSchema[];
20
20
  }
21
- /** One row of `agentify_meta()` -- the migration returns one row per column. */
21
+ /** One row of `exposify_meta()` -- the migration returns one row per column. */
22
22
  interface MetaRow {
23
23
  table_name: string;
24
24
  rls_enabled: boolean;
@@ -60,9 +60,21 @@ interface AgentifySupabaseOptions {
60
60
  /** Overrides for local/CI use -- production reads these from the Deno env automatically. */
61
61
  supabaseUrl?: string;
62
62
  anonKey?: string;
63
+ /**
64
+ * Used ONLY for schema introspection (fetching PostgREST's OpenAPI document
65
+ * + calling `exposify_meta()`) -- never for per-request data access, which
66
+ * always uses the caller's forwarded JWT (or the anon key as its fallback).
67
+ * Required because Supabase's gateway rejects the OpenAPI-discovery route
68
+ * for anon/publishable-tier keys outright (`Invalid API key` / `Only the
69
+ * service_role API key can be used for this endpoint`), regardless of key
70
+ * format -- discovered via a real deployment, not documented anywhere at
71
+ * design time. Auto-injected as `SUPABASE_SERVICE_ROLE_KEY` in every
72
+ * deployed edge function; this option exists for local dev/testing only.
73
+ */
74
+ serviceRoleKey?: string;
63
75
  /** Injectable for tests; defaults to global `fetch`. */
64
76
  fetchImpl?: typeof fetch;
65
- /** Bundled fallback manifest (from `agentify-supabase generate`), used when runtime introspection fails. */
77
+ /** Bundled fallback manifest (from `exposify-supabase generate`), used when runtime introspection fails. */
66
78
  manifest?: unknown;
67
79
  rateLimit?: number;
68
80
  rateWindowMs?: number;
@@ -98,7 +110,7 @@ interface PostgrestColumnProperty {
98
110
  /**
99
111
  * Extracts column schemas for one table from a PostgREST OpenAPI document.
100
112
  * Returns `[]` for a table PostgREST never exposed (not in `definitions` --
101
- * e.g. it exists at the catalog level per `agentify_meta()` but PostgREST's
113
+ * e.g. it exists at the catalog level per `exposify_meta()` but PostgREST's
102
114
  * role lacks USAGE/SELECT grants on it), which the caller treats the same as
103
115
  * "RLS-less": never shown to the agent.
104
116
  */
@@ -113,27 +125,27 @@ declare function columnsFromOpenApi(doc: PostgrestOpenApiDoc, table: string): Co
113
125
  declare function rpcArgSchema(doc: PostgrestOpenApiDoc, fn: string): JsonSchemaObject | null;
114
126
 
115
127
  declare const MANIFEST_VERSION = 1;
116
- interface AgentifyManifest {
128
+ interface ExposifyManifest {
117
129
  version: typeof MANIFEST_VERSION;
118
130
  generatedAt: string;
119
131
  tables: TableSchema[];
120
132
  }
121
- /** Built by `npx agentify-supabase generate` against a local/linked project. */
122
- declare function buildManifest(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[], generatedAt: string): AgentifyManifest;
133
+ /** Built by `npx exposify-supabase generate` against a local/linked project. */
134
+ declare function buildManifest(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[], generatedAt: string): ExposifyManifest;
123
135
  /**
124
136
  * Defensive load: a manifest is bundled JSON shipped alongside the function
125
137
  * and can go stale relative to the deployed schema, or simply be malformed
126
138
  * (hand-edited, wrong version). Returns `null` -- never throws -- so the
127
139
  * degradation ladder can fall through to the next rung instead of crashing.
128
140
  */
129
- declare function loadManifest(raw: unknown): AgentifyManifest | null;
141
+ declare function loadManifest(raw: unknown): ExposifyManifest | null;
130
142
 
131
143
  /**
132
- * Merges PostgREST's OpenAPI document (column types) with `agentify_meta()`
144
+ * Merges PostgREST's OpenAPI document (column types) with `exposify_meta()`
133
145
  * rows (RLS status + comments) into the DB tool surface.
134
146
  *
135
147
  * DB exposure policy (spec, locked): RLS-gated, reads auto. A table only
136
- * appears here if `agentify_meta()` reports `rls_enabled: true` for it AND
148
+ * appears here if `exposify_meta()` reports `rls_enabled: true` for it AND
137
149
  * PostgREST actually exposes it in its OpenAPI `definitions` (the role
138
150
  * calling this has grants on it) -- a table failing either check never
139
151
  * appears, silently, exactly like a table PostgREST doesn't know about at
@@ -142,25 +154,34 @@ declare function loadManifest(raw: unknown): AgentifyManifest | null;
142
154
  */
143
155
  declare function introspectTables(openapi: PostgrestOpenApiDoc, metaRows: MetaRow[]): TableSchema[];
144
156
 
145
- declare class MissingAgentifyMetaError extends Error {
157
+ declare class MissingExposifyMetaError extends Error {
146
158
  constructor(cause: string);
147
159
  }
148
160
  /**
149
- * Calls the `agentify_meta()` Postgres function via PostgREST's RPC
150
- * endpoint. Requires the migration from `sql/agentify_meta.sql` to have been
161
+ * Calls the `exposify_meta()` Postgres function via PostgREST's RPC
162
+ * endpoint. Requires the migration from `sql/exposify_meta.sql` to have been
151
163
  * applied; a 404 (unknown route) or PostgREST's `PGRST202` (function not
152
164
  * found in schema cache) both mean "migration missing" and are translated
153
165
  * into a self-diagnosing error (spec: "a missing `agentify_meta()` migration
154
- * produces a self-diagnosing message naming the exact fix").
166
+ * produces a self-diagnosing message naming the exact fix" -- function
167
+ * renamed to `exposify_meta()` post-spec, see the decisions journal).
155
168
  */
156
- declare function fetchAgentifyMeta(postgrestUrl: string, headers: Headers, fetchImpl: typeof fetch): Promise<MetaRow[]>;
169
+ declare function fetchExposifyMeta(postgrestUrl: string, headers: Headers, fetchImpl: typeof fetch): Promise<MetaRow[]>;
157
170
 
158
- /** PostgREST serves its OpenAPI document at the API root when asked for it explicitly. */
171
+ /**
172
+ * PostgREST serves its OpenAPI document at the API root when asked for it
173
+ * explicitly. This is the one call in the whole SDK most likely to fail for
174
+ * a specific, fixable reason (the wrong key tier -- see decisions journal
175
+ * §9), so unlike a generic fetch wrapper this one reads the body on failure
176
+ * and turns that one known failure mode into a self-diagnosing message
177
+ * naming the exact fix, the same pattern `meta.ts` already uses for a
178
+ * missing `exposify_meta()` migration.
179
+ */
159
180
  declare function fetchOpenApi(postgrestUrl: string, headers: Headers, fetchImpl: typeof fetch): Promise<PostgrestOpenApiDoc>;
160
181
 
161
182
  /**
162
183
  * Builds the `Deno.serve`-compatible MCP handler. Everything expensive
163
- * (OpenAPI fetch, `agentify_meta()` call, doc loading/chunking) happens
184
+ * (OpenAPI fetch, `exposify_meta()` call, doc loading/chunking) happens
164
185
  * lazily on first request and is cached in-memory for the life of the edge
165
186
  * instance -- "cold start" in the spec's sense, not literally module load,
166
187
  * since env vars and the incoming JWT aren't available before the first
@@ -168,4 +189,4 @@ declare function fetchOpenApi(postgrestUrl: string, headers: Headers, fetchImpl:
168
189
  */
169
190
  declare function aiReady(options?: AgentifySupabaseOptions): WebMcpHandler;
170
191
 
171
- export { type AgentifyManifest, type AgentifySupabaseOptions, MANIFEST_VERSION, MissingAgentifyMetaError, type PostgrestOpenApiDoc, aiReady, buildManifest, columnsFromOpenApi, fetchAgentifyMeta, fetchOpenApi, introspectTables, loadManifest, rpcArgSchema };
192
+ export { type AgentifySupabaseOptions, type ExposifyManifest, MANIFEST_VERSION, MissingExposifyMetaError, type PostgrestOpenApiDoc, aiReady, buildManifest, columnsFromOpenApi, fetchExposifyMeta, fetchOpenApi, introspectTables, loadManifest, rpcArgSchema };
package/dist/index.js CHANGED
@@ -70,7 +70,7 @@ function warnOnce(message) {
70
70
  if (warned.has(message)) return;
71
71
  warned.add(message);
72
72
  try {
73
- console.warn(`[agentify-supabase] ${message}`);
73
+ console.warn(`[exposify-supabase] ${message}`);
74
74
  } catch {
75
75
  }
76
76
  }
@@ -96,6 +96,7 @@ function buildPostgrestHeaders(incoming, anonKey) {
96
96
  if (value) headers.set(name, value);
97
97
  }
98
98
  if (!headers.has("apikey")) headers.set("apikey", anonKey);
99
+ if (!headers.has("authorization")) headers.set("authorization", `Bearer ${anonKey}`);
99
100
  return headers;
100
101
  }
101
102
  function createPostgrestClient(postgrestUrl, headers, fetchImpl) {
@@ -128,42 +129,49 @@ function createPostgrestClient(postgrestUrl, headers, fetchImpl) {
128
129
  }
129
130
 
130
131
  // src/fetch-openapi.ts
132
+ var WRONG_KEY_TIER_RE = /service_role|secret api key/i;
131
133
  async function fetchOpenApi(postgrestUrl, headers, fetchImpl) {
132
134
  const res = await fetchImpl(postgrestUrl, {
133
135
  headers: { ...Object.fromEntries(headers), accept: "application/openapi+json" }
134
136
  });
135
137
  if (!res.ok) {
136
- throw new Error(`PostgREST OpenAPI fetch failed: HTTP ${res.status}`);
138
+ const body = await res.text().catch(() => "");
139
+ if (res.status === 401 && WRONG_KEY_TIER_RE.test(body)) {
140
+ throw new Error(
141
+ `PostgREST OpenAPI fetch failed: HTTP 401 -- Supabase rejected this request because it wasn't made with the service role key (response: "${body.trim()}"). Schema introspection always needs SUPABASE_SERVICE_ROLE_KEY, never the anon key -- see this package's README, "Why introspection needs the service role key". In a deployed function this is auto-injected automatically; for local dev/testing pass { serviceRoleKey } explicitly (or --key for the CLI).`
142
+ );
143
+ }
144
+ throw new Error(`PostgREST OpenAPI fetch failed: HTTP ${res.status}${body ? `: ${body}` : ""}`);
137
145
  }
138
146
  return await res.json();
139
147
  }
140
148
 
141
149
  // src/meta.ts
142
- var MissingAgentifyMetaError = class extends Error {
150
+ var MissingExposifyMetaError = class extends Error {
143
151
  constructor(cause) {
144
152
  super(cause);
145
- this.name = "MissingAgentifyMetaError";
153
+ this.name = "MissingExposifyMetaError";
146
154
  }
147
155
  };
148
- async function fetchAgentifyMeta(postgrestUrl, headers, fetchImpl) {
149
- const res = await fetchImpl(`${postgrestUrl}/rpc/agentify_meta`, {
156
+ async function fetchExposifyMeta(postgrestUrl, headers, fetchImpl) {
157
+ const res = await fetchImpl(`${postgrestUrl}/rpc/exposify_meta`, {
150
158
  method: "POST",
151
159
  headers: { ...Object.fromEntries(headers), "content-type": "application/json" },
152
160
  body: "{}"
153
161
  });
154
162
  if (res.status === 404) {
155
- throw new MissingAgentifyMetaError(
156
- "agentify_meta() is not reachable at /rpc/agentify_meta (404). Run `npx agentify-supabase init` (or apply sql/agentify_meta.sql directly) and `supabase db push`, then redeploy the function."
163
+ throw new MissingExposifyMetaError(
164
+ "exposify_meta() is not reachable at /rpc/exposify_meta (404). Run `npx exposify-supabase init` (or apply sql/exposify_meta.sql directly) and `supabase db push`, then redeploy the function."
157
165
  );
158
166
  }
159
167
  if (!res.ok) {
160
168
  const body = await res.text().catch(() => "");
161
169
  if (/PGRST202|function .* does not exist/i.test(body)) {
162
- throw new MissingAgentifyMetaError(
163
- "agentify_meta() is missing from the database (PostgREST error PGRST202). Apply the migration in sql/agentify_meta.sql via `supabase db push`, then redeploy the function."
170
+ throw new MissingExposifyMetaError(
171
+ "exposify_meta() is missing from the database (PostgREST error PGRST202). Apply the migration in sql/exposify_meta.sql via `supabase db push`, then redeploy the function."
164
172
  );
165
173
  }
166
- throw new Error(`agentify_meta() call failed: HTTP ${res.status} ${body}`.trim());
174
+ throw new Error(`exposify_meta() call failed: HTTP ${res.status} ${body}`.trim());
167
175
  }
168
176
  return await res.json();
169
177
  }
@@ -194,7 +202,7 @@ function columnsFromOpenApi(doc, table) {
194
202
  // ON COLUMN` into `description`, then appends its own "Note:\nThis is a
195
203
  // Primary/Foreign Key...<pk/|fk/>" boilerplate) -- strip that boilerplate
196
204
  // so a column with no real comment ends up `undefined`, not the
197
- // boilerplate text. agentify_meta()'s copy wins when both exist anyway
205
+ // boilerplate text. exposify_meta()'s copy wins when both exist anyway
198
206
  // -- see introspect.ts's merge.
199
207
  description: prop.description?.replace(/\n*Note:[\s\S]*$/, "").trim() || void 0
200
208
  }));
@@ -229,7 +237,7 @@ function introspectTables(openapi, metaRows) {
229
237
  if (!fromOpenApi) continue;
230
238
  columns.push({
231
239
  ...fromOpenApi,
232
- // agentify_meta()'s comment wins over OpenAPI's mirrored copy (spec:
240
+ // exposify_meta()'s comment wins over OpenAPI's mirrored copy (spec:
233
241
  // "Column comments → param descriptions" sourced from the migration).
234
242
  description: row.column_comment ?? fromOpenApi.description
235
243
  });
@@ -272,7 +280,7 @@ async function resolveTableSchemas(deps) {
272
280
  try {
273
281
  const [openapi, metaRows] = await Promise.all([
274
282
  fetchOpenApi(deps.postgrestUrl, deps.headers, deps.fetchImpl),
275
- fetchAgentifyMeta(deps.postgrestUrl, deps.headers, deps.fetchImpl)
283
+ fetchExposifyMeta(deps.postgrestUrl, deps.headers, deps.fetchImpl)
276
284
  ]);
277
285
  return { tables: introspectTables(openapi, metaRows), mode: "runtime", warnings, openapi };
278
286
  } catch (err) {
@@ -282,7 +290,7 @@ async function resolveTableSchemas(deps) {
282
290
  if (manifest) {
283
291
  warnings.push({
284
292
  rung: "manifest",
285
- reason: "runtime introspection failed; serving tools from the bundled agentify.manifest.json instead"
293
+ reason: "runtime introspection failed; serving tools from the bundled exposify.manifest.json instead"
286
294
  });
287
295
  return { tables: manifest.tables, mode: "manifest", warnings };
288
296
  }
@@ -572,9 +580,9 @@ ${h.chunk.text}`).join("\n\n---\n\n") : "No matching documentation found.";
572
580
  function registerDiagnosticsResource(server, warnings) {
573
581
  if (warnings.length === 0) return;
574
582
  server.registerResource(
575
- "agentify-diagnostics",
576
- "agentify://diagnostics",
577
- { title: "agentify-supabase diagnostics", mimeType: "application/json" },
583
+ "exposify-diagnostics",
584
+ "exposify://diagnostics",
585
+ { title: "exposify-supabase diagnostics", mimeType: "application/json" },
578
586
  async (uri) => ({ contents: [{ uri: uri.href, text: JSON.stringify(warnings, null, 2) }] })
579
587
  );
580
588
  }
@@ -607,6 +615,7 @@ function aiReady(options = {}) {
607
615
  const env = denoEnv();
608
616
  const supabaseUrl = options.supabaseUrl ?? env?.get("SUPABASE_URL");
609
617
  const anonKey = options.anonKey ?? env?.get("SUPABASE_ANON_KEY") ?? "";
618
+ const serviceRoleKey = options.serviceRoleKey ?? env?.get("SUPABASE_SERVICE_ROLE_KEY") ?? "";
610
619
  let kbPromise = null;
611
620
  const getKb = () => {
612
621
  kbPromise ??= (async () => {
@@ -624,12 +633,12 @@ function aiReady(options = {}) {
624
633
  const getLadder = (postgrestUrl) => {
625
634
  ladderPromise ??= resolveTableSchemas({
626
635
  postgrestUrl,
627
- headers: buildPostgrestHeaders(new Headers(), anonKey),
636
+ headers: buildPostgrestHeaders(new Headers(), serviceRoleKey),
628
637
  fetchImpl,
629
638
  manifest: options.manifest
630
639
  }).then((ladder) => {
631
640
  for (const w of ladder.warnings) {
632
- console.error(`[agentify-supabase] degradation ladder: ${w.rung}: ${w.reason}`);
641
+ console.error(`[exposify-supabase] degradation ladder: ${w.rung}: ${w.reason}`);
633
642
  }
634
643
  return ladder;
635
644
  });
@@ -657,7 +666,7 @@ function aiReady(options = {}) {
657
666
  return { status: res.status, bodyText: await res.text() };
658
667
  };
659
668
  return buildSupabaseMcpServer({
660
- appName: "agentify-supabase",
669
+ appName: "exposify-supabase",
661
670
  appVersion: "0.0.0",
662
671
  tables: ladder.tables,
663
672
  cfg,
@@ -675,11 +684,11 @@ function aiReady(options = {}) {
675
684
  }
676
685
  export {
677
686
  MANIFEST_VERSION,
678
- MissingAgentifyMetaError,
687
+ MissingExposifyMetaError,
679
688
  aiReady,
680
689
  buildManifest,
681
690
  columnsFromOpenApi,
682
- fetchAgentifyMeta,
691
+ fetchExposifyMeta,
683
692
  fetchOpenApi,
684
693
  introspectTables,
685
694
  loadManifest,