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,37 @@
1
+ import { n as definePlugin } from "../../plugin-BqR6wKMB.js";
2
+ import { i as insertsOnly, l as withDefault, t as dbName } from "../../shared-_6-uPL7E.js";
3
+ //#region src/plugins/timestamps/index.ts
4
+ /**
5
+ * Stamps the columns generated as `Flags.timestamps`: `createdAt` on insert,
6
+ * `updatedAt` on insert and update. Values the caller passes win. Pair it with
7
+ * the `updated-at` SQL kit trigger to cover writes that bypass the app.
8
+ */
9
+ function timestamps() {
10
+ return definePlugin({
11
+ name: "timestamps",
12
+ beforeMutation(op, { table, now }) {
13
+ const flags = table.flags.timestamps;
14
+ if (!flags) return op;
15
+ const created = dbName(table, flags.createdAt);
16
+ const updated = dbName(table, flags.updatedAt);
17
+ const stamp = now().toISOString();
18
+ switch (op.kind) {
19
+ case "insert": {
20
+ const stampCreated = insertsOnly(op);
21
+ return {
22
+ ...op,
23
+ rows: op.rows.map((row) => withDefault(stampCreated ? withDefault(row, created, stamp) : row, updated, stamp))
24
+ };
25
+ }
26
+ case "update": return {
27
+ ...op,
28
+ set: withDefault(op.set, updated, stamp)
29
+ };
30
+ case "delete": return op;
31
+ default: return op;
32
+ }
33
+ }
34
+ });
35
+ }
36
+ //#endregion
37
+ export { timestamps };
@@ -0,0 +1,24 @@
1
+ import { f as Plugin } from "../../plugin-HvGqCurB.js";
2
+ import { t as StandardSchemaV1 } from "../../standard-CyhRzctb.js";
3
+ //#region src/plugins/validation/index.d.ts
4
+ export interface TableValidators {
5
+ /** Validates inserts and upserts, with app-cased keys. */
6
+ readonly insert?: StandardSchemaV1;
7
+ /** Validates update patches, with app-cased keys. */
8
+ readonly update?: StandardSchemaV1;
9
+ }
10
+ export interface ValidationOptions {
11
+ /**
12
+ * Validators per table key. The `validators` export of a generated
13
+ * `zod()` or `valibot()` module fits as is.
14
+ */
15
+ readonly schemas: Readonly<Record<string, TableValidators>>;
16
+ }
17
+ /**
18
+ * Validates writes with Standard Schema validators (zod, valibot, arktype,
19
+ * ...). Runs after other plugins (`enforce: 'post'`), so it sees the tenant,
20
+ * timestamp and actor columns they fill. Failures become `validation` errors
21
+ * (422) with issue paths; bulk writes prefix the row index.
22
+ */
23
+ export declare function validation(options: ValidationOptions): Plugin<"validation">;
24
+ //#endregion
@@ -0,0 +1,59 @@
1
+ import { n as dbError, t as DbException } from "../../errors-Bl3rkOzo.js";
2
+ import { n as definePlugin } from "../../plugin-BqR6wKMB.js";
3
+ import { t as validate } from "../../standard-FUi3Z3-7.js";
4
+ import { c as toDb, s as toApp } from "../../shared-_6-uPL7E.js";
5
+ //#region src/plugins/validation/index.ts
6
+ async function check(schema, table, row, label, index) {
7
+ const result = await validate(schema, toApp(table, row), label);
8
+ if (result.ok) {
9
+ const output = result.data;
10
+ return typeof output === "object" && output !== null ? toDb(table, output) : { ...row };
11
+ }
12
+ const error = result.error;
13
+ if (index === void 0 || error.kind !== "validation") throw new DbException(error);
14
+ const issues = error.issues.map((issue) => ({
15
+ message: issue.message,
16
+ path: [index, ...issue.path ?? []]
17
+ }));
18
+ throw new DbException(dbError("validation", error.message, { issues }));
19
+ }
20
+ /**
21
+ * Validates writes with Standard Schema validators (zod, valibot, arktype,
22
+ * ...). Runs after other plugins (`enforce: 'post'`), so it sees the tenant,
23
+ * timestamp and actor columns they fill. Failures become `validation` errors
24
+ * (422) with issue paths; bulk writes prefix the row index.
25
+ */
26
+ function validation(options) {
27
+ return definePlugin({
28
+ name: "validation",
29
+ enforce: "post",
30
+ async beforeMutation(op, { table }) {
31
+ const validators = options.schemas[table.key];
32
+ if (!validators) return op;
33
+ switch (op.kind) {
34
+ case "insert": {
35
+ const schema = validators.insert;
36
+ if (!schema) return op;
37
+ const many = op.rows.length > 1;
38
+ const rows = await Promise.all(op.rows.map((row, index) => check(schema, table, row, `${table.key} insert`, many ? index : void 0)));
39
+ return {
40
+ ...op,
41
+ rows
42
+ };
43
+ }
44
+ case "update": {
45
+ const schema = validators.update;
46
+ if (!schema) return op;
47
+ return {
48
+ ...op,
49
+ set: await check(schema, table, op.set, `${table.key} update`, void 0)
50
+ };
51
+ }
52
+ case "delete": return op;
53
+ default: return op;
54
+ }
55
+ }
56
+ });
57
+ }
58
+ //#endregion
59
+ export { validation };
@@ -0,0 +1,37 @@
1
+ import { t as SqlClient } from "./executor-Bje4suQF.js";
2
+ //#region src/postgres/pool.d.ts
3
+ interface PostgresOptions {
4
+ /** Defaults to `$SUPABASE_DB_URL`, then `$DATABASE_URL`. */
5
+ readonly connectionString?: string;
6
+ readonly max?: number;
7
+ /** Statement timeout in milliseconds, applied per transaction. */
8
+ readonly statementTimeout?: number;
9
+ }
10
+ /** JWT claims to run as. `role` must be `authenticated` or `anon`. */
11
+ interface SqlClaims {
12
+ readonly sub?: string;
13
+ readonly role?: string;
14
+ readonly [claim: string]: unknown;
15
+ }
16
+ interface Postgres {
17
+ /** Connects as the connection-string role. On Supabase that bypasses RLS. */
18
+ readonly admin: SqlClient;
19
+ /** Runs every query as the given user, with RLS, like PostgREST does. */
20
+ asUser(claims: SqlClaims): SqlClient;
21
+ /** Runs every query as `anon`. */
22
+ readonly anon: SqlClient;
23
+ /** Runs `fn` in one transaction. Pass claims to run it as a user. */
24
+ transaction<T>(fn: (client: SqlClient) => Promise<T>, options?: {
25
+ readonly claims?: SqlClaims;
26
+ }): Promise<T>;
27
+ end(): Promise<void>;
28
+ [Symbol.asyncDispose](): Promise<void>;
29
+ }
30
+ /**
31
+ * A pooled Postgres connection for jobs, scripts and tests. Each query (or
32
+ * transaction) runs in its own transaction; claims and role are
33
+ * transaction-local, so nothing leaks back into the pool.
34
+ */
35
+ declare function createPostgres(options?: PostgresOptions): Postgres;
36
+ //#endregion
37
+ export { createPostgres as i, PostgresOptions as n, SqlClaims as r, Postgres as t };
@@ -0,0 +1,28 @@
1
+ import { g as Operation } from "../executor-Bazhgsgq.js";
2
+ import { n as fromPgError, r as postgresExecutor, t as SqlClient } from "../executor-Bje4suQF.js";
3
+ import { i as createPostgres, n as PostgresOptions, r as SqlClaims, t as Postgres } from "../pool-BFeqzwQS.js";
4
+ import { t as Compiler } from "../compiler-DL18ukPe.js";
5
+ //#region src/compile/sql.d.ts
6
+ interface SqlQuery {
7
+ readonly text: string;
8
+ readonly params: readonly unknown[];
9
+ }
10
+ /**
11
+ * The SQL for one operation. `rows` returns a `row` json column per result
12
+ * row; `count` returns one `count` column. Either may be absent: a HEAD read
13
+ * only counts, a filter that can never match runs nothing.
14
+ */
15
+ interface SqlPlan {
16
+ readonly rows: SqlQuery | undefined;
17
+ readonly count: SqlQuery | undefined;
18
+ /** No row can match; the executor returns an empty result without a query. */
19
+ readonly never: boolean;
20
+ }
21
+ export declare function quoteIdent(name: string): string;
22
+ /** Compiles an operation to parameterized SQL. Rows come back as json. */
23
+ export declare function compileSql(op: Operation): SqlPlan;
24
+ //#endregion
25
+ //#region src/postgres/compiler.d.ts
26
+ export declare const sqlCompiler: Compiler<SqlPlan>;
27
+ //#endregion
28
+ export { type Postgres, type PostgresOptions, type SqlClaims, type SqlClient, type SqlPlan, type SqlQuery, createPostgres, fromPgError, postgresExecutor };
@@ -0,0 +1,79 @@
1
+ import { n as quoteIdent, t as compileSql } from "../sql-M8l3fnS0.js";
2
+ import { n as postgresExecutor, t as fromPgError } from "../executor-Ceq60hPM.js";
3
+ import { Pool } from "pg";
4
+ //#region src/postgres/pool.ts
5
+ const ROLES = /* @__PURE__ */ new Set(["authenticated", "anon"]);
6
+ function roleOf(claims) {
7
+ const role = claims.role ?? (claims.sub ? "authenticated" : "anon");
8
+ if (!ROLES.has(role)) throw new TypeError(`Cannot run as role "${role}": only "authenticated" and "anon" are assumed. Use postgres.admin for service work.`);
9
+ return role;
10
+ }
11
+ /**
12
+ * A pooled Postgres connection for jobs, scripts and tests. Each query (or
13
+ * transaction) runs in its own transaction; claims and role are
14
+ * transaction-local, so nothing leaks back into the pool.
15
+ */
16
+ function createPostgres(options = {}) {
17
+ const connectionString = options.connectionString ?? process.env["SUPABASE_DB_URL"] ?? process.env["DATABASE_URL"];
18
+ if (!connectionString) throw new TypeError("createPostgres needs a connectionString, $SUPABASE_DB_URL or $DATABASE_URL");
19
+ const pool = new Pool({
20
+ connectionString,
21
+ max: options.max ?? 10
22
+ });
23
+ async function begin(client, session) {
24
+ await client.query("begin");
25
+ if (options.statementTimeout !== void 0) await client.query(`set local statement_timeout = ${Math.trunc(options.statementTimeout)}`);
26
+ if (session) {
27
+ await client.query("select set_config('request.jwt.claims', $1, true)", [JSON.stringify({
28
+ ...session.claims,
29
+ role: session.role
30
+ })]);
31
+ await client.query(`set local role ${session.role}`);
32
+ }
33
+ }
34
+ async function transaction(session, fn) {
35
+ const client = await pool.connect();
36
+ try {
37
+ await begin(client, session);
38
+ const scoped = {
39
+ queryRaw: async (text, params) => (await client.query(text, params)).rows,
40
+ transaction: (inner) => inner(scoped)
41
+ };
42
+ const result = await fn(scoped);
43
+ await client.query("commit");
44
+ return result;
45
+ } catch (cause) {
46
+ await client.query("rollback").catch(() => void 0);
47
+ throw cause;
48
+ } finally {
49
+ client.release();
50
+ }
51
+ }
52
+ const clientFor = (session) => ({
53
+ queryRaw: (text, params) => transaction(session, (client) => client.queryRaw(text, params)),
54
+ transaction: (fn) => transaction(session, fn)
55
+ });
56
+ const sessionFor = (claims) => ({
57
+ claims,
58
+ role: roleOf(claims)
59
+ });
60
+ return {
61
+ admin: clientFor(void 0),
62
+ anon: clientFor({
63
+ claims: { role: "anon" },
64
+ role: "anon"
65
+ }),
66
+ asUser: (claims) => clientFor(sessionFor(claims)),
67
+ transaction: (fn, txOptions) => transaction(txOptions?.claims ? sessionFor(txOptions.claims) : void 0, fn),
68
+ end: () => pool.end(),
69
+ [Symbol.asyncDispose]: () => pool.end()
70
+ };
71
+ }
72
+ //#endregion
73
+ //#region src/postgres/compiler.ts
74
+ const sqlCompiler = {
75
+ target: "sql",
76
+ compile: compileSql
77
+ };
78
+ //#endregion
79
+ export { compileSql, createPostgres, fromPgError, postgresExecutor, quoteIdent, sqlCompiler };
@@ -0,0 +1,43 @@
1
+ import { g as Operation } from "./executor-Bazhgsgq.js";
2
+ //#region src/compile/postgrest.d.ts
3
+ /**
4
+ * A compiled PostgREST request, expressed as calls on the public
5
+ * postgrest-js builder so it works with any supabase-js client.
6
+ */
7
+ interface PostgrestPlan {
8
+ /** `select` parameter, or `undefined` for mutations without returning. */
9
+ readonly select: string | undefined;
10
+ readonly filters: readonly PlanFilter[];
11
+ readonly orders: readonly PlanOrder[];
12
+ readonly limits: readonly PlanLimit[];
13
+ readonly range: {
14
+ readonly from: number;
15
+ readonly to: number;
16
+ } | undefined;
17
+ /** The condition can never match; skip the request. */
18
+ readonly never: boolean;
19
+ }
20
+ type PlanFilter = {
21
+ readonly kind: "filter";
22
+ readonly path: string;
23
+ readonly operator: string;
24
+ readonly value: string;
25
+ } | {
26
+ readonly kind: "or";
27
+ readonly expression: string;
28
+ readonly referencedTable: string | undefined;
29
+ };
30
+ interface PlanOrder {
31
+ readonly column: string;
32
+ readonly ascending: boolean;
33
+ readonly nullsFirst: boolean | undefined;
34
+ readonly referencedTable: string | undefined;
35
+ }
36
+ interface PlanLimit {
37
+ readonly count: number;
38
+ readonly referencedTable: string | undefined;
39
+ }
40
+ /** Compiles an operation into a PostgREST plan. */
41
+ declare function compilePostgrest(op: Operation): PostgrestPlan;
42
+ //#endregion
43
+ export { compilePostgrest as a, PostgrestPlan as i, PlanLimit as n, PlanOrder as r, PlanFilter as t };
@@ -0,0 +1,97 @@
1
+ import { n as dbError } from "./errors-Bl3rkOzo.js";
2
+ //#region src/core/problem.ts
3
+ const PROBLEM_TYPE_BASE = "https://bettersupabase.com/problems/";
4
+ const PROBLEM_CONTENT_TYPE = "application/problem+json";
5
+ const TITLES = {
6
+ not_found: "Not found",
7
+ unauthorized: "Unauthorized",
8
+ forbidden: "Forbidden",
9
+ conflict: "Conflict",
10
+ foreign_key: "Referenced row missing or still referenced",
11
+ check: "Check constraint violated",
12
+ not_null: "Required value missing",
13
+ exclusion: "Exclusion constraint violated",
14
+ invalid_input: "Invalid input",
15
+ raised: "Request rejected",
16
+ timeout: "Timed out",
17
+ serialization: "Concurrent update, retry",
18
+ network: "Database unreachable",
19
+ aborted: "Aborted",
20
+ invalid_request: "Invalid request",
21
+ validation: "Validation failed",
22
+ multiple_rows: "More than one row matched",
23
+ stale: "Row changed since it was read",
24
+ rate_limited: "Too many requests",
25
+ unexpected: "Unexpected error"
26
+ };
27
+ /** Kinds whose message may contain internals (SQL, hostnames), hidden unless `expose` is set. */
28
+ const INTERNAL = /* @__PURE__ */ new Set([
29
+ "unexpected",
30
+ "network",
31
+ "invalid_request"
32
+ ]);
33
+ /** Converts a `DbError` to Problem Details. */
34
+ function toProblem(error, options = {}) {
35
+ const problem = {
36
+ type: `${PROBLEM_TYPE_BASE}${error.kind.replace(/_/g, "-")}`,
37
+ title: TITLES[error.kind],
38
+ status: error.status,
39
+ kind: error.kind
40
+ };
41
+ if (!INTERNAL.has(error.kind) || options.expose) problem["detail"] = error.message;
42
+ if (options.instance) problem["instance"] = options.instance;
43
+ if (error.code) problem["code"] = error.code;
44
+ if (error.hint) problem["hint"] = error.hint;
45
+ if ("constraint" in error && error.constraint) problem["constraint"] = error.constraint;
46
+ if ("columns" in error && error.columns) problem["columns"] = error.columns;
47
+ if ("column" in error && error.column) problem["column"] = error.column;
48
+ if ("issues" in error) problem["issues"] = error.issues;
49
+ if ("required" in error && error.required) problem["required"] = error.required;
50
+ if ("retryAfter" in error && error.retryAfter !== void 0) problem["retryAfter"] = error.retryAfter;
51
+ return problem;
52
+ }
53
+ /**
54
+ * An `application/problem+json` response. A 401 carries an RFC 6750
55
+ * `WWW-Authenticate: Bearer` challenge.
56
+ */
57
+ function problemResponse(error, options = {}) {
58
+ const headers = new Headers(options.headers);
59
+ headers.set("content-type", PROBLEM_CONTENT_TYPE);
60
+ if (error.status === 401 && !headers.has("www-authenticate")) {
61
+ const realm = (options.realm ?? "supabase").replace(/"/g, "");
62
+ const params = error.code === "MISSING_CREDENTIALS" ? "" : ", error=\"invalid_token\"";
63
+ headers.set("www-authenticate", `Bearer realm="${realm}"${params}`);
64
+ }
65
+ if ("retryAfter" in error && error.retryAfter !== void 0 && !headers.has("retry-after")) headers.set("retry-after", String(error.retryAfter));
66
+ return new Response(JSON.stringify(toProblem(error, options)), {
67
+ status: error.status,
68
+ headers
69
+ });
70
+ }
71
+ function isProblem(value) {
72
+ if (typeof value !== "object" || value === null) return false;
73
+ const problem = value;
74
+ return typeof problem["type"] === "string" && typeof problem["status"] === "number" && typeof problem["title"] === "string";
75
+ }
76
+ function isKind(value) {
77
+ return typeof value === "string" && value in TITLES;
78
+ }
79
+ /** Turns a Problem Details body back into a `DbError` (client side). */
80
+ function fromProblem(problem) {
81
+ const kind = isKind(problem.kind) ? problem.kind : "unexpected";
82
+ const extra = { status: problem.status };
83
+ for (const key of [
84
+ "code",
85
+ "hint",
86
+ "constraint",
87
+ "columns",
88
+ "column",
89
+ "required",
90
+ "retryAfter"
91
+ ]) if (problem[key] !== void 0) extra[key] = problem[key];
92
+ extra["issues"] = problem.issues ?? [];
93
+ if (kind !== "validation") delete extra["issues"];
94
+ return dbError(kind, problem.detail ?? problem.title, extra);
95
+ }
96
+ //#endregion
97
+ export { problemResponse as a, isProblem as i, PROBLEM_TYPE_BASE as n, toProblem as o, fromProblem as r, PROBLEM_CONTENT_TYPE as t };
@@ -0,0 +1,47 @@
1
+ import { b as ValidationIssue, m as DbErrorKind, p as DbError } from "./result-CTkCZ6JA.js";
2
+ //#region src/core/problem.d.ts
3
+ /** RFC 9457 Problem Details, with the `DbError` fields as extension members. */
4
+ interface ProblemDetails {
5
+ readonly type: string;
6
+ readonly title: string;
7
+ readonly status: number;
8
+ readonly detail?: string;
9
+ readonly instance?: string;
10
+ /** The `DbError` kind. */
11
+ readonly kind: DbErrorKind;
12
+ readonly code?: string;
13
+ readonly hint?: string;
14
+ readonly constraint?: string;
15
+ readonly columns?: readonly string[];
16
+ readonly column?: string;
17
+ readonly issues?: readonly ValidationIssue[];
18
+ /** The assurance level a `forbidden` answer needs (`aal2`). */
19
+ readonly required?: "aal1" | "aal2";
20
+ /** Seconds until a `rate_limited` caller may retry. */
21
+ readonly retryAfter?: number;
22
+ }
23
+ declare const PROBLEM_TYPE_BASE = "https://bettersupabase.com/problems/";
24
+ declare const PROBLEM_CONTENT_TYPE = "application/problem+json";
25
+ interface ProblemOptions {
26
+ /** URI reference for this occurrence, e.g. the request path. */
27
+ readonly instance?: string;
28
+ /** Include messages of internal errors. Defaults to false. */
29
+ readonly expose?: boolean;
30
+ }
31
+ interface ProblemResponseOptions extends ProblemOptions {
32
+ readonly headers?: HeadersInit;
33
+ /** `realm` of the RFC 6750 challenge on 401. */
34
+ readonly realm?: string;
35
+ }
36
+ /** Converts a `DbError` to Problem Details. */
37
+ declare function toProblem(error: DbError, options?: ProblemOptions): ProblemDetails;
38
+ /**
39
+ * An `application/problem+json` response. A 401 carries an RFC 6750
40
+ * `WWW-Authenticate: Bearer` challenge.
41
+ */
42
+ declare function problemResponse(error: DbError, options?: ProblemResponseOptions): Response;
43
+ declare function isProblem(value: unknown): value is ProblemDetails;
44
+ /** Turns a Problem Details body back into a `DbError` (client side). */
45
+ declare function fromProblem(problem: ProblemDetails): DbError;
46
+ //#endregion
47
+ export { ProblemResponseOptions as a, problemResponse as c, ProblemOptions as i, toProblem as l, PROBLEM_TYPE_BASE as n, fromProblem as o, ProblemDetails as r, isProblem as s, PROBLEM_CONTENT_TYPE as t };
@@ -0,0 +1,2 @@
1
+ import { a as QueryHelpers, c as TableQueries, d as queryCache, f as BetterQueryMeta, i as Queries, l as createQueries, n as InfiniteOptionsOf, o as QueryKeyOf, p as invalidateTables, r as MutationOptionsOf, s as QueryOptionsOf, t as CreateQueriesOptions, u as invalidateOnMutation } from "../index-Dup-nSe9.js";
2
+ export { type BetterQueryMeta, CreateQueriesOptions, InfiniteOptionsOf, MutationOptionsOf, Queries, QueryHelpers, QueryKeyOf, QueryOptionsOf, TableQueries, createQueries, invalidateOnMutation, invalidateTables, queryCache };
@@ -0,0 +1,3 @@
1
+ import { t as invalidateTables } from "../invalidate-DbxuL2jL.js";
2
+ import { n as invalidateOnMutation, r as queryCache, t as createQueries } from "../query-ChvDbsDE.js";
3
+ export { createQueries, invalidateOnMutation, invalidateTables, queryCache };
@@ -0,0 +1,186 @@
1
+ import { t as DbException } from "./errors-Bl3rkOzo.js";
2
+ import { t as invalidationTargets } from "./tables-rZxRxNPW.js";
3
+ import { t as invalidateTables } from "./invalidate-DbxuL2jL.js";
4
+ //#region src/query/index.ts
5
+ /** Query errors stay `DbException`s, even for an `sb` with `mapError()`. */
6
+ const asException = (error) => new DbException(error);
7
+ function isSkip(value) {
8
+ return typeof value === "symbol";
9
+ }
10
+ function withoutSignal(value) {
11
+ if (typeof value !== "object" || value === null) return value;
12
+ const { signal: _signal, ...rest } = value;
13
+ return rest;
14
+ }
15
+ function specQuery(runtime, spec, key) {
16
+ const stale = runtime.staleTime === void 0 ? {} : { staleTime: runtime.staleTime };
17
+ if (isSkip(spec)) return {
18
+ queryKey: key ?? ["bs", "$skip"],
19
+ queryFn: spec,
20
+ meta: { bsTables: [] },
21
+ ...stale
22
+ };
23
+ return {
24
+ queryKey: key ?? [
25
+ "bs",
26
+ spec.table,
27
+ spec.method,
28
+ ...spec.args
29
+ ],
30
+ queryFn: ({ signal }) => runtime.db().$run(spec, { signal }).orThrow(asException),
31
+ meta: { bsTables: runtime.sb.tablesOf(spec) },
32
+ ...stale
33
+ };
34
+ }
35
+ function tableQueries(runtime, table) {
36
+ const key = ["bs", table];
37
+ const specs = runtime.sb.spec[table];
38
+ const stale = runtime.staleTime === void 0 ? {} : { staleTime: runtime.staleTime };
39
+ const read = (method) => (args) => isSkip(args) ? specQuery(runtime, args, [
40
+ ...key,
41
+ method,
42
+ "$skip"
43
+ ]) : specQuery(runtime, specs[method](withoutSignal(args)));
44
+ const repo = () => {
45
+ const found = runtime.db()[table];
46
+ if (!found) throw new TypeError(`better-supabase: unknown table "${table}"`);
47
+ return found;
48
+ };
49
+ const call = (method, ...args) => {
50
+ const fn = repo()[method];
51
+ if (!fn) throw new TypeError(`better-supabase: ${table}.${method} is not available`);
52
+ return fn(...args);
53
+ };
54
+ const mutation = (op, run, tables) => ({
55
+ mutationKey: [...key, op],
56
+ mutationFn: (variables) => run(variables).orThrow(asException),
57
+ onSuccess: (_data, _variables, _result, context) => invalidateTables(context.client, tables)
58
+ });
59
+ return {
60
+ key,
61
+ findMany: read("findMany"),
62
+ findFirst: read("findFirst"),
63
+ findUnique: read("findUnique"),
64
+ count: read("count"),
65
+ aggregate: read("aggregate"),
66
+ exists: read("exists"),
67
+ paginate: read("paginate"),
68
+ findById: (id, args) => isSkip(id) ? specQuery(runtime, id, [
69
+ ...key,
70
+ "findById",
71
+ "$skip"
72
+ ]) : specQuery(runtime, specs["findById"](id, withoutSignal(args))),
73
+ infinite: (args) => {
74
+ const base = withoutSignal(args);
75
+ return {
76
+ queryKey: [
77
+ ...key,
78
+ "infinite",
79
+ base
80
+ ],
81
+ queryFn: ({ signal, pageParam }) => runtime.db().$run(specs["paginate"]({
82
+ ...base,
83
+ after: pageParam
84
+ }), { signal }).orThrow(asException),
85
+ initialPageParam: null,
86
+ getNextPageParam: (last) => last.nextCursor ?? void 0,
87
+ meta: { bsTables: runtime.sb.tablesOf(specs["paginate"]({
88
+ ...base,
89
+ after: null
90
+ })) },
91
+ ...stale
92
+ };
93
+ },
94
+ infinitePages: (args) => {
95
+ const base = withoutSignal(args);
96
+ return {
97
+ queryKey: [
98
+ ...key,
99
+ "infinitePages",
100
+ base
101
+ ],
102
+ queryFn: ({ signal, pageParam }) => runtime.db().$run(specs["paginate"]({
103
+ ...base,
104
+ page: pageParam
105
+ }), { signal }).orThrow(asException),
106
+ initialPageParam: base.page ?? 1,
107
+ getNextPageParam: (last) => last.page.hasMore ? last.page.number + 1 : void 0,
108
+ meta: { bsTables: runtime.sb.tablesOf(specs["paginate"](base)) },
109
+ ...stale
110
+ };
111
+ },
112
+ create: (args) => mutation("create", (data) => call("create", data, args), [table]),
113
+ update: (args) => mutation("update", (variables) => call("update", variables.id, variables.patch, args), [table]),
114
+ upsert: (args) => mutation("upsert", (data) => call("upsert", data, args), [table]),
115
+ delete: (args) => mutation("delete", (id) => call("delete", id, args), invalidationTargets(runtime.sb.meta, table))
116
+ };
117
+ }
118
+ /**
119
+ * TanStack Query option factories for every table, framework-neutral: pass
120
+ * them to `useQuery` (React), `createQuery` (Svelte, Solid), `useQuery`
121
+ * (Vue) or `injectQuery` (Angular). Payload types flow into the cache, errors
122
+ * are `DbException`s, and mutations invalidate every query that read a
123
+ * changed table.
124
+ *
125
+ * ```ts
126
+ * const q = createQueries(sb, () => browser.db);
127
+ * const { data } = useQuery(q.customers.findMany({ select: ['id', 'name'] }));
128
+ * ```
129
+ */
130
+ function createQueries(sb, db, options = {}) {
131
+ const runtime = {
132
+ sb,
133
+ db: typeof db === "function" ? db : () => db,
134
+ staleTime: options.staleTime
135
+ };
136
+ const stale = options.staleTime === void 0 ? {} : { staleTime: options.staleTime };
137
+ const queries = {
138
+ key: ["bs"],
139
+ $spec: (spec) => specQuery(runtime, spec),
140
+ $prefetch: (client, spec) => client.prefetchQuery(specQuery(runtime, spec)),
141
+ $rpc: (name, args, rpcOptions = {}) => ({
142
+ queryKey: [
143
+ "bs",
144
+ "$rpc",
145
+ name,
146
+ isSkip(args) ? "$skip" : args ?? {}
147
+ ],
148
+ queryFn: isSkip(args) ? args : ({ signal }) => runtime.db().$rpc(name, args ?? {}, { signal }).orThrow(asException),
149
+ meta: { bsTables: rpcOptions.tables ?? [] },
150
+ ...stale
151
+ }),
152
+ $rpcMutation: (name) => ({
153
+ mutationKey: [
154
+ "bs",
155
+ "$rpc",
156
+ name
157
+ ],
158
+ mutationFn: (args) => runtime.db().$rpc(name, args ?? {}).orThrow(asException),
159
+ onSuccess: (_data, _variables, _result, context) => invalidateTables(context.client, (sb.options.rpc?.[name]?.invalidates ?? []).flatMap((table) => invalidationTargets(sb.meta, table)))
160
+ })
161
+ };
162
+ for (const table of Object.keys(sb.meta.tables)) {
163
+ let built;
164
+ Object.defineProperty(queries, table, {
165
+ enumerable: true,
166
+ get: () => {
167
+ built ??= tableQueries(runtime, table);
168
+ return built;
169
+ }
170
+ });
171
+ }
172
+ return queries;
173
+ }
174
+ /** Invalidates every query that read a table in the target. */
175
+ function queryCache(client) {
176
+ return {
177
+ name: "tanstack-query",
178
+ invalidate: (target) => invalidateTables(client, target.tables)
179
+ };
180
+ }
181
+ /** Invalidates affected queries for every mutation and declared RPC `sb` performs in this runtime. */
182
+ function invalidateOnMutation(sb, client) {
183
+ return sb.cache(queryCache(client));
184
+ }
185
+ //#endregion
186
+ export { invalidateOnMutation as n, queryCache as r, createQueries as t };
@@ -0,0 +1,7 @@
1
+ import { s as Impersonator } from "../mfa-TVia554c.js";
2
+ import { t as AuthSession } from "../view-B0ltUmTi.js";
3
+ import { r as LiveCountSeed } from "../live-6b9i72Ux.js";
4
+ import { n as hasEntitlement, t as EntitlementKey } from "../entitlements-BBg61DQZ.js";
5
+ import { n as SessionProviderProps, r as useSession, t as SessionProvider } from "../session-C3osL21k.js";
6
+ import { a as BrowserLike, c as LiveCountHookOptions, d as useAuth, f as useBroadcast, h as useSupabase, i as BroadcastOptions, l as LiveQueryHookOptions, m as useLiveQuery, n as BetterSupabaseProvider, o as ClaimsOf, p as useLiveCount, r as BetterSupabaseProviderProps, s as LiveCount, t as BetterHooks, u as createHooks } from "../index-Hf8PP9sP.js";
7
+ export { type AuthSession, BetterHooks, BetterSupabaseProvider, BetterSupabaseProviderProps, BroadcastOptions, BrowserLike, ClaimsOf, type EntitlementKey, type Impersonator, LiveCount, LiveCountHookOptions, type LiveCountSeed, LiveQueryHookOptions, SessionProvider, type SessionProviderProps, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ import { n as useSession, t as SessionProvider } from "../session-CD-3orO_.js";
3
+ import { t as hasEntitlement } from "../entitlements-BC_khwRy.js";
4
+ import { a as useLiveCount, i as useBroadcast, n as createHooks, o as useLiveQuery, r as useAuth, s as useSupabase, t as BetterSupabaseProvider } from "../react-CrOvrnIP.js";
5
+ export { BetterSupabaseProvider, SessionProvider, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };