better-supabase 0.3.0 → 0.5.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 (211) hide show
  1. package/README.md +9 -6
  2. package/bin/better-supabase.js +4 -0
  3. package/dist/{base64-CQeB1eSf.js → base64-D2gsioCY.js} +2 -2
  4. package/dist/channel-FQW-CnGR.d.ts +70 -0
  5. package/dist/claims-BAf6c7is.js +48 -0
  6. package/dist/cli/_format-Cax5EsM9.js +101 -0
  7. package/dist/cli/bin.d.ts +1 -0
  8. package/dist/cli/bin.js +48 -0
  9. package/dist/cli/codemod-DqV7612m.js +388 -0
  10. package/dist/cli/command-CK14fP91.js +502 -0
  11. package/dist/cli/compare-BmIWVEui.js +9 -0
  12. package/dist/cli/config-CPVpXP5I.js +1965 -0
  13. package/dist/cli/config-dtoekM0v.js +2 -0
  14. package/dist/cli/db-DOBPt660.js +52 -0
  15. package/dist/cli/diff-SjkO5rNr.js +752 -0
  16. package/dist/cli/dist-DBPdlPYK.js +1112 -0
  17. package/dist/cli/dist-DreZ24Sz.js +129 -0
  18. package/dist/cli/dist-xn4wlboc.js +732 -0
  19. package/dist/cli/doctor-L7Z8mYTx.js +2863 -0
  20. package/dist/cli/env-DmKv_G4A.js +108 -0
  21. package/dist/cli/env-jIFUADLY.js +511 -0
  22. package/dist/cli/errors-4v4DTtda.js +56 -0
  23. package/dist/cli/fastest-levenshtein-CFN-lNDv.js +120 -0
  24. package/dist/cli/gen-BJc9vdyu.js +2 -0
  25. package/dist/cli/gen-C2osRcr5.js +2204 -0
  26. package/dist/cli/index.d.ts +493 -0
  27. package/dist/cli/index.js +184 -0
  28. package/dist/cli/init-Saf5-R9T.js +550 -0
  29. package/dist/cli/introspect--H7oGmcd.js +81 -0
  30. package/dist/cli/io-k41_z-jT.js +24 -0
  31. package/dist/cli/json5-ChRycquj.js +787 -0
  32. package/dist/cli/jsonc-BUniUpOk.js +2 -0
  33. package/dist/cli/jsonc-D5Fu67jw.js +45 -0
  34. package/dist/cli/keys-ByKHnvP9.js +83 -0
  35. package/dist/cli/ohash-D7bG_EOb.js +277 -0
  36. package/dist/cli/openapi-Cp5Fyta-.js +66 -0
  37. package/dist/cli/project-B_GeXyHh.js +70 -0
  38. package/dist/cli/prompts-BNcNfAve.js +1142 -0
  39. package/dist/cli/prompts-BTYQGunc.js +2 -0
  40. package/dist/cli/read-sets-BfMQcKy8.js +289 -0
  41. package/dist/cli/rolldown-runtime-BPOCksWG.js +24 -0
  42. package/dist/cli/run-BKR2W_79.js +351 -0
  43. package/dist/cli/seed-CUlqWh-8.js +98 -0
  44. package/dist/cli/skills-LJJIhLFR.js +137 -0
  45. package/dist/cli/sql-DzPbfngx.js +348 -0
  46. package/dist/cli/style-C0NFdJRL.js +29 -0
  47. package/dist/cli/toml-DB-MNnFs.js +424 -0
  48. package/dist/cli/version-BDJuO0UR.js +4 -0
  49. package/dist/cli/yaml-CYCbJ6et.js +2252 -0
  50. package/dist/client/index.d.ts +2 -2
  51. package/dist/client/index.js +15 -22
  52. package/dist/clone-BfgmLU90.js +21 -0
  53. package/dist/{compiler-2zqGzxVZ.d.ts → compiler-BXH_aZ7N.d.ts} +2 -2
  54. package/dist/config/index.d.ts +4 -4
  55. package/dist/config/index.js +5 -5
  56. package/dist/{config-DOsGYSiT.js → config-DreERlKo.js} +1 -1
  57. package/dist/{config-C1CYcm6o.d.ts → config-T_ejwyd3.d.ts} +176 -11
  58. package/dist/{define-DUbydmJa.d.ts → define-Bp_BZa5h.d.ts} +37 -29
  59. package/dist/{define-DF341GDj.js → define-cNKzqwL_.js} +356 -89
  60. package/dist/edge/index.d.ts +20 -13
  61. package/dist/edge/index.js +55 -26
  62. package/dist/{entitlements-DvJXoI02.d.ts → entitlements-DUxU4oHe.d.ts} +2 -2
  63. package/dist/env/index.d.ts +2 -2
  64. package/dist/env/index.js +34 -1
  65. package/dist/{errors-Bl3rkOzo.js → errors-DtwXa6b8.js} +2 -1
  66. package/dist/events/index.d.ts +3 -2
  67. package/dist/events/index.js +69 -16
  68. package/dist/events-aIO7vsIk.d.ts +230 -0
  69. package/dist/executor-BunPOO-k.d.ts +402 -0
  70. package/dist/{executor-B26lgbHD.d.ts → executor-Cghdstwu.d.ts} +1 -2
  71. package/dist/{executor-C57jx2pn.js → executor-XycESQPH.js} +3 -3
  72. package/dist/hono/index.d.ts +27 -23
  73. package/dist/hono/index.js +46 -11
  74. package/dist/impersonation-VfaGc4Q_.js +41 -0
  75. package/dist/{index-BNsABq7P.d.ts → index-BGdgBPdf.d.ts} +66 -25
  76. package/dist/{index-qriRM9cZ.d.ts → index-C7f8cOBM.d.ts} +4 -4
  77. package/dist/{index-Dy6kuF0P.d.ts → index-CLg87OBW.d.ts} +6 -1
  78. package/dist/{index-GV77NpRP.d.ts → index-DOESKG27.d.ts} +34 -9
  79. package/dist/{index-CriFFJ_p.d.ts → index-DgILJRXj.d.ts} +43 -17
  80. package/dist/{index-UJ-LUxtI.d.ts → index-DmXSxbLi.d.ts} +14 -14
  81. package/dist/{index-Cf-D2jk1.d.ts → index-Dxn2a5cw.d.ts} +2 -2
  82. package/dist/{index-CTTMS6_1.d.ts → index-SaTsEfGP.d.ts} +86 -9
  83. package/dist/{index-CADlNHK-.d.ts → index-fag8VJZ2.d.ts} +5 -6
  84. package/dist/index.d.ts +34 -17
  85. package/dist/index.js +15 -14
  86. package/dist/jobs/index.d.ts +147 -95
  87. package/dist/jobs/index.js +773 -250
  88. package/dist/{json-schema--aOXAGfU.js → json-schema-m1fiwKgN.js} +23 -2
  89. package/dist/kit-events-CTFDfSwb.js +60 -0
  90. package/dist/kit-transport-B82k1djG.d.ts +33 -0
  91. package/dist/kit-transport-DJlHQUBU.js +55 -0
  92. package/dist/{config-Be4GwsqI.js → kits-CGLLrNPd.js} +38 -10
  93. package/dist/list/index.d.ts +1 -1
  94. package/dist/list/index.js +3 -3
  95. package/dist/{live-D8rV1Ldw.js → live-CdJBOc4f.js} +13 -9
  96. package/dist/{live-QE3UPbGm.d.ts → live-D9W2M5aO.d.ts} +16 -15
  97. package/dist/lookup-BaP5bI27.js +18 -0
  98. package/dist/mcp/index.d.ts +39 -22
  99. package/dist/mcp/index.js +39 -25
  100. package/dist/{mfa-x3V3vGh-.d.ts → mfa-CusDgPaD.d.ts} +5 -1
  101. package/dist/next/image/index.js +6 -3
  102. package/dist/next/index.d.ts +60 -36
  103. package/dist/next/index.js +167 -50
  104. package/dist/notifications/index.d.ts +162 -0
  105. package/dist/notifications/index.js +264 -0
  106. package/dist/openapi/index.d.ts +1 -1
  107. package/dist/openapi/index.js +6 -6
  108. package/dist/orgs/index.d.ts +132 -0
  109. package/dist/orgs/index.js +245 -0
  110. package/dist/orpc/index.d.ts +37 -20
  111. package/dist/orpc/index.js +24 -11
  112. package/dist/otel/index.d.ts +24 -5
  113. package/dist/otel/index.js +124 -64
  114. package/dist/permdock-sql-C2Inc6Xc.d.ts +29 -0
  115. package/dist/permdock-sql-DgOfeixi.js +51 -0
  116. package/dist/plugin-DwARMuQp.js +59 -0
  117. package/dist/{plugin-DVRB8qNE.d.ts → plugin-o144f322.d.ts} +17 -5
  118. package/dist/plugins/actor/index.d.ts +5 -2
  119. package/dist/plugins/actor/index.js +22 -8
  120. package/dist/plugins/rules/index.d.ts +2 -2
  121. package/dist/plugins/rules/index.js +52 -17
  122. package/dist/plugins/soft-delete/index.d.ts +3 -3
  123. package/dist/plugins/soft-delete/index.js +14 -5
  124. package/dist/plugins/tenant/index.d.ts +3 -3
  125. package/dist/plugins/tenant/index.js +45 -21
  126. package/dist/plugins/timestamps/index.d.ts +1 -1
  127. package/dist/plugins/timestamps/index.js +4 -3
  128. package/dist/plugins/validation/index.d.ts +2 -2
  129. package/dist/plugins/validation/index.js +26 -6
  130. package/dist/{pool-B9qsLyFR.d.ts → pool-C9voM4Kw.d.ts} +24 -6
  131. package/dist/postgres/index.d.ts +5 -5
  132. package/dist/postgres/index.js +20 -10
  133. package/dist/{postgrest-DmBlevh-.d.ts → postgrest-CruYKAvV.d.ts} +1 -1
  134. package/dist/{problem-CGt1ADix.js → problem-BW1KhzSG.js} +2 -1
  135. package/dist/{problem-Z2RTQjZD.d.ts → problem-Blxe8S31.d.ts} +1 -1
  136. package/dist/query/index.d.ts +2 -2
  137. package/dist/query/index.js +3 -3
  138. package/dist/{query-BdKLTPlj.js → query-CCbo44lG.js} +18 -18
  139. package/dist/queue-BYUCjr59.d.ts +252 -0
  140. package/dist/react/index.d.ts +8 -7
  141. package/dist/react/index.js +4 -3
  142. package/dist/react/server.d.ts +16 -5
  143. package/dist/react/server.js +12 -4
  144. package/dist/react/session.d.ts +10 -4
  145. package/dist/react/session.js +11 -3
  146. package/dist/{react-DNBYNazu.js → react-C_s7kiNz.js} +138 -46
  147. package/dist/{read-set-D0qBcboM.js → read-set-DQFt1N_Y.js} +71 -15
  148. package/dist/realtime/index.d.ts +3 -3
  149. package/dist/realtime/index.js +14 -12
  150. package/dist/{index-kpgopLNv.d.ts → refresh-BTrQ_ZTp.d.ts} +6 -3
  151. package/dist/{resolve-BRciX2jK.d.ts → resolve-C15TJq77.d.ts} +22 -11
  152. package/dist/{resource-D30rp0GN.js → resource-CRxxwkQq.js} +24 -9
  153. package/dist/{resource-CwVgiI5q.d.ts → resource-D8iqWMr-.d.ts} +6 -6
  154. package/dist/respond-D2OQl62-.d.ts +371 -0
  155. package/dist/{result-DWXatkd6.js → result-BKiliPY7.js} +2 -2
  156. package/dist/server/index.d.ts +17 -15
  157. package/dist/server/index.js +16 -22
  158. package/dist/server-Csx2sRjg.js +655 -0
  159. package/dist/{shared-_6-uPL7E.js → shared-D1dRUrAg.js} +19 -4
  160. package/dist/signers-Cc6r98SW.d.ts +162 -0
  161. package/dist/{simplify-LakqK1vH.js → simplify-_0oUKqCd.js} +27 -95
  162. package/dist/sql/index.d.ts +247 -10
  163. package/dist/sql/index.js +8591 -780
  164. package/dist/{sql-BIgk12ew.js → sql-Coh6n4cC.js} +11 -3
  165. package/dist/ssr/index.d.ts +4 -4
  166. package/dist/ssr/index.js +1 -2
  167. package/dist/{standard-FUi3Z3-7.js → standard-D7r6dMUu.js} +6 -3
  168. package/dist/{standard-eudBiRk1.d.ts → standard-J4YgMOLm.d.ts} +1 -1
  169. package/dist/{stats-D78UQ8Yu.js → stats-Bqob3WqT.js} +34 -9
  170. package/dist/storage/index.d.ts +4 -4
  171. package/dist/storage/index.js +174 -27
  172. package/dist/support-DT-OhSxD.d.ts +88 -0
  173. package/dist/support-Dk6vdmEw.js +291 -0
  174. package/dist/support-view-Da5peKFg.d.ts +17 -0
  175. package/dist/support-view-g4YLBeqX.js +18 -0
  176. package/dist/{tables-Dhha3BXM.js → tables-Cb-3_VWc.js} +14 -0
  177. package/dist/{permdock-sql-Bta_G0v-.js → template-TxrGI5jR.js} +2 -31
  178. package/dist/{temporal-BwshQk0W.js → temporal-CVlIV2DZ.js} +17 -4
  179. package/dist/{temporal-required-CMxeSVPo.js → temporal-required-CAt4Dc9Z.js} +2 -2
  180. package/dist/testing/index.d.ts +113 -24
  181. package/dist/testing/index.js +375 -47
  182. package/dist/{types-DONv9SHN.d.ts → types-Dnf_wRDz.d.ts} +26 -3
  183. package/dist/user-change-CHFEUro1.js +49 -0
  184. package/dist/{webhooks-CAIFQSTM.js → verify-Ce49ETPz.js} +38 -19
  185. package/dist/{version-BfpG2gks.js → version-DM_Roffb.js} +1 -1
  186. package/dist/{view-3Po_PVlr.d.ts → view-BEf0diC5.d.ts} +10 -5
  187. package/dist/{mfa-BxoR1RkN.js → view-Bq64ki8-.js} +210 -64
  188. package/dist/webhooks/index.d.ts +136 -5
  189. package/dist/webhooks/index.js +588 -2
  190. package/dist/wire-D2dT86vi.js +35 -0
  191. package/package.json +50 -10
  192. package/schemas/config-v1.json +131 -1
  193. package/schemas/doctor-report-v1.json +10 -0
  194. package/skills/better-supabase/SKILL.md +60 -10
  195. package/skills/better-supabase/references/plugins.md +13 -14
  196. package/skills/better-supabase/references/troubleshooting.md +25 -4
  197. package/skills/better-supabase-api/SKILL.md +74 -19
  198. package/skills/better-supabase-api/references/adapters.md +48 -27
  199. package/skills/better-supabase-auth/SKILL.md +149 -0
  200. package/skills/better-supabase-auth/references/permdock.md +100 -0
  201. package/skills/better-supabase-testing/SKILL.md +58 -4
  202. package/dist/claims-CoTobNC-.js +0 -25
  203. package/dist/events-Do8jXh4D.d.ts +0 -84
  204. package/dist/executor-DynmIWhq.d.ts +0 -195
  205. package/dist/invalidate-DbxuL2jL.js +0 -18
  206. package/dist/permdock-sql-DImJULY-.d.ts +0 -17
  207. package/dist/plugin-BqR6wKMB.js +0 -23
  208. package/dist/respond-C44EZAJo.js +0 -399
  209. package/dist/respond-DAkPIdTH.d.ts +0 -175
  210. package/dist/result-c2OYgCV2.d.ts +0 -203
  211. package/dist/view-wZ-9qIUz.js +0 -45
