better-supabase 0.2.0 → 0.4.0
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/README.md +37 -33
- package/dist/base64-D2gsioCY.js +42 -0
- package/dist/claims-BAf6c7is.js +48 -0
- package/dist/cli/_format-Cax5EsM9.js +101 -0
- package/dist/cli/bin.js +39 -6
- package/dist/cli/command-CK14fP91.js +502 -0
- package/dist/cli/compare-BmIWVEui.js +9 -0
- package/dist/cli/config-D9R0o1LH.js +1895 -0
- package/dist/cli/config-SSKSTUl2.js +2 -0
- package/dist/cli/db-DOBPt660.js +52 -0
- package/dist/cli/dist-B1MvLv-M.js +129 -0
- package/dist/cli/dist-CFgGknmZ.js +1112 -0
- package/dist/cli/dist-Du7VnEUG.js +621 -0
- package/dist/cli/doctor-BOym3mbL.js +2382 -0
- package/dist/cli/env-BZ0uy8XK.js +108 -0
- package/dist/cli/env-jIFUADLY.js +511 -0
- package/dist/cli/errors-4v4DTtda.js +56 -0
- package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
- package/dist/cli/gen-Ccn_p90L.js +2008 -0
- package/dist/cli/gen-D5cLCULx.js +2 -0
- package/dist/cli/index.d.ts +295 -28
- package/dist/cli/index.js +184 -3
- package/dist/cli/init-ClnxRYBF.js +550 -0
- package/dist/cli/introspect-DxqfkVIv.js +81 -0
- package/dist/cli/io-k41_z-jT.js +24 -0
- package/dist/cli/json5-ChRycquj.js +787 -0
- package/dist/cli/jsonc-BUniUpOk.js +2 -0
- package/dist/cli/jsonc-D5Fu67jw.js +45 -0
- package/dist/cli/keys-ByKHnvP9.js +83 -0
- package/dist/cli/ohash-D7bG_EOb.js +277 -0
- package/dist/cli/openapi-8WHjxiTJ.js +66 -0
- package/dist/cli/project-B_GeXyHh.js +70 -0
- package/dist/cli/prompts-BNcNfAve.js +1142 -0
- package/dist/cli/prompts-BTYQGunc.js +2 -0
- package/dist/cli/read-sets-CMa29t9a.js +971 -0
- package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
- package/dist/cli/run-CP20LKk_.js +347 -0
- package/dist/cli/seed-5J6eu_Xg.js +98 -0
- package/dist/cli/skills-LJJIhLFR.js +137 -0
- package/dist/cli/sql-D4VbqmXi.js +199 -0
- package/dist/cli/style-C0NFdJRL.js +29 -0
- package/dist/cli/toml-DB-MNnFs.js +424 -0
- package/dist/{version-Cqq1m3UY.js → cli/version-5mW_8sY1.js} +1 -1
- package/dist/cli/yaml-CYCbJ6et.js +2252 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.js +15 -25
- package/dist/{compiler-CZ9nIuqy.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
- package/dist/config/index.d.ts +4 -3
- package/dist/config/index.js +5 -3
- package/dist/config-BpxDfnbg.js +129 -0
- package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
- package/dist/{config-ClDXmVtY.js → config-CbvFmGD8.js} +14 -137
- package/dist/{define-BXMYD9AY.js → define-Bh0AqTik.js} +221 -120
- package/dist/{define-e3zI4_4g.d.ts → define-CRS2MRV3.d.ts} +36 -31
- package/dist/edge/index.d.ts +9 -13
- package/dist/edge/index.js +51 -25
- package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
- package/dist/env/index.js +203 -1
- package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
- package/dist/events/index.d.ts +1 -1
- package/dist/events/index.js +139 -1
- package/dist/{events-DOt3nSO-.d.ts → events-B21UOhvP.d.ts} +4 -4
- package/dist/{executor-CGdNQ27g.d.ts → executor-B5IoUxLE.d.ts} +2 -2
- package/dist/{executor-DeppvirK.js → executor-CVt6dD0B.js} +9 -4
- package/dist/{executor-BYXQ9AGn.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
- package/dist/hono/index.d.ts +17 -24
- package/dist/hono/index.js +11 -11
- package/dist/{index-DuP14VHo.d.ts → index-528Ugmyp.d.ts} +30 -10
- package/dist/{index-Bw1bwtC0.d.ts → index-BAy4tsAP.d.ts} +28 -15
- package/dist/{index-CBDufH1M.d.ts → index-BBdvo-5d.d.ts} +24 -26
- package/dist/{index-DbxFr7XG.d.ts → index-BhHEC04r.d.ts} +9 -4
- package/dist/index-Cf-D2jk1.d.ts +50 -0
- package/dist/{index-Ciu5Xlml.d.ts → index-D5IfUIDh.d.ts} +6 -5
- package/dist/{index-K-3WM2Hp.d.ts → index-DFmOpBGC.d.ts} +13 -13
- package/dist/{index-D4v7sncu.d.ts → index-RlLXd8dL.d.ts} +13 -13
- package/dist/index.d.ts +17 -17
- package/dist/index.js +23 -14
- package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
- package/dist/jobs/index.d.ts +33 -26
- package/dist/jobs/index.js +44 -22
- package/dist/{json-schema-BpYqkCKf.js → json-schema-DP6V7cbr.js} +8 -3
- package/dist/lint/index.js +1 -1
- package/dist/list/index.d.ts +2 -2
- package/dist/list/index.js +131 -96
- package/dist/{live-DJyZMD6w.d.ts → live-BHakSWWo.d.ts} +26 -16
- package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
- package/dist/lookup-BaP5bI27.js +18 -0
- package/dist/mcp/index.d.ts +34 -27
- package/dist/mcp/index.js +59 -25
- package/dist/{mfa-TVia554c.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
- package/dist/{mfa-CpLfJSde.js → mfa-byfjruR9.js} +190 -46
- package/dist/next/image/index.js +6 -3
- package/dist/next/index.d.ts +64 -48
- package/dist/next/index.js +63 -50
- package/dist/openapi/index.d.ts +1 -1
- package/dist/openapi/index.js +61 -38
- package/dist/orpc/index.d.ts +18 -21
- package/dist/orpc/index.js +12 -12
- package/dist/otel/index.d.ts +5 -5
- package/dist/otel/index.js +84 -63
- package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-C4KFzLc9.js} +12 -7
- package/dist/permdock-sql-DImJULY-.d.ts +17 -0
- package/dist/{plugin-C4TR9J0Z.d.ts → plugin-bNhvJwRh.d.ts} +5 -4
- package/dist/plugins/actor/index.d.ts +1 -1
- package/dist/plugins/actor/index.js +1 -1
- package/dist/plugins/rules/index.d.ts +2 -2
- package/dist/plugins/rules/index.js +46 -16
- package/dist/plugins/soft-delete/index.d.ts +3 -3
- package/dist/plugins/soft-delete/index.js +4 -4
- package/dist/plugins/tenant/index.d.ts +3 -3
- package/dist/plugins/tenant/index.js +10 -4
- package/dist/plugins/timestamps/index.d.ts +1 -1
- package/dist/plugins/timestamps/index.js +2 -2
- package/dist/plugins/validation/index.d.ts +2 -2
- package/dist/plugins/validation/index.js +3 -3
- package/dist/pool-DFwEQwmN.d.ts +78 -0
- package/dist/postgres/index.d.ts +10 -5
- package/dist/postgres/index.js +37 -16
- package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
- package/dist/{problem-BTkTtRV9.js → problem-BihjPR0t.js} +11 -5
- package/dist/{problem-DlgB0lYl.d.ts → problem-U8UEKccv.d.ts} +6 -4
- package/dist/query/index.d.ts +2 -2
- package/dist/query/index.js +2 -2
- package/dist/{query-ChvDbsDE.js → query-BMMSWbI7.js} +19 -19
- package/dist/react/index.d.ts +7 -7
- package/dist/react/index.js +2 -2
- package/dist/react/server.d.ts +6 -6
- package/dist/react/server.js +3 -3
- package/dist/react/session.d.ts +21 -2
- package/dist/react/session.js +19 -1
- package/dist/{react-C83BdBGt.js → react-CQHMaESR.js} +59 -55
- package/dist/{read-set-4lI3W_gZ.js → read-set-FWK8qQ58.js} +51 -16
- package/dist/realtime/index.d.ts +4 -3
- package/dist/realtime/index.js +19 -17
- package/dist/{index-CG69s15v.d.ts → refresh-BwYkyNOQ.d.ts} +7 -3
- package/dist/{resolve-CjHILk2w.d.ts → resolve-BAqQcwMW.d.ts} +17 -11
- package/dist/{resource-CgZa3YG_.d.ts → resource-BJcaIfxi.d.ts} +17 -8
- package/dist/{resource-BHEWmQJK.js → resource-My_y-Syh.js} +43 -21
- package/dist/{respond-Dne1ARSa.d.ts → respond-DgIbdrt8.d.ts} +39 -14
- package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
- package/dist/{result-CTkCZ6JA.d.ts → result-CqEsbgYs.d.ts} +16 -11
- package/dist/server/index.d.ts +38 -13
- package/dist/server/index.js +43 -14
- package/dist/{respond-DMcbQwfN.js → server-BA75B0mT.js} +151 -96
- package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
- package/dist/{simplify-bwJpLnaS.js → simplify-Cqvfr9dj.js} +76 -39
- package/dist/sql/index.d.ts +139 -0
- package/dist/sql/index.js +1983 -0
- package/dist/{sql-DTjA1LPj.js → sql-B34HU2DI.js} +74 -17
- package/dist/ssr/index.d.ts +4 -4
- package/dist/ssr/index.js +2 -2
- package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
- package/dist/{standard-CyhRzctb.d.ts → standard-BsaCYQJc.d.ts} +1 -1
- package/dist/{stats-C5W-rBfR.js → stats-C_YxA2r1.js} +40 -14
- package/dist/storage/index.d.ts +4 -3
- package/dist/storage/index.js +548 -1
- package/dist/{tables-rZxRxNPW.js → tables-Cb-3_VWc.js} +19 -5
- package/dist/temporal-BwshQk0W.js +27 -0
- package/dist/temporal-required-CBNHT6ni.js +19 -0
- package/dist/testing/index.d.ts +25 -24
- package/dist/testing/index.js +165 -66
- package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
- package/dist/version-Bmf7_FzM.js +4 -0
- package/dist/{view-D1s0wa0T.d.ts → view-B64o81wp.d.ts} +36 -6
- package/dist/{view-aOYizTeE.js → view-BrJI2LKp.js} +11 -2
- package/dist/webhooks/index.d.ts +19 -17
- package/dist/webhooks/index.js +1 -1
- package/dist/{webhooks-DMipjhSP.js → webhooks-Bh_Jf_-l.js} +45 -24
- package/package.json +50 -22
- package/schemas/config-v1.json +31 -1
- package/schemas/doctor-report-v1.json +17 -1
- package/schemas/snapshot-v2.json +35 -0
- package/skills/better-supabase/SKILL.md +77 -18
- package/skills/better-supabase/references/plugins.md +21 -22
- package/skills/better-supabase/references/troubleshooting.md +38 -18
- package/skills/better-supabase-api/SKILL.md +66 -18
- package/skills/better-supabase-api/references/adapters.md +64 -41
- package/skills/better-supabase-auth/SKILL.md +146 -0
- package/skills/better-supabase-auth/references/permdock.md +55 -0
- package/skills/better-supabase-testing/SKILL.md +68 -11
- package/dist/casing-da0uRTqt.js +0 -14
- package/dist/claims-CoTobNC-.js +0 -25
- package/dist/cli-DrSgDOQC.js +0 -6646
- package/dist/env-BEToab0I.js +0 -204
- package/dist/events-BuzTgn2D.js +0 -135
- package/dist/pool-R2m_DkXi.d.ts +0 -37
- package/dist/seed-CzecODcJ.js +0 -101
- package/dist/session-C3LxnQ0e.d.ts +0 -22
- package/dist/session-CqEwzVup.js +0 -20
- package/dist/storage-DuGn38fr.js +0 -527
|
@@ -64,6 +64,10 @@ function join(client, topic, listener) {
|
|
|
64
64
|
left = true;
|
|
65
65
|
current.listeners.delete(listener);
|
|
66
66
|
if (current.listeners.size > 0) return;
|
|
67
|
+
await new Promise((resolve) => {
|
|
68
|
+
setTimeout(resolve, 0);
|
|
69
|
+
});
|
|
70
|
+
if (current.listeners.size > 0 || byTopic.get(topic) !== current) return;
|
|
67
71
|
byTopic.delete(topic);
|
|
68
72
|
await client.removeChannel(current.channel);
|
|
69
73
|
}
|
|
@@ -76,13 +80,13 @@ function join(client, topic, listener) {
|
|
|
76
80
|
* table, so any number of them can run at once.
|
|
77
81
|
*
|
|
78
82
|
* ```ts
|
|
79
|
-
* using live = liveQuery(
|
|
83
|
+
* using live = liveQuery(betterSupabase, supabase, spec, { onChange: () => refetch() });
|
|
80
84
|
* ```
|
|
81
85
|
*/
|
|
82
|
-
function liveQuery(
|
|
83
|
-
const touched = Array.isArray(spec) ? spec :
|
|
84
|
-
const tables = touched.filter((table) =>
|
|
85
|
-
const unwatched = touched.filter((table) => !
|
|
86
|
+
function liveQuery(betterSupabase, client, spec, options) {
|
|
87
|
+
const touched = Array.isArray(spec) ? spec : betterSupabase.tablesOf(spec);
|
|
88
|
+
const tables = touched.filter((table) => betterSupabase.meta.realtime?.[table]);
|
|
89
|
+
const unwatched = touched.filter((table) => !betterSupabase.meta.realtime?.[table]);
|
|
86
90
|
const debounceMs = options.debounceMs ?? 100;
|
|
87
91
|
const changed = /* @__PURE__ */ new Set();
|
|
88
92
|
let timer;
|
|
@@ -94,7 +98,7 @@ function liveQuery(sb, client, spec, options) {
|
|
|
94
98
|
changed.clear();
|
|
95
99
|
options.onChange(batch);
|
|
96
100
|
};
|
|
97
|
-
const memberships = tables.map((table) => join(client, liveTopic(
|
|
101
|
+
const memberships = tables.map((table) => join(client, liveTopic(betterSupabase.meta, table, options.tenant), () => {
|
|
98
102
|
changed.add(table);
|
|
99
103
|
if (timer !== void 0) clearTimeout(timer);
|
|
100
104
|
timer = setTimeout(flush, debounceMs);
|
|
@@ -130,12 +134,12 @@ function liveQuery(sb, client, spec, options) {
|
|
|
130
134
|
* Responses that arrive out of order are dropped.
|
|
131
135
|
*
|
|
132
136
|
* ```ts
|
|
133
|
-
* using live = liveCount(
|
|
137
|
+
* using live = liveCount(betterSupabase, supabase, db, betterSupabase.spec.notes.count(), {
|
|
134
138
|
* onCount: (count) => render(count),
|
|
135
139
|
* });
|
|
136
140
|
* ```
|
|
137
141
|
*/
|
|
138
|
-
function liveCount(
|
|
142
|
+
function liveCount(betterSupabase, client, db, spec, options) {
|
|
139
143
|
let latest = 0;
|
|
140
144
|
let closed = false;
|
|
141
145
|
const refetch = () => {
|
|
@@ -146,7 +150,7 @@ function liveCount(sb, client, db, spec, options) {
|
|
|
146
150
|
else options.onError?.(result.error);
|
|
147
151
|
});
|
|
148
152
|
};
|
|
149
|
-
const live = liveQuery(
|
|
153
|
+
const live = liveQuery(betterSupabase, client, spec, {
|
|
150
154
|
onChange: refetch,
|
|
151
155
|
...options.tenant === void 0 ? {} : { tenant: options.tenant },
|
|
152
156
|
...options.debounceMs === void 0 ? {} : { debounceMs: options.debounceMs },
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
//#region src/schema/lookup.ts
|
|
2
|
+
const lookups = /* @__PURE__ */ new WeakMap();
|
|
3
|
+
/** The cached lookup for a table; metadata is immutable after `defineSchema`. */
|
|
4
|
+
function lookupOf(table) {
|
|
5
|
+
let lookup = lookups.get(table);
|
|
6
|
+
if (!lookup) {
|
|
7
|
+
const entries = Object.entries(table.columns);
|
|
8
|
+
lookup = {
|
|
9
|
+
entries,
|
|
10
|
+
byDb: new Map(entries.map((entry) => [entry[1].db, entry])),
|
|
11
|
+
keys: [table.primaryKey, ...Object.values(table.uniqueKeys)]
|
|
12
|
+
};
|
|
13
|
+
lookups.set(table, lookup);
|
|
14
|
+
}
|
|
15
|
+
return lookup;
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
18
|
+
export { lookupOf as t };
|
package/dist/mcp/index.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-
|
|
2
|
-
import { t as BetterSupabase } from "../define-
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-DONv9SHN.js";
|
|
2
|
+
import { t as BetterSupabase } from "../define-CRS2MRV3.js";
|
|
3
|
+
import { f as ServerContext, n as GuardOptions, p as ServerOptions, u as BetterServer } from "../respond-DgIbdrt8.js";
|
|
4
|
+
import { a as ResourceRouteOptions } from "../resource-BJcaIfxi.js";
|
|
5
5
|
import { StandardSchemaV1 } from "@standard-schema/spec";
|
|
6
|
-
//#region src/mcp/
|
|
6
|
+
//#region src/mcp/mcp.d.ts
|
|
7
7
|
export declare const MCP_PROTOCOL_VERSION: string;
|
|
8
8
|
type Json = Readonly<Record<string, unknown>>;
|
|
9
9
|
/** MCP tool annotations (hints for clients, not guarantees). */
|
|
10
|
-
|
|
10
|
+
interface ToolAnnotations {
|
|
11
11
|
readonly title?: string;
|
|
12
12
|
readonly readOnlyHint?: boolean;
|
|
13
13
|
readonly destructiveHint?: boolean;
|
|
@@ -15,7 +15,7 @@ export interface ToolAnnotations {
|
|
|
15
15
|
readonly openWorldHint?: boolean;
|
|
16
16
|
}
|
|
17
17
|
/** A tool as listed by `tools/list`. */
|
|
18
|
-
|
|
18
|
+
interface ToolInfo {
|
|
19
19
|
readonly name: string;
|
|
20
20
|
readonly title?: string;
|
|
21
21
|
readonly description?: string;
|
|
@@ -23,26 +23,26 @@ export interface ToolInfo {
|
|
|
23
23
|
readonly outputSchema?: Json;
|
|
24
24
|
readonly annotations?: ToolAnnotations;
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
interface ToolContext<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends ServerContext<M, F, E, C, P> {
|
|
27
27
|
readonly request: Request;
|
|
28
28
|
readonly signal: AbortSignal;
|
|
29
29
|
}
|
|
30
|
-
|
|
30
|
+
interface McpTool<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> {
|
|
31
31
|
readonly info: ToolInfo;
|
|
32
32
|
/** Validates arguments; its output is what `run` receives. */
|
|
33
33
|
readonly input?: StandardSchemaV1;
|
|
34
34
|
/** Opaque data for the `authorize` and `visible` hooks. Never sent to clients. */
|
|
35
35
|
readonly meta?: unknown;
|
|
36
|
-
run(args: never, ctx: ToolContext<M, F, E>): unknown;
|
|
36
|
+
run(args: never, ctx: ToolContext<M, F, E, C, P>): unknown;
|
|
37
37
|
}
|
|
38
38
|
/** What the `authorize` and `visible` hooks see of a tool. */
|
|
39
|
-
|
|
39
|
+
interface ToolRef {
|
|
40
40
|
readonly info: ToolInfo;
|
|
41
41
|
/** The tool's `meta`, e.g. a PermDock permission. `undefined` for table tools. */
|
|
42
42
|
readonly meta: unknown;
|
|
43
43
|
}
|
|
44
44
|
/** The outcome of `authorize`. `scopes` turns a refusal into an OAuth challenge. */
|
|
45
|
-
|
|
45
|
+
type ToolDecision = {
|
|
46
46
|
readonly allowed: true;
|
|
47
47
|
} | {
|
|
48
48
|
readonly allowed: false;
|
|
@@ -50,7 +50,7 @@ export type ToolDecision = {
|
|
|
50
50
|
/** OAuth scopes the call needs, sent in an `insufficient_scope` challenge. */
|
|
51
51
|
readonly scopes?: readonly string[];
|
|
52
52
|
};
|
|
53
|
-
|
|
53
|
+
interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E, I, C = unknown, P = unknown> {
|
|
54
54
|
readonly name: string;
|
|
55
55
|
readonly title?: string;
|
|
56
56
|
readonly description: string;
|
|
@@ -64,10 +64,10 @@ export interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E,
|
|
|
64
64
|
readonly annotations?: ToolAnnotations;
|
|
65
65
|
/** Opaque data for the `authorize` and `visible` hooks. Never sent to clients. */
|
|
66
66
|
readonly meta?: unknown;
|
|
67
|
-
run(args: I, ctx: ToolContext<M, F, E>): unknown;
|
|
67
|
+
run(args: I, ctx: ToolContext<M, F, E, C, P>): unknown;
|
|
68
68
|
}
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
type ToolResources<M extends AnyModels> = { readonly [T in TableKey<M>]?: ResourceRouteOptions<M, T> | true; };
|
|
70
|
+
interface McpOptions<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends ServerOptions, GuardOptions {
|
|
71
71
|
readonly name: string;
|
|
72
72
|
readonly version: string;
|
|
73
73
|
readonly title?: string;
|
|
@@ -75,7 +75,7 @@ export interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> exte
|
|
|
75
75
|
readonly instructions?: string;
|
|
76
76
|
/** Tables exposed as tools: `<table>_list`, `_get`, `_create`, `_update`, `_delete`. */
|
|
77
77
|
readonly resources?: ToolResources<M>;
|
|
78
|
-
readonly tools?: readonly McpTool<M, F, E>[];
|
|
78
|
+
readonly tools?: readonly McpTool<M, F, E, C, P>[];
|
|
79
79
|
/**
|
|
80
80
|
* Canonical URL of this MCP server (RFC 9728 `resource`). Defaults to the
|
|
81
81
|
* request URL without query.
|
|
@@ -91,6 +91,13 @@ export interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> exte
|
|
|
91
91
|
readonly scopes?: readonly string[];
|
|
92
92
|
/** Origins allowed to call the server (DNS rebinding protection). Defaults to any. */
|
|
93
93
|
readonly allowedOrigins?: readonly string[];
|
|
94
|
+
/**
|
|
95
|
+
* Hostnames the `Host` header may name, without the port (DNS rebinding
|
|
96
|
+
* protection). List the production, preview and local hosts. Defaults to any.
|
|
97
|
+
*/
|
|
98
|
+
readonly allowedHosts?: readonly string[];
|
|
99
|
+
/** A page that explains how to connect, published as RFC 9728 `resource_documentation`. */
|
|
100
|
+
readonly resourceDocumentation?: string;
|
|
94
101
|
/** Custom jsonb types, as in `createOpenApi`. */
|
|
95
102
|
readonly json?: Readonly<Record<string, unknown>>;
|
|
96
103
|
/** Include internal error messages in tool results. Defaults to `NODE_ENV === 'development'`. */
|
|
@@ -101,27 +108,27 @@ export interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> exte
|
|
|
101
108
|
* validate in the repository). A refusal is a tool error; a refusal with
|
|
102
109
|
* `scopes` is a 403 `insufficient_scope` challenge naming them.
|
|
103
110
|
*/
|
|
104
|
-
readonly authorize?: (ctx: ToolContext<M, F, E>, tool: ToolRef, args: unknown) => ToolDecision | Promise<ToolDecision>;
|
|
111
|
+
readonly authorize?: (ctx: ToolContext<M, F, E, C, P>, tool: ToolRef, args: unknown) => ToolDecision | Promise<ToolDecision>;
|
|
105
112
|
/**
|
|
106
113
|
* Whether the caller sees a tool in `tools/list`. A hidden tool is called
|
|
107
114
|
* like an unknown one. Lists are cached per token (`cacheScope: 'private'`).
|
|
108
115
|
*/
|
|
109
|
-
readonly visible?: (ctx: ToolContext<M, F, E>, tool: ToolRef) => boolean | Promise<boolean>;
|
|
116
|
+
readonly visible?: (ctx: ToolContext<M, F, E, C, P>, tool: ToolRef) => boolean | Promise<boolean>;
|
|
110
117
|
}
|
|
111
|
-
|
|
118
|
+
interface BetterMcp<M extends AnyModels, F extends AnyFunctions, E, C = unknown, P = unknown> extends BetterServer<M, F, E, C, P> {
|
|
112
119
|
/** Everything in one handler: the MCP endpoint and `/.well-known/oauth-protected-resource`. */
|
|
113
120
|
readonly fetch: (request: Request) => Promise<Response>;
|
|
114
121
|
/** The Streamable HTTP endpoint (POST JSON-RPC). */
|
|
115
|
-
readonly
|
|
122
|
+
readonly endpoint: (request: Request) => Promise<Response>;
|
|
116
123
|
/** RFC 9728 protected resource metadata. */
|
|
117
124
|
metadata(request: Request): Response;
|
|
118
125
|
readonly tools: readonly ToolInfo[];
|
|
119
126
|
/** Adds a tool typed against this app's repositories. */
|
|
120
|
-
tool<I = Record<string, unknown>>(definition: ToolDefinition<M, F, E, I>): this;
|
|
127
|
+
tool<I = Record<string, unknown>>(definition: ToolDefinition<M, F, E, I, C, P>): this;
|
|
121
128
|
/** Calls a tool directly, e.g. from tests or another transport. */
|
|
122
|
-
call(name: string, args: unknown, ctx: ToolContext<M, F, E>): Promise<ToolResult>;
|
|
129
|
+
call(name: string, args: unknown, ctx: ToolContext<M, F, E, C, P>): Promise<ToolResult>;
|
|
123
130
|
}
|
|
124
|
-
|
|
131
|
+
interface ToolResult {
|
|
125
132
|
readonly content: readonly {
|
|
126
133
|
readonly type: "text";
|
|
127
134
|
readonly text: string;
|
|
@@ -130,13 +137,13 @@ export interface ToolResult {
|
|
|
130
137
|
readonly isError?: boolean;
|
|
131
138
|
}
|
|
132
139
|
/** A custom tool. Pass it in `createMcp({ tools: [...] })`. */
|
|
133
|
-
export declare function defineTool<M extends AnyModels, F extends AnyFunctions, E, I = Record<string, unknown
|
|
140
|
+
export declare function defineTool<M extends AnyModels, F extends AnyFunctions, E, I = Record<string, unknown>, C = unknown, P = unknown>(definition: ToolDefinition<M, F, E, I, C, P>): McpTool<M, F, E, C, P>;
|
|
134
141
|
/**
|
|
135
142
|
* An MCP server (Streamable HTTP, stateless) whose tools run as the caller:
|
|
136
143
|
* the Bearer token from the MCP client is verified and every query goes
|
|
137
144
|
* through RLS. Answers 401 with RFC 9728 metadata so clients can sign in with
|
|
138
145
|
* Supabase Auth's OAuth server.
|
|
139
146
|
*/
|
|
140
|
-
export declare function createMcp<M extends AnyModels, D, F extends AnyFunctions, E>(
|
|
147
|
+
export declare function createMcp<M extends AnyModels, D, F extends AnyFunctions, E, C = unknown, P = unknown>(betterSupabase: BetterSupabase<M, D, F, E, C, P>, options: McpOptions<M, F, E, C, P>): BetterMcp<M, F, E, C, P>;
|
|
141
148
|
//#endregion
|
|
142
|
-
export type { GuardOptions, ResourceRouteOptions };
|
|
149
|
+
export type { BetterMcp, GuardOptions, McpOptions, McpTool, ResourceRouteOptions, ToolAnnotations, ToolContext, ToolDecision, ToolDefinition, ToolInfo, ToolRef, ToolResources, ToolResult };
|
package/dist/mcp/index.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
import { n as dbError } from "../errors-
|
|
2
|
-
import { t as
|
|
3
|
-
import {
|
|
1
|
+
import { n as dbError } from "../errors-L0xK41y_.js";
|
|
2
|
+
import { t as base64ToText } from "../base64-D2gsioCY.js";
|
|
3
|
+
import { t as validate } from "../standard-BRyBGTtp.js";
|
|
4
|
+
import { o as toProblem } from "../problem-BihjPR0t.js";
|
|
4
5
|
import { t as SPEC_PINS } from "../spec-pins-C3oc-Tdp.js";
|
|
5
|
-
import { t as buildJsonSchema } from "../json-schema-
|
|
6
|
-
import { a as
|
|
7
|
-
import { t as defineResource } from "../resource-
|
|
6
|
+
import { t as buildJsonSchema } from "../json-schema-DP6V7cbr.js";
|
|
7
|
+
import { a as guard, c as settle, i as defaultExpose, n as extendServer, r as withExtra, t as createServer } from "../server-BA75B0mT.js";
|
|
8
|
+
import { t as defineResource } from "../resource-My_y-Syh.js";
|
|
8
9
|
import { fromSupabaseUrl, unauthorizedResponse } from "@supabase/server/oauth-protected-resource";
|
|
9
|
-
//#region src/mcp/
|
|
10
|
+
//#region src/mcp/mcp.ts
|
|
10
11
|
const MCP_PROTOCOL_VERSION = SPEC_PINS.mcp;
|
|
11
12
|
const WELL_KNOWN = "/.well-known/oauth-protected-resource";
|
|
12
13
|
/** Revisions before 2026-07-28 open with an `initialize` handshake. */
|
|
@@ -16,6 +17,16 @@ const LEGACY_VERSIONS = [
|
|
|
16
17
|
"2025-03-26"
|
|
17
18
|
];
|
|
18
19
|
const SUPPORTED_VERSIONS = [MCP_PROTOCOL_VERSION, ...LEGACY_VERSIONS];
|
|
20
|
+
/** The `Host` header's hostname is in the list; a missing or malformed header is not. */
|
|
21
|
+
function hostAllowed(request, allowed) {
|
|
22
|
+
const host = request.headers.get("host");
|
|
23
|
+
if (!host) return false;
|
|
24
|
+
try {
|
|
25
|
+
return allowed.includes(new URL(`http://${host}`).hostname);
|
|
26
|
+
} catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
19
30
|
/** A custom tool. Pass it in `createMcp({ tools: [...] })`. */
|
|
20
31
|
function defineTool(definition) {
|
|
21
32
|
if (!/^[A-Za-z0-9_.-]{1,64}$/.test(definition.name)) throw new TypeError(`Invalid tool name "${definition.name}": use 1-64 letters, digits, "_", "-" or "."`);
|
|
@@ -67,15 +78,15 @@ const ANNOTATIONS = {
|
|
|
67
78
|
openWorldHint: false
|
|
68
79
|
}
|
|
69
80
|
};
|
|
70
|
-
function pageSchema(max) {
|
|
81
|
+
function pageSchema(max, cursor) {
|
|
71
82
|
return {
|
|
72
83
|
type: "object",
|
|
73
84
|
properties: {
|
|
74
|
-
page: {
|
|
85
|
+
...cursor ? { after: { type: "string" } } : { page: {
|
|
75
86
|
type: "integer",
|
|
76
87
|
minimum: 1,
|
|
77
88
|
default: 1
|
|
78
|
-
},
|
|
89
|
+
} },
|
|
79
90
|
size: {
|
|
80
91
|
type: "integer",
|
|
81
92
|
minimum: 1,
|
|
@@ -99,7 +110,23 @@ function tableTools(resource, defs) {
|
|
|
99
110
|
required: [keyParam, ...Object.keys(more)],
|
|
100
111
|
additionalProperties: false
|
|
101
112
|
});
|
|
102
|
-
const
|
|
113
|
+
const cursor = resource.pagination === "cursor";
|
|
114
|
+
const page = cursor ? {
|
|
115
|
+
type: "object",
|
|
116
|
+
properties: {
|
|
117
|
+
items: {
|
|
118
|
+
type: "array",
|
|
119
|
+
items: row
|
|
120
|
+
},
|
|
121
|
+
nextCursor: { type: ["string", "null"] },
|
|
122
|
+
hasMore: { type: "boolean" }
|
|
123
|
+
},
|
|
124
|
+
required: [
|
|
125
|
+
"items",
|
|
126
|
+
"nextCursor",
|
|
127
|
+
"hasMore"
|
|
128
|
+
]
|
|
129
|
+
} : {
|
|
103
130
|
type: "object",
|
|
104
131
|
properties: {
|
|
105
132
|
items: {
|
|
@@ -126,7 +153,7 @@ function tableTools(resource, defs) {
|
|
|
126
153
|
};
|
|
127
154
|
for (const operation of resource.operations) switch (operation) {
|
|
128
155
|
case "list": {
|
|
129
|
-
const { $schema: _, ...listSchema } = resource.list?.jsonSchema ?? pageSchema(resource.maxPageSize);
|
|
156
|
+
const { $schema: _, ...listSchema } = resource.list?.jsonSchema ?? pageSchema(resource.maxPageSize, cursor);
|
|
130
157
|
add("list", `List ${table} rows visible to the caller, one page at a time.`, listSchema, page);
|
|
131
158
|
break;
|
|
132
159
|
}
|
|
@@ -174,7 +201,7 @@ function headerName(value) {
|
|
|
174
201
|
const encoded = /^=\?base64\?(.*)\?=$/.exec(value)?.[1];
|
|
175
202
|
if (encoded === void 0) return value;
|
|
176
203
|
try {
|
|
177
|
-
return
|
|
204
|
+
return base64ToText(encoded);
|
|
178
205
|
} catch {
|
|
179
206
|
return value;
|
|
180
207
|
}
|
|
@@ -204,17 +231,19 @@ function canonical(request) {
|
|
|
204
231
|
* through RLS. Answers 401 with RFC 9728 metadata so clients can sign in with
|
|
205
232
|
* Supabase Auth's OAuth server.
|
|
206
233
|
*/
|
|
207
|
-
function createMcp(
|
|
208
|
-
const server = createServer(
|
|
234
|
+
function createMcp(betterSupabase, options) {
|
|
235
|
+
const server = createServer(betterSupabase, options);
|
|
209
236
|
const expose = options.exposeErrors ?? defaultExpose();
|
|
210
237
|
const defs = buildJsonSchema({
|
|
211
|
-
meta:
|
|
238
|
+
meta: betterSupabase.meta,
|
|
212
239
|
config: { json: options.json ?? {} }
|
|
213
240
|
})["$defs"];
|
|
214
241
|
const registry = /* @__PURE__ */ new Map();
|
|
242
|
+
let allTools;
|
|
215
243
|
const register = (entry) => {
|
|
216
244
|
if (registry.has(entry.info.name)) throw new TypeError(`Duplicate MCP tool "${entry.info.name}"`);
|
|
217
245
|
registry.set(entry.info.name, entry);
|
|
246
|
+
allTools = void 0;
|
|
218
247
|
};
|
|
219
248
|
const failure = (error) => textResult(toProblem(error, { expose }), true);
|
|
220
249
|
const refused = async (tool, args, ctx) => {
|
|
@@ -229,7 +258,7 @@ function createMcp(sb, options) {
|
|
|
229
258
|
const isVisible = async (tool, ctx) => !options.visible || options.visible(ctx, tool);
|
|
230
259
|
for (const [table, raw] of Object.entries(options.resources ?? {})) {
|
|
231
260
|
if (!raw) continue;
|
|
232
|
-
const resource = defineResource(
|
|
261
|
+
const resource = defineResource(betterSupabase, table, raw === true ? {} : raw);
|
|
233
262
|
for (const { info, operation } of tableTools(resource, defs)) {
|
|
234
263
|
const ref = {
|
|
235
264
|
info,
|
|
@@ -275,8 +304,11 @@ function createMcp(sb, options) {
|
|
|
275
304
|
});
|
|
276
305
|
};
|
|
277
306
|
for (const tool of options.tools ?? []) addTool(tool);
|
|
278
|
-
const listTools = () => [...registry.values()].map((entry) => entry.info);
|
|
279
|
-
|
|
307
|
+
const listTools = () => allTools ??= [...registry.values()].map((entry) => entry.info);
|
|
308
|
+
/** Without a `visible` hook every caller sees the same list, built once. */
|
|
309
|
+
const visibleTools = async (context) => {
|
|
310
|
+
if (!options.visible) return listTools();
|
|
311
|
+
const ctx = context();
|
|
280
312
|
const entries = [...registry.values()];
|
|
281
313
|
const shown = await Promise.all(entries.map((entry) => isVisible(entry, ctx)));
|
|
282
314
|
return entries.filter((_, index) => shown[index]).map((e) => e.info);
|
|
@@ -349,9 +381,10 @@ function createMcp(sb, options) {
|
|
|
349
381
|
if (sent === null || headerName(sent) !== name) return mismatch(`Mcp-Name must be ${name}`);
|
|
350
382
|
}
|
|
351
383
|
};
|
|
352
|
-
const
|
|
384
|
+
const endpoint = async (request) => {
|
|
353
385
|
const origin = request.headers.get("origin");
|
|
354
386
|
if (origin && options.allowedOrigins && !options.allowedOrigins.includes(origin)) return rpcError(null, INVALID_REQUEST, "Origin not allowed", 403);
|
|
387
|
+
if (options.allowedHosts && !hostAllowed(request, options.allowedHosts)) return rpcError(null, INVALID_REQUEST, "Host not allowed", 403);
|
|
355
388
|
if (request.method !== "POST") return new Response(null, {
|
|
356
389
|
status: 405,
|
|
357
390
|
headers: { allow: "POST" }
|
|
@@ -409,7 +442,7 @@ function createMcp(sb, options) {
|
|
|
409
442
|
}
|
|
410
443
|
case "ping": return modern ? notFound() : reply({});
|
|
411
444
|
case "tools/list": return reply({
|
|
412
|
-
tools: await visibleTools(toolContext
|
|
445
|
+
tools: await visibleTools(toolContext),
|
|
413
446
|
...modern ? {
|
|
414
447
|
ttlMs: LIST_TTL_MS,
|
|
415
448
|
cacheScope: "private"
|
|
@@ -432,18 +465,19 @@ function createMcp(sb, options) {
|
|
|
432
465
|
resource: resourceOf(request),
|
|
433
466
|
authorization_servers: [...authorizationServers()],
|
|
434
467
|
bearer_methods_supported: ["header"],
|
|
435
|
-
...scopes.length > 0 ? { scopes_supported: scopes } : {}
|
|
468
|
+
...scopes.length > 0 ? { scopes_supported: scopes } : {},
|
|
469
|
+
...options.resourceDocumentation === void 0 ? {} : { resource_documentation: options.resourceDocumentation }
|
|
436
470
|
}, { headers: { "access-control-allow-origin": "*" } });
|
|
437
471
|
const mcp = extendServer(server, {
|
|
438
472
|
get tools() {
|
|
439
|
-
return listTools();
|
|
473
|
+
return [...listTools()];
|
|
440
474
|
},
|
|
441
475
|
tool(definition) {
|
|
442
476
|
addTool(defineTool(definition));
|
|
443
477
|
return mcp;
|
|
444
478
|
},
|
|
445
479
|
call,
|
|
446
|
-
|
|
480
|
+
endpoint,
|
|
447
481
|
metadata,
|
|
448
482
|
fetch(request) {
|
|
449
483
|
const { pathname } = new URL(request.url);
|
|
@@ -454,7 +488,7 @@ function createMcp(sb, options) {
|
|
|
454
488
|
"access-control-allow-methods": "GET"
|
|
455
489
|
}
|
|
456
490
|
}) : metadata(request));
|
|
457
|
-
return
|
|
491
|
+
return endpoint(request);
|
|
458
492
|
}
|
|
459
493
|
});
|
|
460
494
|
return mcp;
|