@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,302 @@
1
+ export type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'is' | 'in' | 'cs' | 'cd' | 'not';
2
+
3
+ export interface QueryState {
4
+ table: string;
5
+ selectCols: string;
6
+ filters: Array<{ key: string; value: string }>;
7
+ orderByStr: string | null;
8
+ limitVal: number | null;
9
+ offsetVal: number | null;
10
+ countMode: 'exact' | null;
11
+ isHead: boolean;
12
+ isSingle: boolean;
13
+ isMaybeSingle: boolean;
14
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE';
15
+ body?: unknown;
16
+ isUpsert: boolean;
17
+ onConflict?: string;
18
+ }
19
+
20
+ export type PostgrestSingleResponse<T> = {
21
+ data: T | null;
22
+ error: { message: string; code: string; details: string; hint: string } | null;
23
+ count: number | null;
24
+ status: number;
25
+ statusText: string;
26
+ };
27
+
28
+ export type PostgrestResponse<T> = {
29
+ data: T[] | null;
30
+ error: { message: string; code: string; details: string; hint: string } | null;
31
+ count: number | null;
32
+ status: number;
33
+ statusText: string;
34
+ };
35
+
36
+ export class FilterBuilder<T = unknown> {
37
+ protected state: QueryState;
38
+ protected url: string;
39
+ protected key: string;
40
+ protected getAuthHeader: () => string;
41
+
42
+ constructor(
43
+ url: string,
44
+ key: string,
45
+ getAuthHeader: () => string,
46
+ table: string,
47
+ method: 'GET' | 'POST' | 'PATCH' | 'DELETE' = 'GET',
48
+ body?: unknown,
49
+ isUpsert = false,
50
+ onConflict?: string
51
+ ) {
52
+ this.url = url;
53
+ this.key = key;
54
+ this.getAuthHeader = getAuthHeader;
55
+ this.state = {
56
+ table,
57
+ selectCols: '*',
58
+ filters: [],
59
+ orderByStr: null,
60
+ limitVal: null,
61
+ offsetVal: null,
62
+ countMode: null,
63
+ isHead: false,
64
+ isSingle: false,
65
+ isMaybeSingle: false,
66
+ method,
67
+ body,
68
+ isUpsert,
69
+ onConflict,
70
+ };
71
+ }
72
+
73
+ select(cols: string, options?: { count?: 'exact'; head?: boolean }): this {
74
+ this.state.selectCols = cols || '*';
75
+ if (options?.count) this.state.countMode = options.count;
76
+ if (options?.head) this.state.isHead = true;
77
+ return this;
78
+ }
79
+
80
+ eq(column: string, value: unknown): this {
81
+ this.state.filters.push({ key: column, value: `eq.${value}` });
82
+ return this;
83
+ }
84
+
85
+ neq(column: string, value: unknown): this {
86
+ this.state.filters.push({ key: column, value: `neq.${value}` });
87
+ return this;
88
+ }
89
+
90
+ gt(column: string, value: unknown): this {
91
+ this.state.filters.push({ key: column, value: `gt.${value}` });
92
+ return this;
93
+ }
94
+
95
+ gte(column: string, value: unknown): this {
96
+ this.state.filters.push({ key: column, value: `gte.${value}` });
97
+ return this;
98
+ }
99
+
100
+ lt(column: string, value: unknown): this {
101
+ this.state.filters.push({ key: column, value: `lt.${value}` });
102
+ return this;
103
+ }
104
+
105
+ lte(column: string, value: unknown): this {
106
+ this.state.filters.push({ key: column, value: `lte.${value}` });
107
+ return this;
108
+ }
109
+
110
+ like(column: string, pattern: string): this {
111
+ this.state.filters.push({ key: column, value: `like.${pattern}` });
112
+ return this;
113
+ }
114
+
115
+ ilike(column: string, pattern: string): this {
116
+ this.state.filters.push({ key: column, value: `ilike.${pattern}` });
117
+ return this;
118
+ }
119
+
120
+ is(column: string, value: boolean | null): this {
121
+ this.state.filters.push({ key: column, value: `is.${value}` });
122
+ return this;
123
+ }
124
+
125
+ in(column: string, values: unknown[]): this {
126
+ this.state.filters.push({ key: column, value: `in.(${values.join(',')})` });
127
+ return this;
128
+ }
129
+
130
+ contains(column: string, value: unknown): this {
131
+ this.state.filters.push({ key: column, value: `cs.${JSON.stringify(value)}` });
132
+ return this;
133
+ }
134
+
135
+ not(column: string, operator: string, value: unknown): this {
136
+ this.state.filters.push({ key: column, value: `not.${operator}.${value}` });
137
+ return this;
138
+ }
139
+
140
+ or(filters: string, options?: { foreignTable?: string }): this {
141
+ const key = options?.foreignTable ? `${options.foreignTable}.or` : 'or';
142
+ this.state.filters.push({ key, value: `(${filters})` });
143
+ return this;
144
+ }
145
+
146
+ order(column: string, options?: { ascending?: boolean; nullsFirst?: boolean }): this {
147
+ const dir = options?.ascending === false ? 'desc' : 'asc';
148
+ const nulls = options?.nullsFirst ? '.nullsfirst' : '';
149
+ this.state.orderByStr = (this.state.orderByStr ? this.state.orderByStr + ',' : '') + `${column}.${dir}${nulls}`;
150
+ return this;
151
+ }
152
+
153
+ limit(count: number): this {
154
+ this.state.limitVal = count;
155
+ return this;
156
+ }
157
+
158
+ range(from: number, to: number): this {
159
+ this.state.offsetVal = from;
160
+ this.state.limitVal = to - from + 1;
161
+ return this;
162
+ }
163
+
164
+ single(): PromiseLike<PostgrestSingleResponse<T>> {
165
+ this.state.isSingle = true;
166
+ return this as unknown as PromiseLike<PostgrestSingleResponse<T>>;
167
+ }
168
+
169
+ maybeSingle(): PromiseLike<PostgrestSingleResponse<T | null>> {
170
+ this.state.isMaybeSingle = true;
171
+ return this as unknown as PromiseLike<PostgrestSingleResponse<T | null>>;
172
+ }
173
+
174
+ then<R>(
175
+ onfulfilled?: ((value: PostgrestResponse<T> | PostgrestSingleResponse<T>) => R) | null,
176
+ onrejected?: ((reason: unknown) => R) | null
177
+ ): Promise<R> {
178
+ return this._execute().then(onfulfilled as never, onrejected as never);
179
+ }
180
+
181
+ catch<R>(onrejected?: ((reason: unknown) => R) | null): Promise<R | PostgrestResponse<T> | PostgrestSingleResponse<T>> {
182
+ return this._execute().catch(onrejected as never);
183
+ }
184
+
185
+ protected buildUrl(): string {
186
+ const params = new URLSearchParams();
187
+
188
+ if (this.state.method === 'GET' || this.state.method === 'DELETE') {
189
+ if (this.state.selectCols && this.state.selectCols !== '*') {
190
+ params.set('select', this.state.selectCols);
191
+ } else if (this.state.selectCols) {
192
+ params.set('select', this.state.selectCols);
193
+ }
194
+ for (const { key, value } of this.state.filters) {
195
+ params.set(key, value);
196
+ }
197
+ if (this.state.orderByStr) params.set('order', this.state.orderByStr);
198
+ if (this.state.limitVal != null) params.set('limit', String(this.state.limitVal));
199
+ if (this.state.offsetVal != null) params.set('offset', String(this.state.offsetVal));
200
+ } else {
201
+ // For POST/PATCH, filters go as query params too
202
+ for (const { key, value } of this.state.filters) {
203
+ params.set(key, value);
204
+ }
205
+ if (this.state.isUpsert && this.state.onConflict) {
206
+ params.set('on_conflict', this.state.onConflict);
207
+ }
208
+ }
209
+
210
+ const qs = params.toString();
211
+ return `${this.url}/rest/v1/${this.state.table}${qs ? '?' + qs : ''}`;
212
+ }
213
+
214
+ protected buildHeaders(): Record<string, string> {
215
+ const headers: Record<string, string> = {
216
+ 'Content-Type': 'application/json',
217
+ apikey: this.key,
218
+ Authorization: this.getAuthHeader(),
219
+ };
220
+
221
+ const prefer: string[] = [];
222
+ if (this.state.countMode) prefer.push(`count=${this.state.countMode}`);
223
+ if (this.state.isUpsert) prefer.push('resolution=merge-duplicates');
224
+ if (prefer.length > 0) headers['Prefer'] = prefer.join(',');
225
+
226
+ if (this.state.isHead) headers['method'] = 'HEAD';
227
+
228
+ return headers;
229
+ }
230
+
231
+ protected async _execute(): Promise<PostgrestResponse<T> | PostgrestSingleResponse<T>> {
232
+ const url = this.buildUrl();
233
+ const headers = this.buildHeaders();
234
+
235
+ const init: RequestInit = {
236
+ method: this.state.method,
237
+ headers,
238
+ };
239
+
240
+ if (this.state.body !== undefined && (this.state.method === 'POST' || this.state.method === 'PATCH')) {
241
+ init.body = JSON.stringify(this.state.body);
242
+ }
243
+
244
+ // Use HEAD for count-only
245
+ if (this.state.isHead) {
246
+ init.method = 'HEAD';
247
+ }
248
+
249
+ const res = await fetch(url, init);
250
+ const contentRange = res.headers.get('Content-Range');
251
+ const count = contentRange ? parseInt(contentRange.split('/')[1]) : null;
252
+
253
+ if (this.state.isHead) {
254
+ return { data: null as never, error: null, count, status: res.status, statusText: res.statusText };
255
+ }
256
+
257
+ if (!res.ok) {
258
+ const errData = await res.json().catch(() => ({})) as Record<string, unknown>;
259
+ return {
260
+ data: null as never,
261
+ error: {
262
+ message: (errData.message ?? errData.error_description ?? 'Query failed') as string,
263
+ code: String(res.status),
264
+ details: (errData.details ?? '') as string,
265
+ hint: (errData.hint ?? '') as string,
266
+ },
267
+ count: null,
268
+ status: res.status,
269
+ statusText: res.statusText,
270
+ };
271
+ }
272
+
273
+ const raw = await res.json() as T[];
274
+
275
+ if (this.state.isSingle) {
276
+ if (!raw || (Array.isArray(raw) && raw.length === 0)) {
277
+ return {
278
+ data: null as never,
279
+ error: { message: 'JSON object requested, multiple (or no) rows returned', code: 'PGRST116', details: '', hint: '' },
280
+ count,
281
+ status: 406,
282
+ statusText: 'Not Acceptable',
283
+ };
284
+ }
285
+ const single = Array.isArray(raw) ? raw[0] : raw;
286
+ return { data: single as T, error: null, count, status: res.status, statusText: res.statusText };
287
+ }
288
+
289
+ if (this.state.isMaybeSingle) {
290
+ const single = Array.isArray(raw) ? (raw[0] ?? null) : raw;
291
+ return { data: single as T, error: null, count, status: res.status, statusText: res.statusText };
292
+ }
293
+
294
+ return {
295
+ data: (Array.isArray(raw) ? raw : [raw]) as T[],
296
+ error: null,
297
+ count,
298
+ status: res.status,
299
+ statusText: res.statusText,
300
+ };
301
+ }
302
+ }
@@ -0,0 +1,86 @@
1
+ import { FilterBuilder } from './filterBuilder';
2
+
3
+ export class SvarabaseQueryBuilder {
4
+ private url: string;
5
+ private key: string;
6
+ private getAuthHeader: () => string;
7
+
8
+ constructor(url: string, key: string, getAuthHeader: () => string) {
9
+ this.url = url;
10
+ this.key = key;
11
+ this.getAuthHeader = getAuthHeader;
12
+ }
13
+
14
+ from<T = Record<string, unknown>>(table: string) {
15
+ return new TableQuery<T>(this.url, this.key, this.getAuthHeader, table);
16
+ }
17
+
18
+ async rpc<T = unknown>(
19
+ functionName: string,
20
+ params: Record<string, unknown> = {},
21
+ options?: { count?: 'exact'; head?: boolean }
22
+ ): Promise<{ data: T | null; error: Error | null; count?: number | null }> {
23
+ try {
24
+ const res = await fetch(`${this.url}/rest/v1/rpc/${functionName}`, {
25
+ method: 'POST',
26
+ headers: {
27
+ 'Content-Type': 'application/json',
28
+ apikey: this.key,
29
+ Authorization: this.getAuthHeader(),
30
+ ...(options?.count ? { Prefer: `count=${options.count}` } : {}),
31
+ },
32
+ body: JSON.stringify(params),
33
+ });
34
+
35
+ const contentRange = res.headers.get('Content-Range');
36
+ const count = contentRange ? parseInt(contentRange.split('/')[1]) : null;
37
+
38
+ if (!res.ok) {
39
+ const errData = await res.json().catch(() => ({})) as Record<string, unknown>;
40
+ return { data: null, error: new Error((errData.message ?? 'RPC failed') as string), count };
41
+ }
42
+
43
+ const data = await res.json() as T;
44
+ return { data, error: null, count };
45
+ } catch (e: unknown) {
46
+ return { data: null, error: e as Error };
47
+ }
48
+ }
49
+ }
50
+
51
+ export class TableQuery<T> {
52
+ private url: string;
53
+ private key: string;
54
+ private getAuthHeader: () => string;
55
+ private table: string;
56
+
57
+ constructor(url: string, key: string, getAuthHeader: () => string, table: string) {
58
+ this.url = url;
59
+ this.key = key;
60
+ this.getAuthHeader = getAuthHeader;
61
+ this.table = table;
62
+ }
63
+
64
+ select(cols = '*', options?: { count?: 'exact'; head?: boolean }): FilterBuilder<T> {
65
+ const fb = new FilterBuilder<T>(this.url, this.key, this.getAuthHeader, this.table, 'GET');
66
+ return fb.select(cols, options);
67
+ }
68
+
69
+ insert(values: Partial<T> | Partial<T>[], options?: { count?: 'exact' }): FilterBuilder<T> {
70
+ return new FilterBuilder<T>(this.url, this.key, this.getAuthHeader, this.table, 'POST', values);
71
+ }
72
+
73
+ upsert(values: Partial<T> | Partial<T>[], options?: { onConflict?: string; count?: 'exact' }): FilterBuilder<T> {
74
+ return new FilterBuilder<T>(this.url, this.key, this.getAuthHeader, this.table, 'POST', values, true, options?.onConflict);
75
+ }
76
+
77
+ update(values: Partial<T>, options?: { count?: 'exact' }): FilterBuilder<T> {
78
+ return new FilterBuilder<T>(this.url, this.key, this.getAuthHeader, this.table, 'PATCH', values);
79
+ }
80
+
81
+ delete(options?: { count?: 'exact' }): FilterBuilder<T> {
82
+ return new FilterBuilder<T>(this.url, this.key, this.getAuthHeader, this.table, 'DELETE');
83
+ }
84
+ }
85
+
86
+ export { FilterBuilder };
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
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
+ export type { SvarabaseClientOptions } from './client';
7
+ export type { Session, UserData, AuthChangeEvent, AuthListener } from './auth/session';
8
+ export type { PostgrestResponse, PostgrestSingleResponse } from './db/filterBuilder';
9
+
10
+ import { SvarabaseClient } from './client';
11
+ import type { SvarabaseClientOptions } from './client';
12
+
13
+ // Drop-in replacement for @supabase/supabase-js createClient
14
+ export function createClient(
15
+ supabaseUrl: string,
16
+ supabaseKey: string,
17
+ options?: SvarabaseClientOptions
18
+ ): SvarabaseClient {
19
+ return new SvarabaseClient(supabaseUrl, supabaseKey, options);
20
+ }
21
+
22
+ export default createClient;
@@ -0,0 +1,38 @@
1
+ // Realtime stub - Yesvara uses Socket.IO instead of Supabase Realtime
2
+ // This stub provides API compatibility without actual websocket functionality
3
+
4
+ export type RealtimeChannelStatus = 'SUBSCRIBED' | 'CLOSED' | 'CHANNEL_ERROR' | 'TIMED_OUT';
5
+
6
+ export class RealtimeChannel {
7
+ private channelId: string;
8
+
9
+ constructor(channelId: string) {
10
+ this.channelId = channelId;
11
+ }
12
+
13
+ on(
14
+ _type: 'postgres_changes' | 'broadcast' | 'presence',
15
+ _filter: Record<string, unknown>,
16
+ _callback: (payload: unknown) => void
17
+ ): this {
18
+ return this;
19
+ }
20
+
21
+ subscribe(_callback?: (status: RealtimeChannelStatus) => void): this {
22
+ return this;
23
+ }
24
+
25
+ unsubscribe(): Promise<'ok' | 'error' | 'timed out'> {
26
+ return Promise.resolve('ok');
27
+ }
28
+ }
29
+
30
+ export class SvarabaseRealtimeClient {
31
+ channel(channelId: string): RealtimeChannel {
32
+ return new RealtimeChannel(channelId);
33
+ }
34
+
35
+ removeChannel(_channel: RealtimeChannel): void {}
36
+
37
+ removeAllChannels(): void {}
38
+ }
@@ -0,0 +1,174 @@
1
+ export interface StorageUploadOptions {
2
+ cacheControl?: string;
3
+ contentType?: string;
4
+ upsert?: boolean;
5
+ }
6
+
7
+ export interface FileObject {
8
+ name: string;
9
+ id: string;
10
+ updated_at: string;
11
+ created_at: string;
12
+ last_accessed_at: string;
13
+ metadata: Record<string, unknown>;
14
+ }
15
+
16
+ export class SvarabaseStorageClient {
17
+ private url: string;
18
+ private key: string;
19
+ private getAuthHeader: () => string;
20
+
21
+ constructor(url: string, key: string, getAuthHeader: () => string) {
22
+ this.url = `${url}/storage/v1`;
23
+ this.key = key;
24
+ this.getAuthHeader = getAuthHeader;
25
+ }
26
+
27
+ from(bucketId: string): StorageBucketApi {
28
+ return new StorageBucketApi(this.url, this.key, this.getAuthHeader, bucketId);
29
+ }
30
+
31
+ async listBuckets(): Promise<{ data: unknown[] | null; error: Error | null }> {
32
+ try {
33
+ const res = await this._fetch('/bucket');
34
+ const data = await res.json();
35
+ return { data, error: null };
36
+ } catch (e: unknown) { return { data: null, error: e as Error }; }
37
+ }
38
+
39
+ async createBucket(id: string, options?: { public?: boolean; fileSizeLimit?: number; allowedMimeTypes?: string[] }): Promise<{ data: unknown; error: Error | null }> {
40
+ try {
41
+ const res = await this._fetch('/bucket', {
42
+ method: 'POST',
43
+ body: JSON.stringify({ id, name: id, public: options?.public ?? false, file_size_limit: options?.fileSizeLimit, allowed_mime_types: options?.allowedMimeTypes }),
44
+ });
45
+ const data = await res.json();
46
+ if (!res.ok) return { data: null, error: new Error((data as Record<string, string>).message) };
47
+ return { data, error: null };
48
+ } catch (e: unknown) { return { data: null, error: e as Error }; }
49
+ }
50
+
51
+ private _fetch(path: string, init: RequestInit = {}): Promise<Response> {
52
+ return fetch(`${this.url}${path}`, {
53
+ ...init,
54
+ headers: {
55
+ 'Content-Type': 'application/json',
56
+ apikey: this.key,
57
+ Authorization: this.getAuthHeader(),
58
+ ...(init.headers as Record<string, string> ?? {}),
59
+ },
60
+ });
61
+ }
62
+ }
63
+
64
+ class StorageBucketApi {
65
+ private url: string;
66
+ private key: string;
67
+ private getAuthHeader: () => string;
68
+ private bucketId: string;
69
+
70
+ constructor(url: string, key: string, getAuthHeader: () => string, bucketId: string) {
71
+ this.url = url;
72
+ this.key = key;
73
+ this.getAuthHeader = getAuthHeader;
74
+ this.bucketId = bucketId;
75
+ }
76
+
77
+ private get baseUrl() { return `${this.url}/object/${this.bucketId}`; }
78
+
79
+ async upload(
80
+ path: string,
81
+ fileBody: File | Blob | Buffer | ArrayBuffer | ReadableStream | string,
82
+ options?: StorageUploadOptions
83
+ ): Promise<{ data: { path: string } | null; error: Error | null }> {
84
+ try {
85
+ const headers: Record<string, string> = {
86
+ apikey: this.key,
87
+ Authorization: this.getAuthHeader(),
88
+ };
89
+
90
+ if (options?.contentType) headers['content-type'] = options.contentType;
91
+ if (options?.cacheControl) headers['cache-control'] = options.cacheControl;
92
+ if (options?.upsert) headers['x-upsert'] = 'true';
93
+
94
+ const res = await fetch(`${this.baseUrl}/${path}`, {
95
+ method: 'POST',
96
+ headers,
97
+ body: fileBody as BodyInit,
98
+ });
99
+
100
+ if (!res.ok) {
101
+ const data = await res.json().catch(() => ({})) as Record<string, string>;
102
+ return { data: null, error: new Error(data.message ?? 'Upload failed') };
103
+ }
104
+
105
+ return { data: { path }, error: null };
106
+ } catch (e: unknown) { return { data: null, error: e as Error }; }
107
+ }
108
+
109
+ getPublicUrl(path: string): { data: { publicUrl: string } } {
110
+ const publicUrl = `${this.url}/object/public/${this.bucketId}/${path}`;
111
+ return { data: { publicUrl } };
112
+ }
113
+
114
+ async createSignedUrl(
115
+ path: string,
116
+ expiresIn: number,
117
+ options?: { download?: boolean | string }
118
+ ): Promise<{ data: { signedUrl: string } | null; error: Error | null }> {
119
+ try {
120
+ const res = await fetch(`${this.url}/object/sign/${this.bucketId}/${path}`, {
121
+ method: 'POST',
122
+ headers: {
123
+ 'Content-Type': 'application/json',
124
+ apikey: this.key,
125
+ Authorization: this.getAuthHeader(),
126
+ },
127
+ body: JSON.stringify({ expiresIn }),
128
+ });
129
+
130
+ const data = await res.json() as Record<string, string>;
131
+ if (!res.ok) return { data: null, error: new Error(data.message ?? 'Signing failed') };
132
+
133
+ return { data: { signedUrl: data.signedURL ?? data.signedUrl }, error: null };
134
+ } catch (e: unknown) { return { data: null, error: e as Error }; }
135
+ }
136
+
137
+ async remove(paths: string[]): Promise<{ data: FileObject[] | null; error: Error | null }> {
138
+ try {
139
+ const res = await fetch(`${this.url}/object/${this.bucketId}`, {
140
+ method: 'DELETE',
141
+ headers: {
142
+ 'Content-Type': 'application/json',
143
+ apikey: this.key,
144
+ Authorization: this.getAuthHeader(),
145
+ },
146
+ body: JSON.stringify({ prefixes: paths }),
147
+ });
148
+
149
+ const data = await res.json() as FileObject[] | Record<string, string>;
150
+ if (!res.ok) return { data: null, error: new Error((data as Record<string, string>).message) };
151
+ return { data: data as FileObject[], error: null };
152
+ } catch (e: unknown) { return { data: null, error: e as Error }; }
153
+ }
154
+
155
+ async list(
156
+ prefix?: string,
157
+ options?: { limit?: number; offset?: number; search?: string }
158
+ ): Promise<{ data: FileObject[] | null; error: Error | null }> {
159
+ try {
160
+ const res = await fetch(`${this.url}/object/list/${this.bucketId}`, {
161
+ method: 'POST',
162
+ headers: {
163
+ 'Content-Type': 'application/json',
164
+ apikey: this.key,
165
+ Authorization: this.getAuthHeader(),
166
+ },
167
+ body: JSON.stringify({ prefix: prefix ?? '', limit: options?.limit ?? 100, offset: options?.offset, search: options?.search }),
168
+ });
169
+
170
+ const data = await res.json();
171
+ return { data: data as FileObject[], error: null };
172
+ } catch (e: unknown) { return { data: null, error: e as Error }; }
173
+ }
174
+ }