@@ -1,66 +1,60 @@
1
- import { i as tenantClaimPaths, n as claimAt, r as claimsOf } from "./claims-CoTobNC-.js";
2
- import { t as invalidateTables } from "./invalidate-DbxuL2jL.js";
3
- import { n as liveQuery, t as liveCount } from "./live-D8rV1Ldw.js";
1
+ import { i as tenantClaimPaths, n as claimAt, r as claimsOf } from "./claims-BAf6c7is.js";
2
+ import { n as invalidateTables, t as clearOnUserChange } from "./user-change-CHFEUro1.js";
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
- const BrowserContext = createContext(null);
8
+ const ClientContext = createContext(null);
8
9
  const LOADING = {
9
10
  status: "loading",
10
11
  user: null,
11
12
  claims: null
12
13
  };
13
- /** Provides the browser client to the hooks. */
14
+ /** Provides the better-supabase client to the hooks. */
14
15
  function BetterSupabaseProvider(props) {
15
- const { browser, queryClient } = props;
16
- const user = useRef(void 0);
17
- useEffect(() => browser.auth.subscribe(() => {
18
- const snapshot = browser.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
- }), [browser, queryClient]);
16
+ const { client, queryClient } = props;
17
+ useEffect(() => queryClient ? clearOnUserChange(queryClient, client.auth) : void 0, [client, queryClient]);
24
18
  const value = useMemo(() => ({
25
- browser,
19
+ client,
26
20
  queryClient
27
- }), [browser, queryClient]);
28
- return createElement(BrowserContext.Provider, { value }, props.children);
21
+ }), [client, queryClient]);
22
+ return createElement(ClientContext.Provider, { value }, props.children);
29
23
  }
