better-supabase 0.4.0 → 0.5.1

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 (180) hide show
  1. package/README.md +135 -16
  2. package/dist/actor-DaJjqD5R.d.ts +43 -0
  3. package/dist/channel-FQW-CnGR.d.ts +70 -0
  4. package/dist/cli/bin.js +1 -1
  5. package/dist/cli/codemod-DqV7612m.js +388 -0
  6. package/dist/cli/{config-D9R0o1LH.js → config-CPVpXP5I.js} +78 -8
  7. package/dist/cli/{config-SSKSTUl2.js → config-dtoekM0v.js} +1 -1
  8. package/dist/cli/{read-sets-CMa29t9a.js → diff-SjkO5rNr.js} +1 -220
  9. package/dist/cli/{dist-CFgGknmZ.js → dist-DBPdlPYK.js} +4 -4
  10. package/dist/cli/{dist-B1MvLv-M.js → dist-DreZ24Sz.js} +1 -1
  11. package/dist/cli/{dist-Du7VnEUG.js → dist-xn4wlboc.js} +112 -1
  12. package/dist/cli/{doctor-BOym3mbL.js → doctor-C_A-KEAz.js} +533 -50
  13. package/dist/cli/{env-BZ0uy8XK.js → env-DmKv_G4A.js} +1 -1
  14. package/dist/cli/gen-CIdSQZad.js +2 -0
  15. package/dist/cli/{gen-Ccn_p90L.js → gen-CjElD1Vw.js} +321 -88
  16. package/dist/cli/index.d.ts +1 -1
  17. package/dist/cli/index.js +4 -4
  18. package/dist/cli/{init-ClnxRYBF.js → init-DMa6M7hV.js} +2 -2
  19. package/dist/cli/{introspect-DxqfkVIv.js → introspect-BKlaIu_M.js} +2 -2
  20. package/dist/cli/{openapi-8WHjxiTJ.js → openapi-Cp5Fyta-.js} +1 -1
  21. package/dist/cli/read-sets-VQcPgxU9.js +436 -0
  22. package/dist/cli/{run-CP20LKk_.js → run-Bo_NLT5n.js} +16 -12
  23. package/dist/cli/{seed-5J6eu_Xg.js → seed-CUlqWh-8.js} +1 -1
  24. package/dist/cli/sql-CVx-1O8G.js +373 -0
  25. package/dist/cli/{version-5mW_8sY1.js → version-BCtlgDQx.js} +1 -1
  26. package/dist/client/index.d.ts +1 -1
  27. package/dist/client/index.js +3 -10
  28. package/dist/clone-BfgmLU90.js +21 -0
  29. package/dist/{compiler-DB3aSFOA.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
  30. package/dist/config/index.d.ts +4 -4
  31. package/dist/config/index.js +4 -4
  32. package/dist/{config-C1CYcm6o.d.ts → config-Bv7gvfzC.d.ts} +185 -11
  33. package/dist/{config-CbvFmGD8.js → config-DreERlKo.js} +1 -1
  34. package/dist/{define-CRS2MRV3.d.ts → define-Bp_BZa5h.d.ts} +11 -7
  35. package/dist/{define-Bh0AqTik.js → define-cNKzqwL_.js} +202 -16
  36. package/dist/edge/index.d.ts +16 -5
  37. package/dist/edge/index.js +10 -7
  38. package/dist/{entitlements-Dm-FHpNH.d.ts → entitlements-BiJZ4t49.d.ts} +1 -1
  39. package/dist/env/index.d.ts +2 -2
  40. package/dist/env/index.js +34 -1
  41. package/dist/{errors-L0xK41y_.js → errors-DtwXa6b8.js} +1 -0
  42. package/dist/events/index.d.ts +3 -2
  43. package/dist/events/index.js +65 -12
  44. package/dist/events-aIO7vsIk.d.ts +230 -0
  45. package/dist/{result-CqEsbgYs.d.ts → executor-BunPOO-k.d.ts} +200 -1
  46. package/dist/{executor-CZ7ImRiD.d.ts → executor-Cghdstwu.d.ts} +1 -2
  47. package/dist/{executor-CVt6dD0B.js → executor-XycESQPH.js} +3 -3
  48. package/dist/hono/index.d.ts +17 -6
  49. package/dist/hono/index.js +40 -5
  50. package/dist/impersonation-BgSHAYLf.js +133 -0
  51. package/dist/{resolve-BAqQcwMW.d.ts → impersonation-BmePsuxz.d.ts} +36 -6
  52. package/dist/{index-BhHEC04r.d.ts → index-C7f8cOBM.d.ts} +2 -2
  53. package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
  54. package/dist/{index-Cf-D2jk1.d.ts → index-CWMDdEr4.d.ts} +2 -2
  55. package/dist/{index-D5IfUIDh.d.ts → index-DOESKG27.d.ts} +33 -8
  56. package/dist/{index-DFmOpBGC.d.ts → index-DgILJRXj.d.ts} +35 -9
  57. package/dist/{index-RlLXd8dL.d.ts → index-DmXSxbLi.d.ts} +4 -4
  58. package/dist/{index-BBdvo-5d.d.ts → index-Dp2VDwog.d.ts} +49 -6
  59. package/dist/{index-528Ugmyp.d.ts → index-SaTsEfGP.d.ts} +76 -8
  60. package/dist/{index-BAy4tsAP.d.ts → index-fag8VJZ2.d.ts} +3 -4
  61. package/dist/index.d.ts +30 -13
  62. package/dist/index.js +10 -9
  63. package/dist/jobs/index.d.ts +147 -101
  64. package/dist/jobs/index.js +744 -237
  65. package/dist/{json-schema-DP6V7cbr.js → json-schema-m1fiwKgN.js} +22 -1
  66. package/dist/kit-events-CTFDfSwb.js +60 -0
  67. package/dist/kit-transport-B82k1djG.d.ts +33 -0
  68. package/dist/kit-transport-DJlHQUBU.js +55 -0
  69. package/dist/{config-BpxDfnbg.js → kits-CGLLrNPd.js} +37 -9
  70. package/dist/list/index.d.ts +1 -1
  71. package/dist/{live-BHakSWWo.d.ts → live-D9W2M5aO.d.ts} +6 -5
  72. package/dist/mcp/index.d.ts +25 -8
  73. package/dist/mcp/index.js +23 -14
  74. package/dist/{mfa-C7MNQ1Ob.d.ts → mfa-Cu96oJdw.d.ts} +2 -16
  75. package/dist/next/index.d.ts +31 -10
  76. package/dist/next/index.js +136 -27
  77. package/dist/notifications/index.d.ts +162 -0
  78. package/dist/notifications/index.js +264 -0
  79. package/dist/openapi/index.d.ts +1 -1
  80. package/dist/openapi/index.js +2 -2
  81. package/dist/orgs/index.d.ts +132 -0
  82. package/dist/orgs/index.js +245 -0
  83. package/dist/orpc/index.d.ts +28 -8
  84. package/dist/orpc/index.js +16 -3
  85. package/dist/otel/index.d.ts +21 -2
  86. package/dist/otel/index.js +50 -9
  87. package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
  88. package/dist/permdock-sql-DgOfeixi.js +51 -0
  89. package/dist/plugin-DwARMuQp.js +59 -0
  90. package/dist/{plugin-bNhvJwRh.d.ts → plugin-o144f322.d.ts} +17 -5
  91. package/dist/plugins/actor/index.d.ts +5 -2
  92. package/dist/plugins/actor/index.js +22 -8
  93. package/dist/plugins/rules/index.d.ts +2 -2
  94. package/dist/plugins/rules/index.js +15 -10
  95. package/dist/plugins/soft-delete/index.d.ts +3 -3
  96. package/dist/plugins/soft-delete/index.js +14 -5
  97. package/dist/plugins/tenant/index.d.ts +2 -2
  98. package/dist/plugins/tenant/index.js +38 -20
  99. package/dist/plugins/timestamps/index.d.ts +1 -1
  100. package/dist/plugins/timestamps/index.js +4 -3
  101. package/dist/plugins/validation/index.d.ts +2 -2
  102. package/dist/plugins/validation/index.js +26 -6
  103. package/dist/{pool-DFwEQwmN.d.ts → pool-C9voM4Kw.d.ts} +17 -6
  104. package/dist/postgres/index.d.ts +5 -5
  105. package/dist/postgres/index.js +20 -11
  106. package/dist/{postgrest-Bc-0Xw62.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
  107. package/dist/{problem-BihjPR0t.js → problem-BW1KhzSG.js} +2 -1
  108. package/dist/{problem-U8UEKccv.d.ts → problem-Blxe8S31.d.ts} +1 -1
  109. package/dist/query/index.d.ts +2 -2
  110. package/dist/query/index.js +3 -3
  111. package/dist/{query-BMMSWbI7.js → query-CCbo44lG.js} +2 -2
  112. package/dist/queue-BYUCjr59.d.ts +252 -0
  113. package/dist/react/index.d.ts +8 -7
  114. package/dist/react/index.js +4 -3
  115. package/dist/react/server.d.ts +14 -3
  116. package/dist/react/server.js +10 -2
  117. package/dist/react/session.d.ts +7 -1
  118. package/dist/react/session.js +9 -1
  119. package/dist/{react-CQHMaESR.js → react-C_s7kiNz.js} +103 -11
  120. package/dist/{read-set-FWK8qQ58.js → read-set-DQFt1N_Y.js} +22 -1
  121. package/dist/realtime/index.d.ts +3 -3
  122. package/dist/realtime/index.js +5 -4
  123. package/dist/{refresh-BwYkyNOQ.d.ts → refresh-CFPX0Qft.d.ts} +1 -1
  124. package/dist/{resource-BJcaIfxi.d.ts → resource-DNRFP7KR.d.ts} +5 -5
  125. package/dist/{resource-My_y-Syh.js → resource-mqfJW4YF.js} +22 -7
  126. package/dist/respond-BR1fJWOB.d.ts +377 -0
  127. package/dist/{result-Bj6yt33D.js → result-BKiliPY7.js} +1 -1
  128. package/dist/server/index.d.ts +15 -12
  129. package/dist/server/index.js +11 -17
  130. package/dist/{server-BA75B0mT.js → server-B1mZ7CgQ.js} +259 -37
  131. package/dist/{shared-DnoCdMDr.js → shared-D1dRUrAg.js} +16 -2
  132. package/dist/signers-Cc6r98SW.d.ts +162 -0
  133. package/dist/{simplify-Cqvfr9dj.js → simplify-_0oUKqCd.js} +4 -115
  134. package/dist/sql/index.d.ts +283 -11
  135. package/dist/sql/index.js +8705 -775
  136. package/dist/{sql-B34HU2DI.js → sql-Coh6n4cC.js} +9 -2
  137. package/dist/ssr/index.d.ts +4 -4
  138. package/dist/ssr/index.js +1 -2
  139. package/dist/{standard-BRyBGTtp.js → standard-D7r6dMUu.js} +6 -3
  140. package/dist/{standard-BsaCYQJc.d.ts → standard-J4YgMOLm.d.ts} +1 -1
  141. package/dist/{stats-C_YxA2r1.js → stats-Bqob3WqT.js} +4 -4
  142. package/dist/storage/index.d.ts +4 -4
  143. package/dist/storage/index.js +155 -26
  144. package/dist/support-CEPwlw2G.d.ts +89 -0
  145. package/dist/support-view-B8SlToQX.d.ts +17 -0
  146. package/dist/support-view-g4YLBeqX.js +18 -0
  147. package/dist/support-wHnK6zp-.js +293 -0
  148. package/dist/{permdock-sql-C4KFzLc9.js → template-TxrGI5jR.js} +2 -31
  149. package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
  150. package/dist/{temporal-required-CBNHT6ni.js → temporal-required-CAt4Dc9Z.js} +2 -2
  151. package/dist/testing/index.d.ts +132 -10
  152. package/dist/testing/index.js +476 -8
  153. package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
  154. package/dist/user-change-CHFEUro1.js +49 -0
  155. package/dist/{webhooks-Bh_Jf_-l.js → verify-Ce49ETPz.js} +5 -5
  156. package/dist/{version-Bmf7_FzM.js → version-DOYTYwHP.js} +1 -1
  157. package/dist/{mfa-byfjruR9.js → view-Bn6HNKUe.js} +97 -98
  158. package/dist/{view-B64o81wp.d.ts → view-CbgObAeb.d.ts} +22 -31
  159. package/dist/webhooks/index.d.ts +135 -4
  160. package/dist/webhooks/index.js +588 -2
  161. package/dist/wire-D2dT86vi.js +35 -0
  162. package/package.json +27 -19
  163. package/schemas/config-v1.json +131 -1
  164. package/schemas/doctor-report-v1.json +11 -0
  165. package/skills/better-supabase/SKILL.md +9 -6
  166. package/skills/better-supabase/references/plugins.md +6 -3
  167. package/skills/better-supabase/references/troubleshooting.md +1 -0
  168. package/skills/better-supabase-api/SKILL.md +9 -2
  169. package/skills/better-supabase-api/references/adapters.md +11 -8
  170. package/skills/better-supabase-auth/SKILL.md +18 -7
  171. package/skills/better-supabase-auth/references/permdock.md +78 -11
  172. package/dist/cli/gen-D5cLCULx.js +0 -2
  173. package/dist/cli/sql-D4VbqmXi.js +0 -199
  174. package/dist/events-B21UOhvP.d.ts +0 -84
  175. package/dist/executor-B5IoUxLE.d.ts +0 -195
  176. package/dist/invalidate-CgxmRamI.js +0 -24
  177. package/dist/permdock-sql-DImJULY-.d.ts +0 -17
  178. package/dist/plugin-BqR6wKMB.js +0 -23
  179. package/dist/respond-DgIbdrt8.d.ts +0 -194
  180. package/dist/view-BrJI2LKp.js +0 -45
@@ -1,5 +1,5 @@
1
- import { n as quoteIdent, t as compileSql } from "../sql-B34HU2DI.js";
2
- import { n as postgresExecutor, t as fromPgError } from "../executor-CVt6dD0B.js";
1
+ import { n as quoteIdent, t as compileSql } from "../sql-Coh6n4cC.js";
2
+ import { n as postgresExecutor, t as fromPgError } from "../executor-XycESQPH.js";
3
3
  import { Pool } from "pg";
4
4
  //#region src/postgres/pool.ts
5
5
  const DEFAULT_TIMEOUTS = {
@@ -22,6 +22,10 @@ function openPool(options) {
22
22
  idleTimeoutMillis: options.idleTimeout ?? 1e4
23
23
  });
24
24
  }
25
+ const RESERVED_SETTINGS = /* @__PURE__ */ new Set(["role", "request.jwt.claims"]);
26
+ function checkSettings(settings = {}) {
27
+ for (const name of Object.keys(settings)) if (!name.includes(".") || RESERVED_SETTINGS.has(name)) throw new TypeError(`Session setting "${name}": use a custom setting with a dot, such as better_supabase.tenant`);
28
+ }
25
29
  /**
26
30
  * A pooled Postgres connection for jobs, scripts and tests. Each query (or
27
31
  * transaction) runs in its own transaction; claims and role are
@@ -39,8 +43,8 @@ function createPostgres(options = {}) {
39
43
  };
40
44
  /** Opens the transaction, then sets the timeouts, claims and role in one round trip. */
41
45
  async function begin(client, session) {
42
- await client.query("begin");
43
- const settings = [];
46
+ await client.query(session?.readOnly ? "begin read only" : "begin");
47
+ const settings = Object.entries(session?.settings ?? {});
44
48
  const timeout = session?.role === "authenticated" ? timeouts.authenticated : session?.role === "anon" ? timeouts.anon : timeouts.admin;
45
49
  if (timeout !== void 0) settings.push(["statement_timeout", String(Math.trunc(timeout))]);
46
50
  if (options.idleInTransactionTimeout !== void 0) settings.push(["idle_in_transaction_session_timeout", String(Math.trunc(options.idleInTransactionTimeout))]);
@@ -73,19 +77,24 @@ function createPostgres(options = {}) {
73
77
  queryRaw: (text, params) => transaction(session, (client) => client.queryRaw(text, params)),
74
78
  transaction: (fn) => transaction(session, fn)
75
79
  });
76
- const sessionFor = (claims) => ({
77
- claims,
78
- role: roleOf(claims)
79
- });
80
+ const sessionFor = (claims, sessionOptions = {}) => {
81
+ checkSettings(sessionOptions.settings);
82
+ return {
83
+ claims,
84
+ role: roleOf(claims),
85
+ ...sessionOptions.settings ? { settings: sessionOptions.settings } : {},
86
+ ...sessionOptions.readOnly ? { readOnly: true } : {}
87
+ };
88
+ };
80
89
  return {
81
90
  admin: clientFor(void 0),
82
91
  anon: clientFor({
83
92
  claims: { role: "anon" },
84
93
  role: "anon"
85
94
  }),
86
- asUser: (claims) => clientFor(sessionFor(claims)),
87
- executorFor: (claims) => postgresExecutor(clientFor(sessionFor(claims))),
88
- transaction: (fn, txOptions) => transaction(txOptions?.claims ? sessionFor(txOptions.claims) : void 0, fn),
95
+ asUser: (claims, sessionOptions) => clientFor(sessionFor(claims, sessionOptions)),
96
+ executorFor: (claims, sessionOptions) => postgresExecutor(clientFor(sessionFor(claims, sessionOptions))),
97
+ transaction: (fn, txOptions) => transaction(txOptions?.claims ? sessionFor(txOptions.claims, txOptions) : void 0, fn),
89
98
  end: () => pool.end(),
90
99
  [Symbol.asyncDispose]: () => pool.end()
91
100
  };
@@ -1,4 +1,4 @@
1
- import { g as Operation } from "./executor-B5IoUxLE.js";
1
+ import { U as Operation } from "./executor-BunPOO-k.js";
2
2
  //#region src/compile/postgrest.d.ts
3
3
  /**
4
4
  * A compiled PostgREST request, expressed as calls on the public
@@ -1,4 +1,4 @@
1
- import { n as dbError } from "./errors-L0xK41y_.js";
1
+ import { n as dbError } from "./errors-DtwXa6b8.js";
2
2
  //#region src/core/problem.ts
3
3
  const PROBLEM_TYPE_BASE = "https://bettersupabase.com/problems/";
4
4
  const PROBLEM_CONTENT_TYPE = "application/problem+json";
@@ -12,6 +12,7 @@ const TITLES = {
12
12
  not_null: "Required value missing",
13
13
  exclusion: "Exclusion constraint violated",
14
14
  invalid_input: "Invalid input",
15
+ invalid_value: "Stored value not representable",
15
16
  raised: "Request rejected",
16
17
  timeout: "Timed out",
17
18
  serialization: "Concurrent update, retry",
@@ -1,4 +1,4 @@
1
- import { b as ValidationIssue, m as DbErrorKind, p as DbError } from "./result-CqEsbgYs.js";
1
+ import { _ as DbError, v as DbErrorKind, w as ValidationIssue } from "./executor-BunPOO-k.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 {
@@ -1,2 +1,2 @@
1
- import { a as QueryHelpers, c as TableQueries, d as queryCache, f as BetterQueryMeta, i as QueriesOptions, l as createQueries, n as InfiniteOptionsOf, o as QueryKeyOf, p as invalidateTables, r as MutationOptionsOf, s as QueryOptionsOf, t as BetterQueries, u as invalidateOnMutation } from "../index-DFmOpBGC.js";
2
- export { type BetterQueries, type BetterQueryMeta, type InfiniteOptionsOf, type MutationOptionsOf, type QueriesOptions, type QueryHelpers, type QueryKeyOf, type QueryOptionsOf, type TableQueries, createQueries, invalidateOnMutation, invalidateTables, queryCache };
1
+ import { a as MutationOptionsOf, c as QueryKeyOf, d as createQueries, f as invalidateOnMutation, h as invalidateTables, i as InfiniteOptionsOf, l as QueryOptionsOf, m as BetterQueryMeta, n as clearOnUserChange, o as QueriesOptions, p as queryCache, r as BetterQueries, s as QueryHelpers, t as UserChangeSource, u as TableQueries } from "../index-DgILJRXj.js";
2
+ export { type BetterQueries, type BetterQueryMeta, type InfiniteOptionsOf, type MutationOptionsOf, type QueriesOptions, type QueryHelpers, type QueryKeyOf, type QueryOptionsOf, type TableQueries, type UserChangeSource, clearOnUserChange, createQueries, invalidateOnMutation, invalidateTables, queryCache };
@@ -1,3 +1,3 @@
1
- import { t as invalidateTables } from "../invalidate-CgxmRamI.js";
2
- import { n as invalidateOnMutation, r as queryCache, t as createQueries } from "../query-BMMSWbI7.js";
3
- export { createQueries, invalidateOnMutation, invalidateTables, queryCache };
1
+ import { n as invalidateTables, t as clearOnUserChange } from "../user-change-CHFEUro1.js";
2
+ import { n as invalidateOnMutation, r as queryCache, t as createQueries } from "../query-CCbo44lG.js";
3
+ export { clearOnUserChange, createQueries, invalidateOnMutation, invalidateTables, queryCache };
@@ -1,6 +1,6 @@
1
- import { t as DbException } from "./errors-L0xK41y_.js";
1
+ import { t as DbException } from "./errors-DtwXa6b8.js";
2
2
  import { t as invalidationTargets } from "./tables-Cb-3_VWc.js";
3
- import { t as invalidateTables } from "./invalidate-CgxmRamI.js";
3
+ import { n as invalidateTables } from "./user-change-CHFEUro1.js";
4
4
  //#region src/query/queries.ts
5
5
  /** Query errors stay `DbException`s, even for an `betterSupabase` with `mapError()`. */
6
6
  const asException = (error) => new DbException(error);
@@ -0,0 +1,252 @@
1
+ /// <reference lib="esnext.temporal" />
2
+ import { _ as DbError, a as AsyncResult } from "./executor-BunPOO-k.js";
3
+ import { g as RequestContext, t as Actor } from "./plugin-o144f322.js";
4
+ import { t as SqlClient } from "./executor-Cghdstwu.js";
5
+ import { StandardSchemaV1 } from "@standard-schema/spec";
6
+ //#region src/jobs/queue.d.ts
7
+ type QueueSchemas = Readonly<Record<string, StandardSchemaV1>>;
8
+ type PayloadIn<Q extends QueueSchemas, N extends keyof Q> = StandardSchemaV1.InferInput<Q[N]>;
9
+ type PayloadOut<Q extends QueueSchemas, N extends keyof Q> = StandardSchemaV1.InferOutput<Q[N]>;
10
+ /** The actor and tenant of the request that enqueued a job. */
11
+ interface JobContext {
12
+ readonly actor?: Actor;
13
+ readonly tenant?: string;
14
+ }
15
+ interface Job<P = unknown> {
16
+ /** The pgmq message id. */
17
+ readonly id: number;
18
+ readonly queue: string;
19
+ readonly payload: P;
20
+ /** 1 on the first run (pgmq's `read_ct`). */
21
+ readonly attempts: number;
22
+ readonly maxAttempts: number;
23
+ readonly enqueuedAt: Temporal.Instant;
24
+ /** End of the lease: the job becomes claimable again after this. */
25
+ readonly visibleUntil: Temporal.Instant;
26
+ readonly lastError: string | null;
27
+ /**
28
+ * The actor and tenant recorded at enqueue, ready for `db.$with(job.context)`.
29
+ * Without a recorded tenant, `tenant()` applies its `onMissing` unless the
30
+ * worker runs with `allTenants: true`.
31
+ */
32
+ readonly context: RequestContext;
33
+ }
34
+ interface EnqueueOptions {
35
+ /** Run no earlier than this. */
36
+ readonly runAt?: Temporal.Instant;
37
+ /** Seconds to wait before running. */
38
+ readonly delay?: number;
39
+ /** Defaults to 5. After this many failed attempts the job is archived as dead. */
40
+ readonly maxAttempts?: number;
41
+ /**
42
+ * While a job with this key is waiting or running, enqueueing again returns
43
+ * its id. SQL connections only.
44
+ */
45
+ readonly dedupeKey?: string;
46
+ /**
47
+ * The request context whose actor and tenant the job records, next to the
48
+ * payload. The tenant is `context.tenant`, the one `tenant()` resolved, or
49
+ * the `tenant_id` claim (then `app_metadata.tenant_id`).
50
+ */
51
+ readonly context?: RequestContext;
52
+ }
53
+ interface ScheduleOptions {
54
+ /** The actor and tenant every scheduled run records, as in `enqueue`. */
55
+ readonly context?: RequestContext;
56
+ /**
57
+ * The IANA time zone the cron fields are read in. Defaults to `UTC`.
58
+ * Other zones need `kits.jobs.options.scheduler: "drain"`.
59
+ */
60
+ readonly timeZone?: string;
61
+ }
62
+ interface RunSchedulesOptions {
63
+ /** Seconds a claimed schedule stays leased. Defaults to 60. */
64
+ readonly lease?: number;
65
+ /** Defaults to 100. */
66
+ readonly batch?: number;
67
+ }
68
+ interface DrainOptions extends Omit<WorkOptions, "pollInterval" | "maxPollInterval" | "signal"> {
69
+ /** Stop claiming after this many ms; claimed jobs still finish. */
70
+ readonly budgetMs?: number;
71
+ }
72
+ interface DrainRouteOptions<Q extends QueueSchemas> extends Omit<DrainOptions, "onError"> {
73
+ /**
74
+ * The bearer token callers send (`Authorization: Bearer <secret>`), such
75
+ * as Vercel's `CRON_SECRET`. Required.
76
+ */
77
+ readonly secret: string | undefined;
78
+ /** The queues to drain, in order, with their handlers. */
79
+ readonly handlers: { readonly [N in Extract<keyof Q, string>]?: JobHandler<PayloadOut<Q, N>>; };
80
+ /** Total time in ms for schedules and queues. Defaults to 50000. */
81
+ readonly budgetMs?: number;
82
+ /** Enqueue due schedules first (the drain scheduler). Defaults to true. */
83
+ readonly schedules?: boolean;
84
+ readonly onError?: (error: DbError, job?: Job) => void;
85
+ }
86
+ interface DrainRouteResult {
87
+ /** Schedules whose run was enqueued. */
88
+ readonly schedules: number;
89
+ readonly queues: Readonly<Record<string, DrainResult>>;
90
+ /** True when the budget ran out before every queue was empty. */
91
+ readonly budgetExhausted: boolean;
92
+ }
93
+ interface ClaimOptions {
94
+ /** Defaults to 1. */
95
+ readonly batch?: number;
96
+ /** Seconds a claimed job stays invisible to other workers. Defaults to 300. */
97
+ readonly lease?: number;
98
+ }
99
+ interface FailOptions {
100
+ /** Retry after this many seconds instead of the exponential backoff. SQL connections only. */
101
+ readonly retryIn?: number;
102
+ }
103
+ type JobHandler<P> = (payload: P, job: Job<P>, signal: AbortSignal) => unknown;
104
+ interface WorkOptions extends ClaimOptions {
105
+ /** Jobs handled at once. Defaults to 1. */
106
+ readonly concurrency?: number;
107
+ /**
108
+ * First wait between polls when the queue is empty, in ms. Defaults to
109
+ * 1000. Each empty poll doubles the wait up to `maxPollInterval`; a claimed
110
+ * job resets it.
111
+ */
112
+ readonly pollInterval?: number;
113
+ /** Longest wait between empty polls, in ms. Defaults to 30000. */
114
+ readonly maxPollInterval?: number;
115
+ /** Stops the loop; running jobs finish first. */
116
+ readonly signal?: AbortSignal;
117
+ readonly onError?: (error: DbError, job: Job) => void;
118
+ /**
119
+ * Run jobs that recorded no tenant with a context that `tenant()` and
120
+ * tenant buckets don't scope. Off by default, so such jobs get the
121
+ * `onMissing` of `tenant()`.
122
+ */
123
+ readonly allTenants?: boolean;
124
+ }
125
+ interface DrainResult {
126
+ readonly succeeded: number;
127
+ readonly failed: number;
128
+ }
129
+ interface Jobs<Q extends QueueSchemas> {
130
+ /** Validates the payload with the queue's schema, then enqueues it. Returns the job id. */
131
+ enqueue<N extends Extract<keyof Q, string>>(queue: N, payload: PayloadIn<Q, N>, options?: EnqueueOptions): AsyncResult<number>;
132
+ claim<N extends Extract<keyof Q, string>>(queue: N, options?: ClaimOptions): AsyncResult<Job<PayloadOut<Q, N>>[]>;
133
+ /** Archives the job. `false` when the lease was lost (another worker claimed it since). */
134
+ complete(job: Job): AsyncResult<boolean>;
135
+ /** Returns the new state: `queued` (will retry), `dead` (archived), or `null` when the lease was lost. */
136
+ fail(job: Job, error: unknown, options?: FailOptions): AsyncResult<"queued" | "dead" | null>;
137
+ /** Pushes the lease out by `lease` seconds. `false` when it was lost or the transport can't. */
138
+ extend(job: Job, lease: number): AsyncResult<boolean>;
139
+ /**
140
+ * Enqueues a dead letter again with its payload and attempts, and removes
141
+ * it from the archive. Returns the new id, or `null` when `id` is not a
142
+ * dead letter of `queue`. SQL connections only.
143
+ */
144
+ replay(queue: Extract<keyof Q, string>, id: number): AsyncResult<number | null>;
145
+ /**
146
+ * Enqueues `payload` on a cron schedule (`'0 3 * * *'`, `'@daily'`,
147
+ * `'30 seconds'`), with pg_cron or the drain scheduler
148
+ * (`kits.jobs.options.scheduler`). Re-scheduling a name replaces it. SQL
149
+ * connections only.
150
+ */
151
+ schedule<N extends Extract<keyof Q, string>>(name: string, cron: string, queue: N, payload: PayloadIn<Q, N>, options?: ScheduleOptions): AsyncResult<void>;
152
+ unschedule(name: string): AsyncResult<boolean>;
153
+ /**
154
+ * Enqueues one run of every due schedule (the drain scheduler) and moves
155
+ * it to its next time. Returns how many ran. Missed runs collapse into one.
156
+ */
157
+ runSchedules(options?: RunSchedulesOptions): AsyncResult<number>;
158
+ /** Processes ready jobs until the queue is empty or the budget is spent. For cron and edge invocations. */
159
+ drain<N extends Extract<keyof Q, string>>(queue: N, handler: JobHandler<PayloadOut<Q, N>>, options?: DrainOptions): Promise<DrainResult>;
160
+ /**
161
+ * A route handler for a cron caller such as Vercel Cron: checks the bearer
162
+ * secret, runs due schedules, then drains each queue in `handlers` within
163
+ * the budget, and answers with a `DrainRouteResult`.
164
+ */
165
+ drainRoute(options: DrainRouteOptions<Q>): (request: Request) => Promise<Response>;
166
+ /** Polls and processes jobs until `signal` aborts. */
167
+ work<N extends Extract<keyof Q, string>>(queue: N, handler: JobHandler<PayloadOut<Q, N>>, options?: WorkOptions): Promise<DrainResult>;
168
+ }
169
+ /** The body of a queued message, on pgmq and the table backend alike. */
170
+ interface QueueMessageBody {
171
+ readonly payload?: unknown;
172
+ readonly max_attempts?: number;
173
+ readonly last_error?: string;
174
+ }
175
+ /** A claimed message, as `better_supabase.claim_jobs` returns it. */
176
+ interface QueueMessageRow {
177
+ readonly id: string | number;
178
+ /** 1 on the first claim; a lease token for complete, fail and extend. */
179
+ readonly attempts: number;
180
+ readonly enqueued_at: Date | string;
181
+ readonly visible_until: Date | string;
182
+ readonly message: QueueMessageBody | null;
183
+ }
184
+ /** A schedule whose run is due, claimed by `runSchedules`. */
185
+ interface DueSchedule {
186
+ readonly name: string;
187
+ readonly cron: string;
188
+ readonly timeZone: string;
189
+ readonly queue: string;
190
+ /** The payload as stored, with its recorded context. */
191
+ readonly payload: unknown;
192
+ readonly nextRun: Temporal.Instant;
193
+ }
194
+ /** A Supabase client, or anything with `.schema(name).rpc(fn, args)`. */
195
+ interface QueueRpcClient {
196
+ schema(name: string): {
197
+ rpc(fn: string, args: Readonly<Record<string, unknown>>): PromiseLike<{
198
+ data: unknown;
199
+ error: unknown;
200
+ }>;
201
+ };
202
+ }
203
+ /**
204
+ * Where `createJobs` stores jobs. `sqlQueueBackend` (the `jobs` SQL kit
205
+ * module, on pgmq or its table backend) and `pgmqPublicBackend` ship with
206
+ * the package; `testQueueBackend` from `better-supabase/testing` checks
207
+ * another one.
208
+ */
209
+ interface QueueBackend {
210
+ readonly apiVersion: 1;
211
+ readonly name: string;
212
+ /** Whether `extend` works, so long-running batches keep their lease. */
213
+ readonly leases: boolean;
214
+ send(queue: string, payload: unknown, delay: number, maxAttempts: number, dedupeKey: string | undefined): Promise<number>;
215
+ read(queue: string, lease: number, batch: number): Promise<readonly QueueMessageRow[]>;
216
+ /** `false` when the lease was lost. */
217
+ complete(job: Job): Promise<boolean>;
218
+ fail(job: Job, error: string, retryIn: number | undefined): Promise<"queued" | "dead" | null>;
219
+ extend(job: Job, lease: number): Promise<boolean>;
220
+ /** Re-enqueues a dead letter; `null` when it isn't one. Absent when the backend can't. */
221
+ replay?(queue: string, id: number): Promise<number | null>;
222
+ schedule(name: string, cron: string, queue: string, payload: unknown, timeZone: string, nextRun: Temporal.Instant): Promise<void>;
223
+ unschedule(name: string): Promise<boolean>;
224
+ /** Leases due schedules (the drain scheduler); absent or empty with pg_cron. */
225
+ dueSchedules?(lease: number, batch: number): Promise<readonly DueSchedule[]>;
226
+ /** Moves a schedule from `ran` to `next`; `false` when it changed since the claim. */
227
+ advanceSchedule?(name: string, ran: Temporal.Instant, next: Temporal.Instant): Promise<boolean>;
228
+ }
229
+ /**
230
+ * The `jobs` SQL kit module over a service SQL connection. The same
231
+ * functions back both `kits.jobs.options.backend` values (pgmq and table)
232
+ * and both schedulers.
233
+ */
234
+ declare function sqlQueueBackend(sql: SqlClient): QueueBackend;
235
+ /**
236
+ * Supabase's `pgmq_public` RPCs (Integrations, Queues, "Expose Queues via
237
+ * PostgREST"). No lease check on complete, no retry delay, no deduplication
238
+ * and no schedules: a failed job reappears when its lease ends.
239
+ */
240
+ declare function pgmqPublicBackend(client: QueueRpcClient): QueueBackend;
241
+ /**
242
+ * Typed jobs on Supabase Queues (pgmq), over the `jobs` SQL kit module.
243
+ *
244
+ * Pass a service SQL connection (`postgres.admin`, `ctx.sql`) for the full
245
+ * feature set, a service-role Supabase client to go through the
246
+ * `pgmq_public` RPCs where no direct connection exists (edge functions), or
247
+ * any `QueueBackend`. Queue names are lowercase letters, digits and
248
+ * underscores.
249
+ */
250
+ declare function createJobs<const Q extends QueueSchemas>(source: SqlClient | QueueRpcClient | QueueBackend, queues: Q): Jobs<Q>;
251
+ //#endregion
252
+ export { sqlQueueBackend as C, pgmqPublicBackend as S, QueueSchemas as _, DrainRouteResult as a, WorkOptions as b, FailOptions as c, JobHandler as d, Jobs as f, QueueRpcClient as g, QueueMessageRow as h, DrainRouteOptions as i, Job as l, QueueMessageBody as m, DrainOptions as n, DueSchedule as o, QueueBackend as p, DrainResult as r, EnqueueOptions as s, ClaimOptions as t, JobContext as u, RunSchedulesOptions as v, createJobs as x, ScheduleOptions as y };
@@ -1,7 +1,8 @@
1
- import { s as Impersonator } from "../mfa-C7MNQ1Ob.js";
2
- import { t as AuthSession } from "../view-B64o81wp.js";
3
- import { r as LiveCountSeed } from "../live-BHakSWWo.js";
4
- import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-Dm-FHpNH.js";
5
- import { a as ClaimsOf, 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 ClientLike, p as useBroadcast, r as BetterSupabaseProviderProps, s as LiveCount, t as BetterHooks, u as ProfileOf } from "../index-BBdvo-5d.js";
6
- import { SessionProvider, SessionProviderProps, useSession } from "./session.js";
7
- export { type AuthSession, type BetterHooks, BetterSupabaseProvider, type BetterSupabaseProviderProps, type BroadcastOptions, type ClaimsOf, type ClientLike, 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
+ import { n as Impersonator } from "../impersonation-BmePsuxz.js";
2
+ import { t as AuthSession } from "../view-CbgObAeb.js";
3
+ import { r as LiveCountSeed } from "../live-D9W2M5aO.js";
4
+ import { n as supportOf, t as SupportView } from "../support-view-B8SlToQX.js";
5
+ import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-BiJZ4t49.js";
6
+ import { _ as useBroadcast, a as BetterHooks, b as useSupabase, c as BroadcastOptions, d as LiveCount, f as LiveCountHookOptions, g as useAuth, h as createHooks, i as useNotifications, l as ClaimsOf, m as ProfileOf, n as NotificationsState, o as BetterSupabaseProvider, p as LiveQueryHookOptions, r as UseNotificationsOptions, s as BetterSupabaseProviderProps, t as NotificationSource, u as ClientLike, v as useLiveCount, y as useLiveQuery } from "../index-Dp2VDwog.js";
7
+ import { SessionProvider, SessionProviderProps, useSession, useSupportSession } from "./session.js";
8
+ export { type AuthSession, type BetterHooks, BetterSupabaseProvider, type BetterSupabaseProviderProps, type BroadcastOptions, type ClaimsOf, type ClientLike, type EntitlementKey, type Impersonator, type LiveCount, type LiveCountHookOptions, type LiveCountSeed, type LiveQueryHookOptions, type MembershipClaim, type NotificationSource, type NotificationsState, type ProfileOf, SessionProvider, type SessionProviderProps, type SupportView, type UseNotificationsOptions, createHooks, hasEntitlement, supportOf, useAuth, useBroadcast, useLiveCount, useLiveQuery, useNotifications, useSession, useSupabase, useSupportSession };
@@ -1,5 +1,6 @@
1
1
  "use client";
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-CQHMaESR.js";
2
+ import { t as supportOf } from "../support-view-g4YLBeqX.js";
3
+ import { SessionProvider, useSession, useSupportSession } from "./session.js";
4
+ import { a as useBroadcast, c as useSupabase, i as useAuth, n as BetterSupabaseProvider, o as useLiveCount, r as createHooks, s as useLiveQuery, t as useNotifications } from "../react-C_s7kiNz.js";
4
5
  import { t as hasEntitlement } from "../entitlements-CTEBm7IB.js";
5
- export { BetterSupabaseProvider, SessionProvider, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };
6
+ export { BetterSupabaseProvider, SessionProvider, createHooks, hasEntitlement, supportOf, useAuth, useBroadcast, useLiveCount, useLiveQuery, useNotifications, useSession, useSupabase, useSupportSession };
@@ -1,13 +1,24 @@
1
- import { t as AuthSession } from "../view-B64o81wp.js";
2
- import { a as ClaimsOf, f as useAuth, g as useSupabase, h as useLiveQuery, i as BroadcastOptions, l as LiveQueryHookOptions, n as BetterSupabaseProvider, o as ClientLike, p as useBroadcast, r as BetterSupabaseProviderProps, t as BetterHooks, u as ProfileOf } from "../index-BBdvo-5d.js";
1
+ import { Z as QuerySpec } from "../define-Bp_BZa5h.js";
2
+ import { n as Impersonator } from "../impersonation-BmePsuxz.js";
3
+ import { t as AuthSession } from "../view-CbgObAeb.js";
4
+ import { r as LiveCountSeed } from "../live-D9W2M5aO.js";
5
+ import { n as supportOf, t as SupportView } from "../support-view-B8SlToQX.js";
6
+ import { n as MembershipClaim, r as hasEntitlement, t as EntitlementKey } from "../entitlements-BiJZ4t49.js";
7
+ import { _ as useBroadcast, a as BetterHooks, b as useSupabase, c as BroadcastOptions, d as LiveCount, f as LiveCountHookOptions, g as useAuth, l as ClaimsOf, m as ProfileOf, n as NotificationsState, o as BetterSupabaseProvider, p as LiveQueryHookOptions, r as UseNotificationsOptions, s as BetterSupabaseProviderProps, t as NotificationSource, u as ClientLike, y as useLiveQuery } from "../index-Dp2VDwog.js";
3
8
  import { SessionProvider, SessionProviderProps } from "./session.js";
4
9
  //#region src/react/server.d.ts
5
10
  /** The `react-server` build of `useSession`: await `bs.session()` instead. */
6
11
  export declare const useSession: <C = unknown, P = unknown>() => AuthSession<C, P>;
12
+ /** The `react-server` build of `useSupportSession`: use `supportOf(await bs.session())` instead. */
13
+ export declare const useSupportSession: () => SupportView | undefined;
14
+ /** The `react-server` build of `useLiveCount`: render the `bs.liveCount()` seed instead. */
15
+ export declare const useLiveCount: (source: QuerySpec<string, "count", number> | LiveCountSeed | null | undefined, options?: LiveCountHookOptions) => LiveCount;
16
+ /** The `react-server` build of `useNotifications`: call `notifications.list()` instead. */
17
+ export declare const useNotifications: <T>(options: UseNotificationsOptions<T>) => NotificationsState<T>;
7
18
  /**
8
19
  * The `react-server` build of `createHooks`: importing a module that creates
9
20
  * hooks is safe on the server, calling a hook there throws.
10
21
  */
11
22
  export declare function createHooks<B extends ClientLike>(): BetterHooks<B>;
12
23
  //#endregion
13
- export { type AuthSession, type BetterHooks, BetterSupabaseProvider, type BetterSupabaseProviderProps, type BroadcastOptions, type ClaimsOf, type ClientLike, type LiveQueryHookOptions, type ProfileOf, SessionProvider, type SessionProviderProps, useAuth, useBroadcast, useLiveQuery, useSupabase };
24
+ export { type AuthSession, type BetterHooks, BetterSupabaseProvider, type BetterSupabaseProviderProps, type BroadcastOptions, type ClaimsOf, type ClientLike, type EntitlementKey, type Impersonator, type LiveCount, type LiveCountHookOptions, type LiveCountSeed, type LiveQueryHookOptions, type MembershipClaim, type NotificationSource, type NotificationsState, type ProfileOf, SessionProvider, type SessionProviderProps, type SupportView, type UseNotificationsOptions, hasEntitlement, supportOf, useAuth, useBroadcast, useLiveQuery, useSupabase };
@@ -1,4 +1,6 @@
1
- import { i as useBroadcast, o as useLiveQuery, r as useAuth, s as useSupabase, t as BetterSupabaseProvider } from "../react-CQHMaESR.js";
1
+ import { t as supportOf } from "../support-view-g4YLBeqX.js";
2
+ import { a as useBroadcast, c as useSupabase, i as useAuth, n as BetterSupabaseProvider, s as useLiveQuery } from "../react-C_s7kiNz.js";
3
+ import { t as hasEntitlement } from "../entitlements-CTEBm7IB.js";
2
4
  import { SessionProvider } from "./session.js";
3
5
  //#region src/react/server.ts
4
6
  function clientOnly(name) {
@@ -8,6 +10,12 @@ function clientOnly(name) {
8
10
  }
9
11
  /** The `react-server` build of `useSession`: await `bs.session()` instead. */
10
12
  const useSession = clientOnly("useSession");
13
+ /** The `react-server` build of `useSupportSession`: use `supportOf(await bs.session())` instead. */
14
+ const useSupportSession = clientOnly("useSupportSession");
15
+ /** The `react-server` build of `useLiveCount`: render the `bs.liveCount()` seed instead. */
16
+ const useLiveCount = clientOnly("useLiveCount");
17
+ /** The `react-server` build of `useNotifications`: call `notifications.list()` instead. */
18
+ const useNotifications = clientOnly("useNotifications");
11
19
  /**
12
20
  * The `react-server` build of `createHooks`: importing a module that creates
13
21
  * hooks is safe on the server, calling a hook there throws.
@@ -22,4 +30,4 @@ function createHooks() {
22
30
  };
23
31
  }
24
32
  //#endregion
25
- export { BetterSupabaseProvider, SessionProvider, createHooks, useAuth, useBroadcast, useLiveQuery, useSession, useSupabase };
33
+ export { BetterSupabaseProvider, SessionProvider, createHooks, hasEntitlement, supportOf, useAuth, useBroadcast, useLiveCount, useLiveQuery, useNotifications, useSession, useSupabase, useSupportSession };
@@ -1,4 +1,5 @@
1
- import { t as AuthSession } from "../view-B64o81wp.js";
1
+ import { t as AuthSession } from "../view-CbgObAeb.js";
2
+ import { t as SupportView } from "../support-view-B8SlToQX.js";
2
3
  import { ReactNode } from "react";
3
4
  //#region src/react/session.d.ts
4
5
  export interface SessionProviderProps {
@@ -18,4 +19,9 @@ export declare function SessionProvider(props: SessionProviderProps): ReactNode;
18
19
  * `betterSupabase.userMetadata(schema)`); `createHooks().useSession` infers both.
19
20
  */
20
21
  export declare function useSession<C = unknown, P = unknown>(): AuthSession<C, P>;
22
+ /**
23
+ * The support session the page renders in, for a "Viewing as" banner, or
24
+ * `undefined` for a normal session. Needs `<SessionProvider>` like `useSession`.
25
+ */
26
+ export declare function useSupportSession(): SupportView | undefined;
21
27
  //#endregion
@@ -1,4 +1,5 @@
1
1
  "use client";
2
+ import { t as supportOf } from "../support-view-g4YLBeqX.js";
2
3
  import { createContext, createElement, use, useContext } from "react";
3
4
  //#region src/react/session.ts
4
5
  const SessionContext = createContext(null);
@@ -17,5 +18,12 @@ function useSession() {
17
18
  if (!promise) throw new Error("better-supabase: useSession() needs <SessionProvider sessionPromise={...}>");
18
19
  return use(promise);
19
20
  }
21
+ /**
22
+ * The support session the page renders in, for a "Viewing as" banner, or
23
+ * `undefined` for a normal session. Needs `<SessionProvider>` like `useSession`.
24
+ */
25
+ function useSupportSession() {
26
+ return supportOf(useSession());
27
+ }
20
28
  //#endregion
21
- export { SessionProvider, useSession };
29
+ export { SessionProvider, useSession, useSupportSession };
@@ -1,8 +1,9 @@
1
1
  import { i as tenantClaimPaths, n as claimAt, r as claimsOf } from "./claims-BAf6c7is.js";
2
- import { t as invalidateTables } from "./invalidate-CgxmRamI.js";
2
+ import { n as invalidateTables, t as clearOnUserChange } from "./user-change-CHFEUro1.js";
3
3
  import { n as liveQuery, t as liveCount } from "./live-CdJBOc4f.js";
4
4
  import { useSession } from "./react/session.js";
5
- import { createContext, createElement, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ import { REALTIME_SUBSCRIBE_STATES } from "@supabase/supabase-js";
6
+ import { createContext, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
7
  //#region src/react/hooks.ts
7
8
  const ClientContext = createContext(null);
8
9
  const LOADING = {
@@ -13,14 +14,7 @@ const LOADING = {
13
14
  /** Provides the better-supabase client to the hooks. */
14
15
  function BetterSupabaseProvider(props) {
15
16
  const { client, queryClient } = props;
16
- const user = useRef(void 0);
17
- useEffect(() => client.auth.subscribe(() => {
18
- const snapshot = client.auth.current();
19
- if (snapshot.status === "loading") return;
20
- const id = snapshot.user?.id ?? null;
21
- if (user.current !== void 0 && user.current !== id) queryClient?.removeQueries({ queryKey: ["bs"] });
22
- user.current = id;
23
- }), [client, queryClient]);
17
+ useEffect(() => queryClient ? clearOnUserChange(queryClient, client.auth) : void 0, [client, queryClient]);
24
18
  const value = useMemo(() => ({
25
19
  client,
26
20
  queryClient
@@ -243,4 +237,102 @@ function useLiveCount(source, options = {}) {
243
237
  };
244
238
  }
245
239
  //#endregion
246
- export { useLiveCount as a, useBroadcast as i, createHooks as n, useLiveQuery as o, useAuth as r, useSupabase as s, BetterSupabaseProvider as t };
240
+ //#region src/react/notifications.ts
241
+ const unread = (items) => items.filter((item) => typeof item === "object" && item !== null && "readAt" in item && item.readAt === null).length;
242
+ /** Joins a private topic and returns the function that leaves it. */
243
+ function watchTopic(supabase, topic, watch) {
244
+ let closed = false;
245
+ let joined = false;
246
+ const channel = supabase.channel(topic, { config: { private: true } });
247
+ channel.on("broadcast", { event: "*" }, (message) => {
248
+ watch.onMessage(message.event, message["payload"]);
249
+ });
250
+ (async () => {
251
+ watch.onStatus("joining");
252
+ await supabase.realtime.setAuth();
253
+ if (closed) return;
254
+ channel.subscribe((state) => {
255
+ switch (state) {
256
+ case REALTIME_SUBSCRIBE_STATES.SUBSCRIBED:
257
+ if (joined) watch.onRejoin();
258
+ joined = true;
259
+ watch.onStatus("subscribed");
260
+ return;
261
+ case REALTIME_SUBSCRIBE_STATES.CLOSED:
262
+ watch.onStatus("closed");
263
+ return;
264
+ case REALTIME_SUBSCRIBE_STATES.CHANNEL_ERROR:
265
+ case REALTIME_SUBSCRIBE_STATES.TIMED_OUT:
266
+ watch.onStatus("error");
267
+ return;
268
+ default: throw new Error(`Unknown realtime status ${String(state)}`);
269
+ }
270
+ });
271
+ })();
272
+ return () => {
273
+ closed = true;
274
+ supabase.removeChannel(channel);
275
+ watch.onStatus("closed");
276
+ };
277
+ }
278
+ /**
279
+ * The signed-in user's notifications, kept current: loads them, then loads
280
+ * again after each broadcast on `topic` and after the channel rejoins.
281
+ *
282
+ * ```tsx
283
+ * const { items, count } = useNotifications({
284
+ * topic: `org:${orgId}:notifications:${userId}`,
285
+ * load: () => listNotifications(orgId),
286
+ * });
287
+ * ```
288
+ */
289
+ function useNotifications(options) {
290
+ const supabase = useSupabase();
291
+ const auth = useAuth();
292
+ const [items, setItems] = useState(void 0);
293
+ const [error, setError] = useState(void 0);
294
+ const [status, setStatus] = useState("closed");
295
+ const latest = useRef(options);
296
+ latest.current = options;
297
+ const userId = auth.user?.id ?? null;
298
+ const refresh = useCallback(async () => {
299
+ const current = latest.current;
300
+ try {
301
+ const merged = (await Promise.all([current.load, ...current.sources ?? []].map((source) => source()))).flat();
302
+ setItems(current.sort ? [...merged].sort(current.sort) : merged);
303
+ setError(void 0);
304
+ } catch (cause) {
305
+ setError(cause);
306
+ }
307
+ }, []);
308
+ const topic = options.topic;
309
+ useEffect(() => {
310
+ if (auth.status !== "signed-in") return;
311
+ refresh();
312
+ if (!topic) return;
313
+ return watchTopic(supabase, topic, {
314
+ onMessage: (event, payload) => {
315
+ latest.current.onMessage?.(event, payload);
316
+ refresh();
317
+ },
318
+ onRejoin: () => void refresh(),
319
+ onStatus: setStatus
320
+ });
321
+ }, [
322
+ supabase,
323
+ topic,
324
+ userId,
325
+ auth.status,
326
+ refresh
327
+ ]);
328
+ const list = items ?? [];
329
+ return {
330
+ items,
331
+ count: (options.count ?? unread)(list),
332
+ status,
333
+ error,
334
+ refresh
335
+ };
336
+ }
337
+ //#endregion
338
+ export { useBroadcast as a, useSupabase as c, useAuth as i, BetterSupabaseProvider as n, useLiveCount as o, createHooks as r, useLiveQuery as s, useNotifications as t };