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
@@ -0,0 +1,550 @@
1
+ import { a as list, r as defineCliCommand } from "./command-CK14fP91.js";
2
+ import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
3
+ import { t as VERSION } from "./version-5mW_8sY1.js";
4
+ import { n as findConfig } from "./config-D9R0o1LH.js";
5
+ import { n as installCommand, t as detectProject } from "./project-B_GeXyHh.js";
6
+ import { posix, resolve } from "node:path";
7
+ import { existsSync } from "node:fs";
8
+ //#region src/cli/templates.ts
9
+ const INTEGRATIONS = [
10
+ "next",
11
+ "hono",
12
+ "orpc",
13
+ "edge",
14
+ "mcp",
15
+ "client",
16
+ "react"
17
+ ];
18
+ const join$1 = (...parts) => posix.normalize(posix.join(...parts));
19
+ const libDir = (context) => join$1(context.srcDir, "lib");
20
+ /** `lib/supabase/`: the shared definition, `server.ts` and `client.ts`. */
21
+ const supabaseDir = (context) => join$1(libDir(context), "supabase");
22
+ /** `lib/supabase/index.ts`, imported as `lib/supabase` on both sides. */
23
+ const libEntry = (context) => join$1(supabaseDir(context), "index.ts");
24
+ /** Import path of `lib/supabase/index.ts`: the folder, unless the project keeps `.ts` extensions. */
25
+ function importLib(context, from) {
26
+ const path = importFrom(context, from, libEntry(context));
27
+ return context.tsExtensions ? path : path.replace(/^(\.\.?\/.+)\/index$/, "$1");
28
+ }
29
+ /** Relative import from one project file to another, following the project's extension style. */
30
+ function importFrom(context, from, to, keepTs = context.tsExtensions) {
31
+ let path = posix.relative(posix.dirname(from), to);
32
+ if (!path.startsWith(".")) path = `./${path}`;
33
+ return keepTs ? path : path.replace(/\.tsx?$/, "");
34
+ }
35
+ function publicEnv(context) {
36
+ const read = (prefix, access) => access === "vite" ? {
37
+ url: `import.meta.env.${prefix}SUPABASE_URL`,
38
+ key: `import.meta.env.${prefix}SUPABASE_PUBLISHABLE_KEY`
39
+ } : {
40
+ url: `process.env.${prefix}SUPABASE_URL!`,
41
+ key: `process.env.${prefix}SUPABASE_PUBLISHABLE_KEY!`
42
+ };
43
+ if (context.frameworks.includes("next")) return read("NEXT_PUBLIC_", "process");
44
+ if (context.frameworks.includes("expo")) return read("EXPO_PUBLIC_", "process");
45
+ if (context.frameworks.includes("vite")) return read("VITE_", "vite");
46
+ return read("", "process");
47
+ }
48
+ const denoJson = (context) => `${JSON.stringify({ imports: {
49
+ "better-supabase": `npm:better-supabase@^${context.version}`,
50
+ "better-supabase/": `npm:/better-supabase@^${context.version}/`,
51
+ "@supabase/supabase-js": "npm:@supabase/supabase-js@^2",
52
+ zod: "npm:zod@^4"
53
+ } }, null, 2)}\n`;
54
+ const SHARED = "supabase/functions/_shared/supabase.ts";
55
+ function shared(context) {
56
+ return {
57
+ path: SHARED,
58
+ contents: `import { defineSupabase } from 'better-supabase';
59
+
60
+ import { schema } from '${importFrom(context, SHARED, context.generated, true)}';
61
+
62
+ export const betterSupabase = defineSupabase(schema);
63
+ `
64
+ };
65
+ }
66
+ /** `supabase/functions/<name>/server.ts`: the function's `bs`. */
67
+ function functionServer(name, contents) {
68
+ return {
69
+ path: `supabase/functions/${name}/server.ts`,
70
+ contents: contents("../_shared/supabase.ts")
71
+ };
72
+ }
73
+ /** Edge Functions import `_shared/supabase.ts`, so they don't need the app definition. */
74
+ const needsLib = (names) => names.length === 0 || names.some((name) => name !== "edge" && name !== "mcp");
75
+ /** The files `init` writes: the config and, unless `lib` is false, the isomorphic definition. */
76
+ function baseFiles(context, casing, lib = true) {
77
+ const config = {
78
+ path: "better-supabase.config.ts",
79
+ contents: `import { defineConfig } from 'better-supabase/config';
80
+
81
+ export default defineConfig({
82
+ casing: '${casing}',
83
+ output: '${context.generated}',
84
+ });
85
+ `
86
+ };
87
+ if (!lib) return [config];
88
+ const path = libEntry(context);
89
+ const generated = importFrom(context, path, context.generated);
90
+ return [config, {
91
+ path,
92
+ contents: `import { defineSupabase } from 'better-supabase';
93
+
94
+ import { schema } from '${generated}';
95
+
96
+ export type { Functions, Models } from '${generated}';
97
+
98
+ export const betterSupabase = defineSupabase(schema);
99
+ `
100
+ }];
101
+ }
102
+ const TEMPLATES = {
103
+ client: {
104
+ description: "Browser client with cookie sessions shared with the server",
105
+ requires: [],
106
+ packages: ["@supabase/ssr"],
107
+ files: (context) => {
108
+ const path = join$1(supabaseDir(context), "client.ts");
109
+ const env = publicEnv(context);
110
+ return [{
111
+ path,
112
+ contents: `import { createClient } from 'better-supabase/client';
113
+
114
+ import { betterSupabase } from '${importLib(context, path)}';
115
+
116
+ export const bs = createClient(betterSupabase, {
117
+ env: {
118
+ url: ${env.url},
119
+ publishableKey: ${env.key},
120
+ },
121
+ });
122
+ `
123
+ }];
124
+ }
125
+ },
126
+ next: {
127
+ description: "Next.js proxy, Server Components, route handlers and actions",
128
+ requires: ["client"],
129
+ packages: [
130
+ "@supabase/ssr",
131
+ "@supabase/server",
132
+ "server-only"
133
+ ],
134
+ files: (context) => {
135
+ const server = join$1(supabaseDir(context), "server.ts");
136
+ const proxy = join$1(context.srcDir, "proxy.ts");
137
+ return [{
138
+ path: server,
139
+ contents: `import 'server-only';
140
+
141
+ import { createNext } from 'better-supabase/next';
142
+
143
+ import { betterSupabase } from '${importLib(context, server)}';
144
+
145
+ export const bs = createNext(betterSupabase);
146
+ `
147
+ }, {
148
+ path: proxy,
149
+ contents: `import type { NextRequest } from 'next/server';
150
+
151
+ import { bs } from '${importFrom(context, proxy, server)}';
152
+
153
+ export const proxy = (request: NextRequest) => bs.proxy(request);
154
+
155
+ export const config = {
156
+ matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\\\.(?:svg|png|jpg|webp)$).*)'],
157
+ };
158
+ `
159
+ }];
160
+ },
161
+ next: ["Read the session in Server Components with `const { db } = await bs.context()`."]
162
+ },
163
+ react: {
164
+ description: "React provider and typed hooks with TanStack Query",
165
+ requires: ["client"],
166
+ packages: ["@tanstack/react-query"],
167
+ files: (context) => {
168
+ const client = join$1(supabaseDir(context), "client.ts");
169
+ const providers = context.frameworks.includes("next") ? join$1(context.srcDir, "app", "providers.tsx") : join$1(context.srcDir, "providers.tsx");
170
+ const hooks = join$1(libDir(context), "hooks.ts");
171
+ return [{
172
+ path: providers,
173
+ contents: `${context.frameworks.includes("next") ? "'use client';\n\n" : ""}import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
174
+ import { BetterSupabaseProvider } from 'better-supabase/react';
175
+ import { type ReactNode, useState } from 'react';
176
+
177
+ import { bs } from '${importFrom(context, providers, client)}';
178
+
179
+ export function Providers({ children }: { children: ReactNode }) {
180
+ const [queryClient] = useState(() => new QueryClient());
181
+ return (
182
+ <QueryClientProvider client={queryClient}>
183
+ <BetterSupabaseProvider client={bs} queryClient={queryClient}>
184
+ {children}
185
+ </BetterSupabaseProvider>
186
+ </QueryClientProvider>
187
+ );
188
+ }
189
+ `
190
+ }, {
191
+ path: hooks,
192
+ contents: `import { createHooks } from 'better-supabase/react';
193
+
194
+ import type { bs } from '${importFrom(context, hooks, client)}';
195
+
196
+ export const { useDb, useQueries, useAuth, useSupabase } = createHooks<typeof bs>();
197
+ `
198
+ }];
199
+ },
200
+ next: ["Wrap your app in <Providers>."]
201
+ },
202
+ hono: {
203
+ description: "Hono middleware, handlers and REST resources",
204
+ requires: [],
205
+ packages: ["hono", "@supabase/server"],
206
+ files: (context) => {
207
+ const path = join$1(context.srcDir, "server.ts");
208
+ return [{
209
+ path,
210
+ contents: `import { Hono } from 'hono';
211
+ import { type HonoEnv, createHono } from 'better-supabase/hono';
212
+
213
+ import { betterSupabase, type Functions, type Models } from '${importLib(context, path)}';
214
+
215
+ export const bs = createHono(betterSupabase);
216
+
217
+ const app = new Hono<HonoEnv<Models, Functions, unknown>>()
218
+ .onError(bs.onError)
219
+ .use('/api/*', bs.middleware())
220
+ .get('/api/me', (c) => c.json({ kind: c.var.auth.kind }));
221
+
222
+ export default app;
223
+ `
224
+ }];
225
+ }
226
+ },
227
+ orpc: {
228
+ description: "oRPC middleware with typed context and ORPCError mapping",
229
+ requires: [],
230
+ packages: ["@orpc/server", "@supabase/server"],
231
+ files: (context) => {
232
+ const path = join$1(context.srcDir, "router.ts");
233
+ return [{
234
+ path,
235
+ contents: `import { os } from '@orpc/server';
236
+ import { createOrpc, type OrpcRequestContext } from 'better-supabase/orpc';
237
+
238
+ import { betterSupabase } from '${importLib(context, path)}';
239
+
240
+ export const bs = createOrpc(betterSupabase);
241
+
242
+ const base = os.$context<OrpcRequestContext>();
243
+ const authed = base.use(bs.middleware());
244
+
245
+ export const router = {
246
+ me: authed.handler(({ context }) => ({ kind: context.auth.kind })),
247
+ };
248
+ `
249
+ }];
250
+ }
251
+ },
252
+ edge: {
253
+ description: "A Supabase Edge Function with CORS and Problem Details",
254
+ requires: [],
255
+ packages: [],
256
+ files: (context) => [
257
+ shared(context),
258
+ functionServer("api", (shared) => `import { createEdge } from 'better-supabase/edge';
259
+
260
+ import { betterSupabase } from '${shared}';
261
+
262
+ export const bs = createEdge(betterSupabase, { cors: true });
263
+ `),
264
+ {
265
+ path: "supabase/functions/api/index.ts",
266
+ contents: `import { bs } from './server.ts';
267
+
268
+ Deno.serve(bs.handler((_request, { auth }) => ({ kind: auth.kind })));
269
+ `
270
+ },
271
+ {
272
+ path: "supabase/functions/api/deno.json",
273
+ contents: denoJson(context)
274
+ }
275
+ ],
276
+ next: ["Serve it locally with `supabase functions serve api`."]
277
+ },
278
+ mcp: {
279
+ description: "An MCP server (Streamable HTTP) as an Edge Function",
280
+ requires: [],
281
+ packages: [],
282
+ files: (context) => [
283
+ shared(context),
284
+ functionServer("mcp", (shared) => `import { createMcp } from 'better-supabase/mcp';
285
+
286
+ import { betterSupabase } from '${shared}';
287
+
288
+ export const bs = createMcp(betterSupabase, {
289
+ name: 'app',
290
+ version: '0.1.0',
291
+ resources: {},
292
+ });
293
+ `),
294
+ {
295
+ path: "supabase/functions/mcp/index.ts",
296
+ contents: `import { bs } from './server.ts';
297
+
298
+ Deno.serve(bs.fetch);
299
+ `
300
+ },
301
+ {
302
+ path: "supabase/functions/mcp/deno.json",
303
+ contents: denoJson(context)
304
+ }
305
+ ],
306
+ next: ["List tables under `resources` to expose them as tools, e.g. `customers: true`."]
307
+ }
308
+ };
309
+ /** The integration and what it requires, dependencies first, without duplicates. */
310
+ function resolveIntegrations(names) {
311
+ const ordered = [];
312
+ const visit = (name) => {
313
+ if (ordered.includes(name)) return;
314
+ for (const dependency of TEMPLATES[name].requires) visit(dependency);
315
+ ordered.push(name);
316
+ };
317
+ for (const name of names) visit(name);
318
+ return ordered;
319
+ }
320
+ function isIntegration(name) {
321
+ return INTEGRATIONS.includes(name);
322
+ }
323
+ /** Integrations `init` adds for the frameworks it finds. */
324
+ function suggestedIntegrations(frameworks) {
325
+ const names = [];
326
+ for (const framework of frameworks) switch (framework) {
327
+ case "next":
328
+ names.push("next");
329
+ break;
330
+ case "hono":
331
+ names.push("hono");
332
+ break;
333
+ case "orpc":
334
+ names.push("orpc");
335
+ break;
336
+ case "vite":
337
+ case "expo":
338
+ names.push("client");
339
+ break;
340
+ case "tanstack-query":
341
+ names.push("react");
342
+ break;
343
+ default: throw new TypeError(`Unknown framework ${String(framework)}`);
344
+ }
345
+ return resolveIntegrations(names);
346
+ }
347
+ //#endregion
348
+ //#region src/cli/commands/init.ts
349
+ const INIT_ARGS = {
350
+ casing: {
351
+ type: "string",
352
+ description: "Row keys in camelCase (camel, the default) or the database's snake_case (snake)",
353
+ valueHint: "camel|snake"
354
+ },
355
+ with: {
356
+ type: "string",
357
+ description: "Integrations to add next to the detected ones: next, hono, orpc, edge, mcp, client, react",
358
+ valueHint: "integration,..."
359
+ },
360
+ force: {
361
+ type: "boolean",
362
+ description: "Overwrite files that exist"
363
+ },
364
+ "dry-run": {
365
+ type: "boolean",
366
+ description: "Show what would be written"
367
+ }
368
+ };
369
+ const ADD_ARGS = {
370
+ integration: {
371
+ type: "positional",
372
+ required: false,
373
+ description: "next, hono, orpc, edge, mcp, client or react"
374
+ },
375
+ force: {
376
+ type: "boolean",
377
+ description: "Overwrite files that exist"
378
+ },
379
+ "dry-run": {
380
+ type: "boolean",
381
+ description: "Show what would be written"
382
+ }
383
+ };
384
+ const CANCELLED = {
385
+ code: 1,
386
+ error: "Cancelled."
387
+ };
388
+ /** `--force`, or the person's answer when files exist; `undefined` when they cancel. */
389
+ async function overwrite(root, files, args, prompts) {
390
+ if (args.force === true) return true;
391
+ const existing = [...new Set(files.map((file) => file.path).filter((path) => existsSync(resolve(root, path))))];
392
+ if (!prompts || existing.length === 0) return false;
393
+ return prompts.confirm(`${existing.map((path) => display(root, path)).join(", ")} ${existing.length === 1 ? "exists" : "exist"}. Overwrite?`);
394
+ }
395
+ async function writeFiles(root, files, args, force) {
396
+ const dryRun = args["dry-run"] === true;
397
+ const lines = [];
398
+ const seen = /* @__PURE__ */ new Set();
399
+ for (const file of files) {
400
+ if (seen.has(file.path)) continue;
401
+ seen.add(file.path);
402
+ const path = resolve(root, file.path);
403
+ const shown = display(root, file.path);
404
+ if (existsSync(path) && !force) {
405
+ lines.push(`Kept ${shown} (exists)`);
406
+ continue;
407
+ }
408
+ if (dryRun) {
409
+ lines.push(`Would write ${shown}`);
410
+ continue;
411
+ }
412
+ lines.push(`${await writeIfChanged(path, file.contents) ? "Wrote " : "Same "} ${shown}`);
413
+ }
414
+ return lines;
415
+ }
416
+ function context(project, generated) {
417
+ return {
418
+ srcDir: project.srcDir,
419
+ generated,
420
+ tsExtensions: project.tsExtensions,
421
+ frameworks: project.frameworks,
422
+ version: VERSION
423
+ };
424
+ }
425
+ function integrationFiles(names, templateContext) {
426
+ return names.flatMap((name) => TEMPLATES[name].files(templateContext));
427
+ }
428
+ function packagesFor(project, names) {
429
+ const wanted = [
430
+ "better-supabase",
431
+ "@supabase/supabase-js",
432
+ ...names.flatMap((name) => TEMPLATES[name].packages)
433
+ ];
434
+ return [...new Set(wanted)].filter((name) => !(name in project.dependencies));
435
+ }
436
+ /** pg as a dev dependency, when the project lacks it. */
437
+ function devPackages(project) {
438
+ return "pg" in project.dependencies ? [] : [["pg"]];
439
+ }
440
+ function chooseIntegrations(prompts, initial) {
441
+ return prompts.multiselect("Integrations", INTEGRATIONS.map((name) => ({
442
+ value: name,
443
+ label: name,
444
+ hint: TEMPLATES[name].description
445
+ })), initial);
446
+ }
447
+ function parseIntegrations(values) {
448
+ const unknown = values.filter((value) => !isIntegration(value));
449
+ if (unknown.length > 0) return `Unknown integration ${unknown.join(", ")}. Available: ${INTEGRATIONS.join(", ")}`;
450
+ return values.filter(isIntegration);
451
+ }
452
+ async function runInit(config, args, prompts) {
453
+ const casing = args.casing ?? (prompts ? await prompts.select("Row keys", [{
454
+ value: "camel",
455
+ label: "camelCase",
456
+ hint: "customerId"
457
+ }, {
458
+ value: "snake",
459
+ label: "snake_case",
460
+ hint: "customer_id, as in the database"
461
+ }], "camel") : "camel");
462
+ if (casing === void 0) return CANCELLED;
463
+ if (casing !== "camel" && casing !== "snake") return {
464
+ code: 2,
465
+ error: "--casing must be \"camel\" or \"snake\""
466
+ };
467
+ const project = await detectProject(config.root);
468
+ const requested = parseIntegrations(list(args.with));
469
+ if (typeof requested === "string") return {
470
+ code: 2,
471
+ error: requested
472
+ };
473
+ const detected = resolveIntegrations([...suggestedIntegrations(project.frameworks), ...requested]);
474
+ const chosen = args.with === void 0 && prompts ? await chooseIntegrations(prompts, detected) : detected;
475
+ if (chosen === void 0) return CANCELLED;
476
+ const integrations = resolveIntegrations(chosen);
477
+ const templateContext = context(project, findConfig(config.root) !== void 0 ? config.output : posix.normalize(posix.join(project.srcDir, "lib/supabase/generated.ts")));
478
+ const files = [...baseFiles(templateContext, casing, needsLib(integrations)), ...integrationFiles(integrations, templateContext)];
479
+ const force = await overwrite(config.root, files, args, prompts);
480
+ if (force === void 0) return CANCELLED;
481
+ const lines = [
482
+ project.frameworks.length > 0 ? `Found ${project.frameworks.join(", ")}.` : "No framework found.",
483
+ "",
484
+ ...await writeFiles(config.root, files, args, force)
485
+ ];
486
+ const packages = packagesFor(project, integrations);
487
+ const steps = [
488
+ ...packages.length > 0 ? [installCommand(project.packageManager, packages)] : [],
489
+ ...devPackages(project).map((names) => installCommand(project.packageManager, names, true)),
490
+ ...project.hasSupabase ? [] : ["supabase init"],
491
+ "supabase start",
492
+ "better-supabase env",
493
+ "better-supabase gen"
494
+ ];
495
+ lines.push("", "Next:", ...steps.map((step, index) => ` ${index + 1}. ${step}`));
496
+ for (const name of integrations) for (const hint of TEMPLATES[name].next ?? []) lines.push(` - ${hint}`);
497
+ lines.push("", "Agent skills: better-supabase skills install");
498
+ return {
499
+ code: 0,
500
+ output: lines.join("\n")
501
+ };
502
+ }
503
+ async function runAdd(config, args, prompts) {
504
+ const named = args._.length === 0 && prompts ? await chooseIntegrations(prompts, []) : args._;
505
+ if (named === void 0) return CANCELLED;
506
+ if (named.length === 0) return {
507
+ code: 2,
508
+ error: `Name at least one integration: ${INTEGRATIONS.join(", ")}`
509
+ };
510
+ const requested = parseIntegrations(named);
511
+ if (typeof requested === "string") return {
512
+ code: 2,
513
+ error: requested
514
+ };
515
+ const project = await detectProject(config.root);
516
+ const integrations = resolveIntegrations(requested);
517
+ const templateContext = context(project, config.output);
518
+ const files = integrationFiles(integrations, templateContext);
519
+ const force = await overwrite(config.root, files, args, prompts);
520
+ if (force === void 0) return CANCELLED;
521
+ const lines = await writeFiles(config.root, files, args, force);
522
+ const lib = libEntry(templateContext);
523
+ if (!existsSync(resolve(config.root, lib)) && integrations.some((name) => needsLib([name]))) lines.push("", `Warning: ${lib} is missing. Run \`better-supabase init\` first.`);
524
+ const packages = packagesFor(project, integrations).filter((name) => name !== "better-supabase" && name !== "@supabase/supabase-js");
525
+ if (packages.length > 0) lines.push("", `Install: ${installCommand(project.packageManager, packages)}`);
526
+ for (const name of integrations) for (const hint of TEMPLATES[name].next ?? []) lines.push(`- ${hint}`);
527
+ return {
528
+ code: 0,
529
+ output: lines.join("\n")
530
+ };
531
+ }
532
+ const initCommand = defineCliCommand({
533
+ meta: {
534
+ name: "init",
535
+ description: "Writes better-supabase.config.ts, src/lib/supabase/index.ts and glue for the frameworks in package.json"
536
+ },
537
+ args: INIT_ARGS,
538
+ lists: ["with"],
539
+ run: (args, { config, io }) => runInit(config, args, io.prompts)
540
+ });
541
+ const addCommand = defineCliCommand({
542
+ meta: {
543
+ name: "add",
544
+ description: "Adds glue for an integration to an existing project"
545
+ },
546
+ args: ADD_ARGS,
547
+ run: (args, { config, io }) => runAdd(config, args, io.prompts)
548
+ });
549
+ //#endregion
550
+ export { addCommand, initCommand };
@@ -0,0 +1,81 @@
1
+ import { r as defineCliCommand } from "./command-CK14fP91.js";
2
+ import { r as writeIfChanged, t as display } from "./io-k41_z-jT.js";
3
+ import { r as painter } from "./style-C0NFdJRL.js";
4
+ import { l as fileDiff } from "./read-sets-CMa29t9a.js";
5
+ import { _ as restrictSchemas, a as sourceArgs, o as loadSnapshot, t as SOURCE_ARGS, u as serializeSnapshot, v as serializeGenerator } from "./gen-Ccn_p90L.js";
6
+ import { n as withSpinner } from "./prompts-BNcNfAve.js";
7
+ import { resolve } from "node:path";
8
+ import { readFile } from "node:fs/promises";
9
+ //#region src/cli/commands/introspect.ts
10
+ const ARGS = {
11
+ out: {
12
+ type: "string",
13
+ description: "Defaults to supabase/snapshot.json, or supabase/generator-metadata.json",
14
+ valueHint: "file"
15
+ },
16
+ format: {
17
+ type: "string",
18
+ description: "snapshot (the default) or generator-metadata (postgrest-typegen's input)",
19
+ valueHint: "snapshot|generator-metadata"
20
+ },
21
+ check: {
22
+ type: "boolean",
23
+ description: "Fail when the file is out of date"
24
+ },
25
+ ...SOURCE_ARGS
26
+ };
27
+ const introspectCommand = defineCliCommand({
28
+ meta: {
29
+ name: "introspect",
30
+ description: "Saves the database schema as a snapshot for offline gen"
31
+ },
32
+ args: ARGS,
33
+ run: async (args, { config, env, io, signal }) => {
34
+ const format = args.format ?? "snapshot";
35
+ if (format !== "snapshot" && format !== "generator-metadata") return {
36
+ code: 2,
37
+ error: "--format must be \"snapshot\" or \"generator-metadata\""
38
+ };
39
+ const { snapshotPath: _ignored, ...source } = await sourceArgs(args, io);
40
+ const snapshot = await withSpinner(io.prompts, "Reading the schema", () => loadSnapshot(config, env, {
41
+ ...source,
42
+ live: true,
43
+ ...signal ? { signal } : {}
44
+ }));
45
+ const out = args.out ?? (format === "snapshot" ? "supabase/snapshot.json" : "supabase/generator-metadata.json");
46
+ const contents = format === "snapshot" ? serializeSnapshot(snapshot) : `${await serializeGenerator(restrictSchemas(snapshot.generator, config.schemas))}\n`;
47
+ if (args.check === true) {
48
+ const current = await readFile(resolve(config.root, out), "utf8").catch(() => void 0);
49
+ const file = display(config.root, out);
50
+ return current === contents ? {
51
+ code: 0,
52
+ output: `${file} is up to date.`,
53
+ data: {
54
+ file,
55
+ upToDate: true
56
+ }
57
+ } : {
58
+ code: 1,
59
+ output: fileDiff(file, current, contents, painter(io.color)),
60
+ error: `${file} is out of date. Run \`better-supabase introspect\`.`,
61
+ data: {
62
+ file,
63
+ upToDate: false
64
+ }
65
+ };
66
+ }
67
+ const wrote = await writeIfChanged(resolve(config.root, out), contents);
68
+ const tables = snapshot.extras.tables.length;
69
+ return {
70
+ code: 0,
71
+ output: `${wrote ? "Wrote" : "Unchanged"} ${display(config.root, out)} (${tables} tables).`,
72
+ data: {
73
+ file: display(config.root, out),
74
+ written: wrote,
75
+ tables
76
+ }
77
+ };
78
+ }
79
+ });
80
+ //#endregion
81
+ export { introspectCommand };
@@ -0,0 +1,24 @@
1
+ import { dirname, relative, resolve, sep } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { mkdir, readFile, writeFile } from "node:fs/promises";
4
+ //#region src/cli/io.ts
5
+ /** Writes a file when its contents changed. Returns whether it wrote. */
6
+ async function writeIfChanged(path, contents) {
7
+ if (existsSync(path)) {
8
+ if (await readFile(path, "utf8") === contents) return false;
9
+ }
10
+ await mkdir(dirname(path), { recursive: true });
11
+ await writeFile(path, contents);
12
+ return true;
13
+ }
14
+ /** Relative ESM import specifier from one file to another, keeping `.ts`. */
15
+ function importPath(fromFile, toFile) {
16
+ let path = relative(dirname(fromFile), toFile).split(sep).join("/");
17
+ if (!path.startsWith(".")) path = `./${path}`;
18
+ return path;
19
+ }
20
+ function display(root, path) {
21
+ return relative(root, resolve(root, path)).split(sep).join("/") || ".";
22
+ }
23
+ //#endregion
24
+ export { importPath as n, writeIfChanged as r, display as t };