@motherbase/client 1.2.8 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,220 +1,265 @@
1
- # @motherbase/client
2
-
3
- A **supabase-js-shaped** client SDK for the Motherbase self-hosted backend. It
4
- keeps the same entry points (`createClient`, `.from()`, `.auth`, `.storage`,
5
- `.channel()`, `.functions`) and the same `{ data, error }` result convention, so
6
- supabase-js code reads and reviews the same way here.
7
-
8
- **Scope, honestly.** For the **data layer**, this is a package swap: change the
9
- import and the client factory, keep your queries. Every `supabase-js` data call
10
- in the contract suite is either supported or supported with a stated difference,
11
- including relational embedding (`select('*, author(*)')`), `.rpc()`, native
12
- `upsert`, `count`, `csv()`, the full filter set and PostgREST error codes.
13
- Auth and realtime are close, with named gaps (no PKCE code exchange, no `mfa`
14
- namespace on the client, no private channels).
15
-
16
- **Storage is path-addressed too**: `upload('avatars/123/pic.png')` then
17
- `download('avatars/123/pic.png')` round trips, `list(prefix)` works, and keys are
18
- traversal-checked. Still missing there: `move`, `copy`, batch signed URLs and
19
- image transforms, and authorization is ownership plus bucket tokens rather than
20
- RLS policies on the objects table. See
21
- [`docs/migrating-from-supabase.md`](../../docs/migrating-from-supabase.md).
22
-
23
- Do not plan a migration from the paragraph above. Plan it from
24
- [`COMPATIBILITY.md`](./COMPATIBILITY.md), which is generated from contract tests
25
- and is exhaustive, per method, with the exact difference for every partial row.
26
-
27
- Typed, isomorphic (browser, Next.js server & client, Expo / React Native), built
28
- on global `fetch` with zero runtime dependencies (`socket.io-client` is an
29
- optional peer dependency, used only for realtime).
30
-
31
- ## Install
32
-
33
- ```bash
34
- npm i @motherbase/client
35
- # optional, only if you use realtime:
36
- npm i socket.io-client
37
- ```
38
-
39
- ## Quickstart
40
-
41
- ```ts
42
- import { createClient, localStorageStorage } from '@motherbase/client';
43
-
44
- const client = createClient(
45
- 'http://localhost:5001',
46
- 'your-anon-key', // optional; sent as an `apikey` header
47
- { storage: localStorageStorage() } // omit for in-memory (SSR-safe) sessions
48
- );
49
-
50
- await client.hydrate(); // restore a persisted session (optional)
51
-
52
- // Auth
53
- await client.auth.signUp({ email: 'me@example.com', password: 'secret123' });
54
- const { data, error } = await client.auth.signInWithPassword({
55
- email: 'me@example.com',
56
- password: 'secret123',
57
- });
58
-
59
- // Query builder (awaitable, PostgREST-style)
60
- const { data: products } = await client
61
- .from('products')
62
- .select('id,name,price')
63
- .lt('price', 50)
64
- .order('name', { ascending: false })
65
- .limit(20);
66
-
67
- // Storage. `uploaded.path` is the key you passed, so later download/remove/
68
- // signed-URL calls take the same string.
69
- const { data: uploaded } = await client.storage.from('avatars').upload(`${userId}/me.png`, myBlob, {
70
- visibility: 'public',
71
- });
72
-
73
- // Realtime: postgres_changes, broadcast, presence over one channel (Supabase-shaped)
74
- const room = client
75
- .channel('room-1', { config: { broadcast: { self: true }, presence: { key: userId } } })
76
- .on('postgres_changes', { event: '*', schema: 'public', table: 'messages', filter: 'room_id=eq.42' }, p => {
77
- console.log(p.eventType, p.new, p.old);
78
- })
79
- .on('broadcast', { event: 'cursor' }, ({ payload }) => console.log(payload))
80
- .on('presence', { event: 'sync' }, () => console.log(room.presenceState()))
81
- .subscribe();
82
-
83
- await room.send({ type: 'broadcast', event: 'cursor', payload: { x: 1, y: 2 } });
84
- await room.track({ online_at: Date.now() });
85
-
86
- // Edge functions
87
- const { data: result } = await client.functions.invoke('hello', { body: { name: 'Ada' } });
88
- ```
89
-
90
- Every data/auth/storage call resolves to `{ data, error }` and **never throws**
91
- for expected API errors. `error` is `null` on success, otherwise
92
- `{ message, status?, code? }`.
93
-
94
- The SDK attaches the access token automatically and revalidates it on a 401
95
- (single-flight: concurrent requests share one revalidation). Sessions are Better
96
- Auth bearer sessions, so there is no separate refresh token to rotate: when the
97
- token is no longer valid the SDK clears the session and emits `SIGNED_OUT`.
98
-
99
- ## API surface (cheatsheet)
100
-
101
- ```ts
102
- // Data. Filters: eq neq gt gte lt lte like ilike likeAllOf likeAnyOf ilikeAllOf
103
- // ilikeAnyOf in is not or filter match textSearch contains containedBy overlaps
104
- // rangeGt rangeGte rangeLt rangeLte rangeAdjacent
105
- client.from(table).select(cols).eq(col, v).in(col, [..]).order(col, { ascending, nullsFirst, referencedTable })
106
- client.from(table).select(cols).limit(n, { referencedTable }).range(from, to).abortSignal(signal)
107
- client.from(table).select(cols).or('a.eq.1,and(b.gt.2,c.is.null)') // nests, as PostgREST does
108
- client.from(table).select(cols).not(col, 'eq', v).filter(col, 'isdistinct', v)
109
- client.from(table).select(cols).textSearch(col, q, { type: 'websearch', config: 'english' })
110
- client.from(table).select(cols).single() | .maybeSingle() | .returns<T>() | .csv() | .explain()
111
- client.from(table).insert(row) | .insert([rowA, rowB], { defaultToNull: false })
112
- client.from(table).upsert(rows, { onConflict, ignoreDuplicates })
113
- client.from(table).update(values).eq(..) // a filter is required
114
- client.from(table).delete().eq(..) // a filter is required
115
- // Every call resolves { data, error, count }. `count` is null unless you ask:
116
- // .select('*', { count: 'exact', head: true }).
117
- // Values are quoted for you: .in('name', ['a,b']) is one value, not two.
118
- // Writes always return their rows: no .select() needed, and no way to opt out.
119
- // An unbounded select is capped at 100 rows server side: paginate with .range().
120
- // explain() needs an admin caller; a normal caller gets 403.
121
-
122
- // Relational embedding. Compiles to a correlated LATERAL subquery inside the
123
- // same RLS transaction, so embedded rows are policy-filtered too.
124
- client.from('posts').select('id, author(name), comments(id)') // object / array by FK shape
125
- client.from('posts').select('id, author!inner(name)') // inner join semantics
126
- client.from('posts').select('id, comments(count)') // aggregate
127
-
128
- // Database functions (typed from the generated Database["public"]["Functions"]).
129
- // Runs in the caller's RLS transaction. SECURITY DEFINER functions are refused
130
- // unless an operator allowlists them.
131
- client.rpc(fn, args, { get, head, count })
132
-
133
- // Auth → { data, error }
134
- client.auth.signUp({ email, password, username? })
135
- client.auth.signInWithPassword({ email, password })
136
- client.auth.signInWithOtp({ email }) // emails a code
137
- client.auth.verifyOtp({ email, token }) // completes the OTP sign-in
138
- client.auth.signInWithOAuth({ provider, options: { redirectTo, scopes, skipBrowserRedirect } })
139
- client.auth.resetPasswordForEmail(email)
140
- client.auth.resetPassword({ email, otp, password })
141
- client.auth.updateUser({ data, password, currentPassword })
142
- client.auth.setSession({ access_token })
143
- client.auth.signOut()
144
- client.auth.getUser() // { data: { user } }, revalidates against the server
145
- client.auth.getSession() // { data: { session } }, local only
146
- client.auth.refreshSession()
147
- client.auth.onAuthStateChange((event, session) => {}) // SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED
148
- client.auth.deleteUser({ password }) // self-service, needs the backend flow enabled
149
-
150
- // Storage → { data, error }. `path` is the object key inside the bucket, and it
151
- // round trips. The file id returned by upload still works anywhere a path does.
152
- client.storage.from(bucket).upload(path, file, { visibility, upsert })
153
- client.storage.from(bucket).download(path)
154
- client.storage.from(bucket).remove([path])
155
- client.storage.from(bucket).list(prefix, { limit, offset, search, sortBy })
156
- client.storage.from(bucket).createSignedUrl(path, expiresIn) // expiresIn is ignored
157
- client.storage.from(bucket).getPublicUrl(path) // sync
158
-
159
- // Realtime
160
- client.channel(name, { config: { broadcast: { self, ack }, presence: { key } } })
161
- .on('postgres_changes', { event, schema, table, filter }, cb)
162
- .on('broadcast', { event }, cb)
163
- .on('presence', { event: 'sync' | 'join' | 'leave' }, cb)
164
- .subscribe(status => {}) // SUBSCRIBED | TIMED_OUT | CLOSED | CHANNEL_ERROR
165
- channel.send({ type: 'broadcast', event, payload })
166
- channel.track(state); channel.untrack(); channel.presenceState()
167
- client.removeChannel(channel)
168
-
169
- // Edge functions → { data, error }
170
- client.functions.invoke(name, { body })
171
- ```
172
-
173
- ## Storage adapters
174
-
175
- | Runtime | Adapter |
176
- | -------- | ------------------------------------------------------------- |
177
- | Browser | `localStorageStorage()` or `cookieStorage()` |
178
- | SSR/Node | `memoryStorage()` (default), `serverCookieStorage(cookieMethods)` (getAll/setAll, see `@motherbase/ssr` for Next.js helpers), or a custom `{ get, set }` adapter |
179
- | Expo/RN | custom `{ get, set }` backed by `AsyncStorage` |
180
-
181
- ```ts
182
- import AsyncStorage from '@react-native-async-storage/async-storage';
183
- import { createClient } from '@motherbase/client';
184
-
185
- const client = createClient(API_URL, ANON_KEY, {
186
- storage: {
187
- async get() {
188
- const raw = await AsyncStorage.getItem('session');
189
- return raw ? JSON.parse(raw) : null;
190
- },
191
- async set(session) {
192
- if (session) await AsyncStorage.setItem('session', JSON.stringify(session));
193
- else await AsyncStorage.removeItem('session');
194
- },
195
- },
196
- });
197
- ```
198
-
199
- ## Compatibility matrix
200
-
201
- The matrix is **not maintained by hand**. It lives in
202
- [`COMPATIBILITY.md`](./COMPATIBILITY.md) and is generated by
203
- `apps/server/tests/sdk-contract.integration.test.ts`, which runs every listed
204
- supabase-js call against a live Motherbase server and a live Postgres. Each row
205
- carries the id of the test that produced it.
206
-
207
- Rows marked `Not supported` assert the current failure behaviour, so the day a
208
- feature lands its test fails and the document has to be regenerated. That is the
209
- mechanism that keeps this page from over-claiming (or under-claiming) again. CI
210
- regenerates the file and fails if the committed copy differs.
211
-
212
- Regenerate it locally with:
213
-
214
- ```bash
215
- npm run test -w apps/server -- sdk-contract
216
- ```
217
-
218
- More examples live in [`docs/sdk.md`](../../docs/sdk.md). Project Studio also
219
- exposes a project-specific Connect page with the exact API URL, WebSocket URL and
220
- project ID to paste into `.env`.
1
+ # @motherbase/client
2
+
3
+ A **supabase-js-shaped** client SDK for the Motherbase self-hosted backend. It
4
+ keeps the same entry points (`createClient`, `.from()`, `.auth`, `.storage`,
5
+ `.channel()`, `.functions`) and the same `{ data, error }` result convention, so
6
+ supabase-js code reads and reviews the same way here.
7
+
8
+ **Scope, honestly.** For the **data layer**, this is a package swap: change the
9
+ import and the client factory, keep your queries. Every `supabase-js` data call
10
+ in the contract suite is either supported or supported with a stated difference,
11
+ including relational embedding (`select('*, author(*)')`), `.rpc()`, native
12
+ `upsert`, `count`, `csv()`, the full filter set and PostgREST error codes.
13
+ Auth and realtime are close, with named gaps (no PKCE code exchange, no `mfa`
14
+ namespace on the client, no private channels).
15
+
16
+ **Storage is path-addressed too**: `upload('avatars/123/pic.png')` then
17
+ `download('avatars/123/pic.png')` round trips, `list(prefix)` works, and keys are
18
+ traversal-checked. Still missing there: `move`, `copy`, batch signed URLs and
19
+ image transforms, and authorization is ownership plus bucket tokens rather than
20
+ RLS policies on the objects table. See
21
+ [`docs/migrating-from-supabase.md`](../../docs/migrating-from-supabase.md).
22
+
23
+ Do not plan a migration from the paragraph above. Plan it from
24
+ [`COMPATIBILITY.md`](./COMPATIBILITY.md), which is generated from contract tests
25
+ and is exhaustive, per method, with the exact difference for every partial row.
26
+
27
+ Typed, isomorphic (browser, Next.js server & client, Expo / React Native), built
28
+ on global `fetch` with zero runtime dependencies (`socket.io-client` is an
29
+ optional peer dependency, used only for realtime).
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm i @motherbase/client
35
+ # optional, only if you use realtime:
36
+ npm i socket.io-client
37
+ ```
38
+
39
+ ## Quickstart
40
+
41
+ ```ts
42
+ import { createClient, localStorageStorage } from '@motherbase/client';
43
+
44
+ const client = createClient(
45
+ 'http://localhost:5001',
46
+ 'your-anon-key', // optional; sent as an `apikey` header
47
+ { storage: localStorageStorage() } // omit for in-memory (SSR-safe) sessions
48
+ );
49
+
50
+ await client.hydrate(); // restore a persisted session (optional)
51
+
52
+ // Auth
53
+ await client.auth.signUp({ email: 'me@example.com', password: 'secret123' });
54
+ const { data, error } = await client.auth.signInWithPassword({
55
+ email: 'me@example.com',
56
+ password: 'secret123',
57
+ });
58
+
59
+ // Query builder (awaitable, PostgREST-style)
60
+ const { data: products } = await client
61
+ .from('products')
62
+ .select('id,name,price')
63
+ .lt('price', 50)
64
+ .order('name', { ascending: false })
65
+ .limit(20);
66
+
67
+ // Storage. `uploaded.path` is the key you passed, so later download/remove/
68
+ // signed-URL calls take the same string.
69
+ const { data: uploaded } = await client.storage.from('avatars').upload(`${userId}/me.png`, myBlob, {
70
+ visibility: 'public',
71
+ });
72
+
73
+ // Realtime: postgres_changes, broadcast, presence over one channel (Supabase-shaped)
74
+ const room = client
75
+ .channel('room-1', { config: { broadcast: { self: true }, presence: { key: userId } } })
76
+ .on('postgres_changes', { event: '*', schema: 'public', table: 'messages', filter: 'room_id=eq.42' }, p => {
77
+ console.log(p.eventType, p.new, p.old);
78
+ })
79
+ .on('broadcast', { event: 'cursor' }, ({ payload }) => console.log(payload))
80
+ .on('presence', { event: 'sync' }, () => console.log(room.presenceState()))
81
+ .subscribe();
82
+
83
+ await room.send({ type: 'broadcast', event: 'cursor', payload: { x: 1, y: 2 } });
84
+ await room.track({ online_at: Date.now() });
85
+
86
+ // Edge functions
87
+ const { data: result } = await client.functions.invoke('hello', { body: { name: 'Ada' } });
88
+ ```
89
+
90
+ Every data/auth/storage call resolves to `{ data, error }` and **never throws**
91
+ for expected API errors. `error` is `null` on success, otherwise
92
+ `{ message, status?, code? }`.
93
+
94
+ The SDK attaches the access token automatically and revalidates it on a 401
95
+ (single-flight: concurrent requests share one revalidation). Sessions are Better
96
+ Auth bearer sessions, so there is no separate refresh token to rotate: when the
97
+ token is no longer valid the SDK clears the session and emits `SIGNED_OUT`.
98
+
99
+ ## API surface (cheatsheet)
100
+
101
+ ```ts
102
+ // Data. Filters: eq neq gt gte lt lte like ilike likeAllOf likeAnyOf ilikeAllOf
103
+ // ilikeAnyOf in is not or filter match textSearch contains containedBy overlaps
104
+ // rangeGt rangeGte rangeLt rangeLte rangeAdjacent
105
+ client.from(table).select(cols).eq(col, v).in(col, [..]).order(col, { ascending, nullsFirst, referencedTable })
106
+ client.from(table).select(cols).limit(n, { referencedTable }).range(from, to).abortSignal(signal)
107
+ client.from(table).select(cols).or('a.eq.1,and(b.gt.2,c.is.null)') // nests, as PostgREST does
108
+ client.from(table).select(cols).not(col, 'eq', v).filter(col, 'isdistinct', v)
109
+ client.from(table).select(cols).textSearch(col, q, { type: 'websearch', config: 'english' })
110
+ client.from(table).select(cols).single() | .maybeSingle() | .returns<T>() | .csv() | .explain()
111
+ client.from(table).insert(row) | .insert([rowA, rowB], { defaultToNull: false })
112
+ client.from(table).upsert(rows, { onConflict, ignoreDuplicates })
113
+ client.from(table).update(values).eq(..) // a filter is required
114
+ client.from(table).delete().eq(..) // a filter is required
115
+ // Every call resolves { data, error, count }. `count` is null unless you ask:
116
+ // .select('*', { count: 'exact', head: true }).
117
+ // Values are quoted for you: .in('name', ['a,b']) is one value, not two.
118
+ // Writes always return their rows: no .select() needed, and no way to opt out.
119
+ // An unbounded select is capped at 100 rows server side: paginate with .range().
120
+ // explain() needs an admin caller; a normal caller gets 403.
121
+
122
+ // Relational embedding. Compiles to a correlated LATERAL subquery inside the
123
+ // same RLS transaction, so embedded rows are policy-filtered too.
124
+ client.from('posts').select('id, author(name), comments(id)') // object / array by FK shape
125
+ client.from('posts').select('id, author!inner(name)') // inner join semantics
126
+ client.from('posts').select('id, comments(count)') // aggregate
127
+
128
+ // Database functions (typed from the generated Database["public"]["Functions"]).
129
+ // Runs in the caller's RLS transaction. SECURITY DEFINER functions are refused
130
+ // unless an operator allowlists them.
131
+ client.rpc(fn, args, { get, head, count })
132
+
133
+ // Auth → { data, error }
134
+ client.auth.signUp({ email, password, username? })
135
+ client.auth.signInWithPassword({ email, password })
136
+ client.auth.signInWithOtp({ email }) // emails a code
137
+ client.auth.verifyOtp({ email, token }) // completes the OTP sign-in
138
+ client.auth.signInWithOAuth({ provider, options: { redirectTo, scopes, skipBrowserRedirect } })
139
+ client.auth.getSessionFromUrl() // finishes the OAuth redirect, see below
140
+ client.auth.resetPasswordForEmail(email)
141
+ client.auth.resetPassword({ email, otp, password })
142
+ client.auth.updateUser({ data, password, currentPassword })
143
+ client.auth.setSession({ access_token })
144
+ client.auth.signOut()
145
+ client.auth.getUser() // { data: { user } }, revalidates against the server
146
+ client.auth.getSession() // { data: { session } }, local only
147
+ client.auth.refreshSession()
148
+ client.auth.onAuthStateChange((event, session) => {}) // SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED
149
+ client.auth.deleteUser({ password }) // self-service, needs the backend flow enabled
150
+
151
+ // Storage → { data, error }. `path` is the object key inside the bucket, and it
152
+ // round trips. The file id returned by upload still works anywhere a path does.
153
+ client.storage.from(bucket).upload(path, file, { visibility, upsert })
154
+ client.storage.from(bucket).download(path)
155
+ client.storage.from(bucket).remove([path])
156
+ client.storage.from(bucket).list(prefix, { limit, offset, search, sortBy })
157
+ client.storage.from(bucket).createSignedUrl(path, expiresIn) // expiresIn is ignored
158
+ client.storage.from(bucket).getPublicUrl(path) // sync
159
+
160
+ // Realtime
161
+ client.channel(name, { config: { broadcast: { self, ack }, presence: { key } } })
162
+ .on('postgres_changes', { event, schema, table, filter }, cb)
163
+ .on('broadcast', { event }, cb)
164
+ .on('presence', { event: 'sync' | 'join' | 'leave' }, cb)
165
+ .subscribe(status => {}) // SUBSCRIBED | TIMED_OUT | CLOSED | CHANNEL_ERROR
166
+ channel.send({ type: 'broadcast', event, payload })
167
+ channel.track(state); channel.untrack(); channel.presenceState()
168
+ client.removeChannel(channel)
169
+
170
+ // Edge functions → { data, error }
171
+ client.functions.invoke(name, { body })
172
+ ```
173
+
174
+ ## Social sign-in (OAuth)
175
+
176
+ A provider callback is a browser redirect, so it cannot answer the SDK with a
177
+ session the way a password sign-in does. Two calls, not one:
178
+
179
+ ```ts
180
+ // 1. Wherever the button lives. Navigates the browser to the backend, which
181
+ // redirects on to the provider.
182
+ await client.auth.signInWithOAuth({
183
+ provider: 'google',
184
+ options: { redirectTo: 'https://app.example.com/auth/callback' },
185
+ });
186
+
187
+ // 2. On that page (or on app start). Reads the token the backend appended to
188
+ // the redirect, stores the session, and strips it from the address bar.
189
+ const { data, error } = await client.auth.getSessionFromUrl();
190
+ ```
191
+
192
+ `getSessionFromUrl()` resolves with a null session and no error when there is
193
+ nothing to read, so calling it on every page load is safe.
194
+
195
+ Step 1 is a full-page navigation, not a fetch. That is deliberate: the backend
196
+ sets an OAuth state cookie there and re-checks it on the provider callback, and
197
+ a cookie set on a cross-site fetch response is refused by Safari and Firefox and
198
+ by Chrome in incognito. Navigating makes it a first-party cookie, so the flow
199
+ works in every browser even when your app and your API are on different domains.
200
+ If you are not using the SDK, point the button at the same URL:
201
+
202
+ ```
203
+ https://<your-api>/api/auth/sign-in/social/google?callbackURL=https://app.example.com/auth/callback
204
+ ```
205
+
206
+ `callbackURL` is validated against the trusted origins, so it has to be an
207
+ origin you allowlisted.
208
+
209
+ Two backend settings decide whether this works at all:
210
+
211
+ - **Callback URL.** Studio, Authentication, Sign In / Providers shows the exact
212
+ URL to register in the provider's console.
213
+ - **CORS allowed origins.** Studio, Authentication settings, Security · CORS.
214
+ If your app is served from a different origin than the API, that origin must
215
+ be listed. Otherwise the browser refuses the cookie the sign-in start sets,
216
+ and the provider callback comes back as `?error=state_mismatch`.
217
+
218
+ ## Storage adapters
219
+
220
+ | Runtime | Adapter |
221
+ | -------- | ------------------------------------------------------------- |
222
+ | Browser | `localStorageStorage()` or `cookieStorage()` |
223
+ | SSR/Node | `memoryStorage()` (default), `serverCookieStorage(cookieMethods)` (getAll/setAll, see `@motherbase/ssr` for Next.js helpers), or a custom `{ get, set }` adapter |
224
+ | Expo/RN | custom `{ get, set }` backed by `AsyncStorage` |
225
+
226
+ ```ts
227
+ import AsyncStorage from '@react-native-async-storage/async-storage';
228
+ import { createClient } from '@motherbase/client';
229
+
230
+ const client = createClient(API_URL, ANON_KEY, {
231
+ storage: {
232
+ async get() {
233
+ const raw = await AsyncStorage.getItem('session');
234
+ return raw ? JSON.parse(raw) : null;
235
+ },
236
+ async set(session) {
237
+ if (session) await AsyncStorage.setItem('session', JSON.stringify(session));
238
+ else await AsyncStorage.removeItem('session');
239
+ },
240
+ },
241
+ });
242
+ ```
243
+
244
+ ## Compatibility matrix
245
+
246
+ The matrix is **not maintained by hand**. It lives in
247
+ [`COMPATIBILITY.md`](./COMPATIBILITY.md) and is generated by
248
+ `apps/server/tests/sdk-contract.integration.test.ts`, which runs every listed
249
+ supabase-js call against a live Motherbase server and a live Postgres. Each row
250
+ carries the id of the test that produced it.
251
+
252
+ Rows marked `Not supported` assert the current failure behaviour, so the day a
253
+ feature lands its test fails and the document has to be regenerated. That is the
254
+ mechanism that keeps this page from over-claiming (or under-claiming) again. CI
255
+ regenerates the file and fails if the committed copy differs.
256
+
257
+ Regenerate it locally with:
258
+
259
+ ```bash
260
+ npm run test -w apps/server -- sdk-contract
261
+ ```
262
+
263
+ More examples live in [`docs/sdk.md`](../../docs/sdk.md). Project Studio also
264
+ exposes a project-specific Connect page with the exact API URL, WebSocket URL and
265
+ project ID to paste into `.env`.
package/dist/auth.d.ts CHANGED
@@ -50,6 +50,18 @@ export interface UpdateUserAttributes {
50
50
  /** Arbitrary profile fields (name, image, or any custom additional field). */
51
51
  data?: Record<string, unknown>;
52
52
  }
53
+ /**
54
+ * Read the token an OAuth callback left in the URL fragment and return the URL
55
+ * with it removed, so a reload or a shared link cannot replay it.
56
+ *
57
+ * The provider callback cannot answer the SDK with a JSON body: it is a browser
58
+ * redirect. The backend therefore appends `#access_token=…` to the redirect it
59
+ * issues, the same implicit flow supabase-js reads. Exported for testing.
60
+ */
61
+ export declare function readAccessTokenFromUrl(href: string): {
62
+ accessToken: string;
63
+ cleanedHref: string;
64
+ } | null;
53
65
  export interface Subscription {
54
66
  unsubscribe(): void;
55
67
  }
@@ -172,6 +184,23 @@ export declare class AuthClient {
172
184
  signOut(): Promise<{
173
185
  error: MotherbaseResponse<null>['error'];
174
186
  }>;
187
+ /**
188
+ * Finish an OAuth / SSO sign-in after the provider redirected back.
189
+ *
190
+ * `signInWithOAuth` only starts the flow; the session is created during the
191
+ * redirect the browser makes to the backend callback, so the SDK has to pick
192
+ * it up from the URL the browser landed on. Call this once on the page you
193
+ * pointed `redirectTo` at (or on app start, which is where supabase-js does
194
+ * the equivalent). Without it the user is signed in on the backend and signed
195
+ * out in your app.
196
+ *
197
+ * Resolves with a null session and no error when there is nothing to read, so
198
+ * it is safe to call on every page load.
199
+ */
200
+ getSessionFromUrl(href?: string): Promise<MotherbaseResponse<{
201
+ user: User | null;
202
+ session: Session | null;
203
+ }>>;
175
204
  /** Fetch the current user from Better Auth (requires a valid session). */
176
205
  getUser(): Promise<MotherbaseResponse<{
177
206
  user: User | null;
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}