@svarabase/js 1.0.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 (49) hide show
  1. package/dist/auth/index.d.ts +172 -0
  2. package/dist/auth/index.d.ts.map +1 -0
  3. package/dist/auth/index.js +282 -0
  4. package/dist/auth/index.js.map +1 -0
  5. package/dist/auth/session.d.ts +42 -0
  6. package/dist/auth/session.d.ts.map +1 -0
  7. package/dist/auth/session.js +65 -0
  8. package/dist/auth/session.js.map +1 -0
  9. package/dist/client.d.ts +38 -0
  10. package/dist/client.d.ts.map +1 -0
  11. package/dist/client.js +41 -0
  12. package/dist/client.js.map +1 -0
  13. package/dist/db/filterBuilder.d.ts +84 -0
  14. package/dist/db/filterBuilder.d.ts.map +1 -0
  15. package/dist/db/filterBuilder.js +227 -0
  16. package/dist/db/filterBuilder.js.map +1 -0
  17. package/dist/db/index.d.ts +42 -0
  18. package/dist/db/index.d.ts.map +1 -0
  19. package/dist/db/index.js +67 -0
  20. package/dist/db/index.js.map +1 -0
  21. package/dist/esm/auth/index.js +277 -0
  22. package/dist/esm/auth/session.js +60 -0
  23. package/dist/esm/client.js +36 -0
  24. package/dist/esm/db/filterBuilder.js +222 -0
  25. package/dist/esm/db/index.js +61 -0
  26. package/dist/esm/index.js +11 -0
  27. package/dist/esm/realtime/index.js +23 -0
  28. package/dist/esm/storage/index.js +144 -0
  29. package/dist/index.d.ts +13 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +21 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/realtime/index.d.ts +14 -0
  34. package/dist/realtime/index.d.ts.map +1 -0
  35. package/dist/realtime/index.js +29 -0
  36. package/dist/realtime/index.js.map +1 -0
  37. package/dist/storage/index.d.ts +74 -0
  38. package/dist/storage/index.d.ts.map +1 -0
  39. package/dist/storage/index.js +149 -0
  40. package/dist/storage/index.js.map +1 -0
  41. package/package.json +46 -0
  42. package/src/auth/index.ts +325 -0
  43. package/src/auth/session.ts +94 -0
  44. package/src/client.ts +66 -0
  45. package/src/db/filterBuilder.ts +302 -0
  46. package/src/db/index.ts +86 -0
  47. package/src/index.ts +22 -0
  48. package/src/realtime/index.ts +38 -0
  49. package/src/storage/index.ts +174 -0