30
- function useBrowserContext() {
31
- const value = useContext(BrowserContext);
32
- if (!value) throw new Error("better-supabase: wrap your app in <BetterSupabaseProvider browser={browser}>");
24
+ function useClientContext() {
25
+ const value = useContext(ClientContext);
26
+ if (!value) throw new Error("better-supabase: wrap your app in <BetterSupabaseProvider client={bs}>");
33
27
  return value;
34
28
  }
35
- function useBrowser() {
36
- return useBrowserContext().browser;
29
+ function useClient() {
30
+ return useClientContext().client;
37
31
  }
38
32
  /** The session state for UI: `loading`, `signed-out` or `signed-in` with the user and claims. */
39
33
  function useAuth() {
40
- const browser = useBrowser();
41
- return useSyncExternalStore(browser.auth.subscribe, browser.auth.current, () => LOADING);
34
+ const client = useClient();
35
+ return useSyncExternalStore(client.auth.subscribe, client.auth.current, () => LOADING);
42
36
  }
43
37
  function useSupabase() {
44
- return useBrowser().supabase;
38
+ return useClient().supabase;
45
39
  }
46
40
  /**
47
41
  * Hooks typed for your schema.
48
42
  *
49
43
  * ```ts
50
- * export const { useDb, useQueries, useAuth } = createHooks<typeof browser>();
44
+ * export const { useDb, useQueries, useAuth } = createHooks<typeof bs>();
51
45
  * ```
52
46
  */
53
47
  function createHooks() {
54
48
  return {
55
49
  useDb() {
56
- const browser = useBrowser();
50
+ const client = useClient();
57
51
  useAuth();
58
- return browser.db;
52
+ return client.db;
59
53
  },
60
54
  useQueries() {
61
- const browser = useBrowser();
55
+ const client = useClient();
62
56
  useAuth();
63
- return browser.queries;
57
+ return client.queries;
64
58
  },
65
59
  useSupabase,
66
60
  useAuth,
@@ -76,7 +70,7 @@ function createHooks() {
76
70
  * ```
77
71
  */
78
72
  function useBroadcast(topic, values, handlers, options) {
79
- const { browser, queryClient } = useBrowserContext();
73
+ const { client, queryClient } = useClientContext();
80
74
  const auth = useAuth();
81
75
  const [status, setStatus] = useState("closed");
82
76
  const latest = useRef({
@@ -106,7 +100,7 @@ function useBroadcast(topic, values, handlers, options) {
106
100
  }
107
101
  for (const queryKey of invalidate(message)) queryClient.invalidateQueries({ queryKey });
108
102
  };
109
- const subscription = topic.subscribe(browser.supabase, matched, { "*": forward }, {
103
+ const subscription = topic.subscribe(client.supabase, matched, { "*": forward }, {
110
104
  ...latest.current.options?.self === void 0 ? {} : { self: latest.current.options.self },
111
105
  onStatus: setStatus,
112
106
  onInvalid: (message, issues) => latest.current.options?.onInvalid?.(message, issues)
@@ -116,7 +110,7 @@ function useBroadcast(topic, values, handlers, options) {
116
110
  setStatus("closed");
117
111
  };
118
112
  }, [
119
- browser,
113
+ client,
120
114
  topic,
121
115
  name,
122
116
  userId,
@@ -138,23 +132,23 @@ function claimedTenant(auth, meta) {
138
132
  * resubscribes when the spec, tenant or user changes.
139
133
  *
140
134
  * ```ts
141
- * const spec = sb.spec.customers.findMany({ include: { notes: true } });
135
+ * const spec = betterSupabase.spec.customers.findMany({ include: { notes: true } });
142
136
  * const { data } = useQuery(q.$spec(spec));
143
137
  * useLiveQuery(spec);
144
138
  * ```
145
139
  */
146
140
  function useLiveQuery(spec, options = {}) {
147
- const { browser, queryClient } = useBrowserContext();
141
+ const { client, queryClient } = useClientContext();
148
142
  const auth = useAuth();
149
143
  const [status, setStatus] = useState("closed");
150
144
  if (spec && !queryClient) throw new Error("better-supabase: useLiveQuery needs <BetterSupabaseProvider queryClient={...}>");
151
145
  const key = spec ? JSON.stringify(spec) : null;
152
- const tenant = options.tenant ?? claimedTenant(auth, browser.sb.meta);
146
+ const tenant = options.tenant ?? claimedTenant(auth, client.betterSupabase.meta);
153
147
  const userId = auth.user?.id ?? null;
154
148
  const debounceMs = options.debounceMs;
155
149
  useEffect(() => {
156
150
  if (!key || !queryClient || auth.status === "loading") return;
157
- const live = liveQuery(browser.sb, browser.supabase, JSON.parse(key), {
151
+ const live = liveQuery(client.betterSupabase, client.supabase, JSON.parse(key), {
158
152
  onChange: (tables) => void invalidateTables(queryClient, tables),
159
153
  onStatus: setStatus,
160
154
  ...tenant === void 0 ? {} : { tenant },
@@ -164,7 +158,7 @@ function useLiveQuery(spec, options = {}) {
164
158
  live.unsubscribe();
165
159
  };
166
160
  }, [
167
- browser,
161
+ client,
168
162
  queryClient,
169
163
  key,
170
164
  tenant,
@@ -177,15 +171,15 @@ function useLiveQuery(spec, options = {}) {
177
171
  /**
178
172
  * A count that stays current: refetches only the `count` spec (a HEAD
179
173
  * request) after each debounced change to a table it reads, and after the
180
- * channel rejoins. Takes a spec or a `next.liveCount()` seed; pass `null` to
174
+ * channel rejoins. Takes a spec or a `bs.liveCount()` seed; pass `null` to
181
175
  * pause. Doesn't need a `QueryClient`.
182
176
  *
183
177
  * ```tsx
184
- * const { count } = useLiveCount(seed); // seed = await next.liveCount(spec)
178
+ * const { count } = useLiveCount(seed); // seed = await bs.liveCount(spec)
185
179
  * ```
186
180
  */
187
181
  function useLiveCount(source, options = {}) {
188
- const { browser } = useBrowserContext();
182
+ const { client } = useClientContext();
189
183
  const auth = useAuth();
190
184
  const seed = source && "spec" in source ? source : void 0;
191
185
  const spec = seed ? seed.spec : source;
@@ -197,13 +191,13 @@ function useLiveCount(source, options = {}) {
197
191
  });
198
192
  const [status, setStatus] = useState("closed");
199
193
  const key = spec ? JSON.stringify(spec) : null;
200
- const tenant = options.tenant ?? claimedTenant(auth, browser.sb.meta);
194
+ const tenant = options.tenant ?? claimedTenant(auth, client.betterSupabase.meta);
201
195
  const userId = auth.user?.id ?? null;
202
196
  const debounceMs = options.debounceMs;
203
197
  const hasInitial = initial !== void 0;
204
198
  useEffect(() => {
205
199
  if (!key || auth.status === "loading") return;
206
- const live = liveCount(browser.sb, browser.supabase, browser.db, JSON.parse(key), {
200
+ const live = liveCount(client.betterSupabase, client.supabase, client.db, JSON.parse(key), {
207
201
  immediate: !hasInitial,
208
202
  onCount: (count) => {
209
203
  setState({
@@ -227,7 +221,7 @@ function useLiveCount(source, options = {}) {
227
221
  live.unsubscribe();
228
222
  };
229
223
  }, [
230
- browser,
224
+ client,
231
225
  key,
232
226
  tenant,
233
227
  userId,
@@ -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 };
@@ -1,5 +1,5 @@
1
- import { r as IrBuilder, u as tableMeta } from "./simplify-LakqK1vH.js";
2
- import { n as touchedTables } from "./tables-Dhha3BXM.js";
1
+ import { r as builderFor, s as tableMeta } from "./simplify-_0oUKqCd.js";
2
+ import { n as touchedTables } from "./tables-Cb-3_VWc.js";
3
3
  //#region src/core/spec.ts
4
4
  const READ_METHODS = [
5
5
  "findMany",
@@ -40,7 +40,6 @@ function specOptions(spec) {
40
40
  const value = spec.method === "findById" ? spec.args[1] : spec.args[0];
41
41
  return typeof value === "object" && value !== null ? value : void 0;
42
42
  }
43
- const builders = /* @__PURE__ */ new WeakMap();
44
43
  /**
45
44
  * App keys of every table a spec reads: its table, included relations and
46
45
  * relations used in `where`. Queries keyed by a spec go stale when any of
@@ -48,18 +47,54 @@ const builders = /* @__PURE__ */ new WeakMap();
48
47
  * spec reports the error.
49
48
  */
50
49
  function specTables(meta, spec) {
50
+ const table = meta.tables[spec.table];
51
+ const args = specOptions(spec);
52
+ if (table && args?.["include"] === void 0 && !touchesRelations(table, args?.["where"])) return [spec.table];
53
+ const key = specKey(spec);
54
+ let byKey = tablesByMeta.get(meta);
55
+ if (!byKey) {
56
+ byKey = /* @__PURE__ */ new Map();
57
+ tablesByMeta.set(meta, byKey);
58
+ }
59
+ const known = key === void 0 ? void 0 : byKey.get(key);
60
+ if (known) return [...known];
61
+ let tables;
51
62
  try {
52
- return readTables(meta, spec);
63
+ tables = readTables(meta, spec);
53
64
  } catch {
54
- return [spec.table];
65
+ tables = [spec.table];
55
66
  }
67
+ if (key !== void 0) {
68
+ if (byKey.size >= SPEC_CACHE_SIZE) byKey.clear();
69
+ byKey.set(key, tables);
70
+ }
71
+ return [...tables];
56
72
  }
57
- function readTables(meta, spec) {
58
- let builder = builders.get(meta);
59
- if (!builder) {
60
- builder = new IrBuilder(meta);
61
- builders.set(meta, builder);
73
+ const SPEC_CACHE_SIZE = 500;
74
+ const tablesByMeta = /* @__PURE__ */ new WeakMap();
75
+ /** A text key for the spec, or `undefined` when its arguments do not serialize. */
76
+ function specKey(spec) {
77
+ try {
78
+ return JSON.stringify([
79
+ spec.table,
80
+ spec.method,
81
+ spec.args
82
+ ], (_key, value) => typeof value === "bigint" ? `${value}n` : value);
83
+ } catch {
84
+ return;
85
+ }
86
+ }
87
+ function touchesRelations(table, where) {
88
+ if (typeof where !== "object" || where === null) return false;
89
+ if (Array.isArray(where)) return where.some((item) => touchesRelations(table, item));
90
+ for (const [key, value] of Object.entries(where)) {
91
+ if (key in table.relations) return true;
92
+ if ((key === "AND" || key === "OR" || key === "NOT") && touchesRelations(table, value)) return true;
62
93
  }
94
+ return false;
95
+ }
96
+ function readTables(meta, spec) {
97
+ const builder = builderFor(meta);
63
98
  const table = tableMeta(meta, spec.table);
64
99
  const args = specOptions(spec);
65
100
  const counts = spec.method === "count" || spec.method === "exists" || spec.method === "aggregate";
@@ -98,7 +133,7 @@ function sentinel(name) {
98
133
  *
99
134
  * ```ts
100
135
  * export const appChrome = defineReadSet(
101
- * sb,
136
+ * betterSupabase,
102
137
  * 'app_chrome',
103
138
  * { params: { userId: 'uuid' } },
104
139
  * (s, p) => ({
@@ -111,7 +146,7 @@ function sentinel(name) {
111
146
  * `p` holds placeholders, not values: pass them straight into `where` (or
112
147
  * `in`), where the function reads them from its `p jsonb` argument.
113
148
  */
114
- function defineReadSet(sb, name, options, build) {
149
+ function defineReadSet(betterSupabase, name, options, build) {
115
150
  if (!NAME.test(name)) throw new TypeError(`better-supabase: read set name "${name}" must be snake_case and at most 60 characters`);
116
151
  const params = options.params ?? {};
117
152
  const placeholders = {};
@@ -120,10 +155,11 @@ function defineReadSet(sb, name, options, build) {
120
155
  if (!TYPE.test(type)) throw new TypeError(`better-supabase: read set "${name}" parameter "${key}" has an invalid type "${type}"`);
121
156
  placeholders[key] = type.endsWith("[]") ? [sentinel(key)] : sentinel(key);
122
157
  }
123
- const specs = build(sb.spec, placeholders);
158
+ const specs = build(betterSupabase.spec, placeholders);
124
159
  const entries = Object.entries(specs);
125
160
  if (entries.length === 0 || entries.length > MAX_ENTRIES) throw new TypeError(`better-supabase: read set "${name}" needs between 1 and ${MAX_ENTRIES} reads`);
126
- for (const [key, spec] of entries) if (!isQuerySpec(spec)) throw new TypeError(`better-supabase: read set "${name}" entry "${key}" is not a spec from sb.spec`);
161
+ for (const [key, spec] of entries) if (!isQuerySpec(spec)) throw new TypeError(`better-supabase: read set "${name}" entry "${key}" is not a spec from betterSupabase.spec`);
162
+ warnUnscopedReads(betterSupabase, name, specs);
127
163
  return {
128
164
  kind: "read-set",
129
165
  name,
@@ -131,9 +167,29 @@ function defineReadSet(sb, name, options, build) {
131
167
  params,
132
168
  roles: options.roles ?? ["authenticated"],
133
169
  specs,
134
- definition: sb
170
+ definition: betterSupabase
135
171
  };
136
172
  }
173
+ /**
174
+ * The generated function runs the reads as compiled at `gen` time, without
175
+ * the runtime's `transformQuery` plugins, so tenant and soft-delete filters
176
+ * that only those plugins add never reach it.
177
+ */
178
+ function warnUnscopedReads(betterSupabase, name, specs) {
179
+ const scoping = betterSupabase.plugins.filter((plugin) => plugin.transformQuery !== void 0 && plugin.name !== "rules").map((plugin) => plugin.name);
180
+ if (scoping.length === 0) return;
181
+ const { meta } = betterSupabase;
182
+ const tables = [...new Set(Object.values(specs).flatMap((spec) => specTables(meta, spec)))].filter((key) => {
183
+ const flags = meta.tables[key]?.flags;
184
+ return flags?.tenant !== void 0 || flags?.softDelete !== void 0;
185
+ });
186
+ if (tables.length === 0) return;
187
+ betterSupabase.events.logger.warn(`read set "${name}" reads ${tables.join(", ")}, which ${scoping.join(", ")} scope at runtime; its generated function only sees those filters when the specs spell them out (or RLS enforces them)`, {
188
+ readSet: name,
189
+ tables,
190
+ plugins: scoping
191
+ });
192
+ }
137
193
  function isReadSet(value) {
138
194
  return typeof value === "object" && value !== null && value.kind === "read-set" && typeof value.name === "string";
139
195
  }
@@ -1,5 +1,5 @@
1
- import { x as PermdockTopicPolicy } from "../types-DONv9SHN.js";
2
- import { t as PermdockCatalog } from "../permdock-sql-DImJULY-.js";
1
+ import { S as PermdockTopicPolicy } from "../types-Dnf_wRDz.js";
2
+ import { n as PermdockCatalog } from "../permdock-sql-C2Inc6Xc.js";
3
3
  import { n as TemplateValues, t as TemplateParams } from "../template-mbvlysOo.js";
4
- import { C as defineTopic, S as TriggerOptions, _ as TopicEvent, a as LiveSource, b as TopicOptions, c as liveQuery, d as RealtimeClient, f as RowChange, g as Topic, h as SubscriptionStatus, i as LiveQueryOptions, l as liveTopic, m as Subscription, n as LiveCountOptions, o as LiveSubscription, p as SubscribeOptions, r as LiveCountSeed, s as liveCount, t as CountRunner, u as EventSchemas, v as TopicHandlers, w as rowChange, x as TopicPayload, y as TopicMessage } from "../live-QE3UPbGm.js";
4
+ import { C as defineTopic, S as TriggerOptions, _ as TopicEvent, a as LiveSource, b as TopicOptions, c as liveQuery, d as RealtimeClient, f as RowChange, g as Topic, h as SubscriptionStatus, i as LiveQueryOptions, l as liveTopic, m as Subscription, n as LiveCountOptions, o as LiveSubscription, p as SubscribeOptions, r as LiveCountSeed, s as liveCount, t as CountRunner, u as EventSchemas, v as TopicHandlers, w as rowChange, x as TopicPayload, y as TopicMessage } from "../live-D9W2M5aO.js";
5
5
  export { type CountRunner, type EventSchemas, type LiveCountOptions, type LiveCountSeed, type LiveQueryOptions, type LiveSource, type LiveSubscription, type PermdockCatalog, type PermdockTopicPolicy, type RealtimeClient, type RowChange, type SubscribeOptions, type Subscription, type SubscriptionStatus, type TemplateParams, type TemplateValues, type Topic, type TopicEvent, type TopicHandlers, type TopicMessage, type TopicOptions, type TopicPayload, type TriggerOptions, defineTopic, liveCount, liveQuery, liveTopic, rowChange };
@@ -1,9 +1,10 @@
1
- import { n as dbError } from "../errors-Bl3rkOzo.js";
2
- import { i as ok, n as err, t as AsyncResult } from "../result-DWXatkd6.js";
3
- import { i as tenantClaimPaths } from "../claims-CoTobNC-.js";
4
- import { n as liveQuery, r as liveTopic, t as liveCount } from "../live-D8rV1Ldw.js";
5
- import { s as toApp } from "../shared-_6-uPL7E.js";
6
- import { a as sqlIdent, i as slug, o as sqlString, r as parseTemplate, t as permdockCheck } from "../permdock-sql-Bta_G0v-.js";
1
+ import { n as dbError } from "../errors-DtwXa6b8.js";
2
+ import { i as ok, n as err, t as AsyncResult } from "../result-BKiliPY7.js";
3
+ import { i as tenantClaimPaths } from "../claims-BAf6c7is.js";
4
+ import { n as liveQuery, r as liveTopic, t as liveCount } from "../live-CdJBOc4f.js";
5
+ import { i as sqlString, n as slug, r as sqlIdent, t as parseTemplate } from "../template-TxrGI5jR.js";
6
+ import { c as toApp } from "../shared-D1dRUrAg.js";
7
+ import { n as permdockCheck } from "../permdock-sql-DgOfeixi.js";
7
8
  import { REALTIME_SUBSCRIBE_STATES } from "@supabase/supabase-js";
8
9
  //#region src/realtime/topic.ts
9
10
  const VALUE = /^[\w.@+=-]+$/;
@@ -119,8 +120,8 @@ function defineTopic(template, options = {}) {
119
120
  }
120
121
  return `${lines.join("\n")}\n`;
121
122
  },
122
- triggerSql(sb, table, trigger) {
123
- const meta = sb.meta.tables[table];
123
+ triggerSql(betterSupabase, table, trigger) {
124
+ const meta = betterSupabase.meta.tables[table];
124
125
  if (!meta) throw new TypeError(`defineTopic: unknown table "${table}"`);
125
126
  const values = trigger.values;
126
127
  const parts = template.split(/(\{[^}]+\})/).filter(Boolean).map((part) => {
@@ -236,13 +237,14 @@ function defineTopic(template, options = {}) {
236
237
  const topic = topicOf(values);
237
238
  const checked = await validate(schemas[event], payload);
238
239
  if (!checked.ok) return err(dbError("validation", `Invalid payload for ${event}`, { issues: checked.issues }));
239
- if (isPrivate) await client.realtime.setAuth();
240
+ const open = client.getChannels().some((existing) => existing.topic === `realtime:${topic}`);
241
+ if (isPrivate && !open) await client.realtime.setAuth();
240
242
  const channel = client.channel(topic, { config: { private: isPrivate } });
241
243
  try {
242
244
  const response = await channel.httpSend(event, checked.value);
243
245
  return response.success ? ok(void 0) : err(sendError(response.status, response.error));
244
246
  } finally {
245
- await client.removeChannel(channel);
247
+ if (!open) await client.removeChannel(channel);
246
248
  }
247
249
  });
248
250
  }
@@ -252,8 +254,8 @@ function defineTopic(template, options = {}) {
252
254
  * Reads a `realtime.broadcast_changes` message as an app-cased row change.
253
255
  * Returns `null` for messages about other tables.
254
256
  */
255
- function rowChange(sb, table, message) {
256
- const meta = sb.meta.tables[table];
257
+ function rowChange(betterSupabase, table, message) {
258
+ const meta = betterSupabase.meta.tables[table];
257
259
  const payload = message.payload;
258
260
  if (!meta || !payload || payload.table !== meta.name || payload.schema && payload.schema !== meta.schema) return null;
259
261
  const operation = payload.operation ?? message.event;
@@ -1,6 +1,4 @@
1
- import { m as StoredSession } from "./resolve-BRciX2jK.js";
2
- import "./view-3Po_PVlr.js";
3
- import "./entitlements-DvJXoI02.js";
1
+ import { m as StoredSession } from "./resolve-C15TJq77.js";
4
2
  //#region src/auth/refresh.d.ts
5
3
  interface RefreshOptions {
6
4
  readonly url: string;
@@ -14,6 +12,11 @@ interface RefreshOptions {
14
12
  readonly secretKey: string;
15
13
  };
16
14
  readonly fetch?: typeof fetch;
15
+ /**
16
+ * Milliseconds before a refresh counts as a network failure (the session is
17
+ * kept). 5000 by default.
18
+ */
19
+ readonly timeoutMs?: number;
17
20
  /** Epoch milliseconds, the unit JWT `exp` and `iat` math needs. */
18
21
  readonly now?: () => number;
19
22
  }
@@ -1,8 +1,8 @@
1
- import { p as DbError } from "./result-c2OYgCV2.js";
2
- import { f as Logger, l as RefreshEvent } from "./events-Do8jXh4D.js";
3
- import { h as RequestContext } from "./plugin-DVRB8qNE.js";
4
- import { t as StandardSchemaV1 } from "./standard-eudBiRk1.js";
5
- import { t as BetterSupabaseEnv } from "./index-Dy6kuF0P.js";
1
+ import { _ as DbError } from "./executor-BunPOO-k.js";
2
+ import { E as Logger, l as RefreshEvent } from "./events-aIO7vsIk.js";
3
+ import { g as RequestContext } from "./plugin-o144f322.js";
4
+ import { t as StandardSchemaV1 } from "./standard-J4YgMOLm.js";
5
+ import { t as BetterSupabaseEnv } from "./index-CLg87OBW.js";
6
6
  import { CookieOptions } from "@supabase/ssr";
7
7
  import { JWTClaims, SupabaseEnv, UserClaims } from "@supabase/server";
8
8
  //#region src/auth/session.d.ts
@@ -46,9 +46,9 @@ declare function applyCookieWrites(cookies: readonly CookieRecord[], writes: rea
46
46
  //#endregion
47
47
  //#region src/auth/resolve.d.ts
48
48
  /**
49
- * Who is calling. `C` is the claims type from `sb.claims(schema)`; the
49
+ * Who is calling. `C` is the claims type from `betterSupabase.claims(schema)`; the
50
50
  * verified payload keeps every JWT claim and adds the schema's output. `P`
51
- * is the `user_metadata` type from `sb.userMetadata(schema)`.
51
+ * is the `user_metadata` type from `betterSupabase.userMetadata(schema)`.
52
52
  */
53
53
  type AuthState<C = unknown, P = unknown> = {
54
54
  readonly kind: "user";
@@ -59,7 +59,7 @@ type AuthState<C = unknown, P = unknown> = {
59
59
  /** Seconds since epoch, from the token's `exp`. */
60
60
  readonly expiresAt: number | null;
61
61
  /**
62
- * `user_metadata` parsed by `sb.userMetadata(schema)`; `undefined`
62
+ * `user_metadata` parsed by `betterSupabase.userMetadata(schema)`; `undefined`
63
63
  * without a schema or when the metadata fails it. Users can write this
64
64
  * data themselves: use it for display, never for access.
65
65
  */
@@ -101,8 +101,13 @@ interface ResolveAuthOptions {
101
101
  readonly env: BetterSupabaseEnv;
102
102
  /** Allow refreshing an expiring cookie session. Only where cookies can be written (proxy). */
103
103
  readonly refresh?: boolean;
104
- /** Refresh when the token expires within this many seconds. Defaults to 60. */
104
+ /**
105
+ * With `refresh`, refresh when the token expires within this many seconds.
106
+ * Defaults to 60. Without `refresh` a token stays valid until its `exp`.
107
+ */
105
108
  readonly leeway?: number;
109
+ /** Milliseconds before a refresh counts as a network failure. Defaults to 5000. */
110
+ readonly refreshTimeoutMs?: number;
106
111
  /**
107
112
  * Accept `sb_secret_` keys (`apikey` header) as service callers: `true` for
108
113
  * any configured key, or the key names allowed (`['cron']`). Defaults to false.
@@ -114,15 +119,21 @@ interface ResolveAuthOptions {
114
119
  readonly name?: string;
115
120
  readonly options?: CookieOptions;
116
121
  };
122
+ /**
123
+ * Read the session cookie. Defaults to true. `false` suits endpoints only
124
+ * bearer clients call (MCP): a browser's cookie then can't authorize a
125
+ * request another site makes it send.
126
+ */
127
+ readonly cookies?: boolean;
117
128
  /** Inline JWKS instead of fetching `env.jwksUrl` (tests, air-gapped). */
118
129
  readonly jwks?: SupabaseEnv["jwks"];
119
130
  /**
120
- * Validates the verified claims (`sb.claims(schema)` sets it). A failure
131
+ * Validates the verified claims (`betterSupabase.claims(schema)` sets it). A failure
121
132
  * resolves to `{ kind: 'invalid', reason: 'claims' }`.
122
133
  */
123
134
  readonly claims?: StandardSchemaV1;
124
135
  /**
125
- * Parses `user_metadata` into `profile` (`sb.userMetadata(schema)` sets
136
+ * Parses `user_metadata` into `profile` (`betterSupabase.userMetadata(schema)` sets
126
137
  * it). A failure leaves `profile` undefined and warns once; the session
127
138
  * stays valid.
128
139
  */