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.
Files changed (190) hide show
  1. package/README.md +37 -33
  2. package/dist/base64-D2gsioCY.js +42 -0
  3. package/dist/claims-BAf6c7is.js +48 -0
  4. package/dist/cli/_format-Cax5EsM9.js +101 -0
  5. package/dist/cli/bin.js +39 -6
  6. package/dist/cli/command-CK14fP91.js +502 -0
  7. package/dist/cli/compare-BmIWVEui.js +9 -0
  8. package/dist/cli/config-D9R0o1LH.js +1895 -0
  9. package/dist/cli/config-SSKSTUl2.js +2 -0
  10. package/dist/cli/db-DOBPt660.js +52 -0
  11. package/dist/cli/dist-B1MvLv-M.js +129 -0
  12. package/dist/cli/dist-CFgGknmZ.js +1112 -0
  13. package/dist/cli/dist-Du7VnEUG.js +621 -0
  14. package/dist/cli/doctor-BOym3mbL.js +2382 -0
  15. package/dist/cli/env-BZ0uy8XK.js +108 -0
  16. package/dist/cli/env-jIFUADLY.js +511 -0
  17. package/dist/cli/errors-4v4DTtda.js +56 -0
  18. package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
  19. package/dist/cli/gen-Ccn_p90L.js +2008 -0
  20. package/dist/cli/gen-D5cLCULx.js +2 -0
  21. package/dist/cli/index.d.ts +295 -28
  22. package/dist/cli/index.js +184 -3
  23. package/dist/cli/init-ClnxRYBF.js +550 -0
  24. package/dist/cli/introspect-DxqfkVIv.js +81 -0
  25. package/dist/cli/io-k41_z-jT.js +24 -0
  26. package/dist/cli/json5-ChRycquj.js +787 -0
  27. package/dist/cli/jsonc-BUniUpOk.js +2 -0
  28. package/dist/cli/jsonc-D5Fu67jw.js +45 -0
  29. package/dist/cli/keys-ByKHnvP9.js +83 -0
  30. package/dist/cli/ohash-D7bG_EOb.js +277 -0
  31. package/dist/cli/openapi-8WHjxiTJ.js +66 -0
  32. package/dist/cli/project-B_GeXyHh.js +70 -0
  33. package/dist/cli/prompts-BNcNfAve.js +1142 -0
  34. package/dist/cli/prompts-BTYQGunc.js +2 -0
  35. package/dist/cli/read-sets-CMa29t9a.js +971 -0
  36. package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
  37. package/dist/cli/run-CP20LKk_.js +347 -0
  38. package/dist/cli/seed-5J6eu_Xg.js +98 -0
  39. package/dist/cli/skills-LJJIhLFR.js +137 -0
  40. package/dist/cli/sql-D4VbqmXi.js +199 -0
  41. package/dist/cli/style-C0NFdJRL.js +29 -0
  42. package/dist/cli/toml-DB-MNnFs.js +424 -0
  43. package/dist/{version-Cqq1m3UY.js → cli/version-5mW_8sY1.js} +1 -1
  44. package/dist/cli/yaml-CYCbJ6et.js +2252 -0
  45. package/dist/client/index.d.ts +2 -2
  46. package/dist/client/index.js +15 -25
  47. package/dist/{compiler-CZ9nIuqy.d.ts → compiler-DB3aSFOA.d.ts} +2 -2
  48. package/dist/config/index.d.ts +4 -3
  49. package/dist/config/index.js +5 -3
  50. package/dist/config-BpxDfnbg.js +129 -0
  51. package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
  52. package/dist/{config-ClDXmVtY.js → config-CbvFmGD8.js} +14 -137
  53. package/dist/{define-BXMYD9AY.js → define-Bh0AqTik.js} +221 -120
  54. package/dist/{define-e3zI4_4g.d.ts → define-CRS2MRV3.d.ts} +36 -31
  55. package/dist/edge/index.d.ts +9 -13
  56. package/dist/edge/index.js +51 -25
  57. package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-Dm-FHpNH.d.ts} +2 -2
  58. package/dist/env/index.js +203 -1
  59. package/dist/{errors-Bl3rkOzo.js → errors-L0xK41y_.js} +1 -1
  60. package/dist/events/index.d.ts +1 -1
  61. package/dist/events/index.js +139 -1
  62. package/dist/{events-DOt3nSO-.d.ts → events-B21UOhvP.d.ts} +4 -4
  63. package/dist/{executor-CGdNQ27g.d.ts → executor-B5IoUxLE.d.ts} +2 -2
  64. package/dist/{executor-DeppvirK.js → executor-CVt6dD0B.js} +9 -4
  65. package/dist/{executor-BYXQ9AGn.d.ts → executor-CZ7ImRiD.d.ts} +2 -2
  66. package/dist/hono/index.d.ts +17 -24
  67. package/dist/hono/index.js +11 -11
  68. package/dist/{index-DuP14VHo.d.ts → index-528Ugmyp.d.ts} +30 -10
  69. package/dist/{index-Bw1bwtC0.d.ts → index-BAy4tsAP.d.ts} +28 -15
  70. package/dist/{index-CBDufH1M.d.ts → index-BBdvo-5d.d.ts} +24 -26
  71. package/dist/{index-DbxFr7XG.d.ts → index-BhHEC04r.d.ts} +9 -4
  72. package/dist/index-Cf-D2jk1.d.ts +50 -0
  73. package/dist/{index-Ciu5Xlml.d.ts → index-D5IfUIDh.d.ts} +6 -5
  74. package/dist/{index-K-3WM2Hp.d.ts → index-DFmOpBGC.d.ts} +13 -13
  75. package/dist/{index-D4v7sncu.d.ts → index-RlLXd8dL.d.ts} +13 -13
  76. package/dist/index.d.ts +17 -17
  77. package/dist/index.js +23 -14
  78. package/dist/{invalidate-DbxuL2jL.js → invalidate-CgxmRamI.js} +7 -1
  79. package/dist/jobs/index.d.ts +33 -26
  80. package/dist/jobs/index.js +44 -22
  81. package/dist/{json-schema-BpYqkCKf.js → json-schema-DP6V7cbr.js} +8 -3
  82. package/dist/lint/index.js +1 -1
  83. package/dist/list/index.d.ts +2 -2
  84. package/dist/list/index.js +131 -96
  85. package/dist/{live-DJyZMD6w.d.ts → live-BHakSWWo.d.ts} +26 -16
  86. package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
  87. package/dist/lookup-BaP5bI27.js +18 -0
  88. package/dist/mcp/index.d.ts +34 -27
  89. package/dist/mcp/index.js +59 -25
  90. package/dist/{mfa-TVia554c.d.ts → mfa-C7MNQ1Ob.d.ts} +1 -1
  91. package/dist/{mfa-CpLfJSde.js → mfa-byfjruR9.js} +190 -46
  92. package/dist/next/image/index.js +6 -3
  93. package/dist/next/index.d.ts +64 -48
  94. package/dist/next/index.js +63 -50
  95. package/dist/openapi/index.d.ts +1 -1
  96. package/dist/openapi/index.js +61 -38
  97. package/dist/orpc/index.d.ts +18 -21
  98. package/dist/orpc/index.js +12 -12
  99. package/dist/otel/index.d.ts +5 -5
  100. package/dist/otel/index.js +84 -63
  101. package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-C4KFzLc9.js} +12 -7
  102. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  103. package/dist/{plugin-C4TR9J0Z.d.ts → plugin-bNhvJwRh.d.ts} +5 -4
  104. package/dist/plugins/actor/index.d.ts +1 -1
  105. package/dist/plugins/actor/index.js +1 -1
  106. package/dist/plugins/rules/index.d.ts +2 -2
  107. package/dist/plugins/rules/index.js +46 -16
  108. package/dist/plugins/soft-delete/index.d.ts +3 -3
  109. package/dist/plugins/soft-delete/index.js +4 -4
  110. package/dist/plugins/tenant/index.d.ts +3 -3
  111. package/dist/plugins/tenant/index.js +10 -4
  112. package/dist/plugins/timestamps/index.d.ts +1 -1
  113. package/dist/plugins/timestamps/index.js +2 -2
  114. package/dist/plugins/validation/index.d.ts +2 -2
  115. package/dist/plugins/validation/index.js +3 -3
  116. package/dist/pool-DFwEQwmN.d.ts +78 -0
  117. package/dist/postgres/index.d.ts +10 -5
  118. package/dist/postgres/index.js +37 -16
  119. package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-Bc-0Xw62.d.ts} +1 -1
  120. package/dist/{problem-BTkTtRV9.js → problem-BihjPR0t.js} +11 -5
  121. package/dist/{problem-DlgB0lYl.d.ts → problem-U8UEKccv.d.ts} +6 -4
  122. package/dist/query/index.d.ts +2 -2
  123. package/dist/query/index.js +2 -2
  124. package/dist/{query-ChvDbsDE.js → query-BMMSWbI7.js} +19 -19
  125. package/dist/react/index.d.ts +7 -7
  126. package/dist/react/index.js +2 -2
  127. package/dist/react/server.d.ts +6 -6
  128. package/dist/react/server.js +3 -3
  129. package/dist/react/session.d.ts +21 -2
  130. package/dist/react/session.js +19 -1
  131. package/dist/{react-C83BdBGt.js → react-CQHMaESR.js} +59 -55
  132. package/dist/{read-set-4lI3W_gZ.js → read-set-FWK8qQ58.js} +51 -16
  133. package/dist/realtime/index.d.ts +4 -3
  134. package/dist/realtime/index.js +19 -17
  135. package/dist/{index-CG69s15v.d.ts → refresh-BwYkyNOQ.d.ts} +7 -3
  136. package/dist/{resolve-CjHILk2w.d.ts → resolve-BAqQcwMW.d.ts} +17 -11
  137. package/dist/{resource-CgZa3YG_.d.ts → resource-BJcaIfxi.d.ts} +17 -8
  138. package/dist/{resource-BHEWmQJK.js → resource-My_y-Syh.js} +43 -21
  139. package/dist/{respond-Dne1ARSa.d.ts → respond-DgIbdrt8.d.ts} +39 -14
  140. package/dist/{result-DWXatkd6.js → result-Bj6yt33D.js} +2 -2
  141. package/dist/{result-CTkCZ6JA.d.ts → result-CqEsbgYs.d.ts} +16 -11
  142. package/dist/server/index.d.ts +38 -13
  143. package/dist/server/index.js +43 -14
  144. package/dist/{respond-DMcbQwfN.js → server-BA75B0mT.js} +151 -96
  145. package/dist/{shared-_6-uPL7E.js → shared-DnoCdMDr.js} +4 -3
  146. package/dist/{simplify-bwJpLnaS.js → simplify-Cqvfr9dj.js} +76 -39
  147. package/dist/sql/index.d.ts +139 -0
  148. package/dist/sql/index.js +1983 -0
  149. package/dist/{sql-DTjA1LPj.js → sql-B34HU2DI.js} +74 -17
  150. package/dist/ssr/index.d.ts +4 -4
  151. package/dist/ssr/index.js +2 -2
  152. package/dist/{standard-FUi3Z3-7.js → standard-BRyBGTtp.js} +2 -2
  153. package/dist/{standard-CyhRzctb.d.ts → standard-BsaCYQJc.d.ts} +1 -1
  154. package/dist/{stats-C5W-rBfR.js → stats-C_YxA2r1.js} +40 -14
  155. package/dist/storage/index.d.ts +4 -3
  156. package/dist/storage/index.js +548 -1
  157. package/dist/{tables-rZxRxNPW.js → tables-Cb-3_VWc.js} +19 -5
  158. package/dist/temporal-BwshQk0W.js +27 -0
  159. package/dist/temporal-required-CBNHT6ni.js +19 -0
  160. package/dist/testing/index.d.ts +25 -24
  161. package/dist/testing/index.js +165 -66
  162. package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
  163. package/dist/version-Bmf7_FzM.js +4 -0
  164. package/dist/{view-D1s0wa0T.d.ts → view-B64o81wp.d.ts} +36 -6
  165. package/dist/{view-aOYizTeE.js → view-BrJI2LKp.js} +11 -2
  166. package/dist/webhooks/index.d.ts +19 -17
  167. package/dist/webhooks/index.js +1 -1
  168. package/dist/{webhooks-DMipjhSP.js → webhooks-Bh_Jf_-l.js} +45 -24
  169. package/package.json +50 -22
  170. package/schemas/config-v1.json +31 -1
  171. package/schemas/doctor-report-v1.json +17 -1
  172. package/schemas/snapshot-v2.json +35 -0
  173. package/skills/better-supabase/SKILL.md +77 -18
  174. package/skills/better-supabase/references/plugins.md +21 -22
  175. package/skills/better-supabase/references/troubleshooting.md +38 -18
  176. package/skills/better-supabase-api/SKILL.md +66 -18
  177. package/skills/better-supabase-api/references/adapters.md +64 -41
  178. package/skills/better-supabase-auth/SKILL.md +146 -0
  179. package/skills/better-supabase-auth/references/permdock.md +55 -0
  180. package/skills/better-supabase-testing/SKILL.md +68 -11
  181. package/dist/casing-da0uRTqt.js +0 -14
  182. package/dist/claims-CoTobNC-.js +0 -25
  183. package/dist/cli-DrSgDOQC.js +0 -6646
  184. package/dist/env-BEToab0I.js +0 -204
  185. package/dist/events-BuzTgn2D.js +0 -135
  186. package/dist/pool-R2m_DkXi.d.ts +0 -37
  187. package/dist/seed-CzecODcJ.js +0 -101
  188. package/dist/session-C3LxnQ0e.d.ts +0 -22
  189. package/dist/session-CqEwzVup.js +0 -20
  190. 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(sb, supabase, spec, { onChange: () => refetch() });
