better-supabase 0.0.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (157) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +4 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/claims-CoTobNC-.js +25 -0
  6. package/dist/cli/bin.d.ts +1 -0
  7. package/dist/cli/bin.js +15 -0
  8. package/dist/cli/index.d.ts +226 -0
  9. package/dist/cli/index.js +3 -0
  10. package/dist/cli-DrSgDOQC.js +6646 -0
  11. package/dist/client/index.d.ts +2 -0
  12. package/dist/client/index.js +112 -0
  13. package/dist/compiler-CZ9nIuqy.d.ts +14 -0
  14. package/dist/config/index.d.ts +3 -0
  15. package/dist/config/index.js +3 -0
  16. package/dist/config-ClDXmVtY.js +277 -0
  17. package/dist/define-BXMYD9AY.js +1071 -0
  18. package/dist/define-e3zI4_4g.d.ts +679 -0
  19. package/dist/edge/index.d.ts +43 -0
  20. package/dist/edge/index.js +103 -0
  21. package/dist/entitlements-CTEBm7IB.js +20 -0
  22. package/dist/entitlements-Dng-ctuT.d.ts +42 -0
  23. package/dist/env/index.d.ts +2 -0
  24. package/dist/env/index.js +2 -0
  25. package/dist/env-BEToab0I.js +204 -0
  26. package/dist/errors-Bl3rkOzo.js +196 -0
  27. package/dist/events/index.d.ts +2 -0
  28. package/dist/events/index.js +2 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/events-DOt3nSO-.d.ts +84 -0
  31. package/dist/executor-BYXQ9AGn.d.ts +25 -0
  32. package/dist/executor-CGdNQ27g.d.ts +195 -0
  33. package/dist/executor-DeppvirK.js +89 -0
  34. package/dist/guards-BnzDvFrz.js +10 -0
  35. package/dist/hono/index.d.ts +45 -0
  36. package/dist/hono/index.js +70 -0
  37. package/dist/index-Bw1bwtC0.d.ts +144 -0
  38. package/dist/index-CBDufH1M.d.ts +120 -0
  39. package/dist/index-CG69s15v.d.ts +38 -0
  40. package/dist/index-Ciu5Xlml.d.ts +74 -0
  41. package/dist/index-D4v7sncu.d.ts +62 -0
  42. package/dist/index-DOsjFvMf.d.ts +618 -0
  43. package/dist/index-DbxFr7XG.d.ts +76 -0
  44. package/dist/index-DuP14VHo.d.ts +216 -0
  45. package/dist/index-Dy6kuF0P.d.ts +71 -0
  46. package/dist/index-K-3WM2Hp.d.ts +117 -0
  47. package/dist/index.d.ts +110 -0
  48. package/dist/index.js +43 -0
  49. package/dist/invalidate-DbxuL2jL.js +18 -0
  50. package/dist/jobs/index.d.ts +173 -0
  51. package/dist/jobs/index.js +478 -0
  52. package/dist/json-schema-BpYqkCKf.js +182 -0
  53. package/dist/lint/index.d.ts +75 -0
  54. package/dist/lint/index.js +167 -0
  55. package/dist/list/index.d.ts +2 -0
  56. package/dist/list/index.js +410 -0
  57. package/dist/live-D8rV1Ldw.js +168 -0
  58. package/dist/live-DJyZMD6w.d.ts +191 -0
  59. package/dist/logger-glM28NVN.js +21 -0
  60. package/dist/mcp/index.d.ts +142 -0
  61. package/dist/mcp/index.js +463 -0
  62. package/dist/mfa-CpLfJSde.js +541 -0
  63. package/dist/mfa-TVia554c.d.ts +44 -0
  64. package/dist/next/image/index.d.ts +34 -0
  65. package/dist/next/image/index.js +40 -0
  66. package/dist/next/index.d.ts +210 -0
  67. package/dist/next/index.js +427 -0
  68. package/dist/openapi/index.d.ts +2 -0
  69. package/dist/openapi/index.js +332 -0
  70. package/dist/orpc/index.d.ts +46 -0
  71. package/dist/orpc/index.js +50 -0
  72. package/dist/otel/index.d.ts +40 -0
  73. package/dist/otel/index.js +168 -0
  74. package/dist/path-Cv__-OTj.d.ts +17 -0
  75. package/dist/permdock-sql-CghlHPuH.js +117 -0
  76. package/dist/plugin-BqR6wKMB.js +23 -0
  77. package/dist/plugin-C4TR9J0Z.d.ts +118 -0
  78. package/dist/plugins/actor/index.d.ts +13 -0
  79. package/dist/plugins/actor/index.js +43 -0
  80. package/dist/plugins/rules/index.d.ts +89 -0
  81. package/dist/plugins/rules/index.js +167 -0
  82. package/dist/plugins/soft-delete/index.d.ts +38 -0
  83. package/dist/plugins/soft-delete/index.js +69 -0
  84. package/dist/plugins/tenant/index.d.ts +49 -0
  85. package/dist/plugins/tenant/index.js +75 -0
  86. package/dist/plugins/timestamps/index.d.ts +9 -0
  87. package/dist/plugins/timestamps/index.js +37 -0
  88. package/dist/plugins/validation/index.d.ts +24 -0
  89. package/dist/plugins/validation/index.js +59 -0
  90. package/dist/pool-R2m_DkXi.d.ts +37 -0
  91. package/dist/postgres/index.d.ts +28 -0
  92. package/dist/postgres/index.js +79 -0
  93. package/dist/postgrest-cAEQ_IQN.d.ts +43 -0
  94. package/dist/problem-BTkTtRV9.js +97 -0
  95. package/dist/problem-DlgB0lYl.d.ts +47 -0
  96. package/dist/query/index.d.ts +2 -0
  97. package/dist/query/index.js +3 -0
  98. package/dist/query-ChvDbsDE.js +186 -0
  99. package/dist/react/index.d.ts +7 -0
  100. package/dist/react/index.js +5 -0
  101. package/dist/react/server.d.ts +13 -0
  102. package/dist/react/server.js +25 -0
  103. package/dist/react/session.d.ts +2 -0
  104. package/dist/react/session.js +3 -0
  105. package/dist/react-C83BdBGt.js +242 -0
  106. package/dist/read-set-4lI3W_gZ.js +216 -0
  107. package/dist/realtime/index.d.ts +4 -0
  108. package/dist/realtime/index.js +268 -0
  109. package/dist/resolve-CjHILk2w.d.ts +170 -0
  110. package/dist/resource-BHEWmQJK.js +175 -0
  111. package/dist/resource-CgZa3YG_.d.ts +65 -0
  112. package/dist/respond-DMcbQwfN.js +383 -0
  113. package/dist/respond-Dne1ARSa.d.ts +169 -0
  114. package/dist/result-CTkCZ6JA.d.ts +198 -0
  115. package/dist/result-DWXatkd6.js +103 -0
  116. package/dist/scope-CDHcS7dE.js +90 -0
  117. package/dist/seed-CzecODcJ.js +101 -0
  118. package/dist/server/index.d.ts +42 -0
  119. package/dist/server/index.js +68 -0
  120. package/dist/session-C3LxnQ0e.d.ts +22 -0
  121. package/dist/session-CqEwzVup.js +20 -0
  122. package/dist/shared-_6-uPL7E.js +57 -0
  123. package/dist/simplify-bwJpLnaS.js +665 -0
  124. package/dist/spec-pins-C3oc-Tdp.js +20 -0
  125. package/dist/spec-pins-CqorWDnO.d.ts +20 -0
  126. package/dist/sql-DTjA1LPj.js +260 -0
  127. package/dist/ssr/index.d.ts +5 -0
  128. package/dist/ssr/index.js +4 -0
  129. package/dist/standard-CyhRzctb.d.ts +7 -0
  130. package/dist/standard-FUi3Z3-7.js +21 -0
  131. package/dist/stats-C5W-rBfR.js +566 -0
  132. package/dist/storage/index.d.ts +5 -0
  133. package/dist/storage/index.js +2 -0
  134. package/dist/storage-DuGn38fr.js +527 -0
  135. package/dist/tables-rZxRxNPW.js +59 -0
  136. package/dist/template-mbvlysOo.d.ts +6 -0
  137. package/dist/testing/index.d.ts +325 -0
  138. package/dist/testing/index.js +819 -0
  139. package/dist/types-6cYK9pbJ.js +29 -0
  140. package/dist/types-C83v5SQz.d.ts +248 -0
  141. package/dist/version-Cqq1m3UY.js +4 -0
  142. package/dist/view-D1s0wa0T.d.ts +49 -0
  143. package/dist/view-aOYizTeE.js +36 -0
  144. package/dist/webhooks/index.d.ts +152 -0
  145. package/dist/webhooks/index.js +2 -0
  146. package/dist/webhooks-DMipjhSP.js +139 -0
  147. package/package.json +250 -17
  148. package/schemas/config-v1.json +373 -0
  149. package/schemas/doctor-report-v1.json +106 -0
  150. package/schemas/snapshot-v2.json +1006 -0
  151. package/skills/better-supabase/SKILL.md +84 -0
  152. package/skills/better-supabase/references/plugins.md +65 -0
  153. package/skills/better-supabase/references/troubleshooting.md +39 -0
  154. package/skills/better-supabase-api/SKILL.md +67 -0
  155. package/skills/better-supabase-api/references/adapters.md +108 -0
  156. package/skills/better-supabase-testing/SKILL.md +66 -0
  157. package/index.js +0 -1
