better-supabase 0.0.0 → 0.2.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 (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +4 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/claims-CoTobNC-.js +25 -0
  6. package/dist/cli/bin.d.ts +1 -0
  7. package/dist/cli/bin.js +15 -0
  8. package/dist/cli/index.d.ts +226 -0
  9. package/dist/cli/index.js +3 -0
  10. package/dist/cli-DrSgDOQC.js +6646 -0
  11. package/dist/client/index.d.ts +2 -0
  12. package/dist/client/index.js +112 -0
  13. package/dist/compiler-CZ9nIuqy.d.ts +14 -0
  14. package/dist/config/index.d.ts +3 -0
  15. package/dist/config/index.js +3 -0
  16. package/dist/config-ClDXmVtY.js +277 -0
  17. package/dist/define-BXMYD9AY.js +1071 -0
  18. package/dist/define-e3zI4_4g.d.ts +679 -0
  19. package/dist/edge/index.d.ts +43 -0
  20. package/dist/edge/index.js +103 -0
  21. package/dist/entitlements-CTEBm7IB.js +20 -0
  22. package/dist/entitlements-Dng-ctuT.d.ts +42 -0
  23. package/dist/env/index.d.ts +2 -0
  24. package/dist/env/index.js +2 -0
  25. package/dist/env-BEToab0I.js +204 -0
  26. package/dist/errors-Bl3rkOzo.js +196 -0
  27. package/dist/events/index.d.ts +2 -0
  28. package/dist/events/index.js +2 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/events-DOt3nSO-.d.ts +84 -0
  31. package/dist/executor-BYXQ9AGn.d.ts +25 -0
  32. package/dist/executor-CGdNQ27g.d.ts +195 -0
  33. package/dist/executor-DeppvirK.js +89 -0
  34. package/dist/guards-BnzDvFrz.js +10 -0
  35. package/dist/hono/index.d.ts +45 -0
  36. package/dist/hono/index.js +70 -0
  37. package/dist/index-Bw1bwtC0.d.ts +144 -0
  38. package/dist/index-CBDufH1M.d.ts +120 -0
  39. package/dist/index-CG69s15v.d.ts +38 -0
  40. package/dist/index-Ciu5Xlml.d.ts +74 -0
  41. package/dist/index-D4v7sncu.d.ts +62 -0
  42. package/dist/index-DOsjFvMf.d.ts +618 -0
  43. package/dist/index-DbxFr7XG.d.ts +76 -0
  44. package/dist/index-DuP14VHo.d.ts +216 -0
  45. package/dist/index-Dy6kuF0P.d.ts +71 -0
  46. package/dist/index-K-3WM2Hp.d.ts +117 -0
  47. package/dist/index.d.ts +110 -0
  48. package/dist/index.js +43 -0
  49. package/dist/invalidate-DbxuL2jL.js +18 -0
  50. package/dist/jobs/index.d.ts +173 -0
  51. package/dist/jobs/index.js +478 -0
  52. package/dist/json-schema-BpYqkCKf.js +182 -0
  53. package/dist/lint/index.d.ts +75 -0
  54. package/dist/lint/index.js +167 -0
  55. package/dist/list/index.d.ts +2 -0
  56. package/dist/list/index.js +410 -0
  57. package/dist/live-D8rV1Ldw.js +168 -0
  58. package/dist/live-DJyZMD6w.d.ts +191 -0
  59. package/dist/logger-glM28NVN.js +21 -0
  60. package/dist/mcp/index.d.ts +142 -0
  61. package/dist/mcp/index.js +463 -0
  62. package/dist/mfa-CpLfJSde.js +541 -0
  63. package/dist/mfa-TVia554c.d.ts +44 -0
  64. package/dist/next/image/index.d.ts +34 -0
  65. package/dist/next/image/index.js +40 -0
  66. package/dist/next/index.d.ts +210 -0
  67. package/dist/next/index.js +427 -0
  68. package/dist/openapi/index.d.ts +2 -0
  69. package/dist/openapi/index.js +332 -0
  70. package/dist/orpc/index.d.ts +46 -0
  71. package/dist/orpc/index.js +50 -0
  72. package/dist/otel/index.d.ts +40 -0
  73. package/dist/otel/index.js +168 -0
  74. package/dist/path-Cv__-OTj.d.ts +17 -0
  75. package/dist/permdock-sql-CghlHPuH.js +117 -0
  76. package/dist/plugin-BqR6wKMB.js +23 -0
  77. package/dist/plugin-C4TR9J0Z.d.ts +118 -0
  78. package/dist/plugins/actor/index.d.ts +13 -0
  79. package/dist/plugins/actor/index.js +43 -0
  80. package/dist/plugins/rules/index.d.ts +89 -0
  81. package/dist/plugins/rules/index.js +167 -0
  82. package/dist/plugins/soft-delete/index.d.ts +38 -0
  83. package/dist/plugins/soft-delete/index.js +69 -0
  84. package/dist/plugins/tenant/index.d.ts +49 -0
  85. package/dist/plugins/tenant/index.js +75 -0
  86. package/dist/plugins/timestamps/index.d.ts +9 -0
  87. package/dist/plugins/timestamps/index.js +37 -0
  88. package/dist/plugins/validation/index.d.ts +24 -0
  89. package/dist/plugins/validation/index.js +59 -0
  90. package/dist/pool-R2m_DkXi.d.ts +37 -0
  91. package/dist/postgres/index.d.ts +28 -0
  92. package/dist/postgres/index.js +79 -0
  93. package/dist/postgrest-cAEQ_IQN.d.ts +43 -0
  94. package/dist/problem-BTkTtRV9.js +97 -0
  95. package/dist/problem-DlgB0lYl.d.ts +47 -0
  96. package/dist/query/index.d.ts +2 -0
  97. package/dist/query/index.js +3 -0
  98. package/dist/query-ChvDbsDE.js +186 -0
  99. package/dist/react/index.d.ts +7 -0
  100. package/dist/react/index.js +5 -0
  101. package/dist/react/server.d.ts +13 -0
  102. package/dist/react/server.js +25 -0
  103. package/dist/react/session.d.ts +2 -0
  104. package/dist/react/session.js +3 -0
  105. package/dist/react-C83BdBGt.js +242 -0
  106. package/dist/read-set-4lI3W_gZ.js +216 -0
  107. package/dist/realtime/index.d.ts +4 -0
  108. package/dist/realtime/index.js +268 -0
  109. package/dist/resolve-CjHILk2w.d.ts +170 -0
  110. package/dist/resource-BHEWmQJK.js +175 -0
  111. package/dist/resource-CgZa3YG_.d.ts +65 -0
  112. package/dist/respond-DMcbQwfN.js +383 -0
  113. package/dist/respond-Dne1ARSa.d.ts +169 -0
  114. package/dist/result-CTkCZ6JA.d.ts +198 -0
  115. package/dist/result-DWXatkd6.js +103 -0
  116. package/dist/scope-CDHcS7dE.js +90 -0
  117. package/dist/seed-CzecODcJ.js +101 -0
  118. package/dist/server/index.d.ts +42 -0
  119. package/dist/server/index.js +68 -0
  120. package/dist/session-C3LxnQ0e.d.ts +22 -0
  121. package/dist/session-CqEwzVup.js +20 -0
  122. package/dist/shared-_6-uPL7E.js +57 -0
  123. package/dist/simplify-bwJpLnaS.js +665 -0
  124. package/dist/spec-pins-C3oc-Tdp.js +20 -0
  125. package/dist/spec-pins-CqorWDnO.d.ts +20 -0
  126. package/dist/sql-DTjA1LPj.js +260 -0
  127. package/dist/ssr/index.d.ts +5 -0
  128. package/dist/ssr/index.js +4 -0
  129. package/dist/standard-CyhRzctb.d.ts +7 -0
  130. package/dist/standard-FUi3Z3-7.js +21 -0
  131. package/dist/stats-C5W-rBfR.js +566 -0
  132. package/dist/storage/index.d.ts +5 -0
  133. package/dist/storage/index.js +2 -0
  134. package/dist/storage-DuGn38fr.js +527 -0
  135. package/dist/tables-rZxRxNPW.js +59 -0
  136. package/dist/template-mbvlysOo.d.ts +6 -0
  137. package/dist/testing/index.d.ts +325 -0
  138. package/dist/testing/index.js +819 -0
  139. package/dist/types-6cYK9pbJ.js +29 -0
  140. package/dist/types-C83v5SQz.d.ts +248 -0
  141. package/dist/version-Cqq1m3UY.js +4 -0
  142. package/dist/view-D1s0wa0T.d.ts +49 -0
  143. package/dist/view-aOYizTeE.js +36 -0
  144. package/dist/webhooks/index.d.ts +152 -0
  145. package/dist/webhooks/index.js +2 -0
  146. package/dist/webhooks-DMipjhSP.js +139 -0
  147. package/package.json +250 -17
  148. package/schemas/config-v1.json +373 -0
  149. package/schemas/doctor-report-v1.json +106 -0
  150. package/schemas/snapshot-v2.json +1006 -0
  151. package/skills/better-supabase/SKILL.md +84 -0
  152. package/skills/better-supabase/references/plugins.md +65 -0
  153. package/skills/better-supabase/references/troubleshooting.md +39 -0
  154. package/skills/better-supabase-api/SKILL.md +67 -0
  155. package/skills/better-supabase-api/references/adapters.md +108 -0
  156. package/skills/better-supabase-testing/SKILL.md +66 -0
  157. package/index.js +0 -1
@@ -0,0 +1,69 @@
1
+ import { t as and } from "../../types-6cYK9pbJ.js";
2
+ import { i as ok, t as AsyncResult } from "../../result-DWXatkd6.js";
3
+ import { n as definePlugin } from "../../plugin-BqR6wKMB.js";
4
+ import { n as scopeOperation } from "../../scope-CDHcS7dE.js";
5
+ import { a as isNotNull, o as isNull, t as dbName } from "../../shared-_6-uPL7E.js";
6
+ //#region src/plugins/soft-delete/index.ts
7
+ function deletedColumn(table) {
8
+ return dbName(table, table.flags.softDelete);
9
+ }
10
+ function scopeFor(table) {
11
+ const column = deletedColumn(table);
12
+ return column ? isNull(column) : void 0;
13
+ }
14
+ /**
15
+ * Soft delete for tables generated with `Flags.softDelete`:
16
+ *
17
+ * - reads, updates and deletes skip deleted rows, including inside includes
18
+ * and relation filters (`withDeleted` / `onlyDeleted` change the root table);
19
+ * - `delete` sets the column instead (`{ hard: true }` really deletes);
20
+ * - `restore(key)` clears it.
21
+ *
22
+ * Deletes become updates without `RETURNING`, so a SELECT policy that hides
23
+ * deleted rows does not make them fail.
24
+ */
25
+ function softDelete() {
26
+ return definePlugin({
27
+ name: "softDelete",
28
+ enforce: "pre",
29
+ transformQuery(op, { options }) {
30
+ const hardDelete = op.kind === "delete" && options["hard"] === true;
31
+ const withDeleted = options["withDeleted"] === true || hardDelete;
32
+ const onlyDeleted = options["onlyDeleted"] === true;
33
+ const scoped = scopeOperation(op, scopeFor, !withDeleted && !onlyDeleted);
34
+ const column = deletedColumn(op.table);
35
+ if (!onlyDeleted || !column || scoped.kind === "insert") return scoped;
36
+ return {
37
+ ...scoped,
38
+ where: and(scoped.where, isNotNull(column))
39
+ };
40
+ },
41
+ beforeMutation(op, { table, options, now }) {
42
+ if (op.kind !== "delete" || options["hard"] === true) return op;
43
+ const column = deletedColumn(table);
44
+ if (!column) return op;
45
+ return {
46
+ kind: "update",
47
+ table,
48
+ set: { [column]: now().toISOString() },
49
+ where: op.where,
50
+ returning: void 0
51
+ };
52
+ },
53
+ repository({ table, base }) {
54
+ const flag = table.flags.softDelete;
55
+ if (!flag) return void 0;
56
+ const update = base["update"];
57
+ return { restore: (key, args) => AsyncResult.from(async () => {
58
+ const result = await update(key, { [flag]: null }, {
59
+ ...args,
60
+ withDeleted: true,
61
+ returning: false
62
+ });
63
+ return result.ok ? ok(void 0) : result;
64
+ }) };
65
+ }
66
+ });
67
+ }
68
+ //#endregion
69
+ export { softDelete };
@@ -0,0 +1,49 @@
1
+ import { A as SchemaMeta } from "../../types-C83v5SQz.js";
2
+ import { f as Plugin, h as RequestContext, m as RepositoryExtension, o as HasFlag } from "../../plugin-C4TR9J0Z.js";
3
+ //#region src/plugins/tenant/index.d.ts
4
+ /**
5
+ * Dotted paths to the string claims of `C` (up to three levels), e.g.
6
+ * `'tenant_id' | 'app_metadata.tenant_id'`. Any string without a claims type.
7
+ */
8
+ export type ClaimPath<C> = unknown extends C ? string : StringPaths<C, [1, 2, 3]>;
9
+ type StringPaths<T, Depth extends readonly unknown[]> = T extends object ? T extends readonly unknown[] ? never : { [K in keyof T & string]-?: (NonNullable<T[K]> extends string ? K : never) | (Depth extends readonly [unknown, ...infer Rest] ? `${K}.${StringPaths<NonNullable<T[K]>, Rest>}` : never); }[keyof T & string] : never;
10
+ /** `C` is the claims type (`sb.claims(schema)`'s output); it types `claim`. */
11
+ export interface TenantOptions<C = unknown> {
12
+ /**
13
+ * JWT claim path(s) holding the tenant id when `context.tenant` is not set.
14
+ * Dots walk into objects. Defaults to `config.claims.tenant` (`tenant_id`),
15
+ * then the same key in `app_metadata`. `user_metadata` is never a source:
16
+ * users can write it.
17
+ */
18
+ readonly claim?: ClaimPath<C> | readonly ClaimPath<C>[];
19
+ /** Custom resolution, e.g. from a header validated against memberships. */
20
+ readonly resolve?: (context: RequestContext) => string | undefined;
21
+ /**
22
+ * What happens on a tenant table without a tenant in the context:
23
+ * `error` (default) fails with `forbidden`; `skip` leaves it to RLS.
24
+ */
25
+ readonly onMissing?: "error" | "skip";
26
+ }
27
+ export interface TenantFindArgs {
28
+ /** Skip the tenant filter for this call (admin tooling, jobs). RLS still applies. */
29
+ readonly allTenants?: boolean;
30
+ }
31
+ export interface TenantExtension extends RepositoryExtension {
32
+ readonly findArgs: HasFlag<this["M"], this["T"], "tenant"> extends true ? TenantFindArgs : unknown;
33
+ readonly methods: unknown;
34
+ readonly deleteArgs: HasFlag<this["M"], this["T"], "tenant"> extends true ? TenantFindArgs : unknown;
35
+ }
36
+ /**
37
+ * The tenant for a request: `context.tenant`, then the configured claim.
38
+ * `schema` supplies the default claim (`config.claims.tenant`).
39
+ */
40
+ export declare function resolveTenant<C = unknown>(context: RequestContext, options?: TenantOptions<C>, schema?: Pick<SchemaMeta, "claims">): string | undefined;
41
+ /**
42
+ * Multi-tenancy for tables generated with `Flags.tenant`. Scopes reads,
43
+ * updates and deletes to the request's tenant (nested includes and relation
44
+ * filters too), fills the tenant column on insert and rejects writes that
45
+ * would move rows to another tenant. It complements RLS; it does not replace
46
+ * it.
47
+ */
48
+ export declare function tenant<C = unknown>(options?: TenantOptions<C>): Plugin<"tenant", TenantExtension>;
49
+ //#endregion
@@ -0,0 +1,75 @@
1
+ import { n as dbError, t as DbException } from "../../errors-Bl3rkOzo.js";
2
+ import { n as definePlugin } from "../../plugin-BqR6wKMB.js";
3
+ import { n as scopeOperation } from "../../scope-CDHcS7dE.js";
4
+ import { i as tenantClaimPaths, n as claimAt, r as claimsOf } from "../../claims-CoTobNC-.js";
5
+ import { n as equals, r as forbidden, t as dbName } from "../../shared-_6-uPL7E.js";
6
+ //#region src/plugins/tenant/index.ts
7
+ /**
8
+ * The tenant for a request: `context.tenant`, then the configured claim.
9
+ * `schema` supplies the default claim (`config.claims.tenant`).
10
+ */
11
+ function resolveTenant(context, options = {}, schema) {
12
+ if (options.resolve) return options.resolve(context);
13
+ if (typeof context.tenant === "string") return context.tenant;
14
+ const claim = options.claim;
15
+ const paths = typeof claim === "string" ? [claim] : claim ?? tenantClaimPaths(claimsOf(schema).tenant);
16
+ for (const path of paths) {
17
+ const value = claimAt(context.claims, path);
18
+ if (value !== void 0) return value;
19
+ }
20
+ }
21
+ function tenantColumn(table) {
22
+ return dbName(table, table.flags.tenant);
23
+ }
24
+ /**
25
+ * Multi-tenancy for tables generated with `Flags.tenant`. Scopes reads,
26
+ * updates and deletes to the request's tenant (nested includes and relation
27
+ * filters too), fills the tenant column on insert and rejects writes that
28
+ * would move rows to another tenant. It complements RLS; it does not replace
29
+ * it.
30
+ */
31
+ function tenant(options = {}) {
32
+ const onMissing = options.onMissing ?? "error";
33
+ const current = (table, context, schema) => {
34
+ const id = resolveTenant(context, options, schema);
35
+ if (id === void 0 && onMissing === "error" && tenantColumn(table)) throw new DbException(dbError("forbidden", `No tenant in the request context for "${table.key}"`));
36
+ return id;
37
+ };
38
+ return definePlugin({
39
+ name: "tenant",
40
+ transformQuery(op, { context, schema, options: call }) {
41
+ if (call["allTenants"] === true) return op;
42
+ const id = current(op.table, context, schema);
43
+ if (id === void 0) return op;
44
+ return scopeOperation(op, (table) => {
45
+ const column = tenantColumn(table);
46
+ return column ? equals(column, id) : void 0;
47
+ });
48
+ },
49
+ beforeMutation(op, { table, context, schema, options: call }) {
50
+ const column = tenantColumn(table);
51
+ if (!column || call["allTenants"] === true) return op;
52
+ const id = current(table, context, schema);
53
+ if (id === void 0) return op;
54
+ switch (op.kind) {
55
+ case "insert": return {
56
+ ...op,
57
+ rows: op.rows.map((row) => {
58
+ if (column in row && row[column] !== id) forbidden(`Cannot write a ${table.key} row for another tenant`);
59
+ return {
60
+ ...row,
61
+ [column]: id
62
+ };
63
+ })
64
+ };
65
+ case "update":
66
+ if (column in op.set && op.set[column] !== id) forbidden(`Cannot move a ${table.key} row to another tenant`);
67
+ return op;
68
+ case "delete": return op;
69
+ default: return op;
70
+ }
71
+ }
72
+ });
73
+ }
74
+ //#endregion
75
+ export { resolveTenant, tenant };
@@ -0,0 +1,9 @@
1
+ import { f as Plugin } from "../../plugin-C4TR9J0Z.js";
2
+ //#region src/plugins/timestamps/index.d.ts
3
+ /**
4
+ * Stamps the columns generated as `Flags.timestamps`: `createdAt` on insert,
5
+ * `updatedAt` on insert and update. Values the caller passes win. Pair it with
6
+ * the `updated-at` SQL kit trigger to cover writes that bypass the app.
7
+ */
8
+ export declare function timestamps(): Plugin<"timestamps">;
9
+ //#endregion
@@ -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-C4TR9J0Z.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-BYXQ9AGn.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-CGdNQ27g.js";
2
+ import { n as fromPgError, r as postgresExecutor, t as SqlClient } from "../executor-BYXQ9AGn.js";
3
+ import { i as createPostgres, n as PostgresOptions, r as SqlClaims, t as Postgres } from "../pool-R2m_DkXi.js";
4
+ import { t as Compiler } from "../compiler-CZ9nIuqy.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-DTjA1LPj.js";
2
+ import { n as postgresExecutor, t as fromPgError } from "../executor-DeppvirK.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-CGdNQ27g.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) 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) problem["required"] = error.required;
50
+ if ("retryAfter" in error) 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 && !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-K-3WM2Hp.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 };