better-supabase 0.0.0 → 0.1.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 (154) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +88 -1
  3. package/bin/better-supabase.js +3 -0
  4. package/dist/casing-da0uRTqt.js +14 -0
  5. package/dist/cli/bin.d.ts +1 -0
  6. package/dist/cli/bin.js +15 -0
  7. package/dist/cli/index.d.ts +226 -0
  8. package/dist/cli/index.js +3 -0
  9. package/dist/cli-CoByWYja.js +6472 -0
  10. package/dist/client/index.d.ts +2 -0
  11. package/dist/client/index.js +112 -0
  12. package/dist/compiler-DL18ukPe.d.ts +14 -0
  13. package/dist/config/index.d.ts +3 -0
  14. package/dist/config/index.js +3 -0
  15. package/dist/config-D-aR2ZoN.js +276 -0
  16. package/dist/define-CTfWw-SS.d.ts +662 -0
  17. package/dist/define-QGkq04oe.js +1068 -0
  18. package/dist/edge/index.d.ts +43 -0
  19. package/dist/edge/index.js +103 -0
  20. package/dist/entitlements-BBg61DQZ.d.ts +20 -0
  21. package/dist/entitlements-BC_khwRy.js +17 -0
  22. package/dist/env/index.d.ts +2 -0
  23. package/dist/env/index.js +2 -0
  24. package/dist/env-BEToab0I.js +204 -0
  25. package/dist/errors-Bl3rkOzo.js +196 -0
  26. package/dist/events/index.d.ts +2 -0
  27. package/dist/events/index.js +2 -0
  28. package/dist/events-2OJsVG_d.d.ts +84 -0
  29. package/dist/events-BuzTgn2D.js +135 -0
  30. package/dist/executor-Bazhgsgq.d.ts +195 -0
  31. package/dist/executor-Bje4suQF.d.ts +25 -0
  32. package/dist/executor-Ceq60hPM.js +89 -0
  33. package/dist/hono/index.d.ts +45 -0
  34. package/dist/hono/index.js +70 -0
  35. package/dist/index-BD6wKthp.d.ts +597 -0
  36. package/dist/index-DQVvHzjW.d.ts +74 -0
  37. package/dist/index-DRid0qIv.d.ts +76 -0
  38. package/dist/index-Dup-nSe9.d.ts +117 -0
  39. package/dist/index-Dy6kuF0P.d.ts +71 -0
  40. package/dist/index-Hf8PP9sP.d.ts +116 -0
  41. package/dist/index-JXHBuS53.d.ts +144 -0
  42. package/dist/index-Nxo3TJcm.d.ts +208 -0
  43. package/dist/index-Sw43J8HG.d.ts +62 -0
  44. package/dist/index-cEeweTeM.d.ts +38 -0
  45. package/dist/index.d.ts +110 -0
  46. package/dist/index.js +42 -0
  47. package/dist/invalidate-DbxuL2jL.js +18 -0
  48. package/dist/jobs/index.d.ts +173 -0
  49. package/dist/jobs/index.js +477 -0
  50. package/dist/json-schema-BbwO2mZg.js +181 -0
  51. package/dist/lint/index.d.ts +75 -0
  52. package/dist/lint/index.js +167 -0
  53. package/dist/list/index.d.ts +2 -0
  54. package/dist/list/index.js +410 -0
  55. package/dist/live-6b9i72Ux.d.ts +180 -0
  56. package/dist/live-D8rV1Ldw.js +168 -0
  57. package/dist/mcp/index.d.ts +105 -0
  58. package/dist/mcp/index.js +330 -0
  59. package/dist/mfa-CpogP66z.js +521 -0
  60. package/dist/mfa-TVia554c.d.ts +44 -0
  61. package/dist/next/image/index.d.ts +34 -0
  62. package/dist/next/image/index.js +40 -0
  63. package/dist/next/index.d.ts +210 -0
  64. package/dist/next/index.js +426 -0
  65. package/dist/openapi/index.d.ts +2 -0
  66. package/dist/openapi/index.js +332 -0
  67. package/dist/orpc/index.d.ts +46 -0
  68. package/dist/orpc/index.js +50 -0
  69. package/dist/otel/index.d.ts +40 -0
  70. package/dist/otel/index.js +168 -0
  71. package/dist/path-Cv__-OTj.d.ts +17 -0
  72. package/dist/plugin-BqR6wKMB.js +23 -0
  73. package/dist/plugin-HvGqCurB.d.ts +118 -0
  74. package/dist/plugins/actor/index.d.ts +13 -0
  75. package/dist/plugins/actor/index.js +43 -0
  76. package/dist/plugins/rules/index.d.ts +88 -0
  77. package/dist/plugins/rules/index.js +174 -0
  78. package/dist/plugins/soft-delete/index.d.ts +38 -0
  79. package/dist/plugins/soft-delete/index.js +69 -0
  80. package/dist/plugins/tenant/index.d.ts +43 -0
  81. package/dist/plugins/tenant/index.js +73 -0
  82. package/dist/plugins/timestamps/index.d.ts +9 -0
  83. package/dist/plugins/timestamps/index.js +37 -0
  84. package/dist/plugins/validation/index.d.ts +24 -0
  85. package/dist/plugins/validation/index.js +59 -0
  86. package/dist/pool-BFeqzwQS.d.ts +37 -0
  87. package/dist/postgres/index.d.ts +28 -0
  88. package/dist/postgres/index.js +79 -0
  89. package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
  90. package/dist/problem-DPu6Fh-t.js +97 -0
  91. package/dist/problem-DlgB0lYl.d.ts +47 -0
  92. package/dist/query/index.d.ts +2 -0
  93. package/dist/query/index.js +3 -0
  94. package/dist/query-ChvDbsDE.js +186 -0
  95. package/dist/react/index.d.ts +7 -0
  96. package/dist/react/index.js +5 -0
  97. package/dist/react/server.d.ts +13 -0
  98. package/dist/react/server.js +25 -0
  99. package/dist/react/session.d.ts +2 -0
  100. package/dist/react/session.js +3 -0
  101. package/dist/react-CrOvrnIP.js +241 -0
  102. package/dist/read-set-DhX2d04c.js +216 -0
  103. package/dist/realtime/index.d.ts +3 -0
  104. package/dist/realtime/index.js +253 -0
  105. package/dist/resolve-DW8h9NPm.d.ts +155 -0
  106. package/dist/resource-CYiu6Ygi.js +175 -0
  107. package/dist/resource-SkPTRJDD.d.ts +65 -0
  108. package/dist/respond-4-dhoV7x.js +380 -0
  109. package/dist/respond-PoTpy5DT.d.ts +169 -0
  110. package/dist/result-CTkCZ6JA.d.ts +198 -0
  111. package/dist/result-DWXatkd6.js +103 -0
  112. package/dist/scope-CDHcS7dE.js +90 -0
  113. package/dist/seed-CzecODcJ.js +101 -0
  114. package/dist/server/index.d.ts +42 -0
  115. package/dist/server/index.js +68 -0
  116. package/dist/session-C3osL21k.d.ts +21 -0
  117. package/dist/session-CD-3orO_.js +19 -0
  118. package/dist/shared-_6-uPL7E.js +57 -0
  119. package/dist/simplify-oHiDKRVQ.js +665 -0
  120. package/dist/spec-pins-CJPVghHy.d.ts +20 -0
  121. package/dist/spec-pins-t6sPaHvl.js +20 -0
  122. package/dist/sql-M8l3fnS0.js +260 -0
  123. package/dist/ssr/index.d.ts +5 -0
  124. package/dist/ssr/index.js +4 -0
  125. package/dist/standard-CyhRzctb.d.ts +7 -0
  126. package/dist/standard-FUi3Z3-7.js +21 -0
  127. package/dist/stats-3iy8u3_P.js +566 -0
  128. package/dist/storage/index.d.ts +4 -0
  129. package/dist/storage/index.js +2 -0
  130. package/dist/storage-BWXVUXGk.js +461 -0
  131. package/dist/tables-rZxRxNPW.js +59 -0
  132. package/dist/template-BeOGnynM.js +93 -0
  133. package/dist/template-mbvlysOo.d.ts +6 -0
  134. package/dist/testing/index.d.ts +294 -0
  135. package/dist/testing/index.js +751 -0
  136. package/dist/types-6cYK9pbJ.js +29 -0
  137. package/dist/types-C2fslP1z.d.ts +195 -0
  138. package/dist/version-Cqq1m3UY.js +4 -0
  139. package/dist/view-B0ltUmTi.d.ts +43 -0
  140. package/dist/view-B6F9QbG-.js +35 -0
  141. package/dist/webhooks/index.d.ts +152 -0
  142. package/dist/webhooks/index.js +2 -0
  143. package/dist/webhooks-DbMTJJX2.js +139 -0
  144. package/package.json +246 -17
  145. package/schemas/config-v1.json +328 -0
  146. package/schemas/doctor-report-v1.json +105 -0
  147. package/schemas/snapshot-v2.json +1002 -0
  148. package/skills/better-supabase/SKILL.md +84 -0
  149. package/skills/better-supabase/references/plugins.md +56 -0
  150. package/skills/better-supabase/references/troubleshooting.md +39 -0
  151. package/skills/better-supabase-api/SKILL.md +67 -0
  152. package/skills/better-supabase-api/references/adapters.md +108 -0
  153. package/skills/better-supabase-testing/SKILL.md +65 -0
  154. package/index.js +0 -1
