better-supabase 0.2.0 → 0.3.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 (139) hide show
  1. package/README.md +34 -31
  2. package/dist/base64-CQeB1eSf.js +42 -0
  3. package/dist/client/index.d.ts +1 -1
  4. package/dist/client/index.js +4 -14
  5. package/dist/{compiler-CZ9nIuqy.d.ts → compiler-2zqGzxVZ.d.ts} +2 -2
  6. package/dist/config/index.d.ts +4 -3
  7. package/dist/config/index.js +5 -3
  8. package/dist/config-Be4GwsqI.js +129 -0
  9. package/dist/{index-DOsjFvMf.d.ts → config-C1CYcm6o.d.ts} +49 -49
  10. package/dist/{config-ClDXmVtY.js → config-DOsGYSiT.js} +14 -137
  11. package/dist/{define-BXMYD9AY.js → define-DF341GDj.js} +71 -51
  12. package/dist/{define-e3zI4_4g.d.ts → define-DUbydmJa.d.ts} +10 -9
  13. package/dist/edge/index.d.ts +4 -4
  14. package/dist/edge/index.js +4 -4
  15. package/dist/{entitlements-Dng-ctuT.d.ts → entitlements-DvJXoI02.d.ts} +1 -1
  16. package/dist/env/index.js +203 -1
  17. package/dist/events/index.d.ts +1 -1
  18. package/dist/events/index.js +139 -1
  19. package/dist/{events-DOt3nSO-.d.ts → events-Do8jXh4D.d.ts} +3 -3
  20. package/dist/{executor-BYXQ9AGn.d.ts → executor-B26lgbHD.d.ts} +2 -2
  21. package/dist/{executor-DeppvirK.js → executor-C57jx2pn.js} +7 -2
  22. package/dist/{executor-CGdNQ27g.d.ts → executor-DynmIWhq.d.ts} +2 -2
  23. package/dist/hono/index.d.ts +5 -5
  24. package/dist/hono/index.js +4 -4
  25. package/dist/{index-CBDufH1M.d.ts → index-BNsABq7P.d.ts} +8 -8
  26. package/dist/{index-Bw1bwtC0.d.ts → index-CADlNHK-.d.ts} +27 -14
  27. package/dist/{index-DuP14VHo.d.ts → index-CTTMS6_1.d.ts} +19 -8
  28. package/dist/index-Cf-D2jk1.d.ts +50 -0
  29. package/dist/{index-K-3WM2Hp.d.ts → index-CriFFJ_p.d.ts} +4 -4
  30. package/dist/{index-Ciu5Xlml.d.ts → index-GV77NpRP.d.ts} +5 -4
  31. package/dist/{index-D4v7sncu.d.ts → index-UJ-LUxtI.d.ts} +3 -3
  32. package/dist/{index-CG69s15v.d.ts → index-kpgopLNv.d.ts} +4 -3
  33. package/dist/{index-DbxFr7XG.d.ts → index-qriRM9cZ.d.ts} +7 -2
  34. package/dist/index.d.ts +12 -12
  35. package/dist/index.js +16 -7
  36. package/dist/jobs/index.d.ts +25 -24
  37. package/dist/jobs/index.js +16 -10
  38. package/dist/{json-schema-BpYqkCKf.js → json-schema--aOXAGfU.js} +7 -2
  39. package/dist/lint/index.js +1 -1
  40. package/dist/list/index.d.ts +2 -2
  41. package/dist/list/index.js +128 -93
  42. package/dist/{live-DJyZMD6w.d.ts → live-QE3UPbGm.d.ts} +16 -6
  43. package/dist/mcp/index.d.ts +24 -17
  44. package/dist/mcp/index.js +41 -12
  45. package/dist/{mfa-CpLfJSde.js → mfa-BxoR1RkN.js} +74 -19
  46. package/dist/{mfa-TVia554c.d.ts → mfa-x3V3vGh-.d.ts} +1 -1
  47. package/dist/next/index.d.ts +39 -26
  48. package/dist/next/index.js +28 -23
  49. package/dist/openapi/index.d.ts +1 -1
  50. package/dist/openapi/index.js +57 -34
  51. package/dist/orpc/index.d.ts +6 -6
  52. package/dist/orpc/index.js +3 -3
  53. package/dist/otel/index.d.ts +2 -2
  54. package/dist/otel/index.js +4 -2
  55. package/dist/{permdock-sql-CghlHPuH.js → permdock-sql-Bta_G0v-.js} +11 -6
  56. package/dist/permdock-sql-DImJULY-.d.ts +17 -0
  57. package/dist/{plugin-C4TR9J0Z.d.ts → plugin-DVRB8qNE.d.ts} +5 -4
  58. package/dist/plugins/actor/index.d.ts +1 -1
  59. package/dist/plugins/rules/index.d.ts +2 -2
  60. package/dist/plugins/rules/index.js +4 -4
  61. package/dist/plugins/soft-delete/index.d.ts +3 -3
  62. package/dist/plugins/soft-delete/index.js +2 -2
  63. package/dist/plugins/tenant/index.d.ts +2 -2
  64. package/dist/plugins/timestamps/index.d.ts +1 -1
  65. package/dist/plugins/timestamps/index.js +1 -1
  66. package/dist/plugins/validation/index.d.ts +2 -2
  67. package/dist/pool-B9qsLyFR.d.ts +71 -0
  68. package/dist/postgres/index.d.ts +10 -5
  69. package/dist/postgres/index.js +36 -16
  70. package/dist/{postgrest-cAEQ_IQN.d.ts → postgrest-DmBlevh-.d.ts} +1 -1
  71. package/dist/{problem-BTkTtRV9.js → problem-CGt1ADix.js} +10 -4
  72. package/dist/{problem-DlgB0lYl.d.ts → problem-Z2RTQjZD.d.ts} +6 -4
  73. package/dist/query/index.d.ts +2 -2
  74. package/dist/query/index.js +1 -1
  75. package/dist/{query-ChvDbsDE.js → query-BdKLTPlj.js} +2 -2
  76. package/dist/react/index.d.ts +7 -7
  77. package/dist/react/index.js +2 -2
  78. package/dist/react/server.d.ts +3 -3
  79. package/dist/react/server.js +1 -1
  80. package/dist/react/session.d.ts +21 -2
  81. package/dist/react/session.js +19 -1
  82. package/dist/{react-C83BdBGt.js → react-DNBYNazu.js} +20 -16
  83. package/dist/{read-set-4lI3W_gZ.js → read-set-D0qBcboM.js} +3 -3
  84. package/dist/realtime/index.d.ts +4 -3
  85. package/dist/realtime/index.js +7 -6
  86. package/dist/{resolve-CjHILk2w.d.ts → resolve-BRciX2jK.d.ts} +6 -5
  87. package/dist/{resource-CgZa3YG_.d.ts → resource-CwVgiI5q.d.ts} +16 -7
  88. package/dist/{resource-BHEWmQJK.js → resource-D30rp0GN.js} +38 -16
  89. package/dist/{respond-DMcbQwfN.js → respond-C44EZAJo.js} +35 -19
  90. package/dist/{respond-Dne1ARSa.d.ts → respond-DAkPIdTH.d.ts} +15 -9
  91. package/dist/{result-CTkCZ6JA.d.ts → result-c2OYgCV2.d.ts} +13 -8
  92. package/dist/server/index.d.ts +35 -10
  93. package/dist/server/index.js +38 -9
  94. package/dist/{simplify-bwJpLnaS.js → simplify-LakqK1vH.js} +10 -16
  95. package/dist/sql/index.d.ts +139 -0
  96. package/dist/sql/index.js +1983 -0
  97. package/dist/{sql-DTjA1LPj.js → sql-BIgk12ew.js} +72 -16
  98. package/dist/ssr/index.d.ts +4 -4
  99. package/dist/ssr/index.js +2 -2
  100. package/dist/{standard-CyhRzctb.d.ts → standard-eudBiRk1.d.ts} +1 -1
  101. package/dist/{stats-C5W-rBfR.js → stats-D78UQ8Yu.js} +8 -7
  102. package/dist/storage/index.d.ts +4 -3
  103. package/dist/storage/index.js +530 -1
  104. package/dist/{tables-rZxRxNPW.js → tables-Dhha3BXM.js} +5 -5
  105. package/dist/temporal-BwshQk0W.js +27 -0
  106. package/dist/temporal-required-CMxeSVPo.js +19 -0
  107. package/dist/testing/index.d.ts +13 -11
  108. package/dist/testing/index.js +128 -29
  109. package/dist/{types-C83v5SQz.d.ts → types-DONv9SHN.d.ts} +14 -11
  110. package/dist/{version-Cqq1m3UY.js → version-BfpG2gks.js} +1 -1
  111. package/dist/{view-D1s0wa0T.d.ts → view-3Po_PVlr.d.ts} +34 -4
  112. package/dist/{view-aOYizTeE.js → view-wZ-9qIUz.js} +11 -2
  113. package/dist/webhooks/index.d.ts +18 -16
  114. package/dist/webhooks/index.js +1 -1
  115. package/dist/{webhooks-DMipjhSP.js → webhooks-CAIFQSTM.js} +11 -9
  116. package/package.json +24 -28
  117. package/schemas/config-v1.json +31 -1
  118. package/schemas/doctor-report-v1.json +17 -1
  119. package/schemas/snapshot-v2.json +35 -0
  120. package/skills/better-supabase/SKILL.md +23 -11
  121. package/skills/better-supabase/references/plugins.md +21 -18
  122. package/skills/better-supabase/references/troubleshooting.md +15 -15
  123. package/skills/better-supabase-api/SKILL.md +7 -7
  124. package/skills/better-supabase-api/references/adapters.md +30 -25
  125. package/skills/better-supabase-testing/SKILL.md +11 -8
  126. package/bin/better-supabase.js +0 -4
  127. package/dist/casing-da0uRTqt.js +0 -14
  128. package/dist/cli/bin.d.ts +0 -1
  129. package/dist/cli/bin.js +0 -15
  130. package/dist/cli/index.d.ts +0 -226
  131. package/dist/cli/index.js +0 -3
  132. package/dist/cli-DrSgDOQC.js +0 -6646
  133. package/dist/env-BEToab0I.js +0 -204
  134. package/dist/events-BuzTgn2D.js +0 -135
  135. package/dist/pool-R2m_DkXi.d.ts +0 -37
  136. package/dist/seed-CzecODcJ.js +0 -101
  137. package/dist/session-C3LxnQ0e.d.ts +0 -22
  138. package/dist/session-CqEwzVup.js +0 -20
  139. package/dist/storage-DuGn38fr.js +0 -527
