@motherbase/client 1.0.1 → 1.1.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 +167 -121
- package/dist/auth.d.ts +50 -45
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +97 -91
- package/dist/auth.js.map +1 -1
- package/dist/channel.d.ts +104 -0
- package/dist/channel.d.ts.map +1 -0
- package/dist/channel.js +179 -0
- package/dist/channel.js.map +1 -0
- package/dist/functions.d.ts +24 -0
- package/dist/functions.d.ts.map +1 -0
- package/dist/functions.js +24 -0
- package/dist/functions.js.map +1 -0
- package/dist/http.d.ts +29 -2
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +58 -5
- package/dist/http.js.map +1 -1
- package/dist/index.d.ts +52 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +66 -20
- package/dist/index.js.map +1 -1
- package/dist/postgrest.d.ts +58 -0
- package/dist/postgrest.d.ts.map +1 -0
- package/dist/postgrest.js +166 -0
- package/dist/postgrest.js.map +1 -0
- package/dist/realtime.d.ts +86 -2
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +304 -9
- package/dist/realtime.js.map +1 -1
- package/dist/storage.d.ts +48 -22
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +94 -26
- package/dist/storage.js.map +1 -1
- package/dist/types.d.ts +49 -6
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +0 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -1
package/README.md
CHANGED
|
@@ -1,121 +1,167 @@
|
|
|
1
|
-
# @motherbase/client
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
`
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
//
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
await client.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
1
|
+
# @motherbase/client
|
|
2
|
+
|
|
3
|
+
A **Supabase-js-compatible** client SDK for the Motherbase self-hosted backend.
|
|
4
|
+
Migrating from Supabase is mostly a package swap — replace
|
|
5
|
+
`@supabase/supabase-js` with `@motherbase/client` and keep the same
|
|
6
|
+
`createClient`, `.from()`, `.auth`, `.storage`, `.channel()` and `.functions`
|
|
7
|
+
shapes.
|
|
8
|
+
|
|
9
|
+
Typed, isomorphic (browser, Next.js server & client, Expo / React Native), built
|
|
10
|
+
on global `fetch` with zero runtime dependencies (`socket.io-client` is an
|
|
11
|
+
optional peer dependency, used only for realtime).
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm i @motherbase/client
|
|
17
|
+
# optional, only if you use realtime:
|
|
18
|
+
npm i socket.io-client
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Quickstart
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { createClient, localStorageStorage } from '@motherbase/client';
|
|
25
|
+
|
|
26
|
+
const client = createClient(
|
|
27
|
+
'http://localhost:5001',
|
|
28
|
+
'your-anon-key', // optional; sent as an `apikey` header
|
|
29
|
+
{ storage: localStorageStorage() } // omit for in-memory (SSR-safe) sessions
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
await client.hydrate(); // restore a persisted session (optional)
|
|
33
|
+
|
|
34
|
+
// Auth
|
|
35
|
+
await client.auth.signUp({ email: 'me@example.com', password: 'secret123' });
|
|
36
|
+
const { data, error } = await client.auth.signInWithPassword({
|
|
37
|
+
email: 'me@example.com',
|
|
38
|
+
password: 'secret123',
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// Query builder (awaitable, PostgREST-style)
|
|
42
|
+
const { data: products } = await client
|
|
43
|
+
.from('products')
|
|
44
|
+
.select('id,name,price')
|
|
45
|
+
.lt('price', 50)
|
|
46
|
+
.order('name', { ascending: false })
|
|
47
|
+
.limit(20);
|
|
48
|
+
|
|
49
|
+
// Storage
|
|
50
|
+
const { data: uploaded } = await client.storage.from('avatars').upload('me.png', myBlob, {
|
|
51
|
+
visibility: 'public',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// Realtime — postgres_changes, broadcast, presence over one channel (Supabase-shaped)
|
|
55
|
+
const room = client
|
|
56
|
+
.channel('room-1', { config: { broadcast: { self: true }, presence: { key: userId } } })
|
|
57
|
+
.on('postgres_changes', { event: '*', schema: 'public', table: 'messages', filter: 'room_id=eq.42' }, p => {
|
|
58
|
+
console.log(p.eventType, p.new, p.old);
|
|
59
|
+
})
|
|
60
|
+
.on('broadcast', { event: 'cursor' }, ({ payload }) => console.log(payload))
|
|
61
|
+
.on('presence', { event: 'sync' }, () => console.log(room.presenceState()))
|
|
62
|
+
.subscribe();
|
|
63
|
+
|
|
64
|
+
await room.send({ type: 'broadcast', event: 'cursor', payload: { x: 1, y: 2 } });
|
|
65
|
+
await room.track({ online_at: Date.now() });
|
|
66
|
+
|
|
67
|
+
// Edge functions
|
|
68
|
+
const { data: result } = await client.functions.invoke('hello', { body: { name: 'Ada' } });
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Every data/auth/storage call resolves to `{ data, error }` and **never throws**
|
|
72
|
+
for expected API errors. `error` is `null` on success, otherwise
|
|
73
|
+
`{ message, status?, code? }`.
|
|
74
|
+
|
|
75
|
+
The SDK attaches the access token automatically and transparently refreshes it on
|
|
76
|
+
a 401 (single-flight: concurrent requests share one refresh). When the refresh
|
|
77
|
+
token is invalid it clears the session and emits `SIGNED_OUT`.
|
|
78
|
+
|
|
79
|
+
## API surface (cheatsheet)
|
|
80
|
+
|
|
81
|
+
```ts
|
|
82
|
+
// Data
|
|
83
|
+
client.from(table).select(cols).eq(col, v).in(col, [..]).order(col, { ascending }).limit(n).range(from, to)
|
|
84
|
+
client.from(table).select(cols).single() | .maybeSingle()
|
|
85
|
+
client.from(table).insert(rows) | .update(values).eq(..) | .upsert(rows) | .delete().eq(..)
|
|
86
|
+
|
|
87
|
+
// Auth → { data, error }
|
|
88
|
+
client.auth.signUp({ email, password })
|
|
89
|
+
client.auth.signInWithPassword({ email, password })
|
|
90
|
+
client.auth.signOut()
|
|
91
|
+
client.auth.getUser() // { data: { user } }
|
|
92
|
+
client.auth.getSession() // { data: { session } }
|
|
93
|
+
client.auth.refreshSession()
|
|
94
|
+
client.auth.onAuthStateChange((event, session) => {}) // SIGNED_IN | SIGNED_OUT | TOKEN_REFRESHED
|
|
95
|
+
|
|
96
|
+
// Storage → { data, error }
|
|
97
|
+
client.storage.from(bucket).upload(path, file, { visibility })
|
|
98
|
+
client.storage.from(bucket).download(path)
|
|
99
|
+
client.storage.from(bucket).remove([path])
|
|
100
|
+
client.storage.from(bucket).createSignedUrl(path, expiresIn)
|
|
101
|
+
client.storage.from(bucket).getPublicUrl(path) // sync
|
|
102
|
+
|
|
103
|
+
// Realtime
|
|
104
|
+
client.channel(name, { config: { broadcast: { self, ack }, presence: { key } } })
|
|
105
|
+
.on('postgres_changes', { event, schema, table, filter }, cb)
|
|
106
|
+
.on('broadcast', { event }, cb)
|
|
107
|
+
.on('presence', { event: 'sync' | 'join' | 'leave' }, cb)
|
|
108
|
+
.subscribe(status => {}) // SUBSCRIBED | TIMED_OUT | CLOSED | CHANNEL_ERROR
|
|
109
|
+
channel.send({ type: 'broadcast', event, payload })
|
|
110
|
+
channel.track(state); channel.untrack(); channel.presenceState()
|
|
111
|
+
client.removeChannel(channel)
|
|
112
|
+
|
|
113
|
+
// Edge functions → { data, error }
|
|
114
|
+
client.functions.invoke(name, { body })
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Storage adapters
|
|
118
|
+
|
|
119
|
+
| Runtime | Adapter |
|
|
120
|
+
| -------- | ------------------------------------------------------------- |
|
|
121
|
+
| Browser | `localStorageStorage()` or `cookieStorage()` |
|
|
122
|
+
| SSR/Node | `memoryStorage()` (default) or `serverCookieStorage(adapter)` |
|
|
123
|
+
| Expo/RN | custom `{ get, set }` backed by `AsyncStorage` |
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
import AsyncStorage from '@react-native-async-storage/async-storage';
|
|
127
|
+
import { createClient } from '@motherbase/client';
|
|
128
|
+
|
|
129
|
+
const client = createClient(API_URL, ANON_KEY, {
|
|
130
|
+
storage: {
|
|
131
|
+
async get() {
|
|
132
|
+
const raw = await AsyncStorage.getItem('session');
|
|
133
|
+
return raw ? JSON.parse(raw) : null;
|
|
134
|
+
},
|
|
135
|
+
async set(session) {
|
|
136
|
+
if (session) await AsyncStorage.setItem('session', JSON.stringify(session));
|
|
137
|
+
else await AsyncStorage.removeItem('session');
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Compatibility matrix
|
|
144
|
+
|
|
145
|
+
| Area | Status | Notes |
|
|
146
|
+
| ------------------------------------------------------------- | ------------- | ------------------------------------------------------- |
|
|
147
|
+
| `createClient(url, key, options)` | Supported | `key` sent as `apikey` header. |
|
|
148
|
+
| `from().select/insert/update/delete` | Supported | Awaitable `{ data, error }`. |
|
|
149
|
+
| filters `eq/neq/gt/gte/lt/lte/like/ilike/in/is` | Supported | Mapped to the `/rest/v1` PostgREST dialect. |
|
|
150
|
+
| `order/limit/range/single/maybeSingle` | Supported | `range` → limit/offset. |
|
|
151
|
+
| `upsert()` | Partial | Emulated as `insert` (no `onConflict`). |
|
|
152
|
+
| relational embedding (`select('*, author(*)')`) | Not supported | Backend blocks joins. |
|
|
153
|
+
| `.rpc()` | Not supported | No exposed Postgres functions — use `functions.invoke`. |
|
|
154
|
+
| `.or/.not/.filter/.textSearch/.contains` | Not supported | Not in the backend dialect. |
|
|
155
|
+
| `auth.signUp/signInWithPassword/signOut` | Supported | |
|
|
156
|
+
| `auth.getUser/getSession/refreshSession/onAuthStateChange` | Supported | Events: `SIGNED_IN`, `SIGNED_OUT`, `TOKEN_REFRESHED`. |
|
|
157
|
+
| OAuth / magic link / OTP | Not supported | Email + password only. |
|
|
158
|
+
| `storage upload/download/remove/createSignedUrl/getPublicUrl` | Partial | `path` == server file id returned by `upload`. |
|
|
159
|
+
| `storage.list()` | Stub | Returns an unsupported error instead of throwing. |
|
|
160
|
+
| `channel().on('postgres_changes').subscribe()` | Supported | INSERT/UPDATE/DELETE/`*`, RLS-filtered, column `filter`. |
|
|
161
|
+
| `channel().on('broadcast')` + `.send()` | Supported | `config.broadcast.self` / `.ack`. |
|
|
162
|
+
| `channel().on('presence')` + `.track/.untrack/.presenceState` | Supported | `sync`/`join`/`leave`, keyed by `config.presence.key`. |
|
|
163
|
+
| `functions.invoke(name, { body })` | Supported | Maps to `/api/edge/:name`. |
|
|
164
|
+
|
|
165
|
+
More examples live in [`docs/sdk.md`](../../docs/sdk.md). Project Studio also
|
|
166
|
+
exposes a project-specific Connect page with the exact API URL, WebSocket URL and
|
|
167
|
+
project ID to paste into `.env`.
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,61 +1,66 @@
|
|
|
1
1
|
import type { HttpClient } from './http.js';
|
|
2
2
|
import type { SessionStore } from './session.js';
|
|
3
|
-
import type {
|
|
4
|
-
interface
|
|
3
|
+
import type { AuthChangeListener, MotherbaseResponse, Session, User } from './types.js';
|
|
4
|
+
export interface SignUpCredentials {
|
|
5
5
|
email: string;
|
|
6
6
|
password: string;
|
|
7
|
+
/** Optional extra profile field supported by the backend. */
|
|
7
8
|
username?: string;
|
|
8
9
|
}
|
|
9
|
-
interface
|
|
10
|
+
export interface SignInWithPasswordCredentials {
|
|
10
11
|
email: string;
|
|
11
12
|
password: string;
|
|
12
13
|
}
|
|
13
|
-
interface
|
|
14
|
-
|
|
15
|
-
secretCode: string;
|
|
16
|
-
newPassword: string;
|
|
17
|
-
}
|
|
18
|
-
interface AppleAuthParams {
|
|
19
|
-
identityToken: string;
|
|
20
|
-
email?: string | null;
|
|
21
|
-
fullName?: {
|
|
22
|
-
givenName?: string | null;
|
|
23
|
-
familyName?: string | null;
|
|
24
|
-
} | null;
|
|
14
|
+
export interface Subscription {
|
|
15
|
+
unsubscribe(): void;
|
|
25
16
|
}
|
|
26
|
-
|
|
17
|
+
/**
|
|
18
|
+
* Supabase-compatible auth client. Maps supabase-js method names onto the
|
|
19
|
+
* Motherbase auth endpoints and reuses the existing token/refresh/session-store
|
|
20
|
+
* machinery. Returns `{ data, error }` and never throws for expected failures.
|
|
21
|
+
*/
|
|
22
|
+
export declare class AuthClient {
|
|
27
23
|
private readonly http;
|
|
28
24
|
private readonly store;
|
|
29
25
|
constructor(http: HttpClient, store: SessionStore);
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
26
|
+
signUp(credentials: SignUpCredentials): Promise<MotherbaseResponse<{
|
|
27
|
+
user: User | null;
|
|
28
|
+
session: Session | null;
|
|
29
|
+
}>>;
|
|
30
|
+
signInWithPassword(credentials: SignInWithPasswordCredentials): Promise<MotherbaseResponse<{
|
|
31
|
+
user: User | null;
|
|
32
|
+
session: Session | null;
|
|
33
|
+
}>>;
|
|
34
|
+
signOut(): Promise<{
|
|
35
|
+
error: MotherbaseResponse<null>['error'];
|
|
36
|
+
}>;
|
|
37
|
+
/** Fetch the current user from Better Auth (requires a valid session). */
|
|
38
|
+
getUser(): Promise<MotherbaseResponse<{
|
|
39
|
+
user: User | null;
|
|
40
|
+
}>>;
|
|
41
|
+
/** The locally-held session, if any (no network call). */
|
|
42
|
+
getSession(): Promise<MotherbaseResponse<{
|
|
43
|
+
session: Session | null;
|
|
44
|
+
}>>;
|
|
45
|
+
/**
|
|
46
|
+
* Revalidate the current session against the server. Better Auth bearer
|
|
47
|
+
* sessions have no refresh token — if the session is still valid the user is
|
|
48
|
+
* refreshed; otherwise the local session is cleared.
|
|
49
|
+
*/
|
|
50
|
+
refreshSession(): Promise<MotherbaseResponse<{
|
|
51
|
+
user: User | null;
|
|
52
|
+
session: Session | null;
|
|
53
|
+
}>>;
|
|
54
|
+
onAuthStateChange(callback: AuthChangeListener): {
|
|
55
|
+
data: {
|
|
56
|
+
subscription: Subscription;
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Internal: refresh used by the HttpClient's 401 path. Returns true on success.
|
|
61
|
+
* Not part of the public Supabase surface.
|
|
62
|
+
*/
|
|
63
|
+
_refresh(): Promise<boolean>;
|
|
58
64
|
private persist;
|
|
59
65
|
}
|
|
60
|
-
export {};
|
|
61
66
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,KAAK,EAEV,kBAAkB,EAElB,kBAAkB,EAClB,OAAO,EACP,IAAI,EACL,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAyBD,MAAM,WAAW,YAAY;IAC3B,WAAW,IAAI,IAAI,CAAC;CACrB;AAED;;;;GAIG;AACH,qBAAa,UAAU;IACT,OAAO,CAAC,QAAQ,CAAC,IAAI;IAAc,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAxC,IAAI,EAAE,UAAU,EAAmB,KAAK,EAAE,YAAY;IAE7E,MAAM,CACV,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAAC,kBAAkB,CAAC;QAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAexE,kBAAkB,CACtB,WAAW,EAAE,6BAA6B,GACzC,OAAO,CAAC,kBAAkB,CAAC;QAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAexE,OAAO,IAAI,OAAO,CAAC;QAAE,KAAK,EAAE,kBAAkB,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAA;KAAE,CAAC;IAUtE,0EAA0E;IACpE,OAAO,IAAI,OAAO,CAAC,kBAAkB,CAAC;QAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IASnE,0DAA0D;IACpD,UAAU,IAAI,OAAO,CAAC,kBAAkB,CAAC;QAAE,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAI5E;;;;OAIG;IACG,cAAc,IAAI,OAAO,CAAC,kBAAkB,CAAC;QAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC,CAAC;IAoBnG,iBAAiB,CAAC,QAAQ,EAAE,kBAAkB,GAAG;QAAE,IAAI,EAAE;YAAE,YAAY,EAAE,YAAY,CAAA;SAAE,CAAA;KAAE;IAOzF;;;OAGG;IACG,QAAQ,IAAI,OAAO,CAAC,OAAO,CAAC;YAKpB,OAAO;CAKtB"}
|
package/dist/auth.js
CHANGED
|
@@ -1,110 +1,116 @@
|
|
|
1
|
-
|
|
1
|
+
import { toError } from './http.js';
|
|
2
|
+
import { HTTP_ROUTES } from '@motherbase/contracts';
|
|
3
|
+
/** Build a clean Session from the Better Auth auth payload. */
|
|
2
4
|
function toSession(payload) {
|
|
3
|
-
|
|
4
|
-
return { accessToken, refreshToken, user: user };
|
|
5
|
+
return { accessToken: payload.token, user: payload.user };
|
|
5
6
|
}
|
|
6
|
-
|
|
7
|
+
const EVENT_MAP = {
|
|
8
|
+
signIn: 'SIGNED_IN',
|
|
9
|
+
signOut: 'SIGNED_OUT',
|
|
10
|
+
tokenRefreshed: 'TOKEN_REFRESHED',
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Supabase-compatible auth client. Maps supabase-js method names onto the
|
|
14
|
+
* Motherbase auth endpoints and reuses the existing token/refresh/session-store
|
|
15
|
+
* machinery. Returns `{ data, error }` and never throws for expected failures.
|
|
16
|
+
*/
|
|
17
|
+
export class AuthClient {
|
|
7
18
|
constructor(http, store) {
|
|
8
19
|
this.http = http;
|
|
9
20
|
this.store = store;
|
|
10
21
|
}
|
|
11
|
-
async
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
25
|
-
return this.persist(payload);
|
|
26
|
-
}
|
|
27
|
-
/** Passwordless email flow: requests a verification code. */
|
|
28
|
-
loginWithEmail(params) {
|
|
29
|
-
return this.http.request('/api/auth/email', { method: 'PUT', body: params, anonymous: true });
|
|
30
|
-
}
|
|
31
|
-
async validateEmail(params) {
|
|
32
|
-
const payload = await this.http.request('/api/auth/validate-email', {
|
|
33
|
-
method: 'PUT',
|
|
34
|
-
body: params,
|
|
35
|
-
anonymous: true,
|
|
36
|
-
});
|
|
37
|
-
return this.persist(payload);
|
|
38
|
-
}
|
|
39
|
-
forgotPassword(params) {
|
|
40
|
-
return this.http.request('/api/auth/forgot-password', { method: 'POST', body: params, anonymous: true });
|
|
41
|
-
}
|
|
42
|
-
resetPassword(params) {
|
|
43
|
-
return this.http.request('/api/auth/reset-password', { method: 'POST', body: params, anonymous: true });
|
|
44
|
-
}
|
|
45
|
-
async loginWithGoogle(params) {
|
|
46
|
-
const payload = await this.http.request('/api/auth/google', {
|
|
47
|
-
method: 'PUT',
|
|
48
|
-
body: params,
|
|
49
|
-
anonymous: true,
|
|
50
|
-
});
|
|
51
|
-
return this.persist(payload);
|
|
52
|
-
}
|
|
53
|
-
google(idToken) {
|
|
54
|
-
return this.loginWithGoogle({ idToken });
|
|
55
|
-
}
|
|
56
|
-
async loginWithApple(params) {
|
|
57
|
-
const payload = await this.http.request('/api/auth/apple', {
|
|
58
|
-
method: 'PUT',
|
|
59
|
-
body: params,
|
|
60
|
-
anonymous: true,
|
|
61
|
-
});
|
|
62
|
-
return this.persist(payload);
|
|
63
|
-
}
|
|
64
|
-
apple(params) {
|
|
65
|
-
return this.loginWithApple(params);
|
|
22
|
+
async signUp(credentials) {
|
|
23
|
+
try {
|
|
24
|
+
const payload = await this.http.request(HTTP_ROUTES.authSignUp, {
|
|
25
|
+
method: 'POST',
|
|
26
|
+
body: { email: credentials.email, password: credentials.password, name: credentials.username ?? credentials.email },
|
|
27
|
+
anonymous: true,
|
|
28
|
+
raw: true,
|
|
29
|
+
});
|
|
30
|
+
const session = await this.persist(payload);
|
|
31
|
+
return { data: { user: session.user, session }, error: null };
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
return { data: { user: null, session: null }, error: toError(err) };
|
|
35
|
+
}
|
|
66
36
|
}
|
|
67
|
-
|
|
68
|
-
async refresh() {
|
|
69
|
-
const session = this.store.get();
|
|
70
|
-
if (!session?.refreshToken)
|
|
71
|
-
return false;
|
|
37
|
+
async signInWithPassword(credentials) {
|
|
72
38
|
try {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
39
|
+
const payload = await this.http.request(HTTP_ROUTES.authSignIn, {
|
|
40
|
+
method: 'POST',
|
|
41
|
+
body: { email: credentials.email, password: credentials.password },
|
|
42
|
+
anonymous: true,
|
|
43
|
+
raw: true,
|
|
44
|
+
});
|
|
45
|
+
const session = await this.persist(payload);
|
|
46
|
+
return { data: { user: session.user, session }, error: null };
|
|
76
47
|
}
|
|
77
|
-
catch {
|
|
78
|
-
|
|
79
|
-
return false;
|
|
48
|
+
catch (err) {
|
|
49
|
+
return { data: { user: null, session: null }, error: toError(err) };
|
|
80
50
|
}
|
|
81
51
|
}
|
|
82
|
-
async
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
body: { refreshToken: session.refreshToken },
|
|
89
|
-
anonymous: true,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
catch {
|
|
93
|
-
// best-effort; clear locally regardless
|
|
94
|
-
}
|
|
52
|
+
async signOut() {
|
|
53
|
+
try {
|
|
54
|
+
await this.http.request(HTTP_ROUTES.authSignOut, { method: 'POST', raw: true });
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// best-effort; clear locally regardless
|
|
95
58
|
}
|
|
96
59
|
await this.store.set(null, 'signOut');
|
|
60
|
+
return { error: null };
|
|
97
61
|
}
|
|
98
|
-
/** Fetch the current user from
|
|
99
|
-
getUser() {
|
|
100
|
-
|
|
62
|
+
/** Fetch the current user from Better Auth (requires a valid session). */
|
|
63
|
+
async getUser() {
|
|
64
|
+
try {
|
|
65
|
+
const payload = await this.http.request(HTTP_ROUTES.authSession, { raw: true });
|
|
66
|
+
return { data: { user: payload?.user ?? null }, error: null };
|
|
67
|
+
}
|
|
68
|
+
catch (err) {
|
|
69
|
+
return { data: { user: null }, error: toError(err) };
|
|
70
|
+
}
|
|
101
71
|
}
|
|
102
|
-
/** The locally-held session, if any. */
|
|
103
|
-
getSession() {
|
|
104
|
-
return this.store.get();
|
|
72
|
+
/** The locally-held session, if any (no network call). */
|
|
73
|
+
async getSession() {
|
|
74
|
+
return { data: { session: this.store.get() }, error: null };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Revalidate the current session against the server. Better Auth bearer
|
|
78
|
+
* sessions have no refresh token — if the session is still valid the user is
|
|
79
|
+
* refreshed; otherwise the local session is cleared.
|
|
80
|
+
*/
|
|
81
|
+
async refreshSession() {
|
|
82
|
+
const current = this.store.get();
|
|
83
|
+
if (!current?.accessToken) {
|
|
84
|
+
return { data: { user: null, session: null }, error: { message: 'Auth session missing!' } };
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const payload = await this.http.request(HTTP_ROUTES.authSession, { raw: true });
|
|
88
|
+
if (!payload?.user) {
|
|
89
|
+
await this.store.set(null, 'signOut');
|
|
90
|
+
return { data: { user: null, session: null }, error: { message: 'Session expired' } };
|
|
91
|
+
}
|
|
92
|
+
const session = { accessToken: current.accessToken, user: payload.user };
|
|
93
|
+
await this.store.set(session, 'tokenRefreshed');
|
|
94
|
+
return { data: { user: session.user, session }, error: null };
|
|
95
|
+
}
|
|
96
|
+
catch (err) {
|
|
97
|
+
await this.store.set(null, 'signOut');
|
|
98
|
+
return { data: { user: null, session: null }, error: toError(err) };
|
|
99
|
+
}
|
|
105
100
|
}
|
|
106
|
-
onAuthStateChange(
|
|
107
|
-
|
|
101
|
+
onAuthStateChange(callback) {
|
|
102
|
+
const unsubscribe = this.store.subscribe((event, session) => {
|
|
103
|
+
callback(EVENT_MAP[event], session);
|
|
104
|
+
});
|
|
105
|
+
return { data: { subscription: { unsubscribe } } };
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Internal: refresh used by the HttpClient's 401 path. Returns true on success.
|
|
109
|
+
* Not part of the public Supabase surface.
|
|
110
|
+
*/
|
|
111
|
+
async _refresh() {
|
|
112
|
+
const result = await this.refreshSession();
|
|
113
|
+
return result.error === null;
|
|
108
114
|
}
|
|
109
115
|
async persist(payload) {
|
|
110
116
|
const session = toSession(payload);
|