83
+ * using live = liveQuery(betterSupabase, supabase, spec, { onChange: () => refetch() });
80
84
  * ```
81
85
  */
82
- function liveQuery(sb, client, spec, options) {
83
- const touched = Array.isArray(spec) ? spec : sb.tablesOf(spec);
84
- const tables = touched.filter((table) => sb.meta.realtime?.[table]);
85
- const unwatched = touched.filter((table) => !sb.meta.realtime?.[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(sb.meta, table, options.tenant), () => {
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(sb, supabase, db, sb.spec.notes.count(), {
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(sb, client, db, spec, options) {
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(sb, client, spec, {
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 };
@@ -1,13 +1,13 @@
1
- import { N as TableKey, n as AnyModels, t as AnyFunctions } from "../types-C83v5SQz.js";
2
- import { t as BetterSupabase } from "../define-e3zI4_4g.js";
3
- import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-Dne1ARSa.js";
4
- import { i as ResourceRouteOptions } from "../resource-CgZa3YG_.js";
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/index.d.ts
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
- export interface ToolAnnotations {
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
- export interface ToolInfo {
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
- export interface ToolContext<M extends AnyModels, F extends AnyFunctions, E> extends ServerContext<M, F, E> {
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
- export interface McpTool<M extends AnyModels, F extends AnyFunctions, E> {
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
- export interface ToolRef {
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
- export type ToolDecision = {
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
- export interface ToolDefinition<M extends AnyModels, F extends AnyFunctions, E, I> {
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
- export type ToolResources<M extends AnyModels> = { readonly [T in TableKey<M>]?: ResourceRouteOptions<M, T> | true; };
70
- export interface McpOptions<M extends AnyModels, F extends AnyFunctions, E> extends ServerOptions, GuardOptions {
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
- export interface BetterMcp<M extends AnyModels, F extends AnyFunctions, E> extends BetterServer<M, F, E> {
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 handler: (request: Request) => Promise<Response>;
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
- export interface ToolResult {
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>>(definition: ToolDefinition<M, F, E, I>): McpTool<M, F, E>;
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>(sb: BetterSupabase<M, D, F, E>, options: McpOptions<M, F, E>): BetterMcp<M, F, 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-Bl3rkOzo.js";
2
- import { t as validate } from "../standard-FUi3Z3-7.js";
3
- import { o as toProblem } from "../problem-BTkTtRV9.js";
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-BpYqkCKf.js";
6
- import { a as settle, c as withExtra, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-DMcbQwfN.js";
7
- import { t as defineResource } from "../resource-BHEWmQJK.js";
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/index.ts
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 page = {
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 new TextDecoder().decode(Uint8Array.from(atob(encoded), (char) => char.charCodeAt(0)));
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(sb, options) {
208
- const server = createServer(sb, options);
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: sb.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(sb, table, raw === true ? {} : raw);
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
- const visibleTools = async (ctx) => {
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 handler = async (request) => {
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
- handler,
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 handler(request);
491
+ return endpoint(request);
458
492
  }
459
493
  });
460
494
  return mcp;
@@ -1,4 +1,4 @@
1
- import { p as DbError } from "./result-CTkCZ6JA.js";
1
+ import { p as DbError } from "./result-CqEsbgYs.js";
2
2
  //#region src/auth/impersonation.d.ts
3
3
  /** The admin acting as the user, from the RFC 8693 `act` claim. */
4
4
  interface Impersonator {