better-supabase 0.0.0 → 0.1.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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +3 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +15 -0
  7. package/dist/cli/index.d.ts +226 -0
  8. package/dist/cli/index.js +3 -0
  9. package/dist/cli-CoByWYja.js +6472 -0
  10. package/dist/client/index.d.ts +2 -0
  11. package/dist/client/index.js +112 -0
  12. package/dist/compiler-DL18ukPe.d.ts +14 -0
  13. package/dist/config/index.d.ts +3 -0
  14. package/dist/config/index.js +3 -0
  15. package/dist/config-D-aR2ZoN.js +276 -0
  16. package/dist/define-CTfWw-SS.d.ts +662 -0
  17. package/dist/define-QGkq04oe.js +1068 -0
  18. package/dist/edge/index.d.ts +43 -0
  19. package/dist/edge/index.js +103 -0
  20. package/dist/entitlements-BBg61DQZ.d.ts +20 -0
  21. package/dist/entitlements-BC_khwRy.js +17 -0
  22. package/dist/env/index.d.ts +2 -0
  23. package/dist/env/index.js +2 -0
  24. package/dist/env-BEToab0I.js +204 -0
  25. package/dist/errors-Bl3rkOzo.js +196 -0
  26. package/dist/events/index.d.ts +2 -0
  27. package/dist/events/index.js +2 -0
  28. package/dist/events-2OJsVG_d.d.ts +84 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/executor-Bazhgsgq.d.ts +195 -0
  31. package/dist/executor-Bje4suQF.d.ts +25 -0
  32. package/dist/executor-Ceq60hPM.js +89 -0
  33. package/dist/hono/index.d.ts +45 -0
  34. package/dist/hono/index.js +70 -0
  35. package/dist/index-BD6wKthp.d.ts +597 -0
  36. package/dist/index-DQVvHzjW.d.ts +74 -0
  37. package/dist/index-DRid0qIv.d.ts +76 -0
  38. package/dist/index-Dup-nSe9.d.ts +117 -0
  39. package/dist/index-Dy6kuF0P.d.ts +71 -0
  40. package/dist/index-Hf8PP9sP.d.ts +116 -0
  41. package/dist/index-JXHBuS53.d.ts +144 -0
  42. package/dist/index-Nxo3TJcm.d.ts +208 -0
  43. package/dist/index-Sw43J8HG.d.ts +62 -0
  44. package/dist/index-cEeweTeM.d.ts +38 -0
  45. package/dist/index.d.ts +110 -0
  46. package/dist/index.js +42 -0
  47. package/dist/invalidate-DbxuL2jL.js +18 -0
  48. package/dist/jobs/index.d.ts +173 -0
  49. package/dist/jobs/index.js +477 -0
  50. package/dist/json-schema-BbwO2mZg.js +181 -0
  51. package/dist/lint/index.d.ts +75 -0
  52. package/dist/lint/index.js +167 -0
  53. package/dist/list/index.d.ts +2 -0
  54. package/dist/list/index.js +410 -0
  55. package/dist/live-6b9i72Ux.d.ts +180 -0
  56. package/dist/live-D8rV1Ldw.js +168 -0
  57. package/dist/mcp/index.d.ts +105 -0
  58. package/dist/mcp/index.js +330 -0
  59. package/dist/mfa-CpogP66z.js +521 -0
  60. package/dist/mfa-TVia554c.d.ts +44 -0
  61. package/dist/next/image/index.d.ts +34 -0
  62. package/dist/next/image/index.js +40 -0
  63. package/dist/next/index.d.ts +210 -0
  64. package/dist/next/index.js +426 -0
  65. package/dist/openapi/index.d.ts +2 -0
  66. package/dist/openapi/index.js +332 -0
  67. package/dist/orpc/index.d.ts +46 -0
  68. package/dist/orpc/index.js +50 -0
  69. package/dist/otel/index.d.ts +40 -0
  70. package/dist/otel/index.js +168 -0
  71. package/dist/path-Cv__-OTj.d.ts +17 -0
  72. package/dist/plugin-BqR6wKMB.js +23 -0
  73. package/dist/plugin-HvGqCurB.d.ts +118 -0
  74. package/dist/plugins/actor/index.d.ts +13 -0
  75. package/dist/plugins/actor/index.js +43 -0
  76. package/dist/plugins/rules/index.d.ts +88 -0
  77. package/dist/plugins/rules/index.js +174 -0
  78. package/dist/plugins/soft-delete/index.d.ts +38 -0
  79. package/dist/plugins/soft-delete/index.js +69 -0
  80. package/dist/plugins/tenant/index.d.ts +43 -0
  81. package/dist/plugins/tenant/index.js +73 -0
  82. package/dist/plugins/timestamps/index.d.ts +9 -0
  83. package/dist/plugins/timestamps/index.js +37 -0
  84. package/dist/plugins/validation/index.d.ts +24 -0
  85. package/dist/plugins/validation/index.js +59 -0
  86. package/dist/pool-BFeqzwQS.d.ts +37 -0
  87. package/dist/postgres/index.d.ts +28 -0
  88. package/dist/postgres/index.js +79 -0
  89. package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
  90. package/dist/problem-DPu6Fh-t.js +97 -0
  91. package/dist/problem-DlgB0lYl.d.ts +47 -0
  92. package/dist/query/index.d.ts +2 -0
  93. package/dist/query/index.js +3 -0
  94. package/dist/query-ChvDbsDE.js +186 -0
  95. package/dist/react/index.d.ts +7 -0
  96. package/dist/react/index.js +5 -0
  97. package/dist/react/server.d.ts +13 -0
  98. package/dist/react/server.js +25 -0
  99. package/dist/react/session.d.ts +2 -0
  100. package/dist/react/session.js +3 -0
  101. package/dist/react-CrOvrnIP.js +241 -0
  102. package/dist/read-set-DhX2d04c.js +216 -0
  103. package/dist/realtime/index.d.ts +3 -0
  104. package/dist/realtime/index.js +253 -0
  105. package/dist/resolve-DW8h9NPm.d.ts +155 -0
  106. package/dist/resource-CYiu6Ygi.js +175 -0
  107. package/dist/resource-SkPTRJDD.d.ts +65 -0
  108. package/dist/respond-4-dhoV7x.js +380 -0
  109. package/dist/respond-PoTpy5DT.d.ts +169 -0
  110. package/dist/result-CTkCZ6JA.d.ts +198 -0
  111. package/dist/result-DWXatkd6.js +103 -0
  112. package/dist/scope-CDHcS7dE.js +90 -0
  113. package/dist/seed-CzecODcJ.js +101 -0
  114. package/dist/server/index.d.ts +42 -0
  115. package/dist/server/index.js +68 -0
  116. package/dist/session-C3osL21k.d.ts +21 -0
  117. package/dist/session-CD-3orO_.js +19 -0
  118. package/dist/shared-_6-uPL7E.js +57 -0
  119. package/dist/simplify-oHiDKRVQ.js +665 -0
  120. package/dist/spec-pins-CJPVghHy.d.ts +20 -0
  121. package/dist/spec-pins-t6sPaHvl.js +20 -0
  122. package/dist/sql-M8l3fnS0.js +260 -0
  123. package/dist/ssr/index.d.ts +5 -0
  124. package/dist/ssr/index.js +4 -0
  125. package/dist/standard-CyhRzctb.d.ts +7 -0
  126. package/dist/standard-FUi3Z3-7.js +21 -0
  127. package/dist/stats-3iy8u3_P.js +566 -0
  128. package/dist/storage/index.d.ts +4 -0
  129. package/dist/storage/index.js +2 -0
  130. package/dist/storage-BWXVUXGk.js +461 -0
  131. package/dist/tables-rZxRxNPW.js +59 -0
  132. package/dist/template-BeOGnynM.js +93 -0
  133. package/dist/template-mbvlysOo.d.ts +6 -0
  134. package/dist/testing/index.d.ts +294 -0
  135. package/dist/testing/index.js +751 -0
  136. package/dist/types-6cYK9pbJ.js +29 -0
  137. package/dist/types-C2fslP1z.d.ts +195 -0
  138. package/dist/version-Cqq1m3UY.js +4 -0
  139. package/dist/view-B0ltUmTi.d.ts +43 -0
  140. package/dist/view-B6F9QbG-.js +35 -0
  141. package/dist/webhooks/index.d.ts +152 -0
  142. package/dist/webhooks/index.js +2 -0
  143. package/dist/webhooks-DbMTJJX2.js +139 -0
  144. package/package.json +246 -17
  145. package/schemas/config-v1.json +328 -0
  146. package/schemas/doctor-report-v1.json +105 -0
  147. package/schemas/snapshot-v2.json +1002 -0
  148. package/skills/better-supabase/SKILL.md +84 -0
  149. package/skills/better-supabase/references/plugins.md +56 -0
  150. package/skills/better-supabase/references/troubleshooting.md +39 -0
  151. package/skills/better-supabase-api/SKILL.md +67 -0
  152. package/skills/better-supabase-api/references/adapters.md +108 -0
  153. package/skills/better-supabase-testing/SKILL.md +65 -0
  154. package/index.js +0 -1