@@ -0,0 +1,186 @@
1
+ import { t as DbException } from "./errors-Bl3rkOzo.js";
2
+ import { t as invalidationTargets } from "./tables-rZxRxNPW.js";
3
+ import { t as invalidateTables } from "./invalidate-DbxuL2jL.js";
4
+ //#region src/query/index.ts
5
+ /** Query errors stay `DbException`s, even for an `sb` with `mapError()`. */
6
+ const asException = (error) => new DbException(error);
7
+ function isSkip(value) {
8
+ return typeof value === "symbol";
9
+ }
10
+ function withoutSignal(value) {
11
+ if (typeof value !== "object" || value === null) return value;
12
+ const { signal: _signal, ...rest } = value;
13
+ return rest;
14
+ }
15
+ function specQuery(runtime, spec, key) {
16
+ const stale = runtime.staleTime === void 0 ? {} : { staleTime: runtime.staleTime };
17
+ if (isSkip(spec)) return {
18
+ queryKey: key ?? ["bs", "$skip"],
19
+ queryFn: spec,
20
+ meta: { bsTables: [] },
21
+ ...stale
22
+ };
23
+ return {
24
+ queryKey: key ?? [
25
+ "bs",
26
+ spec.table,
27
+ spec.method,
28
+ ...spec.args
29
+ ],
30
+ queryFn: ({ signal }) => runtime.db().$run(spec, { signal }).orThrow(asException),
31
+ meta: { bsTables: runtime.sb.tablesOf(spec) },
32
+ ...stale
33
+ };
34
+ }
35
+ function tableQueries(runtime, table) {
36
+ const key = ["bs", table];
37
+ const specs = runtime.sb.spec[table];
38
+ const stale = runtime.staleTime === void 0 ? {} : { staleTime: runtime.staleTime };
39
+ const read = (method) => (args) => isSkip(args) ? specQuery(runtime, args, [
40
+ ...key,
41
+ method,
42
+ "$skip"
43
+ ]) : specQuery(runtime, specs[method](withoutSignal(args)));
44
+ const repo = () => {
45
+ const found = runtime.db()[table];
46
+ if (!found) throw new TypeError(`better-supabase: unknown table "${table}"`);
47
+ return found;
48
+ };
49
+ const call = (method, ...args) => {
50
+ const fn = repo()[method];
51
+ if (!fn) throw new TypeError(`better-supabase: ${table}.${method} is not available`);
52
+ return fn(...args);
53
+ };
54
+ const mutation = (op, run, tables) => ({
55
+ mutationKey: [...key, op],
56
+ mutationFn: (variables) => run(variables).orThrow(asException),
57
+ onSuccess: (_data, _variables, _result, context) => invalidateTables(context.client, tables)
58
+ });
59
+ return {
60
+ key,
61
+ findMany: read("findMany"),
62
+ findFirst: read("findFirst"),
63
+ findUnique: read("findUnique"),
64
+ count: read("count"),
65
+ aggregate: read("aggregate"),
66
+ exists: read("exists"),
67
+ paginate: read("paginate"),
68
+ findById: (id, args) => isSkip(id) ? specQuery(runtime, id, [
69
+ ...key,
70
+ "findById",
71
+ "$skip"
72
+ ]) : specQuery(runtime, specs["findById"](id, withoutSignal(args))),
73
+ infinite: (args) => {
74
+ const base = withoutSignal(args);
75
+ return {
76
+ queryKey: [
77
+ ...key,
78
+ "infinite",
79
+ base
80
+ ],
81
+ queryFn: ({ signal, pageParam }) => runtime.db().$run(specs["paginate"]({
82
+ ...base,
83
+ after: pageParam
84
+ }), { signal }).orThrow(asException),
85
+ initialPageParam: null,
86
+ getNextPageParam: (last) => last.nextCursor ?? void 0,
87
+ meta: { bsTables: runtime.sb.tablesOf(specs["paginate"]({
88
+ ...base,
89
+ after: null
90
+ })) },
91
+ ...stale
92
+ };
93
+ },
94
+ infinitePages: (args) => {
95
+ const base = withoutSignal(args);
96
+ return {
97
+ queryKey: [
98
+ ...key,
99
+ "infinitePages",
100
+ base
101
+ ],
102
+ queryFn: ({ signal, pageParam }) => runtime.db().$run(specs["paginate"]({
103
+ ...base,
104
+ page: pageParam
105
+ }), { signal }).orThrow(asException),
106
+ initialPageParam: base.page ?? 1,
107
+ getNextPageParam: (last) => last.page.hasMore ? last.page.number + 1 : void 0,
108
+ meta: { bsTables: runtime.sb.tablesOf(specs["paginate"](base)) },
109
+ ...stale
110
+ };
111
+ },
112
+ create: (args) => mutation("create", (data) => call("create", data, args), [table]),
113
+ update: (args) => mutation("update", (variables) => call("update", variables.id, variables.patch, args), [table]),
114
+ upsert: (args) => mutation("upsert", (data) => call("upsert", data, args), [table]),
115
+ delete: (args) => mutation("delete", (id) => call("delete", id, args), invalidationTargets(runtime.sb.meta, table))
116
+ };
117
+ }
118
+ /**
119
+ * TanStack Query option factories for every table, framework-neutral: pass
120
+ * them to `useQuery` (React), `createQuery` (Svelte, Solid), `useQuery`
121
+ * (Vue) or `injectQuery` (Angular). Payload types flow into the cache, errors
122
+ * are `DbException`s, and mutations invalidate every query that read a
123
+ * changed table.
124
+ *
125
+ * ```ts
126
+ * const q = createQueries(sb, () => browser.db);
127
+ * const { data } = useQuery(q.customers.findMany({ select: ['id', 'name'] }));
128
+ * ```
129
+ */
130
+ function createQueries(sb, db, options = {}) {
131
+ const runtime = {
132
+ sb,
133
+ db: typeof db === "function" ? db : () => db,
134
+ staleTime: options.staleTime
135
+ };
136
+ const stale = options.staleTime === void 0 ? {} : { staleTime: options.staleTime };
137
+ const queries = {
138
+ key: ["bs"],
139
+ $spec: (spec) => specQuery(runtime, spec),
140
+ $prefetch: (client, spec) => client.prefetchQuery(specQuery(runtime, spec)),
141
+ $rpc: (name, args, rpcOptions = {}) => ({
142
+ queryKey: [
143
+ "bs",
144
+ "$rpc",
145
+ name,
146
+ isSkip(args) ? "$skip" : args ?? {}
147
+ ],
148
+ queryFn: isSkip(args) ? args : ({ signal }) => runtime.db().$rpc(name, args ?? {}, { signal }).orThrow(asException),
149
+ meta: { bsTables: rpcOptions.tables ?? [] },
150
+ ...stale
151
+ }),
152
+ $rpcMutation: (name) => ({
153
+ mutationKey: [
154
+ "bs",
155
+ "$rpc",
156
+ name
157
+ ],
158
+ mutationFn: (args) => runtime.db().$rpc(name, args ?? {}).orThrow(asException),
159
+ onSuccess: (_data, _variables, _result, context) => invalidateTables(context.client, (sb.options.rpc?.[name]?.invalidates ?? []).flatMap((table) => invalidationTargets(sb.meta, table)))
160
+ })
161
+ };
162
+ for (const table of Object.keys(sb.meta.tables)) {
163
+ let built;
164
+ Object.defineProperty(queries, table, {
165
+ enumerable: true,
166
+ get: () => {
167
+ built ??= tableQueries(runtime, table);
168
+ return built;
169
+ }
170
+ });
171
+ }
172
+ return queries;
173
+ }
174
+ /** Invalidates every query that read a table in the target. */
175
+ function queryCache(client) {
176
+ return {
177
+ name: "tanstack-query",
178
+ invalidate: (target) => invalidateTables(client, target.tables)
179
+ };
180
+ }
181
+ /** Invalidates affected queries for every mutation and declared RPC `sb` performs in this runtime. */
182
+ function invalidateOnMutation(sb, client) {
183
+ return sb.cache(queryCache(client));
184
+ }
185
+ //#endregion
186
+ export { invalidateOnMutation as n, queryCache as r, createQueries as t };
@@ -0,0 +1,7 @@
1
+ import { s as Impersonator } from "../mfa-TVia554c.js";
2
+ import { t as AuthSession } from "../view-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 };
@@ -0,0 +1,5 @@
1
+ "use client";
2
+ import { n as useSession, t as SessionProvider } from "../session-CqEwzVup.js";
3
+ 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
+ export { BetterSupabaseProvider, SessionProvider, createHooks, hasEntitlement, useAuth, useBroadcast, useLiveCount, useLiveQuery, useSession, useSupabase };
@@ -0,0 +1,13 @@
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";
4
+ //#region src/react/server.d.ts
5
+ /** The `react-server` build of `useSession`: await `next.session()` instead. */
6
+ export declare const useSession: <C = unknown, P = unknown>() => AuthSession<C, P>;
7
+ /**
8
+ * The `react-server` build of `createHooks`: importing a module that creates
9
+ * hooks is safe on the server, calling a hook there throws.
10
+ */
11
+ export declare function createHooks<B extends BrowserLike>(): BetterHooks<B>;
12
+ //#endregion
13
+ export { type AuthSession, type BetterHooks, BetterSupabaseProvider, type BetterSupabaseProviderProps, type BroadcastOptions, type BrowserLike, type ClaimsOf, type LiveQueryHookOptions, type ProfileOf, SessionProvider, type SessionProviderProps, useAuth, useBroadcast, useLiveQuery, useSupabase };
@@ -0,0 +1,25 @@
1
+ import { i as useBroadcast, o as useLiveQuery, r as useAuth, s as useSupabase, t as BetterSupabaseProvider } from "../react-C83BdBGt.js";
2
+ import { SessionProvider } from "./session.js";
3
+ //#region src/react/server.ts
4
+ function clientOnly(name) {
5
+ return () => {
6
+ throw new Error(`better-supabase: ${name}() runs in Client Components only. In Server Components use \`next.server()\` or \`db.$run(spec)\`.`);
7
+ };
8
+ }
9
+ /** The `react-server` build of `useSession`: await `next.session()` instead. */
10
+ const useSession = clientOnly("useSession");
11
+ /**
12
+ * The `react-server` build of `createHooks`: importing a module that creates
13
+ * hooks is safe on the server, calling a hook there throws.
14
+ */
15
+ function createHooks() {
16
+ return {
17
+ useDb: clientOnly("useDb"),
18
+ useQueries: clientOnly("useQueries"),
19
+ useSupabase: clientOnly("useSupabase"),
20
+ useAuth: clientOnly("useAuth"),
21
+ useSession: clientOnly("useSession")
22
+ };
23
+ }
24
+ //#endregion
25
+ export { BetterSupabaseProvider, SessionProvider, createHooks, useAuth, useBroadcast, useLiveQuery, useSession, useSupabase };
@@ -0,0 +1,2 @@
1
+ import { n as SessionProviderProps, r as useSession, t as SessionProvider } from "../session-C3LxnQ0e.js";
2
+ export { SessionProvider, SessionProviderProps, useSession };
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ import { n as useSession, t as SessionProvider } from "../session-CqEwzVup.js";
3
+ export { SessionProvider, useSession };
@@ -0,0 +1,242 @@
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";
4
+ import { n as useSession } from "./session-CqEwzVup.js";
5
+ import { createContext, createElement, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
+ //#region src/react/index.ts
7
+ const BrowserContext = createContext(null);
8
+ const LOADING = {
9
+ status: "loading",
10
+ user: null,
11
+ claims: null
12
+ };
13
+ /** Provides the browser client to the hooks. */
14
+ 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]);
24
+ const value = useMemo(() => ({
25
+ browser,
26
+ queryClient
27
+ }), [browser, queryClient]);
28
+ return createElement(BrowserContext.Provider, { value }, props.children);
29
+ }
30
+ function useBrowserContext() {
31
+ const value = useContext(BrowserContext);
32
+ if (!value) throw new Error("better-supabase: wrap your app in <BetterSupabaseProvider browser={browser}>");
33
+ return value;
34
+ }
35
+ function useBrowser() {
36
+ return useBrowserContext().browser;
37
+ }
38
+ /** The session state for UI: `loading`, `signed-out` or `signed-in` with the user and claims. */
39
+ function useAuth() {
40
+ const browser = useBrowser();
41
+ return useSyncExternalStore(browser.auth.subscribe, browser.auth.current, () => LOADING);
42
+ }
43
+ function useSupabase() {
44
+ return useBrowser().supabase;
45
+ }
46
+ /**
47
+ * Hooks typed for your schema.
48
+ *
49
+ * ```ts
50
+ * export const { useDb, useQueries, useAuth } = createHooks<typeof browser>();
51
+ * ```
52
+ */
53
+ function createHooks() {
54
+ return {
55
+ useDb() {
56
+ const browser = useBrowser();
57
+ useAuth();
58
+ return browser.db;
59
+ },
60
+ useQueries() {
61
+ const browser = useBrowser();
62
+ useAuth();
63
+ return browser.queries;
64
+ },
65
+ useSupabase,
66
+ useAuth,
67
+ useSession
68
+ };
69
+ }
70
+ /**
71
+ * Subscribes to a topic while mounted. Pass `null` values to pause. It
72
+ * resubscribes when the topic or the signed-in user changes.
73
+ *
74
+ * ```ts
75
+ * useBroadcast(customersTopic, orgId ? { orgId } : null, {}, { invalidate: ['customers'] });
76
+ * ```
77
+ */
78
+ function useBroadcast(topic, values, handlers, options) {
79
+ const { browser, queryClient } = useBrowserContext();
80
+ const auth = useAuth();
81
+ const [status, setStatus] = useState("closed");
82
+ const latest = useRef({
83
+ handlers,
84
+ options
85
+ });
86
+ latest.current = {
87
+ handlers,
88
+ options
89
+ };
90
+ const name = values ? topic.topic(values) : null;
91
+ const userId = auth.user?.id ?? null;
92
+ if (options?.invalidate && !queryClient) throw new Error("better-supabase: useBroadcast({ invalidate }) needs <BetterSupabaseProvider queryClient={...}>");
93
+ useEffect(() => {
94
+ if (!name || auth.status === "loading") return void 0;
95
+ const matched = topic.match(name);
96
+ if (!matched) return void 0;
97
+ const forward = (payload, message) => {
98
+ const current = latest.current;
99
+ const table = current.handlers;
100
+ (table?.[message.event] ?? table?.["*"])?.(payload, message);
101
+ const invalidate = current.options?.invalidate;
102
+ if (!invalidate || !queryClient) return;
103
+ if (typeof invalidate !== "function") {
104
+ invalidateTables(queryClient, invalidate);
105
+ return;
106
+ }
107
+ for (const queryKey of invalidate(message)) queryClient.invalidateQueries({ queryKey });
108
+ };
109
+ const subscription = topic.subscribe(browser.supabase, matched, { "*": forward }, {
110
+ ...latest.current.options?.self === void 0 ? {} : { self: latest.current.options.self },
111
+ onStatus: setStatus,
112
+ onInvalid: (message, issues) => latest.current.options?.onInvalid?.(message, issues)
113
+ });
114
+ return () => {
115
+ subscription.unsubscribe();
116
+ setStatus("closed");
117
+ };
118
+ }, [
119
+ browser,
120
+ topic,
121
+ name,
122
+ userId,
123
+ auth.status,
124
+ queryClient
125
+ ]);
126
+ return status;
127
+ }
128
+ function claimedTenant(auth, meta) {
129
+ if (auth.status !== "signed-in") return void 0;
130
+ for (const path of tenantClaimPaths(claimsOf(meta).tenant)) {
131
+ const value = claimAt(auth.claims, path);
132
+ if (value !== void 0) return value;
133
+ }
134
+ }
135
+ /**
136
+ * Keeps a query fresh: invalidates every cached query that read a table the
137
+ * spec touches whenever one of them changes. Pass `null` to pause. It
138
+ * resubscribes when the spec, tenant or user changes.
139
+ *
140
+ * ```ts
141
+ * const spec = sb.spec.customers.findMany({ include: { notes: true } });
142
+ * const { data } = useQuery(q.$spec(spec));
143
+ * useLiveQuery(spec);
144
+ * ```
145
+ */
146
+ function useLiveQuery(spec, options = {}) {
147
+ const { browser, queryClient } = useBrowserContext();
148
+ const auth = useAuth();
149
+ const [status, setStatus] = useState("closed");
150
+ if (spec && !queryClient) throw new Error("better-supabase: useLiveQuery needs <BetterSupabaseProvider queryClient={...}>");
151
+ const key = spec ? JSON.stringify(spec) : null;
152
+ const tenant = options.tenant ?? claimedTenant(auth, browser.sb.meta);
153
+ const userId = auth.user?.id ?? null;
154
+ const debounceMs = options.debounceMs;
155
+ useEffect(() => {
156
+ if (!key || !queryClient || auth.status === "loading") return void 0;
157
+ const live = liveQuery(browser.sb, browser.supabase, JSON.parse(key), {
158
+ onChange: (tables) => void invalidateTables(queryClient, tables),
159
+ onStatus: setStatus,
160
+ ...tenant === void 0 ? {} : { tenant },
161
+ ...debounceMs === void 0 ? {} : { debounceMs }
162
+ });
163
+ return () => {
164
+ live.unsubscribe();
165
+ };
166
+ }, [
167
+ browser,
168
+ queryClient,
169
+ key,
170
+ tenant,
171
+ userId,
172
+ auth.status,
173
+ debounceMs
174
+ ]);
175
+ return status;
176
+ }
177
+ /**
178
+ * A count that stays current: refetches only the `count` spec (a HEAD
179
+ * 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
181
+ * pause. Doesn't need a `QueryClient`.
182
+ *
183
+ * ```tsx
184
+ * const { count } = useLiveCount(seed); // seed = await next.liveCount(spec)
185
+ * ```
186
+ */
187
+ function useLiveCount(source, options = {}) {
188
+ const { browser } = useBrowserContext();
189
+ const auth = useAuth();
190
+ const seed = source && "spec" in source ? source : void 0;
191
+ const spec = seed ? seed.spec : source;
192
+ const initial = seed?.count ?? options.initial;
193
+ const [state, setState] = useState({
194
+ key: null,
195
+ count: void 0,
196
+ error: void 0
197
+ });
198
+ const [status, setStatus] = useState("closed");
199
+ const key = spec ? JSON.stringify(spec) : null;
200
+ const tenant = options.tenant ?? claimedTenant(auth, browser.sb.meta);
201
+ const userId = auth.user?.id ?? null;
202
+ const debounceMs = options.debounceMs;
203
+ const hasInitial = initial !== void 0;
204
+ useEffect(() => {
205
+ if (!key || auth.status === "loading") return void 0;
206
+ const live = liveCount(browser.sb, browser.supabase, browser.db, JSON.parse(key), {
207
+ immediate: !hasInitial,
208
+ onCount: (count) => setState({
209
+ key,
210
+ count,
211
+ error: void 0
212
+ }),
213
+ onError: (error) => setState((previous) => ({
214
+ key,
215
+ count: previous.key === key ? previous.count : void 0,
216
+ error
217
+ })),
218
+ onStatus: setStatus,
219
+ ...tenant === void 0 ? {} : { tenant },
220
+ ...debounceMs === void 0 ? {} : { debounceMs }
221
+ });
222
+ return () => {
223
+ live.unsubscribe();
224
+ };
225
+ }, [
226
+ browser,
227
+ key,
228
+ tenant,
229
+ userId,
230
+ auth.status,
231
+ debounceMs,
232
+ hasInitial
233
+ ]);
234
+ const fresh = state.key === key;
235
+ return {
236
+ count: fresh && state.count !== void 0 ? state.count : initial,
237
+ status,
238
+ error: fresh ? state.error : void 0
239
+ };
240
+ }
241
+ //#endregion
242
+ export { useLiveCount as a, useBroadcast as i, createHooks as n, useLiveQuery as o, useAuth as r, useSupabase as s, BetterSupabaseProvider as t };