@@ -0,0 +1,13 @@
1
+ import { t as AuthSession } from "../view-B0ltUmTi.js";
2
+ import { n as SessionProviderProps, t as SessionProvider } from "../session-C3osL21k.js";
3
+ import { a as BrowserLike, d as useAuth, f as useBroadcast, h as useSupabase, i as BroadcastOptions, l as LiveQueryHookOptions, m as useLiveQuery, n as BetterSupabaseProvider, o as ClaimsOf, r as BetterSupabaseProviderProps, t as BetterHooks } from "../index-Hf8PP9sP.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>() => AuthSession<C>;
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, 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-CrOvrnIP.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-C3osL21k.js";
2
+ export { SessionProvider, SessionProviderProps, useSession };
@@ -0,0 +1,3 @@
1
+ "use client";
2
+ import { n as useSession, t as SessionProvider } from "../session-CD-3orO_.js";
3
+ export { SessionProvider, useSession };
@@ -0,0 +1,241 @@
1
+ import { t as invalidateTables } from "./invalidate-DbxuL2jL.js";
2
+ import { n as liveQuery, t as liveCount } from "./live-D8rV1Ldw.js";
3
+ import { n as useSession } from "./session-CD-3orO_.js";
4
+ import { createContext, createElement, useContext, useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
5
+ //#region src/react/index.ts
6
+ const BrowserContext = createContext(null);
7
+ const LOADING = {
8
+ status: "loading",
9
+ user: null,
10
+ claims: null
11
+ };
12
+ /** Provides the browser client to the hooks. */
13
+ function BetterSupabaseProvider(props) {
14
+ const { browser, queryClient } = props;
15
+ const user = useRef(void 0);
16
+ useEffect(() => browser.auth.subscribe(() => {
17
+ const snapshot = browser.auth.current();
18
+ if (snapshot.status === "loading") return;
19
+ const id = snapshot.user?.id ?? null;
20
+ if (user.current !== void 0 && user.current !== id) queryClient?.removeQueries({ queryKey: ["bs"] });
21
+ user.current = id;
22
+ }), [browser, queryClient]);
23
+ const value = useMemo(() => ({
24
+ browser,
25
+ queryClient
26
+ }), [browser, queryClient]);
27
+ return createElement(BrowserContext.Provider, { value }, props.children);
28
+ }
29
+ function useBrowserContext() {
30
+ const value = useContext(BrowserContext);
31
+ if (!value) throw new Error("better-supabase: wrap your app in <BetterSupabaseProvider browser={browser}>");
32
+ return value;
33
+ }
34
+ function useBrowser() {
35
+ return useBrowserContext().browser;
36
+ }
37
+ /** The session state for UI: `loading`, `signed-out` or `signed-in` with the user and claims. */
38
+ function useAuth() {
39
+ const browser = useBrowser();
40
+ return useSyncExternalStore(browser.auth.subscribe, browser.auth.current, () => LOADING);
41
+ }
42
+ function useSupabase() {
43
+ return useBrowser().supabase;
44
+ }
45
+ /**
46
+ * Hooks typed for your schema.
47
+ *
48
+ * ```ts
49
+ * export const { useDb, useQueries, useAuth } = createHooks<typeof browser>();
50
+ * ```
51
+ */
52
+ function createHooks() {
53
+ return {
54
+ useDb() {
55
+ const browser = useBrowser();
56
+ useAuth();
57
+ return browser.db;
58
+ },
59
+ useQueries() {
60
+ const browser = useBrowser();
61
+ useAuth();
62
+ return browser.queries;
63
+ },
64
+ useSupabase,
65
+ useAuth,
66
+ useSession
67
+ };
68
+ }
69
+ /**
70
+ * Subscribes to a topic while mounted. Pass `null` values to pause. It
71
+ * resubscribes when the topic or the signed-in user changes.
72
+ *
73
+ * ```ts
74
+ * useBroadcast(customersTopic, orgId ? { orgId } : null, {}, { invalidate: ['customers'] });
75
+ * ```
76
+ */
77
+ function useBroadcast(topic, values, handlers, options) {
78
+ const { browser, queryClient } = useBrowserContext();
79
+ const auth = useAuth();
80
+ const [status, setStatus] = useState("closed");
81
+ const latest = useRef({
82
+ handlers,
83
+ options
84
+ });
85
+ latest.current = {
86
+ handlers,
87
+ options
88
+ };
89
+ const name = values ? topic.topic(values) : null;
90
+ const userId = auth.user?.id ?? null;
91
+ if (options?.invalidate && !queryClient) throw new Error("better-supabase: useBroadcast({ invalidate }) needs <BetterSupabaseProvider queryClient={...}>");
92
+ useEffect(() => {
93
+ if (!name || auth.status === "loading") return void 0;
94
+ const matched = topic.match(name);
95
+ if (!matched) return void 0;
96
+ const forward = (payload, message) => {
97
+ const current = latest.current;
98
+ const table = current.handlers;
99
+ (table?.[message.event] ?? table?.["*"])?.(payload, message);
100
+ const invalidate = current.options?.invalidate;
101
+ if (!invalidate || !queryClient) return;
102
+ if (typeof invalidate !== "function") {
103
+ invalidateTables(queryClient, invalidate);
104
+ return;
105
+ }
106
+ for (const queryKey of invalidate(message)) queryClient.invalidateQueries({ queryKey });
107
+ };
108
+ const subscription = topic.subscribe(browser.supabase, matched, { "*": forward }, {
109
+ ...latest.current.options?.self === void 0 ? {} : { self: latest.current.options.self },
110
+ onStatus: setStatus,
111
+ onInvalid: (message, issues) => latest.current.options?.onInvalid?.(message, issues)
112
+ });
113
+ return () => {
114
+ subscription.unsubscribe();
115
+ setStatus("closed");
116
+ };
117
+ }, [
118
+ browser,
119
+ topic,
120
+ name,
121
+ userId,
122
+ auth.status,
123
+ queryClient
124
+ ]);
125
+ return status;
126
+ }
127
+ function claimedTenant(auth) {
128
+ if (auth.status !== "signed-in") return void 0;
129
+ const top = auth.claims["org_id"];
130
+ if (typeof top === "string") return top;
131
+ const app = auth.claims["app_metadata"];
132
+ return typeof app?.["org_id"] === "string" ? app["org_id"] : void 0;
133
+ }
134
+ /**
135
+ * Keeps a query fresh: invalidates every cached query that read a table the
136
+ * spec touches whenever one of them changes. Pass `null` to pause. It
137
+ * resubscribes when the spec, tenant or user changes.
138
+ *
139
+ * ```ts
140
+ * const spec = sb.spec.customers.findMany({ include: { notes: true } });
141
+ * const { data } = useQuery(q.$spec(spec));
142
+ * useLiveQuery(spec);
143
+ * ```
144
+ */
145
+ function useLiveQuery(spec, options = {}) {
146
+ const { browser, queryClient } = useBrowserContext();
147
+ const auth = useAuth();
148
+ const [status, setStatus] = useState("closed");
149
+ if (spec && !queryClient) throw new Error("better-supabase: useLiveQuery needs <BetterSupabaseProvider queryClient={...}>");
150
+ const key = spec ? JSON.stringify(spec) : null;
151
+ const tenant = options.tenant ?? claimedTenant(auth);
152
+ const userId = auth.user?.id ?? null;
153
+ const debounceMs = options.debounceMs;
154
+ useEffect(() => {
155
+ if (!key || !queryClient || auth.status === "loading") return void 0;
156
+ const live = liveQuery(browser.sb, browser.supabase, JSON.parse(key), {
157
+ onChange: (tables) => void invalidateTables(queryClient, tables),
158
+ onStatus: setStatus,
159
+ ...tenant === void 0 ? {} : { tenant },
160
+ ...debounceMs === void 0 ? {} : { debounceMs }
161
+ });
162
+ return () => {
163
+ live.unsubscribe();
164
+ };
165
+ }, [
166
+ browser,
167
+ queryClient,
168
+ key,
169
+ tenant,
170
+ userId,
171
+ auth.status,
172
+ debounceMs
173
+ ]);
174
+ return status;
175
+ }
176
+ /**
177
+ * A count that stays current: refetches only the `count` spec (a HEAD
178
+ * request) after each debounced change to a table it reads, and after the
179
+ * channel rejoins. Takes a spec or a `next.liveCount()` seed; pass `null` to
180
+ * pause. Doesn't need a `QueryClient`.
181
+ *
182
+ * ```tsx
183
+ * const { count } = useLiveCount(seed); // seed = await next.liveCount(spec)
184
+ * ```
185
+ */
186
+ function useLiveCount(source, options = {}) {
187
+ const { browser } = useBrowserContext();
188
+ const auth = useAuth();
189
+ const seed = source && "spec" in source ? source : void 0;
190
+ const spec = seed ? seed.spec : source;
191
+ const initial = seed?.count ?? options.initial;
192
+ const [state, setState] = useState({
193
+ key: null,
194
+ count: void 0,
195
+ error: void 0
196
+ });
197
+ const [status, setStatus] = useState("closed");
198
+ const key = spec ? JSON.stringify(spec) : null;
199
+ const tenant = options.tenant ?? claimedTenant(auth);
200
+ const userId = auth.user?.id ?? null;
201
+ const debounceMs = options.debounceMs;
202
+ const hasInitial = initial !== void 0;
203
+ useEffect(() => {
204
+ if (!key || auth.status === "loading") return void 0;
205
+ const live = liveCount(browser.sb, browser.supabase, browser.db, JSON.parse(key), {
206
+ immediate: !hasInitial,
207
+ onCount: (count) => setState({
208
+ key,
209
+ count,
210
+ error: void 0
211
+ }),
212
+ onError: (error) => setState((previous) => ({
213
+ key,
214
+ count: previous.key === key ? previous.count : void 0,
215
+ error
216
+ })),
217
+ onStatus: setStatus,
218
+ ...tenant === void 0 ? {} : { tenant },
219
+ ...debounceMs === void 0 ? {} : { debounceMs }
220
+ });
221
+ return () => {
222
+ live.unsubscribe();
223
+ };
224
+ }, [
225
+ browser,
226
+ key,
227
+ tenant,
228
+ userId,
229
+ auth.status,
230
+ debounceMs,
231
+ hasInitial
232
+ ]);
233
+ const fresh = state.key === key;
234
+ return {
235
+ count: fresh && state.count !== void 0 ? state.count : initial,
236
+ status,
237
+ error: fresh ? state.error : void 0
238
+ };
239
+ }
240
+ //#endregion
241
+ export { useLiveCount as a, useBroadcast as i, createHooks as n, useLiveQuery as o, useAuth as r, useSupabase as s, BetterSupabaseProvider as t };
@@ -0,0 +1,216 @@
1
+ import { r as IrBuilder, u as tableMeta } from "./simplify-oHiDKRVQ.js";
2
+ import { n as touchedTables } from "./tables-rZxRxNPW.js";
3
+ //#region src/core/spec.ts
4
+ const READ_METHODS = [
5
+ "findMany",
6
+ "findFirst",
7
+ "findUnique",
8
+ "findById",
9
+ "count",
10
+ "aggregate",
11
+ "exists",
12
+ "paginate"
13
+ ];
14
+ function createSpecs(meta) {
15
+ const specs = {};
16
+ for (const table of Object.keys(meta.tables)) {
17
+ const methods = {};
18
+ for (const method of READ_METHODS) methods[method] = (...args) => ({
19
+ v: 1,
20
+ table,
21
+ method,
22
+ args: trimUndefined(args)
23
+ });
24
+ specs[table] = methods;
25
+ }
26
+ return specs;
27
+ }
28
+ function trimUndefined(args) {
29
+ const out = [...args];
30
+ while (out.length > 0 && out.at(-1) === void 0) out.pop();
31
+ return out;
32
+ }
33
+ function isQuerySpec(value) {
34
+ if (typeof value !== "object" || value === null) return false;
35
+ const spec = value;
36
+ return spec.v === 1 && typeof spec.table === "string" && READ_METHODS.includes(spec.method) && Array.isArray(spec.args);
37
+ }
38
+ /** The argument object of a spec's method (`findById` takes it second). */
39
+ function specOptions(spec) {
40
+ const value = spec.method === "findById" ? spec.args[1] : spec.args[0];
41
+ return typeof value === "object" && value !== null ? value : void 0;
42
+ }
43
+ const builders = /* @__PURE__ */ new WeakMap();
44
+ /**
45
+ * App keys of every table a spec reads: its table, included relations and
46
+ * relations used in `where`. Queries keyed by a spec go stale when any of
47
+ * them changes. Invalid arguments yield just the spec's table; running the
48
+ * spec reports the error.
49
+ */
50
+ function specTables(meta, spec) {
51
+ try {
52
+ return readTables(meta, spec);
53
+ } catch {
54
+ return [spec.table];
55
+ }
56
+ }
57
+ function readTables(meta, spec) {
58
+ let builder = builders.get(meta);
59
+ if (!builder) {
60
+ builder = new IrBuilder(meta);
61
+ builders.set(meta, builder);
62
+ }
63
+ const table = tableMeta(meta, spec.table);
64
+ const args = specOptions(spec);
65
+ const counts = spec.method === "count" || spec.method === "exists" || spec.method === "aggregate";
66
+ return touchedTables({
67
+ kind: "select",
68
+ table,
69
+ selection: counts ? {
70
+ columns: [],
71
+ includes: []
72
+ } : builder.selection(table, args?.["select"], args?.["include"]),
73
+ where: builder.where(table, args?.["where"]),
74
+ orderBy: [],
75
+ limit: void 0,
76
+ offset: void 0,
77
+ count: void 0,
78
+ head: counts,
79
+ single: void 0
80
+ });
81
+ }
82
+ //#endregion
83
+ //#region src/core/read-set.ts
84
+ const NAME = /^[a-z][a-z0-9_]{0,59}$/;
85
+ const PARAM = /^[a-z_][a-z0-9_]*$/i;
86
+ const TYPE = /^(?:[a-z_][a-z0-9_]*\.)?[a-z_][a-z0-9_]*(?:\[\])?$/;
87
+ /** `jsonb_build_object` takes at most 100 arguments. */
88
+ const MAX_ENTRIES = 50;
89
+ /** Placeholders are `NUL bs:<name> NUL`: no real parameter value holds a NUL. */
90
+ const NUL = String.fromCharCode(0);
91
+ const SENTINEL = new RegExp(`${NUL}bs:([a-z_][a-z0-9_]*)${NUL}`, "gi");
92
+ const EXACT = new RegExp(`^${NUL}bs:([a-z_][a-z0-9_]*)${NUL}$`, "i");
93
+ function sentinel(name) {
94
+ return `${NUL}bs:${name}${NUL}`;
95
+ }
96
+ /**
97
+ * Registers reads that run as one round trip:
98
+ *
99
+ * ```ts
100
+ * export const appChrome = defineReadSet(
101
+ * sb,
102
+ * 'app_chrome',
103
+ * { params: { userId: 'uuid' } },
104
+ * (s, p) => ({
105
+ * unread: s.notifications.count({ where: { userId: p.userId, readAt: null } }),
106
+ * me: s.profiles.findFirst({ where: { id: p.userId } }),
107
+ * }),
108
+ * );
109
+ * ```
110
+ *
111
+ * `p` holds placeholders, not values: pass them straight into `where` (or
112
+ * `in`), where the function reads them from its `p jsonb` argument.
113
+ */
114
+ function defineReadSet(sb, name, options, build) {
115
+ if (!NAME.test(name)) throw new TypeError(`better-supabase: read set name "${name}" must be snake_case and at most 60 characters`);
116
+ const params = options.params ?? {};
117
+ const placeholders = {};
118
+ for (const [key, type] of Object.entries(params)) {
119
+ if (!PARAM.test(key)) throw new TypeError(`better-supabase: read set "${name}" has an invalid parameter name "${key}"`);
120
+ if (!TYPE.test(type)) throw new TypeError(`better-supabase: read set "${name}" parameter "${key}" has an invalid type "${type}"`);
121
+ placeholders[key] = type.endsWith("[]") ? [sentinel(key)] : sentinel(key);
122
+ }
123
+ const specs = build(sb.spec, placeholders);
124
+ const entries = Object.entries(specs);
125
+ 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`);
127
+ return {
128
+ kind: "read-set",
129
+ name,
130
+ functionName: `rs_${name}`,
131
+ params,
132
+ roles: options.roles ?? ["authenticated"],
133
+ specs,
134
+ definition: sb
135
+ };
136
+ }
137
+ function isReadSet(value) {
138
+ return typeof value === "object" && value !== null && value.kind === "read-set" && typeof value.name === "string";
139
+ }
140
+ /** A parameter a string or array carries, if it is exactly a placeholder. */
141
+ function placeholderOf(value) {
142
+ if (typeof value === "string") {
143
+ const match = EXACT.exec(value);
144
+ return match ? {
145
+ name: match[1],
146
+ array: false
147
+ } : void 0;
148
+ }
149
+ if (Array.isArray(value) && value.length === 1) {
150
+ const inner = placeholderOf(value[0]);
151
+ return inner && !inner.array ? {
152
+ name: inner.name,
153
+ array: true
154
+ } : inner;
155
+ }
156
+ }
157
+ /** Splits a string around the placeholders inside it. */
158
+ function splitPlaceholders(value) {
159
+ const parts = [];
160
+ let last = 0;
161
+ for (const match of value.matchAll(SENTINEL)) {
162
+ if (match.index > last) parts.push({ text: value.slice(last, match.index) });
163
+ parts.push({ param: match[1] });
164
+ last = match.index + match[0].length;
165
+ }
166
+ if (last < value.length) parts.push({ text: value.slice(last) });
167
+ return parts;
168
+ }
169
+ function hasPlaceholder(value) {
170
+ return value.includes(`${NUL}bs:`);
171
+ }
172
+ /** Whether any string inside `value` is (or holds) a placeholder. */
173
+ function containsPlaceholder(value) {
174
+ if (typeof value === "string") return hasPlaceholder(value);
175
+ if (Array.isArray(value)) return value.some(containsPlaceholder);
176
+ if (typeof value === "object" && value !== null) return Object.values(value).some(containsPlaceholder);
177
+ return false;
178
+ }
179
+ /** Missing or wrongly typed parameters, as messages. */
180
+ function checkParams(set, values) {
181
+ const problems = [];
182
+ for (const [key, type] of Object.entries(set.params)) {
183
+ const value = values?.[key];
184
+ if (value === void 0 || value === null) problems.push(`missing parameter "${key}"`);
185
+ else if (type.endsWith("[]") !== Array.isArray(value)) problems.push(`parameter "${key}" must be ${type.endsWith("[]") ? "an array" : "a single value"}`);
186
+ }
187
+ return problems;
188
+ }
189
+ /** The specs with every placeholder replaced by its value. */
190
+ function bindParams(set, values) {
191
+ const bind = (value) => {
192
+ const placeholder = placeholderOf(value);
193
+ if (placeholder) return values[placeholder.name];
194
+ if (typeof value === "string") {
195
+ if (!hasPlaceholder(value)) return value;
196
+ return splitPlaceholders(value).map((part) => "text" in part ? part.text : String(values[part.param] ?? "")).join("");
197
+ }
198
+ if (Array.isArray(value)) return value.map(bind);
199
+ if (typeof value === "object" && value !== null) {
200
+ if (Object.getPrototypeOf(value) !== Object.prototype) return value;
201
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, bind(entry)]));
202
+ }
203
+ return value;
204
+ };
205
+ return Object.fromEntries(Object.entries(set.specs).map(([key, spec]) => [key, {
206
+ ...spec,
207
+ args: spec.args.map(bind)
208
+ }]));
209
+ }
210
+ /** App keys of every table the set reads. */
211
+ function readSetTables(set, meta) {
212
+ const schema = meta ?? set.definition.meta;
213
+ return [...new Set(Object.values(set.specs).flatMap((spec) => specTables(schema, spec)))];
214
+ }
215
+ //#endregion
216
+ export { hasPlaceholder as a, readSetTables as c, isQuerySpec as d, specTables as f, defineReadSet as i, splitPlaceholders as l, checkParams as n, isReadSet as o, containsPlaceholder as r, placeholderOf as s, bindParams as t, createSpecs as u };
@@ -0,0 +1,3 @@
1
+ import { n as TemplateValues, t as TemplateParams } from "../template-mbvlysOo.js";
2
+ 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-6b9i72Ux.js";
3
+ export { type CountRunner, EventSchemas, type LiveCountOptions, type LiveCountSeed, type LiveQueryOptions, type LiveSource, type LiveSubscription, RealtimeClient, RowChange, SubscribeOptions, Subscription, SubscriptionStatus, type TemplateParams, type TemplateValues, Topic, TopicEvent, TopicHandlers, TopicMessage, TopicOptions, TopicPayload, TriggerOptions, defineTopic, liveCount, liveQuery, liveTopic, rowChange };