@@ -0,0 +1,43 @@
1
+ import { k as TableKey, n as AnyModels, t as AnyFunctions } from "../types-C2fslP1z.js";
2
+ import { t as BetterSupabase } from "../define-CTfWw-SS.js";
3
+ import { d as ServerContext, f as ServerOptions, l as BetterServer, n as GuardOptions } from "../respond-PoTpy5DT.js";
4
+ import { i as ResourceRouteOptions } from "../resource-SkPTRJDD.js";
5
+ //#region src/edge/index.d.ts
6
+ export interface CorsOptions {
7
+ /** Allowed origins, or `*`. Defaults to `*`. */
8
+ readonly origin?: string | readonly string[];
9
+ /** Defaults to the headers supabase-js sends. */
10
+ readonly headers?: readonly string[];
11
+ readonly methods?: readonly string[];
12
+ readonly maxAge?: number;
13
+ }
14
+ export interface EdgeOptions extends ServerOptions {
15
+ /** Answer preflights and add CORS headers. `true` uses Supabase's defaults. */
16
+ readonly cors?: boolean | CorsOptions;
17
+ /** Include error details in problem responses. Defaults to `NODE_ENV === 'development'`. */
18
+ readonly exposeErrors?: boolean;
19
+ }
20
+ export interface HandlerOptions extends GuardOptions {
21
+ /** Refresh an expired cookie session and send the new cookies. */
22
+ readonly refresh?: boolean;
23
+ }
24
+ export type EdgeHandler = (request: Request) => Promise<Response>;
25
+ export type ResourceMap<M extends AnyModels> = { readonly [T in TableKey<M>]?: ResourceRouteOptions<M, T> | true; };
26
+ export interface ResourcesOptions extends HandlerOptions {
27
+ /** Path before the resources, e.g. `/api` (Supabase: `/<function-name>`). */
28
+ readonly basePath?: string;
29
+ }
30
+ export interface BetterEdge<M extends AnyModels, F extends AnyFunctions, E> extends BetterServer<M, F, E> {
31
+ /**
32
+ * A fetch handler (`Deno.serve`, Workers `fetch`) running as the caller.
33
+ * `Result`s become JSON or Problem Details; `undefined` becomes 204.
34
+ */
35
+ handler(fn: (request: Request, ctx: ServerContext<M, F, E>) => unknown, options?: HandlerOptions): EdgeHandler;
36
+ /** REST resources matching `createOpenApi`, e.g. `Deno.serve(bs.resources({ customers: true }))`. */
37
+ resources(map: ResourceMap<M>, options?: ResourcesOptions): EdgeHandler;
38
+ }
39
+ export declare const SUPABASE_CORS_HEADERS: readonly string[];
40
+ /** Fetch-handler integration for Supabase Edge Functions, Deno, Bun and Workers. */
41
+ export declare function createEdge<M extends AnyModels, D, F extends AnyFunctions, E>(sb: BetterSupabase<M, D, F, E>, options?: EdgeOptions): BetterEdge<M, F, E>;
42
+ //#endregion
43
+ export type { GuardOptions, ResourceRouteOptions };
@@ -0,0 +1,103 @@
1
+ import { n as dbError } from "../errors-Bl3rkOzo.js";
2
+ import { a as problemResponse } from "../problem-DPu6Fh-t.js";
3
+ import { i as respond, n as guard, o as createServer, s as extendServer, t as defaultExpose } from "../respond-4-dhoV7x.js";
4
+ import { t as defineResource } from "../resource-CYiu6Ygi.js";
5
+ //#region src/edge/index.ts
6
+ const SUPABASE_CORS_HEADERS = [
7
+ "authorization",
8
+ "x-client-info",
9
+ "apikey",
10
+ "content-type"
11
+ ];
12
+ function corsHeaders(request, cors) {
13
+ const origin = request.headers.get("origin");
14
+ const allowed = cors.origin ?? "*";
15
+ let value;
16
+ if (allowed === "*") value = "*";
17
+ else if (origin && (typeof allowed === "string" ? [allowed] : allowed).includes(origin)) value = origin;
18
+ if (!value) return void 0;
19
+ return {
20
+ "access-control-allow-origin": value,
21
+ "access-control-allow-headers": (cors.headers ?? SUPABASE_CORS_HEADERS).join(", "),
22
+ "access-control-allow-methods": (cors.methods ?? [
23
+ "GET",
24
+ "POST",
25
+ "PATCH",
26
+ "DELETE",
27
+ "OPTIONS"
28
+ ]).join(", "),
29
+ ...cors.maxAge === void 0 ? {} : { "access-control-max-age": String(cors.maxAge) },
30
+ ...value === "*" ? {} : { vary: "origin" }
31
+ };
32
+ }
33
+ function withHeaders(response, headers) {
34
+ let target = response;
35
+ try {
36
+ for (const [name, value] of Object.entries(headers)) target.headers.set(name, value);
37
+ } catch {
38
+ target = new Response(response.body, response);
39
+ for (const [name, value] of Object.entries(headers)) target.headers.set(name, value);
40
+ }
41
+ return target;
42
+ }
43
+ /** Fetch-handler integration for Supabase Edge Functions, Deno, Bun and Workers. */
44
+ function createEdge(sb, options = {}) {
45
+ const server = createServer(sb, options);
46
+ const expose = options.exposeErrors ?? defaultExpose();
47
+ const cors = options.cors === true ? {} : options.cors === false ? void 0 : options.cors;
48
+ const serve = (run, handlerOptions) => {
49
+ return async (request) => {
50
+ const extra = cors ? corsHeaders(request, cors) : void 0;
51
+ if (cors && request.method === "OPTIONS") return new Response(null, {
52
+ status: 204,
53
+ headers: extra ?? {}
54
+ });
55
+ const ctx = await server.context(request, { refresh: handlerOptions.refresh ?? false });
56
+ const instance = new URL(request.url).pathname;
57
+ const denied = guard(ctx.auth, handlerOptions.allow, handlerOptions.aal);
58
+ let response;
59
+ try {
60
+ response = denied ? problemResponse(denied, {
61
+ instance,
62
+ expose
63
+ }) : await run(request, ctx);
64
+ } catch (cause) {
65
+ response = problemResponse(dbError("unexpected", expose && cause instanceof Error ? cause.message : "Internal server error"), {
66
+ instance,
67
+ expose
68
+ });
69
+ }
70
+ response = ctx.resolution.apply(response);
71
+ return extra ? withHeaders(response, extra) : response;
72
+ };
73
+ };
74
+ return extendServer(server, {
75
+ handler(fn, handlerOptions = {}) {
76
+ return serve((request, ctx) => respond(() => fn(request, ctx), {
77
+ instance: new URL(request.url).pathname,
78
+ expose
79
+ }), handlerOptions);
80
+ },
81
+ resources(map, resourcesOptions = {}) {
82
+ const base = (resourcesOptions.basePath ?? "").replace(/\/$/, "");
83
+ const handlers = /* @__PURE__ */ new Map();
84
+ for (const [table, resource] of Object.entries(map)) {
85
+ if (!resource) continue;
86
+ handlers.set(table, defineResource(sb, table, resource === true ? {} : resource));
87
+ }
88
+ return serve(async (request, ctx) => {
89
+ const { pathname } = new URL(request.url);
90
+ const instance = pathname;
91
+ const [table, id, extra] = (pathname.startsWith(`${base}/`) ? pathname.slice(base.length + 1) : void 0)?.split("/") ?? [];
92
+ const handler = table ? handlers.get(table) : void 0;
93
+ if (!handler || extra !== void 0 || id !== void 0 && !handler.keyParam) return problemResponse(dbError("not_found", `No route for ${pathname}`), { instance });
94
+ return handler.handle(request, ctx.db, id === void 0 || id === "" ? void 0 : decodeURIComponent(id), {
95
+ instance,
96
+ expose
97
+ });
98
+ }, resourcesOptions);
99
+ }
100
+ });
101
+ }
102
+ //#endregion
103
+ export { SUPABASE_CORS_HEADERS, createEdge };
@@ -0,0 +1,20 @@
1
+ import { t as AuthSession } from "./view-B0ltUmTi.js";
2
+ //#region src/auth/entitlements.d.ts
3
+ /**
4
+ * Entitlement keys of the `memberships[].entitlements` claim: the literal
5
+ * union when `sb.claims(schema)` declares one (`z.enum([...])`), otherwise
6
+ * `string`.
7
+ */
8
+ type EntitlementKey<C> = C extends {
9
+ readonly memberships?: readonly (infer M)[] | undefined;
10
+ } ? M extends {
11
+ readonly entitlements?: readonly (infer K)[] | undefined;
12
+ } ? K extends string ? K : string : string : string;
13
+ /**
14
+ * Whether the session's `memberships` claim grants `key` in `tenantId`. The
15
+ * claim is as fresh as the token: enforce it in RLS with
16
+ * `better_supabase.has_entitlement()`, and refresh the session after checkout.
17
+ */
18
+ declare function hasEntitlement<C>(session: AuthSession<C>, tenantId: string, key: EntitlementKey<C>): boolean;
19
+ //#endregion
20
+ export { hasEntitlement as n, EntitlementKey as t };
@@ -0,0 +1,17 @@
1
+ //#region src/auth/entitlements.ts
2
+ /**
3
+ * Whether the session's `memberships` claim grants `key` in `tenantId`. The
4
+ * claim is as fresh as the token: enforce it in RLS with
5
+ * `better_supabase.has_entitlement()`, and refresh the session after checkout.
6
+ */
7
+ function hasEntitlement(session, tenantId, key) {
8
+ if (session.kind !== "user") return false;
9
+ const memberships = session.claims.memberships;
10
+ if (!Array.isArray(memberships)) return false;
11
+ return memberships.some((entry) => {
12
+ if (entry?.tenant_id !== tenantId) return false;
13
+ return Array.isArray(entry.entitlements) && entry.entitlements.includes(key);
14
+ });
15
+ }
16
+ //#endregion
17
+ export { hasEntitlement as t };
@@ -0,0 +1,2 @@
1
+ import { a as EnvResult, c as PublicEnv, d as parseEnv, f as publicEnv, i as EnvOptions, l as envSchema, n as ENV_VARIABLES, o as EnvSource, p as toServerEnv, r as EnvIssue, s as EnvValidationError, t as BetterSupabaseEnv, u as loadEnv } from "../index-Dy6kuF0P.js";
2
+ export { BetterSupabaseEnv, ENV_VARIABLES, EnvIssue, EnvOptions, EnvResult, EnvSource, EnvValidationError, PublicEnv, envSchema, loadEnv, parseEnv, publicEnv, toServerEnv };
@@ -0,0 +1,2 @@
1
+ import { a as parseEnv, i as loadEnv, n as EnvValidationError, o as publicEnv, r as envSchema, s as toServerEnv, t as ENV_VARIABLES } from "../env-BEToab0I.js";
2
+ export { ENV_VARIABLES, EnvValidationError, envSchema, loadEnv, parseEnv, publicEnv, toServerEnv };
@@ -0,0 +1,204 @@
1
+ //#region src/env/index.ts
2
+ const PREFIXES = [
3
+ "",
4
+ "NEXT_PUBLIC_",
5
+ "VITE_",
6
+ "PUBLIC_",
7
+ "EXPO_PUBLIC_",
8
+ "NUXT_PUBLIC_"
9
+ ];
10
+ const ENV_VARIABLES = {
11
+ url: PREFIXES.map((prefix) => `${prefix}SUPABASE_URL`),
12
+ publishableKey: PREFIXES.flatMap((prefix) => [`${prefix}SUPABASE_PUBLISHABLE_KEY`, `${prefix}SUPABASE_PUBLISHABLE_DEFAULT_KEY`]),
13
+ secretKey: ["SUPABASE_SECRET_KEY"],
14
+ dbUrl: ["SUPABASE_DB_URL", "DATABASE_URL"],
15
+ jwksUrl: ["SUPABASE_JWKS_URL"],
16
+ readUrl: ["SUPABASE_READ_URL"]
17
+ };
18
+ const LOOPBACK = /^(localhost|127(?:\.\d{1,3}){3}|\[::1\])$/;
19
+ const HOSTED = /^([a-z0-9]{20})\.supabase\.co$/;
20
+ var EnvValidationError = class extends Error {
21
+ name = "EnvValidationError";
22
+ issues;
23
+ constructor(issues) {
24
+ super(`Invalid Supabase environment:\n${issues.map((issue) => ` - ${issue.variables[0] ?? "env"}: ${issue.message}`).join("\n")}`);
25
+ this.issues = issues;
26
+ }
27
+ };
28
+ function first(source, names) {
29
+ for (const name of names) {
30
+ const value = source[name]?.trim();
31
+ if (value) return value;
32
+ }
33
+ }
34
+ /** `SUPABASE_*_KEYS` JSON objects, as `@supabase/server` reads them. */
35
+ function keySet(source, name, kind, issues) {
36
+ const raw = source[name];
37
+ if (!raw) return void 0;
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(raw);
41
+ } catch {
42
+ parsed = void 0;
43
+ }
44
+ const entries = typeof parsed === "object" && parsed !== null && !Array.isArray(parsed) ? Object.entries(parsed) : [];
45
+ if (entries.length === 0 || entries.some(([, value]) => typeof value !== "string")) {
46
+ issues.push({
47
+ variables: [name],
48
+ message: "must be a JSON object of key names to keys"
49
+ });
50
+ return;
51
+ }
52
+ const keys = {};
53
+ for (const [key, value] of entries) if (checkKey(value, kind, [name], issues)) keys[key] = value;
54
+ return keys;
55
+ }
56
+ function preferred(keys) {
57
+ if (!keys) return void 0;
58
+ return keys["default"] ?? Object.values(keys)[0];
59
+ }
60
+ function checkUrl(value, variables, issues) {
61
+ let url;
62
+ try {
63
+ url = new URL(value);
64
+ } catch {
65
+ issues.push({
66
+ variables,
67
+ message: "is not a valid URL"
68
+ });
69
+ return;
70
+ }
71
+ if (url.protocol === "https:") return url;
72
+ if (url.protocol === "http:" && LOOPBACK.test(url.hostname)) return url;
73
+ issues.push({
74
+ variables,
75
+ message: "must use https (http is only allowed for localhost / 127.0.0.1 / [::1])"
76
+ });
77
+ }
78
+ function checkKey(value, kind, variables, issues) {
79
+ if (value.startsWith(`sb_${kind}_`)) return true;
80
+ if (value.startsWith("eyJ")) issues.push({
81
+ variables,
82
+ message: `is a legacy JWT key; use the new sb_${kind}_ key (Project Settings > API Keys)`
83
+ });
84
+ else if (value.startsWith(kind === "publishable" ? "sb_secret_" : "sb_publishable_")) issues.push({
85
+ variables,
86
+ message: kind === "publishable" ? "holds a secret key; never expose sb_secret_ keys as publishable" : "holds a publishable key, not a secret key"
87
+ });
88
+ else issues.push({
89
+ variables,
90
+ message: `must start with sb_${kind}_`
91
+ });
92
+ return false;
93
+ }
94
+ /**
95
+ * Reads and validates Supabase settings. Accepts the `SUPABASE_`,
96
+ * `NEXT_PUBLIC_`, `VITE_`, `PUBLIC_`, `EXPO_PUBLIC_` and `NUXT_PUBLIC_`
97
+ * spellings. Issues name variables, never values.
98
+ */
99
+ function parseEnv(source, options = {}) {
100
+ const issues = [];
101
+ const required = new Set(options.require ?? []);
102
+ const rawUrl = first(source, ENV_VARIABLES.url);
103
+ const url = rawUrl ? checkUrl(rawUrl, ENV_VARIABLES.url, issues) : void 0;
104
+ if (!rawUrl) issues.push({
105
+ variables: ENV_VARIABLES.url,
106
+ message: "is not set"
107
+ });
108
+ const publishableKeys = keySet(source, "SUPABASE_PUBLISHABLE_KEYS", "publishable", issues);
109
+ const singlePublishable = first(source, ENV_VARIABLES.publishableKey);
110
+ const publishableKey = singlePublishable ?? preferred(publishableKeys);
111
+ if (!publishableKey) issues.push({
112
+ variables: ENV_VARIABLES.publishableKey,
113
+ message: "is not set"
114
+ });
115
+ else if (singlePublishable) checkKey(publishableKey, "publishable", ENV_VARIABLES.publishableKey, issues);
116
+ const namedSecrets = keySet(source, "SUPABASE_SECRET_KEYS", "secret", issues);
117
+ const singleSecret = first(source, ENV_VARIABLES.secretKey);
118
+ const secretKey = singleSecret ?? preferred(namedSecrets);
119
+ if (singleSecret) checkKey(singleSecret, "secret", ENV_VARIABLES.secretKey, issues);
120
+ else if (!secretKey && required.has("secretKey")) issues.push({
121
+ variables: ENV_VARIABLES.secretKey,
122
+ message: "is not set"
123
+ });
124
+ const dbUrl = first(source, ENV_VARIABLES.dbUrl);
125
+ if (dbUrl && !/^postgres(ql)?:\/\//.test(dbUrl)) issues.push({
126
+ variables: ENV_VARIABLES.dbUrl,
127
+ message: "must be a postgres:// connection string"
128
+ });
129
+ else if (!dbUrl && required.has("dbUrl")) issues.push({
130
+ variables: ENV_VARIABLES.dbUrl,
131
+ message: "is not set"
132
+ });
133
+ const rawJwks = first(source, ENV_VARIABLES.jwksUrl);
134
+ const jwksOverride = rawJwks ? checkUrl(rawJwks, ENV_VARIABLES.jwksUrl, issues) : void 0;
135
+ const rawRead = first(source, ENV_VARIABLES.readUrl);
136
+ const readUrl = rawRead ? checkUrl(rawRead, ENV_VARIABLES.readUrl, issues) : void 0;
137
+ if (issues.length > 0 || !url || !publishableKey) return {
138
+ ok: false,
139
+ issues
140
+ };
141
+ const base = url.href.replace(/\/+$/, "");
142
+ const projectRef = HOSTED.exec(url.hostname)?.[1];
143
+ return {
144
+ ok: true,
145
+ env: {
146
+ url: base,
147
+ publishableKey,
148
+ jwksUrl: jwksOverride ?? new URL(`${base}/auth/v1/.well-known/jwks.json`),
149
+ ...secretKey ? {
150
+ secretKey,
151
+ secretKeys: {
152
+ ...namedSecrets,
153
+ default: secretKey
154
+ }
155
+ } : {},
156
+ ...dbUrl ? { dbUrl } : {},
157
+ ...projectRef ? { projectRef } : {},
158
+ ...readUrl ? { readUrl: readUrl.href.replace(/\/+$/, "") } : {}
159
+ }
160
+ };
161
+ }
162
+ function processEnv() {
163
+ return globalThis.process?.env ?? {};
164
+ }
165
+ /** Like `parseEnv`, but throws `EnvValidationError`. Reads `process.env` by default. */
166
+ function loadEnv(source = processEnv(), options = {}) {
167
+ const result = parseEnv(source, options);
168
+ if (!result.ok) throw new EnvValidationError(result.issues);
169
+ return result.env;
170
+ }
171
+ /** The env validator as a Standard Schema, for t3-env, framework config or any validator slot. */
172
+ function envSchema(options = {}) {
173
+ return { "~standard": {
174
+ version: 1,
175
+ vendor: "better-supabase",
176
+ validate(value) {
177
+ if (typeof value !== "object" || value === null) return { issues: [{ message: "Expected an environment object" }] };
178
+ const result = parseEnv(value, options);
179
+ if (result.ok) return { value: result.env };
180
+ return { issues: result.issues.map((issue) => ({
181
+ message: `${issue.variables[0] ?? "env"} ${issue.message}`,
182
+ path: issue.variables.slice(0, 1)
183
+ })) };
184
+ }
185
+ } };
186
+ }
187
+ /** The parts that are safe in client bundles. */
188
+ function publicEnv(env) {
189
+ return {
190
+ url: env.url,
191
+ publishableKey: env.publishableKey
192
+ };
193
+ }
194
+ /** Converts to `@supabase/server`'s `SupabaseEnv` (the `env` option of `withSupabase`). */
195
+ function toServerEnv(env) {
196
+ return {
197
+ url: env.url,
198
+ publishableKeys: { default: env.publishableKey },
199
+ secretKeys: env.secretKeys ?? (env.secretKey ? { default: env.secretKey } : {}),
200
+ jwks: env.jwksUrl
201
+ };
202
+ }
203
+ //#endregion
204
+ export { parseEnv as a, loadEnv as i, EnvValidationError as n, publicEnv as o, envSchema as r, toServerEnv as s, ENV_VARIABLES as t };
@@ -0,0 +1,196 @@
1
+ //#region src/core/errors.ts
2
+ const STATUS = {
3
+ not_found: 404,
4
+ unauthorized: 401,
5
+ forbidden: 403,
6
+ conflict: 409,
7
+ foreign_key: 409,
8
+ check: 422,
9
+ not_null: 422,
10
+ exclusion: 409,
11
+ invalid_input: 400,
12
+ raised: 400,
13
+ timeout: 504,
14
+ serialization: 409,
15
+ network: 503,
16
+ aborted: 499,
17
+ invalid_request: 400,
18
+ validation: 422,
19
+ multiple_rows: 409,
20
+ stale: 412,
21
+ rate_limited: 429,
22
+ unexpected: 500
23
+ };
24
+ function statusOf(kind) {
25
+ return STATUS[kind];
26
+ }
27
+ /** Creates a `DbError` with the default status for its kind. */
28
+ function dbError(kind, message, ...[extra]) {
29
+ const error = {
30
+ kind,
31
+ message,
32
+ status: STATUS[kind]
33
+ };
34
+ if (extra) {
35
+ for (const [key, value] of Object.entries(extra)) if (value !== void 0) error[key] = value;
36
+ }
37
+ return error;
38
+ }
39
+ function isDbError(value) {
40
+ return typeof value === "object" && value !== null && typeof value.kind === "string" && typeof value.message === "string" && typeof value.status === "number";
41
+ }
42
+ /**
43
+ * The `DbError` in a `DbError`, a `DbException`, or an error whose `cause`
44
+ * is a `DbError` (what `sb.mapError()` mappers should set).
45
+ */
46
+ function dbErrorOf(value) {
47
+ if (value instanceof DbException) return value.error;
48
+ if (value instanceof Error && isDbError(value.cause)) return value.cause;
49
+ return isDbError(value) ? value : void 0;
50
+ }
51
+ function constraintGuard(kind) {
52
+ return (error, constraint) => {
53
+ const found = dbErrorOf(error);
54
+ if (found?.kind !== kind) return false;
55
+ return constraint === void 0 || found.constraint === constraint;
56
+ };
57
+ }
58
+ /**
59
+ * A unique violation, optionally for one constraint. Accepts a `DbError` or a
60
+ * `DbException`. Pass the generated `UniqueConstraint` type to check names:
61
+ * `isConflict<UniqueConstraint>(error, 'customers_organization_id_kvk_key')`.
62
+ */
63
+ const isConflict = constraintGuard("conflict");
64
+ /** A CHECK violation, optionally for one constraint (`CheckConstraint`). */
65
+ const isCheck = constraintGuard("check");
66
+ /** A foreign key violation, optionally for one constraint (`ForeignKeyConstraint`). */
67
+ const isForeignKey = constraintGuard("foreign_key");
68
+ /** Thrown by `.orThrow()`. Carries the plain `DbError` as `error`. */
69
+ var DbException = class extends Error {
70
+ name = "DbException";
71
+ error;
72
+ constructor(error) {
73
+ super(error.message);
74
+ this.error = error;
75
+ }
76
+ get kind() {
77
+ return this.error.kind;
78
+ }
79
+ };
80
+ const CONSTRAINT_IN_MESSAGE = /constraint "([^"]+)"/;
81
+ const KEY_COLUMNS_IN_DETAILS = /^Key \(([^)]+)\)=/;
82
+ const COLUMN_IN_MESSAGE = /column "([^"]+)"/;
83
+ const RETRY_AFTER = /retry after (\d+)/i;
84
+ const PERMISSION_DENIED = /^permission denied for (table|view|sequence|function|schema) (\S+)/;
85
+ function constraintOf(raw) {
86
+ return raw.constraint ?? CONSTRAINT_IN_MESSAGE.exec(raw.message ?? "")?.[1];
87
+ }
88
+ function columnsOf(raw) {
89
+ return KEY_COLUMNS_IN_DETAILS.exec(raw.details ?? "")?.[1]?.split(",").map((column) => column.trim());
90
+ }
91
+ function optional(raw) {
92
+ const out = {};
93
+ if (raw.code) out.code = raw.code;
94
+ if (raw.details) out.details = raw.details;
95
+ if (raw.hint) out.hint = raw.hint;
96
+ return out;
97
+ }
98
+ /**
99
+ * Maps a PostgREST or Postgres error to a `DbError`. Custom mappers run
100
+ * first; the first one that returns an error wins.
101
+ */
102
+ function mapDbError(raw, mappers = []) {
103
+ const fallback = mapBuiltin(raw);
104
+ for (const mapper of mappers) {
105
+ const mapped = mapper(raw, fallback);
106
+ if (mapped) return mapped;
107
+ }
108
+ return fallback;
109
+ }
110
+ function mapBuiltin(raw) {
111
+ const message = raw.message ?? "Unknown database error";
112
+ const base = optional(raw);
113
+ const code = raw.code ?? "";
114
+ if (raw.name === "AbortError" || code === "20" || message.startsWith("AbortError:")) return dbError("aborted", message, base);
115
+ switch (code) {
116
+ case "23505": {
117
+ const extra = {};
118
+ const constraint = constraintOf(raw);
119
+ const columns = columnsOf(raw);
120
+ if (constraint) extra.constraint = constraint;
121
+ if (columns) extra.columns = columns;
122
+ return dbError("conflict", message, {
123
+ ...base,
124
+ ...extra
125
+ });
126
+ }
127
+ case "23503": {
128
+ const extra = {};
129
+ const constraint = constraintOf(raw);
130
+ const columns = columnsOf(raw);
131
+ if (constraint) extra.constraint = constraint;
132
+ if (columns) extra.columns = columns;
133
+ return dbError("foreign_key", message, {
134
+ ...base,
135
+ ...extra
136
+ });
137
+ }
138
+ case "23514": {
139
+ const constraint = constraintOf(raw);
140
+ return dbError("check", message, {
141
+ ...base,
142
+ ...constraint ? { constraint } : {}
143
+ });
144
+ }
145
+ case "23502": {
146
+ const column = raw.column ?? COLUMN_IN_MESSAGE.exec(message)?.[1];
147
+ return dbError("not_null", message, {
148
+ ...base,
149
+ ...column ? { column } : {}
150
+ });
151
+ }
152
+ case "23P01": {
153
+ const constraint = constraintOf(raw);
154
+ return dbError("exclusion", message, {
155
+ ...base,
156
+ ...constraint ? { constraint } : {}
157
+ });
158
+ }
159
+ case "42501": {
160
+ const object = PERMISSION_DENIED.exec(message);
161
+ const postgrestHint = base.hint?.startsWith("Grant the required");
162
+ if (!object || base.hint && !postgrestHint) return dbError("forbidden", message, base);
163
+ const advice = object[1] === "table" || object[1] === "view" ? `Supabase no longer grants new tables to the Data API roles: add ${object[2]} to \`expose\` and run \`better-supabase sql add grants\`. \`better-supabase doctor\` (BS106) lists every missing grant.` : `Supabase no longer grants new objects to the Data API roles automatically; grant ${object[1] === "function" ? "execute" : "usage"} on ${object[1]} ${object[2]} to the role that needs it.`;
164
+ return dbError("forbidden", message, {
165
+ ...base,
166
+ hint: postgrestHint ? `${base.hint} ${advice}` : advice
167
+ });
168
+ }
169
+ case "P0001": return dbError("raised", message, base);
170
+ case "57014": return dbError("timeout", message, base);
171
+ case "40001":
172
+ case "40P01": return dbError("serialization", message, base);
173
+ case "PGRST116": return /multiple|more than one/i.test(raw.details ?? message) ? dbError("multiple_rows", message, base) : dbError("not_found", message, base);
174
+ case "PGRST301":
175
+ case "PGRST302":
176
+ case "PGRST303": return dbError("unauthorized", message, base);
177
+ case "PGRST123": return dbError("invalid_request", message, {
178
+ ...base,
179
+ hint: base.hint ?? "PostgREST aggregates are off. Run `alter role authenticator set pgrst.db_aggregates_enabled = 'true'; notify pgrst, 'reload config';` (see BS210), or use better-supabase/postgres."
180
+ });
181
+ case "BS429":
182
+ case "PT429": {
183
+ const seconds = RETRY_AFTER.exec(raw.details ?? "")?.[1];
184
+ return dbError("rate_limited", message, {
185
+ ...base,
186
+ ...seconds ? { retryAfter: Number(seconds) } : {}
187
+ });
188
+ }
189
+ }
190
+ if (code.startsWith("22")) return dbError("invalid_input", message, base);
191
+ if (code.startsWith("PGRST1") || code.startsWith("PGRST2")) return dbError("invalid_request", message, base);
192
+ if (code.startsWith("08") || /fetch failed|network/i.test(message)) return dbError("network", message, base);
193
+ return dbError("unexpected", message, base);
194
+ }
195
+ //#endregion
196
+ export { isConflict as a, mapDbError as c, isCheck as i, statusOf as l, dbError as n, isDbError as o, dbErrorOf as r, isForeignKey as s, DbException as t };
@@ -0,0 +1,2 @@
1
+ import { a as HttpMode, c as RowEventData, d as httpSink, f as isCloudEvent, i as ForwardOptions, l as forwardMutations, m as toHttp, n as CloudEventOptions, o as HttpSinkOptions, p as toCloudEvents, r as EventSink, s as ROW_EVENT_TYPES, t as CloudEvent, u as fromHttp } from "../index-DQVvHzjW.js";
2
+ export { CloudEvent, CloudEventOptions, EventSink, ForwardOptions, HttpMode, HttpSinkOptions, ROW_EVENT_TYPES, RowEventData, forwardMutations, fromHttp, httpSink, isCloudEvent, toCloudEvents, toHttp };
@@ -0,0 +1,2 @@
1
+ import { a as isCloudEvent, i as httpSink, n as forwardMutations, o as toCloudEvents, r as fromHttp, s as toHttp, t as ROW_EVENT_TYPES } from "../events-BuzTgn2D.js";
2
+ export { ROW_EVENT_TYPES, forwardMutations, fromHttp, httpSink, isCloudEvent, toCloudEvents, toHttp };