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.
- package/LICENSE +21 -0
- package/README.md +88 -1
- package/bin/better-supabase.js +3 -0
- package/dist/casing-da0uRTqt.js +14 -0
- package/dist/cli/bin.d.ts +1 -0
- package/dist/cli/bin.js +15 -0
- package/dist/cli/index.d.ts +226 -0
- package/dist/cli/index.js +3 -0
- package/dist/cli-CoByWYja.js +6472 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.js +112 -0
- package/dist/compiler-DL18ukPe.d.ts +14 -0
- package/dist/config/index.d.ts +3 -0
- package/dist/config/index.js +3 -0
- package/dist/config-D-aR2ZoN.js +276 -0
- package/dist/define-CTfWw-SS.d.ts +662 -0
- package/dist/define-QGkq04oe.js +1068 -0
- package/dist/edge/index.d.ts +43 -0
- package/dist/edge/index.js +103 -0
- package/dist/entitlements-BBg61DQZ.d.ts +20 -0
- package/dist/entitlements-BC_khwRy.js +17 -0
- package/dist/env/index.d.ts +2 -0
- package/dist/env/index.js +2 -0
- package/dist/env-BEToab0I.js +204 -0
- package/dist/errors-Bl3rkOzo.js +196 -0
- package/dist/events/index.d.ts +2 -0
- package/dist/events/index.js +2 -0
- package/dist/events-2OJsVG_d.d.ts +84 -0
- package/dist/events-BuzTgn2D.js +135 -0
- package/dist/executor-Bazhgsgq.d.ts +195 -0
- package/dist/executor-Bje4suQF.d.ts +25 -0
- package/dist/executor-Ceq60hPM.js +89 -0
- package/dist/hono/index.d.ts +45 -0
- package/dist/hono/index.js +70 -0
- package/dist/index-BD6wKthp.d.ts +597 -0
- package/dist/index-DQVvHzjW.d.ts +74 -0
- package/dist/index-DRid0qIv.d.ts +76 -0
- package/dist/index-Dup-nSe9.d.ts +117 -0
- package/dist/index-Dy6kuF0P.d.ts +71 -0
- package/dist/index-Hf8PP9sP.d.ts +116 -0
- package/dist/index-JXHBuS53.d.ts +144 -0
- package/dist/index-Nxo3TJcm.d.ts +208 -0
- package/dist/index-Sw43J8HG.d.ts +62 -0
- package/dist/index-cEeweTeM.d.ts +38 -0
- package/dist/index.d.ts +110 -0
- package/dist/index.js +42 -0
- package/dist/invalidate-DbxuL2jL.js +18 -0
- package/dist/jobs/index.d.ts +173 -0
- package/dist/jobs/index.js +477 -0
- package/dist/json-schema-BbwO2mZg.js +181 -0
- package/dist/lint/index.d.ts +75 -0
- package/dist/lint/index.js +167 -0
- package/dist/list/index.d.ts +2 -0
- package/dist/list/index.js +410 -0
- package/dist/live-6b9i72Ux.d.ts +180 -0
- package/dist/live-D8rV1Ldw.js +168 -0
- package/dist/mcp/index.d.ts +105 -0
- package/dist/mcp/index.js +330 -0
- package/dist/mfa-CpogP66z.js +521 -0
- package/dist/mfa-TVia554c.d.ts +44 -0
- package/dist/next/image/index.d.ts +34 -0
- package/dist/next/image/index.js +40 -0
- package/dist/next/index.d.ts +210 -0
- package/dist/next/index.js +426 -0
- package/dist/openapi/index.d.ts +2 -0
- package/dist/openapi/index.js +332 -0
- package/dist/orpc/index.d.ts +46 -0
- package/dist/orpc/index.js +50 -0
- package/dist/otel/index.d.ts +40 -0
- package/dist/otel/index.js +168 -0
- package/dist/path-Cv__-OTj.d.ts +17 -0
- package/dist/plugin-BqR6wKMB.js +23 -0
- package/dist/plugin-HvGqCurB.d.ts +118 -0
- package/dist/plugins/actor/index.d.ts +13 -0
- package/dist/plugins/actor/index.js +43 -0
- package/dist/plugins/rules/index.d.ts +88 -0
- package/dist/plugins/rules/index.js +174 -0
- package/dist/plugins/soft-delete/index.d.ts +38 -0
- package/dist/plugins/soft-delete/index.js +69 -0
- package/dist/plugins/tenant/index.d.ts +43 -0
- package/dist/plugins/tenant/index.js +73 -0
- package/dist/plugins/timestamps/index.d.ts +9 -0
- package/dist/plugins/timestamps/index.js +37 -0
- package/dist/plugins/validation/index.d.ts +24 -0
- package/dist/plugins/validation/index.js +59 -0
- package/dist/pool-BFeqzwQS.d.ts +37 -0
- package/dist/postgres/index.d.ts +28 -0
- package/dist/postgres/index.js +79 -0
- package/dist/postgrest-Dm-vhY6R.d.ts +43 -0
- package/dist/problem-DPu6Fh-t.js +97 -0
- package/dist/problem-DlgB0lYl.d.ts +47 -0
- package/dist/query/index.d.ts +2 -0
- package/dist/query/index.js +3 -0
- package/dist/query-ChvDbsDE.js +186 -0
- package/dist/react/index.d.ts +7 -0
- package/dist/react/index.js +5 -0
- package/dist/react/server.d.ts +13 -0
- package/dist/react/server.js +25 -0
- package/dist/react/session.d.ts +2 -0
- package/dist/react/session.js +3 -0
- package/dist/react-CrOvrnIP.js +241 -0
- package/dist/read-set-DhX2d04c.js +216 -0
- package/dist/realtime/index.d.ts +3 -0
- package/dist/realtime/index.js +253 -0
- package/dist/resolve-DW8h9NPm.d.ts +155 -0
- package/dist/resource-CYiu6Ygi.js +175 -0
- package/dist/resource-SkPTRJDD.d.ts +65 -0
- package/dist/respond-4-dhoV7x.js +380 -0
- package/dist/respond-PoTpy5DT.d.ts +169 -0
- package/dist/result-CTkCZ6JA.d.ts +198 -0
- package/dist/result-DWXatkd6.js +103 -0
- package/dist/scope-CDHcS7dE.js +90 -0
- package/dist/seed-CzecODcJ.js +101 -0
- package/dist/server/index.d.ts +42 -0
- package/dist/server/index.js +68 -0
- package/dist/session-C3osL21k.d.ts +21 -0
- package/dist/session-CD-3orO_.js +19 -0
- package/dist/shared-_6-uPL7E.js +57 -0
- package/dist/simplify-oHiDKRVQ.js +665 -0
- package/dist/spec-pins-CJPVghHy.d.ts +20 -0
- package/dist/spec-pins-t6sPaHvl.js +20 -0
- package/dist/sql-M8l3fnS0.js +260 -0
- package/dist/ssr/index.d.ts +5 -0
- package/dist/ssr/index.js +4 -0
- package/dist/standard-CyhRzctb.d.ts +7 -0
- package/dist/standard-FUi3Z3-7.js +21 -0
- package/dist/stats-3iy8u3_P.js +566 -0
- package/dist/storage/index.d.ts +4 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage-BWXVUXGk.js +461 -0
- package/dist/tables-rZxRxNPW.js +59 -0
- package/dist/template-BeOGnynM.js +93 -0
- package/dist/template-mbvlysOo.d.ts +6 -0
- package/dist/testing/index.d.ts +294 -0
- package/dist/testing/index.js +751 -0
- package/dist/types-6cYK9pbJ.js +29 -0
- package/dist/types-C2fslP1z.d.ts +195 -0
- package/dist/version-Cqq1m3UY.js +4 -0
- package/dist/view-B0ltUmTi.d.ts +43 -0
- package/dist/view-B6F9QbG-.js +35 -0
- package/dist/webhooks/index.d.ts +152 -0
- package/dist/webhooks/index.js +2 -0
- package/dist/webhooks-DbMTJJX2.js +139 -0
- package/package.json +246 -17
- package/schemas/config-v1.json +328 -0
- package/schemas/doctor-report-v1.json +105 -0
- package/schemas/snapshot-v2.json +1002 -0
- package/skills/better-supabase/SKILL.md +84 -0
- package/skills/better-supabase/references/plugins.md +56 -0
- package/skills/better-supabase/references/troubleshooting.md +39 -0
- package/skills/better-supabase-api/SKILL.md +67 -0
- package/skills/better-supabase-api/references/adapters.md +108 -0
- package/skills/better-supabase-testing/SKILL.md +65 -0
- 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,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 };
|