@@ -0,0 +1,277 @@
1
+ import { SessionManager } from './session';
2
+ export class SvarabaseAuthClient {
3
+ constructor(url, key, options) {
4
+ this.url = `${url}/auth/v1`;
5
+ this.key = key;
6
+ this.sessionManager = new SessionManager(options?.storageKey ?? 'svarabase.auth.token', options?.persistSession !== false);
7
+ this.admin = this._buildAdminClient();
8
+ // Auto-refresh token if enabled
9
+ if (options?.autoRefreshToken !== false) {
10
+ this._setupAutoRefresh();
11
+ }
12
+ }
13
+ async _fetch(path, init = {}) {
14
+ const session = this.sessionManager.getSession();
15
+ const headers = {
16
+ 'Content-Type': 'application/json',
17
+ apikey: this.key,
18
+ ...(init.headers ?? {}),
19
+ };
20
+ if (session?.access_token) {
21
+ headers['Authorization'] = `Bearer ${session.access_token}`;
22
+ }
23
+ else {
24
+ headers['Authorization'] = `Bearer ${this.key}`;
25
+ }
26
+ return fetch(`${this.url}${path}`, { ...init, headers });
27
+ }
28
+ _setupAutoRefresh() {
29
+ if (typeof setInterval === 'undefined')
30
+ return;
31
+ setInterval(async () => {
32
+ const session = this.sessionManager.getSession();
33
+ if (!session)
34
+ return;
35
+ const now = Math.floor(Date.now() / 1000);
36
+ const expiresAt = session.expires_at ?? 0;
37
+ // Refresh 60 seconds before expiry
38
+ if (expiresAt - now < 60) {
39
+ await this.refreshSession({ refresh_token: session.refresh_token });
40
+ }
41
+ }, 30000);
42
+ }
43
+ async getSession() {
44
+ return { data: { session: this.sessionManager.getSession() }, error: null };
45
+ }
46
+ async setSession(params) {
47
+ try {
48
+ const res = await this._fetch('/setsession', {
49
+ method: 'POST',
50
+ body: JSON.stringify(params),
51
+ });
52
+ if (!res.ok) {
53
+ const data = await res.json();
54
+ return { data: { session: null, user: null }, error: new Error(data.message ?? 'setSession failed') };
55
+ }
56
+ const data = await res.json();
57
+ const session = { ...data, expires_at: data.expires_at ?? Math.floor(Date.now() / 1000) + (data.expires_in ?? 3600) };
58
+ this.sessionManager.setSession(session);
59
+ this.sessionManager.notify('SIGNED_IN', session);
60
+ return { data: { session, user: data.user }, error: null };
61
+ }
62
+ catch (e) {
63
+ return { data: { session: null, user: null }, error: e };
64
+ }
65
+ }
66
+ async signInWithPassword(credentials) {
67
+ try {
68
+ const res = await this._fetch('/token?grant_type=password', {
69
+ method: 'POST',
70
+ body: JSON.stringify(credentials),
71
+ });
72
+ const data = await res.json();
73
+ if (!res.ok)
74
+ return { data: { user: null, session: null }, error: new Error(data.error_description ?? data.message) };
75
+ const session = { ...data, expires_at: Math.floor(Date.now() / 1000) + data.expires_in };
76
+ this.sessionManager.setSession(session);
77
+ this.sessionManager.notify('SIGNED_IN', session);
78
+ return { data: { user: data.user, session }, error: null };
79
+ }
80
+ catch (e) {
81
+ return { data: { user: null, session: null }, error: e };
82
+ }
83
+ }
84
+ async signUp(credentials) {
85
+ try {
86
+ const body = {
87
+ email: credentials.email,
88
+ password: credentials.password,
89
+ phone: credentials.phone,
90
+ data: credentials.options?.data,
91
+ };
92
+ const res = await this._fetch('/signup', {
93
+ method: 'POST',
94
+ body: JSON.stringify(body),
95
+ });
96
+ const data = await res.json();
97
+ if (!res.ok)
98
+ return { data: { user: null, session: null }, error: new Error(data.error_description ?? data.message) };
99
+ const session = { ...data, expires_at: Math.floor(Date.now() / 1000) + data.expires_in };
100
+ this.sessionManager.setSession(session);
101
+ this.sessionManager.notify('SIGNED_IN', session);
102
+ return { data: { user: data.user, session }, error: null };
103
+ }
104
+ catch (e) {
105
+ return { data: { user: null, session: null }, error: e };
106
+ }
107
+ }
108
+ async signOut(options) {
109
+ try {
110
+ await this._fetch('/logout', { method: 'POST', body: JSON.stringify({ scope: options?.scope }) });
111
+ this.sessionManager.setSession(null);
112
+ this.sessionManager.notify('SIGNED_OUT', null);
113
+ return { error: null };
114
+ }
115
+ catch (e) {
116
+ return { error: e };
117
+ }
118
+ }
119
+ signInWithOAuth(params) {
120
+ const redirectTo = params.options?.redirectTo ?? (typeof window !== 'undefined' ? window.location.origin : '');
121
+ // apikey travels as a query param (not a header) because this is a top-level
122
+ // browser navigation - it lets the server know which project's OAuth config to use.
123
+ const url = `${this.url}/authorize?provider=${params.provider}&redirect_to=${encodeURIComponent(redirectTo)}&apikey=${encodeURIComponent(this.key)}`;
124
+ if (typeof window !== 'undefined') {
125
+ window.location.href = url;
126
+ }
127
+ return { data: { provider: params.provider, url }, error: null };
128
+ }
129
+ async getUser(jwt) {
130
+ try {
131
+ const headers = {};
132
+ if (jwt)
133
+ headers['Authorization'] = `Bearer ${jwt}`;
134
+ const res = await fetch(`${this.url}/user`, {
135
+ headers: {
136
+ 'Content-Type': 'application/json',
137
+ apikey: this.key,
138
+ Authorization: jwt ? `Bearer ${jwt}` : `Bearer ${this.sessionManager.getSession()?.access_token ?? this.key}`,
139
+ },
140
+ });
141
+ if (!res.ok) {
142
+ const data = await res.json();
143
+ return { data: { user: null }, error: new Error(data.message) };
144
+ }
145
+ const user = await res.json();
146
+ return { data: { user }, error: null };
147
+ }
148
+ catch (e) {
149
+ return { data: { user: null }, error: e };
150
+ }
151
+ }
152
+ async updateUser(attributes) {
153
+ try {
154
+ const body = { email: attributes.email, password: attributes.password, phone: attributes.phone, data: attributes.data };
155
+ const res = await this._fetch('/user', { method: 'PUT', body: JSON.stringify(body) });
156
+ const data = await res.json();
157
+ if (!res.ok)
158
+ return { data: { user: null }, error: new Error(data.message) };
159
+ return { data: { user: data }, error: null };
160
+ }
161
+ catch (e) {
162
+ return { data: { user: null }, error: e };
163
+ }
164
+ }
165
+ async resetPasswordForEmail(email, options) {
166
+ try {
167
+ await this._fetch('/recover', {
168
+ method: 'POST',
169
+ body: JSON.stringify({ email, options }),
170
+ });
171
+ return { data: {}, error: null };
172
+ }
173
+ catch (e) {
174
+ return { data: {}, error: e };
175
+ }
176
+ }
177
+ async refreshSession(params) {
178
+ try {
179
+ const refreshToken = params?.refresh_token ?? this.sessionManager.getSession()?.refresh_token;
180
+ if (!refreshToken)
181
+ return { data: { session: null, user: null }, error: new Error('No refresh token') };
182
+ const res = await this._fetch('/token?grant_type=refresh_token', {
183
+ method: 'POST',
184
+ body: JSON.stringify({ refresh_token: refreshToken }),
185
+ });
186
+ const data = await res.json();
187
+ if (!res.ok) {
188
+ this.sessionManager.setSession(null);
189
+ return { data: { session: null, user: null }, error: new Error(data.message) };
190
+ }
191
+ const session = { ...data, expires_at: Math.floor(Date.now() / 1000) + data.expires_in };
192
+ this.sessionManager.setSession(session);
193
+ this.sessionManager.notify('TOKEN_REFRESHED', session);
194
+ return { data: { session, user: data.user }, error: null };
195
+ }
196
+ catch (e) {
197
+ return { data: { session: null, user: null }, error: e };
198
+ }
199
+ }
200
+ onAuthStateChange(callback) {
201
+ return this.sessionManager.subscribe(callback);
202
+ }
203
+ _buildAdminClient() {
204
+ const adminUrl = `${this.url}/admin`;
205
+ const key = this.key;
206
+ const adminFetch = (path, init = {}) => fetch(`${adminUrl}${path}`, {
207
+ ...init,
208
+ headers: {
209
+ 'Content-Type': 'application/json',
210
+ Authorization: `Bearer ${key}`,
211
+ apikey: key,
212
+ ...(init.headers ?? {}),
213
+ },
214
+ });
215
+ return {
216
+ async createUser(attrs) {
217
+ try {
218
+ const res = await adminFetch('/users', { method: 'POST', body: JSON.stringify(attrs) });
219
+ const data = await res.json();
220
+ if (!res.ok)
221
+ return { data: { user: null }, error: new Error(data.message) };
222
+ return { data: { user: data }, error: null };
223
+ }
224
+ catch (e) {
225
+ return { data: { user: null }, error: e };
226
+ }
227
+ },
228
+ async updateUserById(uid, attrs) {
229
+ try {
230
+ const res = await adminFetch(`/users/${uid}`, { method: 'PUT', body: JSON.stringify(attrs) });
231
+ const data = await res.json();
232
+ if (!res.ok)
233
+ return { data: { user: null }, error: new Error(data.message) };
234
+ return { data: { user: data }, error: null };
235
+ }
236
+ catch (e) {
237
+ return { data: { user: null }, error: e };
238
+ }
239
+ },
240
+ async deleteUser(id, options) {
241
+ try {
242
+ const qs = options?.shouldSoftDelete === false ? '?is_soft_delete=false' : '';
243
+ await adminFetch(`/users/${id}${qs}`, { method: 'DELETE' });
244
+ return { data: {}, error: null };
245
+ }
246
+ catch (e) {
247
+ return { data: {}, error: e };
248
+ }
249
+ },
250
+ async listUsers(params) {
251
+ try {
252
+ const qs = params ? `?page=${params.page ?? 1}&per_page=${params.perPage ?? 50}` : '';
253
+ const res = await adminFetch(`/users${qs}`);
254
+ const data = await res.json();
255
+ if (!res.ok)
256
+ return { data: { users: [] }, error: new Error(data.message) };
257
+ return { data: { users: data.users }, error: null };
258
+ }
259
+ catch (e) {
260
+ return { data: { users: [] }, error: e };
261
+ }
262
+ },
263
+ async generateLink(params) {
264
+ try {
265
+ const res = await adminFetch('/generate_link', { method: 'POST', body: JSON.stringify(params) });
266
+ const data = await res.json();
267
+ if (!res.ok)
268
+ return { data: null, error: new Error(data.message) };
269
+ return { data, error: null };
270
+ }
271
+ catch (e) {
272
+ return { data: null, error: e };
273
+ }
274
+ },
275
+ };
276
+ }
277
+ }
@@ -0,0 +1,60 @@
1
+ export class SessionManager {
2
+ constructor(storageKey, persistSession = true) {
3
+ this.memorySession = null;
4
+ this.listeners = [];
5
+ this.storageKey = storageKey;
6
+ this.useLocalStorage = persistSession && (() => {
7
+ try {
8
+ if (typeof localStorage === 'undefined')
9
+ return false;
10
+ localStorage.getItem('__svarabase_test__');
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ })();
17
+ }
18
+ getSession() {
19
+ if (this.useLocalStorage) {
20
+ const raw = localStorage.getItem(this.storageKey);
21
+ if (!raw)
22
+ return null;
23
+ try {
24
+ return JSON.parse(raw);
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ return this.memorySession;
31
+ }
32
+ setSession(session) {
33
+ if (this.useLocalStorage) {
34
+ if (session) {
35
+ localStorage.setItem(this.storageKey, JSON.stringify(session));
36
+ }
37
+ else {
38
+ localStorage.removeItem(this.storageKey);
39
+ }
40
+ }
41
+ else {
42
+ this.memorySession = session;
43
+ }
44
+ }
45
+ notify(event, session) {
46
+ this.listeners.forEach(l => l(event, session));
47
+ }
48
+ subscribe(listener) {
49
+ this.listeners.push(listener);
50
+ return {
51
+ data: {
52
+ subscription: {
53
+ unsubscribe: () => {
54
+ this.listeners = this.listeners.filter(l => l !== listener);
55
+ },
56
+ },
57
+ },
58
+ };
59
+ }
60
+ }
@@ -0,0 +1,36 @@
1
+ import { SvarabaseAuthClient } from './auth';
2
+ import { SvarabaseQueryBuilder } from './db';
3
+ import { SvarabaseStorageClient } from './storage';
4
+ import { SvarabaseRealtimeClient } from './realtime';
5
+ export class SvarabaseClient {
6
+ constructor(url, key, options) {
7
+ this.url = url.replace(/\/$/, '');
8
+ this.key = key;
9
+ const getAuthHeader = () => {
10
+ const session = this.auth['sessionManager']?.getSession?.();
11
+ if (session?.access_token)
12
+ return `Bearer ${session.access_token}`;
13
+ return `Bearer ${this.key}`;
14
+ };
15
+ this.auth = new SvarabaseAuthClient(this.url, this.key, {
16
+ autoRefreshToken: options?.auth?.autoRefreshToken,
17
+ persistSession: options?.auth?.persistSession,
18
+ storageKey: options?.auth?.storageKey,
19
+ });
20
+ this._queryBuilder = new SvarabaseQueryBuilder(this.url, this.key, getAuthHeader);
21
+ this.storage = new SvarabaseStorageClient(this.url, this.key, getAuthHeader);
22
+ this.realtime = new SvarabaseRealtimeClient();
23
+ }
24
+ from(table) {
25
+ return this._queryBuilder.from(table);
26
+ }
27
+ rpc(functionName, params, options) {
28
+ return this._queryBuilder.rpc(functionName, params ?? {}, options);
29
+ }
30
+ channel(id) {
31
+ return this.realtime.channel(id);
32
+ }
33
+ removeChannel(channel) {
34
+ return this.realtime.removeChannel(channel);
35
+ }
36
+ }
@@ -0,0 +1,222 @@
1
+ export class FilterBuilder {
2
+ constructor(url, key, getAuthHeader, table, method = 'GET', body, isUpsert = false, onConflict) {
3
+ this.url = url;
4
+ this.key = key;
5
+ this.getAuthHeader = getAuthHeader;
6
+ this.state = {
7
+ table,
8
+ selectCols: '*',
9
+ filters: [],
10
+ orderByStr: null,
11
+ limitVal: null,
12
+ offsetVal: null,
13
+ countMode: null,
14
+ isHead: false,
15
+ isSingle: false,
16
+ isMaybeSingle: false,
17
+ method,
18
+ body,
19
+ isUpsert,
20
+ onConflict,
21
+ };
22
+ }
23
+ select(cols, options) {
24
+ this.state.selectCols = cols || '*';
25
+ if (options?.count)
26
+ this.state.countMode = options.count;
27
+ if (options?.head)
28
+ this.state.isHead = true;
29
+ return this;
30
+ }
31
+ eq(column, value) {
32
+ this.state.filters.push({ key: column, value: `eq.${value}` });
33
+ return this;
34
+ }
35
+ neq(column, value) {
36
+ this.state.filters.push({ key: column, value: `neq.${value}` });
37
+ return this;
38
+ }
39
+ gt(column, value) {
40
+ this.state.filters.push({ key: column, value: `gt.${value}` });
41
+ return this;
42
+ }
43
+ gte(column, value) {
44
+ this.state.filters.push({ key: column, value: `gte.${value}` });
45
+ return this;
46
+ }
47
+ lt(column, value) {
48
+ this.state.filters.push({ key: column, value: `lt.${value}` });
49
+ return this;
50
+ }
51
+ lte(column, value) {
52
+ this.state.filters.push({ key: column, value: `lte.${value}` });
53
+ return this;
54
+ }
55
+ like(column, pattern) {
56
+ this.state.filters.push({ key: column, value: `like.${pattern}` });
57
+ return this;
58
+ }
59
+ ilike(column, pattern) {
60
+ this.state.filters.push({ key: column, value: `ilike.${pattern}` });
61
+ return this;
62
+ }
63
+ is(column, value) {
64
+ this.state.filters.push({ key: column, value: `is.${value}` });
65
+ return this;
66
+ }
67
+ in(column, values) {
68
+ this.state.filters.push({ key: column, value: `in.(${values.join(',')})` });
69
+ return this;
70
+ }
71
+ contains(column, value) {
72
+ this.state.filters.push({ key: column, value: `cs.${JSON.stringify(value)}` });
73
+ return this;
74
+ }
75
+ not(column, operator, value) {
76
+ this.state.filters.push({ key: column, value: `not.${operator}.${value}` });
77
+ return this;
78
+ }
79
+ or(filters, options) {
80
+ const key = options?.foreignTable ? `${options.foreignTable}.or` : 'or';
81
+ this.state.filters.push({ key, value: `(${filters})` });
82
+ return this;
83
+ }
84
+ order(column, options) {
85
+ const dir = options?.ascending === false ? 'desc' : 'asc';
86
+ const nulls = options?.nullsFirst ? '.nullsfirst' : '';
87
+ this.state.orderByStr = (this.state.orderByStr ? this.state.orderByStr + ',' : '') + `${column}.${dir}${nulls}`;
88
+ return this;
89
+ }
90
+ limit(count) {
91
+ this.state.limitVal = count;
92
+ return this;
93
+ }
94
+ range(from, to) {
95
+ this.state.offsetVal = from;
96
+ this.state.limitVal = to - from + 1;
97
+ return this;
98
+ }
99
+ single() {
100
+ this.state.isSingle = true;
101
+ return this;
102
+ }
103
+ maybeSingle() {
104
+ this.state.isMaybeSingle = true;
105
+ return this;
106
+ }
107
+ then(onfulfilled, onrejected) {
108
+ return this._execute().then(onfulfilled, onrejected);
109
+ }
110
+ catch(onrejected) {
111
+ return this._execute().catch(onrejected);
112
+ }
113
+ buildUrl() {
114
+ const params = new URLSearchParams();
115
+ if (this.state.method === 'GET' || this.state.method === 'DELETE') {
116
+ if (this.state.selectCols && this.state.selectCols !== '*') {
117
+ params.set('select', this.state.selectCols);
118
+ }
119
+ else if (this.state.selectCols) {
120
+ params.set('select', this.state.selectCols);
121
+ }
122
+ for (const { key, value } of this.state.filters) {
123
+ params.set(key, value);
124
+ }
125
+ if (this.state.orderByStr)
126
+ params.set('order', this.state.orderByStr);
127
+ if (this.state.limitVal != null)
128
+ params.set('limit', String(this.state.limitVal));
129
+ if (this.state.offsetVal != null)
130
+ params.set('offset', String(this.state.offsetVal));
131
+ }
132
+ else {
133
+ // For POST/PATCH, filters go as query params too
134
+ for (const { key, value } of this.state.filters) {
135
+ params.set(key, value);
136
+ }
137
+ if (this.state.isUpsert && this.state.onConflict) {
138
+ params.set('on_conflict', this.state.onConflict);
139
+ }
140
+ }
141
+ const qs = params.toString();
142
+ return `${this.url}/rest/v1/${this.state.table}${qs ? '?' + qs : ''}`;
143
+ }
144
+ buildHeaders() {
145
+ const headers = {
146
+ 'Content-Type': 'application/json',
147
+ apikey: this.key,
148
+ Authorization: this.getAuthHeader(),
149
+ };
150
+ const prefer = [];
151
+ if (this.state.countMode)
152
+ prefer.push(`count=${this.state.countMode}`);
153
+ if (this.state.isUpsert)
154
+ prefer.push('resolution=merge-duplicates');
155
+ if (prefer.length > 0)
156
+ headers['Prefer'] = prefer.join(',');
157
+ if (this.state.isHead)
158
+ headers['method'] = 'HEAD';
159
+ return headers;
160
+ }
161
+ async _execute() {
162
+ const url = this.buildUrl();
163
+ const headers = this.buildHeaders();
164
+ const init = {
165
+ method: this.state.method,
166
+ headers,
167
+ };
168
+ if (this.state.body !== undefined && (this.state.method === 'POST' || this.state.method === 'PATCH')) {
169
+ init.body = JSON.stringify(this.state.body);
170
+ }
171
+ // Use HEAD for count-only
172
+ if (this.state.isHead) {
173
+ init.method = 'HEAD';
174
+ }
175
+ const res = await fetch(url, init);
176
+ const contentRange = res.headers.get('Content-Range');
177
+ const count = contentRange ? parseInt(contentRange.split('/')[1]) : null;
178
+ if (this.state.isHead) {
179
+ return { data: null, error: null, count, status: res.status, statusText: res.statusText };
180
+ }
181
+ if (!res.ok) {
182
+ const errData = await res.json().catch(() => ({}));
183
+ return {
184
+ data: null,
185
+ error: {
186
+ message: (errData.message ?? errData.error_description ?? 'Query failed'),
187
+ code: String(res.status),
188
+ details: (errData.details ?? ''),
189
+ hint: (errData.hint ?? ''),
190
+ },
191
+ count: null,
192
+ status: res.status,
193
+ statusText: res.statusText,
194
+ };
195
+ }
196
+ const raw = await res.json();
197
+ if (this.state.isSingle) {
198
+ if (!raw || (Array.isArray(raw) && raw.length === 0)) {
199
+ return {
200
+ data: null,
201
+ error: { message: 'JSON object requested, multiple (or no) rows returned', code: 'PGRST116', details: '', hint: '' },
202
+ count,
203
+ status: 406,
204
+ statusText: 'Not Acceptable',
205
+ };
206
+ }
207
+ const single = Array.isArray(raw) ? raw[0] : raw;
208
+ return { data: single, error: null, count, status: res.status, statusText: res.statusText };
209
+ }
210
+ if (this.state.isMaybeSingle) {
211
+ const single = Array.isArray(raw) ? (raw[0] ?? null) : raw;
212
+ return { data: single, error: null, count, status: res.status, statusText: res.statusText };
213
+ }
214
+ return {
215
+ data: (Array.isArray(raw) ? raw : [raw]),
216
+ error: null,
217
+ count,
218
+ status: res.status,
219
+ statusText: res.statusText,
220
+ };
221
+ }
222
+ }
@@ -0,0 +1,61 @@
1
+ import { FilterBuilder } from './filterBuilder';
2
+ export class SvarabaseQueryBuilder {
3
+ constructor(url, key, getAuthHeader) {
4
+ this.url = url;
5
+ this.key = key;
6
+ this.getAuthHeader = getAuthHeader;
7
+ }
8
+ from(table) {
9
+ return new TableQuery(this.url, this.key, this.getAuthHeader, table);
10
+ }
11
+ async rpc(functionName, params = {}, options) {
12
+ try {
13
+ const res = await fetch(`${this.url}/rest/v1/rpc/${functionName}`, {
14
+ method: 'POST',
15
+ headers: {
16
+ 'Content-Type': 'application/json',
17
+ apikey: this.key,
18
+ Authorization: this.getAuthHeader(),
19
+ ...(options?.count ? { Prefer: `count=${options.count}` } : {}),
20
+ },
21
+ body: JSON.stringify(params),
22
+ });
23
+ const contentRange = res.headers.get('Content-Range');
24
+ const count = contentRange ? parseInt(contentRange.split('/')[1]) : null;
25
+ if (!res.ok) {
26
+ const errData = await res.json().catch(() => ({}));
27
+ return { data: null, error: new Error((errData.message ?? 'RPC failed')), count };
28
+ }
29
+ const data = await res.json();
30
+ return { data, error: null, count };
31
+ }
32
+ catch (e) {
33
+ return { data: null, error: e };
34
+ }
35
+ }
36
+ }
37
+ export class TableQuery {
38
+ constructor(url, key, getAuthHeader, table) {
39
+ this.url = url;
40
+ this.key = key;
41
+ this.getAuthHeader = getAuthHeader;
42
+ this.table = table;
43
+ }
44
+ select(cols = '*', options) {
45
+ const fb = new FilterBuilder(this.url, this.key, this.getAuthHeader, this.table, 'GET');
46
+ return fb.select(cols, options);
47
+ }
48
+ insert(values, options) {
49
+ return new FilterBuilder(this.url, this.key, this.getAuthHeader, this.table, 'POST', values);
50
+ }
51
+ upsert(values, options) {
52
+ return new FilterBuilder(this.url, this.key, this.getAuthHeader, this.table, 'POST', values, true, options?.onConflict);
53
+ }
54
+ update(values, options) {
55
+ return new FilterBuilder(this.url, this.key, this.getAuthHeader, this.table, 'PATCH', values);
56
+ }
57
+ delete(options) {
58
+ return new FilterBuilder(this.url, this.key, this.getAuthHeader, this.table, 'DELETE');
59
+ }
60
+ }
61
+ export { FilterBuilder };
@@ -0,0 +1,11 @@
1
+ export { SvarabaseClient } from './client';
2
+ export { SvarabaseAuthClient } from './auth';
3
+ export { SvarabaseQueryBuilder } from './db';
4
+ export { SvarabaseStorageClient } from './storage';
5
+ export { SvarabaseRealtimeClient } from './realtime';
6
+ import { SvarabaseClient } from './client';
7
+ // Drop-in replacement for @supabase/supabase-js createClient
8
+ export function createClient(supabaseUrl, supabaseKey, options) {
9
+ return new SvarabaseClient(supabaseUrl, supabaseKey, options);
10
+ }
11
+ export default createClient;
@@ -0,0 +1,23 @@
1
+ // Realtime stub - Yesvara uses Socket.IO instead of Supabase Realtime
2
+ // This stub provides API compatibility without actual websocket functionality
3
+ export class RealtimeChannel {
4
+ constructor(channelId) {
5
+ this.channelId = channelId;
6
+ }
7
+ on(_type, _filter, _callback) {
8
+ return this;
9
+ }
10
+ subscribe(_callback) {
11
+ return this;
12
+ }
13
+ unsubscribe() {
14
+ return Promise.resolve('ok');
15
+ }
16
+ }
17
+ export class SvarabaseRealtimeClient {
18
+ channel(channelId) {
19
+ return new RealtimeChannel(channelId);
20
+ }
21
+ removeChannel(_channel) { }
22
+ removeAllChannels() { }
23
+ }