@@ -21,7 +21,7 @@ function parseSegment(segment) {
21
21
  });
22
22
  return parts;
23
23
  }
24
- const escapeRegex = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
24
+ const escapeRegex = (text) => text.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&");
25
25
  function parseTemplate(source, separator, validate) {
26
26
  const segments = source.split(separator).map(parseSegment);
27
27
  const params = [];
@@ -87,7 +87,7 @@ function sqlIdent(value) {
87
87
  }
88
88
  /** Postgres identifier-safe name part: `customer-logos` → `customer_logos`. */
89
89
  function slug(value) {
90
- return value.replace(/[^A-Za-z0-9]+/g, "_").replace(/^_|_$/g, "").toLowerCase();
90
+ return value.replaceAll(/[^A-Za-z0-9]+/g, "_").replaceAll(/^_|_$/g, "").toLowerCase();
91
91
  }
92
92
  //#endregion
93
93
  //#region src/core/permdock-sql.ts
@@ -101,17 +101,22 @@ const SPLIT_KEY = /#\d+$/;
101
101
  *
102
102
  * The helpers check role and scope, not a permission's row conditions, so
103
103
  * the condition is only correct for permissions whose grants have none
104
- * beyond the scope. `#n` keys are refused; other row-conditioned keys can't
105
- * be detected here.
104
+ * beyond the scope. `#n` keys are refused, and so are keys the catalog marks
105
+ * with `rowConditions: true` when one is passed.
106
106
  */
107
- function permdockCheck(where, target, key, id) {
107
+ function permdockCheck(where, target, key, id, catalog) {
108
108
  if (key === "") throw new TypeError(`${where}: a PermDock permission key is empty`);
109
109
  if (SPLIT_KEY.test(key)) throw new TypeError(`${where}: "${key}" is one part of a permission PermDock splits by row condition. Use PermDock's generated policies for it.`);
110
+ if (catalog?.permissions.some((permission) => permission.key === key && permission.rowConditions === true)) throw new TypeError(`${where}: "${key}" has row conditions in PermDock's catalog (rowConditions: true), which the SQL helpers don't check. Use PermDock's generated policies for it.`);
110
111
  const schema = sqlIdent(target.schema ?? "public");
111
112
  if (target.scope === "global") return `(select ${schema}.permdock_has(${sqlString(key)}))`;
112
113
  if (!SCOPE.test(target.scope)) throw new TypeError(`${where}: invalid PermDock scope "${target.scope}"`);
113
114
  if (id === void 0) throw new TypeError(`${where}: scope "${target.scope}" needs a segment`);
114
115
  return `${id} in (select t.id::text from ${schema}.${sqlIdent(`permitted_${target.scope}_ids`)}(${sqlString(key)}) as t(id))`;
115
116
  }
117
+ /** The keys a bucket or topic PermDock policy names. */
118
+ function permdockKeys(policy) {
119
+ return Object.values(policy.permdock).filter((key) => typeof key === "string");
120
+ }
116
121
  //#endregion
117
- export { sqlString as a, sqlIdent as i, parseTemplate as n, slug as r, permdockCheck as t };
122
+ export { sqlIdent as a, slug as i, permdockKeys as n, sqlString as o, parseTemplate as r, permdockCheck as t };
@@ -0,0 +1,17 @@
1
+ //#region src/core/permdock-sql.d.ts
2
+ /**
3
+ * The part of PermDock's `permissions.catalog.json` the Storage and Realtime
4
+ * builders read: `import catalog from './permissions.catalog.json'`.
5
+ */
6
+ interface PermdockCatalog {
7
+ readonly permissions: readonly {
8
+ readonly key: string;
9
+ readonly rowConditions?: boolean;
10
+ }[];
11
+ }
12
+ /** The keys a bucket or topic PermDock policy names. */
13
+ declare function permdockKeys(policy: {
14
+ readonly permdock: object;
15
+ }): string[];
16
+ //#endregion
17
+ export { permdockKeys as n, PermdockCatalog as t };
@@ -1,6 +1,7 @@
1
- import { A as SchemaMeta, P as TableMeta, n as AnyModels } from "./types-C83v5SQz.js";
2
- import { g as Operation, h as MutationOp, r as Executor } from "./executor-CGdNQ27g.js";
3
- import { v as ErrorMapper } from "./result-CTkCZ6JA.js";
1
+ /// <reference lib="esnext.temporal" />
2
+ import { A as SchemaMeta, P as TableMeta, n as AnyModels } from "./types-DONv9SHN.js";
3
+ import { g as Operation, h as MutationOp, r as Executor } from "./executor-DynmIWhq.js";
4
+ import { v as ErrorMapper } from "./result-c2OYgCV2.js";
4
5
  //#region src/core/plugin.d.ts
5
6
  /** Who is making the request. */
6
7
  interface Actor {
@@ -28,7 +29,7 @@ interface HookArgs {
28
29
  readonly schema: SchemaMeta;
29
30
  readonly context: RequestContext;
30
31
  readonly options: CallOptions;
31
- readonly now: () => Date;
32
+ readonly now: () => Temporal.Instant;
32
33
  }
33
34
  type MutationKind = "insert" | "upsert" | "update" | "delete";
34
35
  interface MutationEvent {
@@ -1,4 +1,4 @@
1
- import { f as Plugin, h as RequestContext } from "../../plugin-C4TR9J0Z.js";
1
+ import { f as Plugin, h as RequestContext } from "../../plugin-DVRB8qNE.js";
2
2
  //#region src/plugins/actor/index.d.ts
3
3
  export interface ActorOptions {
4
4
  /** Custom actor id resolution. Defaults to `context.actor.id` for users and services. */
@@ -1,5 +1,5 @@
1
- import { g as Operation } from "../../executor-CGdNQ27g.js";
2
- import { f as Plugin, m as RepositoryExtension } from "../../plugin-C4TR9J0Z.js";
1
+ import { g as Operation } from "../../executor-DynmIWhq.js";
2
+ import { f as Plugin, m as RepositoryExtension } from "../../plugin-DVRB8qNE.js";
3
3
  //#region src/plugins/rules/index.d.ts
4
4
  export type RuleLevel = "off" | "warn" | "error";
5
5
  /** A level, or a level with the rule's option: `['error', 500]`. */
@@ -55,7 +55,7 @@ function sensitiveColumns(selection, table) {
55
55
  return found;
56
56
  }
57
57
  function isBrowser() {
58
- return typeof globalThis.window !== "undefined" && typeof globalThis.document !== "undefined";
58
+ return globalThis.window !== void 0 && globalThis.document !== void 0;
59
59
  }
60
60
  const TEXT_TYPES = /* @__PURE__ */ new Set([
61
61
  "text",
@@ -68,7 +68,7 @@ const templatesBySchema = /* @__PURE__ */ new WeakMap();
68
68
  function bucketTemplates(schema) {
69
69
  let templates = templatesBySchema.get(schema);
70
70
  if (!templates) {
71
- templates = Object.values(schema.buckets ?? {}).filter((bucket) => bucket.path.replace(/\{[^}]*\}|\//g, "").length > 0).map((bucket) => new RegExp(`^${bucket.path.split(/\{[^}]*\}/).map((part) => part.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+")}$`));
71
+ templates = Object.values(schema.buckets ?? {}).filter((bucket) => bucket.path.replaceAll(/\{[^}]*\}|\//g, "").length > 0).map((bucket) => new RegExp(`^${bucket.path.split(/\{[^}]*\}/).map((part) => part.replaceAll(/[.*+?^${}()|[\]\\]/g, "\\$&")).join("[^/]+")}$`));
72
72
  templatesBySchema.set(schema, templates);
73
73
  }
74
74
  return templates;
@@ -101,13 +101,13 @@ function checks(rules) {
101
101
  return depth > max ? `includes nest ${depth} levels deep (maximum ${max})` : void 0;
102
102
  },
103
103
  requireTenantContext: (op, { context, schema }) => {
104
- if (!op.table.flags.tenant || context.actor?.kind === "service") return void 0;
104
+ if (!op.table.flags.tenant || context.actor?.kind === "service") return;
105
105
  const claim = optionOf(rules.requireTenantContext, claimsOf(schema).tenant);
106
106
  return context.tenant === void 0 && claimAt(context.claims, claim) === void 0 ? `"${op.table.key}" is tenant-scoped but the request has no tenant (context.tenant or claim "${claim}")` : void 0;
107
107
  },
108
108
  noAdminInBrowser: (_op, { context }) => context.actor?.kind === "service" && isBrowser() ? "a service-role connection is running in a browser" : void 0,
109
109
  noSensitiveSelect: (op, { options }) => {
110
- if (options["sensitive"] === true) return void 0;
110
+ if (options["sensitive"] === true) return;
111
111
  const selection = op.kind === "select" ? op.selection : op.returning;
112
112
  const columns = selection ? sensitiveColumns(selection, op.table) : [];
113
113
  return columns.length > 0 ? `reads sensitive column(s) ${columns.join(", ")}; pass { sensitive: true } to allow` : void 0;
@@ -1,6 +1,6 @@
1
- import { C as PrimaryKeyValue } from "../../types-C83v5SQz.js";
2
- import { t as AsyncResult } from "../../result-CTkCZ6JA.js";
3
- import { f as Plugin, m as RepositoryExtension, o as HasFlag } from "../../plugin-C4TR9J0Z.js";
1
+ import { C as PrimaryKeyValue } from "../../types-DONv9SHN.js";
2
+ import { t as AsyncResult } from "../../result-c2OYgCV2.js";
3
+ import { f as Plugin, m as RepositoryExtension, o as HasFlag } from "../../plugin-DVRB8qNE.js";
4
4
  //#region src/plugins/soft-delete/index.d.ts
5
5
  export interface SoftDeleteFindArgs {
6
6
  /** Include soft-deleted rows of the queried table. */
@@ -45,14 +45,14 @@ function softDelete() {
45
45
  return {
46
46
  kind: "update",
47
47
  table,
48
- set: { [column]: now().toISOString() },
48
+ set: { [column]: now().toString() },
49
49
  where: op.where,
50
50
  returning: void 0
51
51
  };
52
52
  },
53
53
  repository({ table, base }) {
54
54
  const flag = table.flags.softDelete;
55
- if (!flag) return void 0;
55
+ if (!flag) return;
56
56
  const update = base["update"];
57
57
  return { restore: (key, args) => AsyncResult.from(async () => {
58
58
  const result = await update(key, { [flag]: null }, {
@@ -1,5 +1,5 @@
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";
1
+ import { A as SchemaMeta } from "../../types-DONv9SHN.js";
2
+ import { f as Plugin, h as RequestContext, m as RepositoryExtension, o as HasFlag } from "../../plugin-DVRB8qNE.js";
3
3
  //#region src/plugins/tenant/index.d.ts
4
4
  /**
5
5
  * Dotted paths to the string claims of `C` (up to three levels), e.g.
@@ -1,4 +1,4 @@
1
- import { f as Plugin } from "../../plugin-C4TR9J0Z.js";
1
+ import { f as Plugin } from "../../plugin-DVRB8qNE.js";
2
2
  //#region src/plugins/timestamps/index.d.ts
3
3
  /**
4
4
  * Stamps the columns generated as `Flags.timestamps`: `createdAt` on insert,
@@ -14,7 +14,7 @@ function timestamps() {
14
14
  if (!flags) return op;
15
15
  const created = dbName(table, flags.createdAt);
16
16
  const updated = dbName(table, flags.updatedAt);
17
- const stamp = now().toISOString();
17
+ const stamp = now().toString();
18
18
  switch (op.kind) {
19
19
  case "insert": {
20
20
  const stampCreated = insertsOnly(op);
@@ -1,5 +1,5 @@
1
- import { f as Plugin } from "../../plugin-C4TR9J0Z.js";
2
- import { t as StandardSchemaV1 } from "../../standard-CyhRzctb.js";
1
+ import { f as Plugin } from "../../plugin-DVRB8qNE.js";
2
+ import { t as StandardSchemaV1 } from "../../standard-eudBiRk1.js";
3
3
  //#region src/plugins/validation/index.d.ts
4
4
  export interface TableValidators {
5
5
  /** Validates inserts and upserts, with app-cased keys. */
@@ -0,0 +1,71 @@
1
+ import { t as SqlClient } from "./executor-B26lgbHD.js";
2
+ //#region src/postgres/pool.d.ts
3
+ /** The part of a `pg` pool client `createPostgres` uses. */
4
+ interface PgPoolClient {
5
+ query(text: string, values?: unknown[]): Promise<{
6
+ rows: unknown[];
7
+ }>;
8
+ release(): void;
9
+ }
10
+ /** The part of a `pg.Pool` `createPostgres` uses. */
11
+ interface PgPool {
12
+ connect(): Promise<PgPoolClient>;
13
+ end(): Promise<void>;
14
+ }
15
+ /** Statement timeouts in milliseconds by the role a transaction runs as. */
16
+ interface PostgresTimeouts {
17
+ /** The connection-string role (`admin` and `transaction` without claims). Unset by default. */
18
+ readonly admin?: number;
19
+ /** Defaults to 8000, Supabase's setting for the role. */
20
+ readonly authenticated?: number;
21
+ /** Defaults to 3000, Supabase's setting for the role. */
22
+ readonly anon?: number;
23
+ }
24
+ interface PostgresOptions {
25
+ /** Defaults to `$SUPABASE_DB_URL`, then `$DATABASE_URL`. */
26
+ readonly connectionString?: string;
27
+ /** An existing pool to run on instead of opening one. `end()` ends it. */
28
+ readonly pool?: PgPool;
29
+ readonly max?: number;
30
+ /**
31
+ * Statement timeout in milliseconds, applied per transaction: one value
32
+ * for every role, or one per role. `set role` doesn't apply the role's own
33
+ * `statement_timeout`, so `authenticated` and `anon` get Supabase's 8 s
34
+ * and 3 s unless you set them.
35
+ */
36
+ readonly statementTimeout?: number | PostgresTimeouts;
37
+ /** Ends a session that stays idle inside a transaction this long, in milliseconds. */
38
+ readonly idleInTransactionTimeout?: number;
39
+ /** How long `connect` waits for a connection, in milliseconds. Defaults to 10000. */
40
+ readonly connectionTimeout?: number;
41
+ /** How long an unused connection stays open, in milliseconds. Defaults to 10000. */
42
+ readonly idleTimeout?: number;
43
+ }
44
+ /** JWT claims to run as. `role` must be `authenticated` or `anon`. */
45
+ interface SqlClaims {
46
+ readonly sub?: string;
47
+ readonly role?: string;
48
+ readonly [claim: string]: unknown;
49
+ }
50
+ interface Postgres {
51
+ /** Connects as the connection-string role. On Supabase that bypasses RLS. */
52
+ readonly admin: SqlClient;
53
+ /** Runs every query as the given user, with RLS, like PostgREST does. */
54
+ asUser(claims: SqlClaims): SqlClient;
55
+ /** Runs every query as `anon`. */
56
+ readonly anon: SqlClient;
57
+ /** Runs `fn` in one transaction. Pass claims to run it as a user. */
58
+ transaction<T>(fn: (client: SqlClient) => Promise<T>, options?: {
59
+ readonly claims?: SqlClaims;
60
+ }): Promise<T>;
61
+ end(): Promise<void>;
62
+ [Symbol.asyncDispose](): Promise<void>;
63
+ }
64
+ /**
65
+ * A pooled Postgres connection for jobs, scripts and tests. Each query (or
66
+ * transaction) runs in its own transaction; claims and role are
67
+ * transaction-local, so nothing leaks back into the pool.
68
+ */
69
+ declare function createPostgres(options?: PostgresOptions): Postgres;
70
+ //#endregion
71
+ export { PostgresTimeouts as a, PostgresOptions as i, PgPoolClient as n, SqlClaims as o, Postgres as r, createPostgres as s, PgPool as t };
@@ -1,7 +1,7 @@
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";
1
+ import { g as Operation } from "../executor-DynmIWhq.js";
2
+ import { n as fromPgError, r as postgresExecutor, t as SqlClient } from "../executor-B26lgbHD.js";
3
+ import { a as PostgresTimeouts, i as PostgresOptions, n as PgPoolClient, o as SqlClaims, r as Postgres, s as createPostgres, t as PgPool } from "../pool-B9qsLyFR.js";
4
+ import { t as Compiler } from "../compiler-2zqGzxVZ.js";
5
5
  //#region src/compile/sql.d.ts
6
6
  interface SqlQuery {
7
7
  readonly text: string;
@@ -17,6 +17,11 @@ interface SqlPlan {
17
17
  readonly count: SqlQuery | undefined;
18
18
  /** No row can match; the executor returns an empty result without a query. */
19
19
  readonly never: boolean;
20
+ /**
21
+ * The rest of an insert split to stay under the bind-parameter limit. They
22
+ * run after this plan, in the same transaction; rows and counts add up.
23
+ */
24
+ readonly chunks?: readonly SqlPlan[];
20
25
  }
21
26
  export declare function quoteIdent(name: string): string;
22
27
  /** Compiles an operation to parameterized SQL. Rows come back as json. */
@@ -25,4 +30,4 @@ export declare function compileSql(op: Operation): SqlPlan;
25
30
  //#region src/postgres/compiler.d.ts
26
31
  export declare const sqlCompiler: Compiler<SqlPlan>;
27
32
  //#endregion
28
- export { type Postgres, type PostgresOptions, type SqlClaims, type SqlClient, type SqlPlan, type SqlQuery, createPostgres, fromPgError, postgresExecutor };
33
+ export { type PgPool, type PgPoolClient, type Postgres, type PostgresOptions, type PostgresTimeouts, type SqlClaims, type SqlClient, type SqlPlan, type SqlQuery, createPostgres, fromPgError, postgresExecutor };
@@ -1,35 +1,55 @@
1
- import { n as quoteIdent, t as compileSql } from "../sql-DTjA1LPj.js";
2
- import { n as postgresExecutor, t as fromPgError } from "../executor-DeppvirK.js";
1
+ import { n as quoteIdent, t as compileSql } from "../sql-BIgk12ew.js";
2
+ import { n as postgresExecutor, t as fromPgError } from "../executor-C57jx2pn.js";
3
3
  import { Pool } from "pg";
4
4
  //#region src/postgres/pool.ts
5
+ const DEFAULT_TIMEOUTS = {
6
+ authenticated: 8e3,
7
+ anon: 3e3
8
+ };
5
9
  const ROLES = /* @__PURE__ */ new Set(["authenticated", "anon"]);
6
10
  function roleOf(claims) {
7
11
  const role = claims.role ?? (claims.sub ? "authenticated" : "anon");
8
12
  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
13
  return role;
10
14
  }
15
+ function openPool(options) {
16
+ const connectionString = options.connectionString ?? process.env["SUPABASE_DB_URL"] ?? process.env["DATABASE_URL"];
17
+ if (!connectionString) throw new TypeError("createPostgres needs a connectionString, $SUPABASE_DB_URL or $DATABASE_URL");
18
+ return new Pool({
19
+ connectionString,
20
+ max: options.max ?? 10,
21
+ connectionTimeoutMillis: options.connectionTimeout ?? 1e4,
22
+ idleTimeoutMillis: options.idleTimeout ?? 1e4
23
+ });
24
+ }
11
25
  /**
12
26
  * A pooled Postgres connection for jobs, scripts and tests. Each query (or
13
27
  * transaction) runs in its own transaction; claims and role are
14
28
  * transaction-local, so nothing leaks back into the pool.
15
29
  */
16
30
  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
- });
31
+ const pool = options.pool ?? openPool(options);
32
+ const timeouts = typeof options.statementTimeout === "number" ? {
33
+ admin: options.statementTimeout,
34
+ authenticated: options.statementTimeout,
35
+ anon: options.statementTimeout
36
+ } : {
37
+ ...DEFAULT_TIMEOUTS,
38
+ ...options.statementTimeout
39
+ };
40
+ /** Opens the transaction, then sets the timeouts, claims and role in one round trip. */
23
41
  async function begin(client, session) {
24
42
  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
- }
43
+ const settings = [];
44
+ const timeout = session?.role === "authenticated" ? timeouts.authenticated : session?.role === "anon" ? timeouts.anon : timeouts.admin;
45
+ if (timeout !== void 0) settings.push(["statement_timeout", String(Math.trunc(timeout))]);
46
+ if (options.idleInTransactionTimeout !== void 0) settings.push(["idle_in_transaction_session_timeout", String(Math.trunc(options.idleInTransactionTimeout))]);
47
+ if (session) settings.push(["request.jwt.claims", JSON.stringify({
48
+ ...session.claims,
49
+ role: session.role
50
+ })], ["role", session.role]);
51
+ if (settings.length === 0) return;
52
+ await client.query(`select ${settings.map((_, index) => `set_config($${index * 2 + 1}, $${index * 2 + 2}, true)`).join(", ")}`, settings.flat());
33
53
  }
34
54
  async function transaction(session, fn) {
35
55
  const client = await pool.connect();
@@ -1,4 +1,4 @@
1
- import { g as Operation } from "./executor-CGdNQ27g.js";
1
+ import { g as Operation } from "./executor-DynmIWhq.js";
2
2
  //#region src/compile/postgrest.d.ts
3
3
  /**
4
4
  * A compiled PostgREST request, expressed as calls on the public
@@ -33,7 +33,7 @@ const INTERNAL = /* @__PURE__ */ new Set([
33
33
  /** Converts a `DbError` to Problem Details. */
34
34
  function toProblem(error, options = {}) {
35
35
  const problem = {
36
- type: `${PROBLEM_TYPE_BASE}${error.kind.replace(/_/g, "-")}`,
36
+ type: `${PROBLEM_TYPE_BASE}${error.kind.replaceAll("_", "-")}`,
37
37
  title: TITLES[error.kind],
38
38
  status: error.status,
39
39
  kind: error.kind
@@ -47,21 +47,26 @@ function toProblem(error, options = {}) {
47
47
  if ("column" in error && error.column) problem["column"] = error.column;
48
48
  if ("issues" in error) problem["issues"] = error.issues;
49
49
  if ("required" in error) problem["required"] = error.required;
50
+ if ("scopes" in error) problem["scopes"] = error.scopes;
50
51
  if ("retryAfter" in error) problem["retryAfter"] = error.retryAfter;
51
52
  return problem;
52
53
  }
53
54
  /**
54
- * An `application/problem+json` response. A 401 carries an RFC 6750
55
- * `WWW-Authenticate: Bearer` challenge.
55
+ * An `application/problem+json` response. A 401, and a 403 for a missing
56
+ * OAuth scope, carry an RFC 6750 `WWW-Authenticate: Bearer` challenge.
56
57
  */
57
58
  function problemResponse(error, options = {}) {
58
59
  const headers = new Headers(options.headers);
59
60
  headers.set("content-type", PROBLEM_CONTENT_TYPE);
61
+ const realm = (options.realm ?? "supabase").replaceAll("\"", "");
60
62
  if (error.status === 401 && !headers.has("www-authenticate")) {
61
- const realm = (options.realm ?? "supabase").replace(/"/g, "");
62
63
  const params = error.code === "MISSING_CREDENTIALS" ? "" : ", error=\"invalid_token\"";
63
64
  headers.set("www-authenticate", `Bearer realm="${realm}"${params}`);
64
65
  }
66
+ if ("scopes" in error && error.code === "INSUFFICIENT_SCOPE" && !headers.has("www-authenticate")) {
67
+ const scope = error.scopes.join(" ").replaceAll("\"", "");
68
+ headers.set("www-authenticate", `Bearer realm="${realm}", error="insufficient_scope", scope="${scope}"`);
69
+ }
65
70
  if ("retryAfter" in error && !headers.has("retry-after")) headers.set("retry-after", String(error.retryAfter));
66
71
  return new Response(JSON.stringify(toProblem(error, options)), {
67
72
  status: error.status,
@@ -87,6 +92,7 @@ function fromProblem(problem) {
87
92
  "columns",
88
93
  "column",
89
94
  "required",
95
+ "scopes",
90
96
  "retryAfter"
91
97
  ]) if (problem[key] !== void 0) extra[key] = problem[key];
92
98
  extra["issues"] = problem.issues ?? [];
@@ -1,4 +1,4 @@
1
- import { b as ValidationIssue, m as DbErrorKind, p as DbError } from "./result-CTkCZ6JA.js";
1
+ import { b as ValidationIssue, m as DbErrorKind, p as DbError } from "./result-c2OYgCV2.js";
2
2
  //#region src/core/problem.d.ts
3
3
  /** RFC 9457 Problem Details, with the `DbError` fields as extension members. */
4
4
  interface ProblemDetails {
@@ -17,6 +17,8 @@ interface ProblemDetails {
17
17
  readonly issues?: readonly ValidationIssue[];
18
18
  /** The assurance level a `forbidden` answer needs (`aal2`). */
19
19
  readonly required?: "aal1" | "aal2";
20
+ /** The OAuth scopes a `forbidden` answer needs. */
21
+ readonly scopes?: readonly string[];
20
22
  /** Seconds until a `rate_limited` caller may retry. */
21
23
  readonly retryAfter?: number;
22
24
  }
@@ -29,15 +31,15 @@ interface ProblemOptions {
29
31
  readonly expose?: boolean;
30
32
  }
31
33
  interface ProblemResponseOptions extends ProblemOptions {
32
- readonly headers?: HeadersInit;
34
+ readonly headers?: ConstructorParameters<typeof Headers>[0];
33
35
  /** `realm` of the RFC 6750 challenge on 401. */
34
36
  readonly realm?: string;
35
37
  }
36
38
  /** Converts a `DbError` to Problem Details. */
37
39
  declare function toProblem(error: DbError, options?: ProblemOptions): ProblemDetails;
38
40
  /**
39
- * An `application/problem+json` response. A 401 carries an RFC 6750
40
- * `WWW-Authenticate: Bearer` challenge.
41
+ * An `application/problem+json` response. A 401, and a 403 for a missing
42
+ * OAuth scope, carry an RFC 6750 `WWW-Authenticate: Bearer` challenge.
41
43
  */
42
44
  declare function problemResponse(error: DbError, options?: ProblemResponseOptions): Response;
43
45
  declare function isProblem(value: unknown): value is ProblemDetails;
@@ -1,2 +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 };
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-CriFFJ_p.js";
2
+ export { type BetterQueryMeta, type CreateQueriesOptions, type InfiniteOptionsOf, type MutationOptionsOf, type Queries, type QueryHelpers, type QueryKeyOf, type QueryOptionsOf, type TableQueries, createQueries, invalidateOnMutation, invalidateTables, queryCache };
@@ -1,3 +1,3 @@
1
1
  import { t as invalidateTables } from "../invalidate-DbxuL2jL.js";
2
- import { n as invalidateOnMutation, r as queryCache, t as createQueries } from "../query-ChvDbsDE.js";
2
+ import { n as invalidateOnMutation, r as queryCache, t as createQueries } from "../query-BdKLTPlj.js";
3
3
  export { createQueries, invalidateOnMutation, invalidateTables, queryCache };
@@ -1,7 +1,7 @@
1
1
  import { t as DbException } from "./errors-Bl3rkOzo.js";
2
- import { t as invalidationTargets } from "./tables-rZxRxNPW.js";
2
+ import { t as invalidationTargets } from "./tables-Dhha3BXM.js";
3
3
  import { t as invalidateTables } from "./invalidate-DbxuL2jL.js";
4
- //#region src/query/index.ts
4
+ //#region src/query/queries.ts
5
5
  /** Query errors stay `DbException`s, even for an `sb` with `mapError()`. */
6
6
  const asException = (error) => new DbException(error);
7
7
  function isSkip(value) {
@@ -1,7 +1,7 @@
1
- import { s as Impersonator } from "../mfa-TVia554c.js";
2
- import { t as AuthSession } from "../view-D1s0wa0T.js";
3
- import { r as LiveCountSeed } from "../live-DJyZMD6w.js";
4
- import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-Dng-ctuT.js";
5
- import { n as SessionProviderProps, r as useSession, t as SessionProvider } from "../session-C3LxnQ0e.js";
6
- import { a as BrowserLike, c as LiveCountHookOptions, d as createHooks, f as useAuth, g as useSupabase, h as useLiveQuery, i as BroadcastOptions, l as LiveQueryHookOptions, m as useLiveCount, n as BetterSupabaseProvider, o as ClaimsOf, p as useBroadcast, r as BetterSupabaseProviderProps, s as LiveCount, t as BetterHooks, u as ProfileOf } from "../index-CBDufH1M.js";
7
- export { type AuthSession, BetterHooks, BetterSupabaseProvider, BetterSupabaseProviderProps, BroadcastOptions, BrowserLike, ClaimsOf, type EntitlementKey, type Impersonator, LiveCount, LiveCountHookOptions, type LiveCountSeed, LiveQueryHookOptions, type MembershipClaim, ProfileOf, SessionProvider, type SessionProviderProps, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };
1
+ import { s as Impersonator } from "../mfa-x3V3vGh-.js";
2
+ import { t as AuthSession } from "../view-3Po_PVlr.js";
3
+ import { r as LiveCountSeed } from "../live-QE3UPbGm.js";
4
+ import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-DvJXoI02.js";
5
+ import { a as BrowserLike, c as LiveCountHookOptions, d as createHooks, f as useAuth, g as useSupabase, h as useLiveQuery, i as BroadcastOptions, l as LiveQueryHookOptions, m as useLiveCount, n as BetterSupabaseProvider, o as ClaimsOf, p as useBroadcast, r as BetterSupabaseProviderProps, s as LiveCount, t as BetterHooks, u as ProfileOf } from "../index-BNsABq7P.js";
6
+ import { SessionProvider, SessionProviderProps, useSession } from "./session.js";
7
+ export { type AuthSession, type BetterHooks, BetterSupabaseProvider, type BetterSupabaseProviderProps, type BroadcastOptions, type BrowserLike, type ClaimsOf, type EntitlementKey, type Impersonator, type LiveCount, type LiveCountHookOptions, type LiveCountSeed, type LiveQueryHookOptions, type MembershipClaim, type ProfileOf, SessionProvider, type SessionProviderProps, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { n as useSession, t as SessionProvider } from "../session-CqEwzVup.js";
2
+ import { SessionProvider, useSession } from "./session.js";
3
+ import { a as useLiveCount, i as useBroadcast, n as createHooks, o as useLiveQuery, r as useAuth, s as useSupabase, t as BetterSupabaseProvider } from "../react-DNBYNazu.js";
3
4
  import { t as hasEntitlement } from "../entitlements-CTEBm7IB.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-C83BdBGt.js";
5
5
  export { BetterSupabaseProvider, SessionProvider, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };
@@ -1,6 +1,6 @@
1
- import { t as AuthSession } from "../view-D1s0wa0T.js";
2
- import { n as SessionProviderProps, t as SessionProvider } from "../session-C3LxnQ0e.js";
3
- import { a as BrowserLike, f as useAuth, g as useSupabase, h as useLiveQuery, i as BroadcastOptions, l as LiveQueryHookOptions, n as BetterSupabaseProvider, o as ClaimsOf, p as useBroadcast, r as BetterSupabaseProviderProps, t as BetterHooks, u as ProfileOf } from "../index-CBDufH1M.js";
1
+ import { t as AuthSession } from "../view-3Po_PVlr.js";
2
+ import { a as BrowserLike, f as useAuth, g as useSupabase, h as useLiveQuery, i as BroadcastOptions, l as LiveQueryHookOptions, n as BetterSupabaseProvider, o as ClaimsOf, p as useBroadcast, r as BetterSupabaseProviderProps, t as BetterHooks, u as ProfileOf } from "../index-BNsABq7P.js";
3
+ import { SessionProvider, SessionProviderProps } from "./session.js";
4
4
  //#region src/react/server.d.ts
5
5
  /** The `react-server` build of `useSession`: await `next.session()` instead. */
6
6
  export declare const useSession: <C = unknown, P = unknown>() => AuthSession<C, P>;
@@ -1,4 +1,4 @@
1
- import { i as useBroadcast, o as useLiveQuery, r as useAuth, s as useSupabase, t as BetterSupabaseProvider } from "../react-C83BdBGt.js";
1
+ import { i as useBroadcast, o as useLiveQuery, r as useAuth, s as useSupabase, t as BetterSupabaseProvider } from "../react-DNBYNazu.js";
2
2
  import { SessionProvider } from "./session.js";
3
3
  //#region src/react/server.ts
4
4
  function clientOnly(name) {
@@ -1,2 +1,21 @@
1
- import { n as SessionProviderProps, r as useSession, t as SessionProvider } from "../session-C3LxnQ0e.js";
2
- export { SessionProvider, SessionProviderProps, useSession };
1
+ import { t as AuthSession } from "../view-3Po_PVlr.js";
2
+ import { ReactNode } from "react";
3
+ //#region src/react/session.d.ts
4
+ export interface SessionProviderProps {
5
+ /**
6
+ * An unresolved `next.session()` promise, created inside a `<Suspense>`
7
+ * boundary so the layout itself never waits for auth.
8
+ */
9
+ readonly sessionPromise: Promise<AuthSession>;
10
+ readonly children?: ReactNode;
11
+ }
12
+ /** Shares the server-verified session with Client Components. */
13
+ export declare function SessionProvider(props: SessionProviderProps): ReactNode;
14
+ /**
15
+ * The session from the nearest `<SessionProvider>`. Suspends until the
16
+ * promise resolves, so render it inside `<Suspense>`. `C` types the claims
17
+ * (the output of `sb.claims(schema)`) and `P` the profile (the output of
18
+ * `sb.userMetadata(schema)`); `createHooks().useSession` infers both.
19
+ */
20
+ export declare function useSession<C = unknown, P = unknown>(): AuthSession<C, P>;
21
+ //#endregion
@@ -1,3 +1,21 @@
1
1
  "use client";
2
- import { n as useSession, t as SessionProvider } from "../session-CqEwzVup.js";
2
+ import { createContext, createElement, use, useContext } from "react";
3
+ //#region src/react/session.ts
4
+ const SessionContext = createContext(null);
5
+ /** Shares the server-verified session with Client Components. */
6
+ function SessionProvider(props) {
7
+ return createElement(SessionContext.Provider, { value: props.sessionPromise }, props.children);
8
+ }
9
+ /**
10
+ * The session from the nearest `<SessionProvider>`. Suspends until the
11
+ * promise resolves, so render it inside `<Suspense>`. `C` types the claims
12
+ * (the output of `sb.claims(schema)`) and `P` the profile (the output of
13
+ * `sb.userMetadata(schema)`); `createHooks().useSession` infers both.
14
+ */
15
+ function useSession() {
16
+ const promise = useContext(SessionContext);
17
+ if (!promise) throw new Error("better-supabase: useSession() needs <SessionProvider sessionPromise={...}>");
18
+ return use(promise);
19
+ }
20
+ //#endregion
3
21
  export { SessionProvider, useSession };