@airostack/client 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,59 @@
1
1
  # @airostack/client
2
2
 
3
- Isomorphic client for [AiroStack](https://console.airoxlab.com) projects — Supabase-style database queries and end-user auth. Zero dependencies, works in **Node.js**, **browsers**, and **React Native / Expo**.
3
+ Official client for [AiroStack](https://console.airoxlab.com) projects — Supabase-style database queries and end-user authentication over your project's Data API.
4
+
5
+ - **Zero dependencies** — plain `fetch` under the hood
6
+ - **Isomorphic** — Node.js ≥ 18, all browsers, React Native / Expo, edge runtimes
7
+ - **Never throws** — every call resolves to `{ data, error }`
8
+ - **RLS-aware** — signed-in users' JWTs ride along automatically, so your Row Level Security policies apply per user
4
9
 
5
10
  ```bash
6
11
  npm install @airostack/client
7
12
  ```
8
13
 
9
- ## Setup
14
+ ---
15
+
16
+ ## 1. Creating a client
10
17
 
11
18
  ```ts
12
19
  import { createClient } from '@airostack/client';
13
20
 
14
21
  const client = createClient(
15
- 'https://<project-ref>.db.airoxlab.com', // project URL (dashboard → Connect)
16
- '<publishable-key>', // API settingspublishable key
22
+ 'https://<project-ref>.db.airoxlab.com', // Project URL dashboard → project → Connect
23
+ '<publishable-key>', // Publishable keyproject → API settings
17
24
  );
18
25
  ```
19
26
 
20
- **React Native:** pass AsyncStorage so sessions persist:
27
+ ### All options
28
+
29
+ ```ts
30
+ const client = createClient(url, key, {
31
+ fetch: customFetch, // bring your own fetch (rarely needed)
32
+ auth: {
33
+ persistSession: true, // keep users signed in across reloads (default true)
34
+ storage: AsyncStorage, // WHERE sessions persist — see React Native below
35
+ storageKey: 'my.app.auth', // storage key (default 'airostack.auth.token')
36
+ },
37
+ });
38
+ ```
39
+
40
+ ### The two keys
41
+
42
+ | Key | Where to use | Powers |
43
+ |---|---|---|
44
+ | **Publishable** | browsers, mobile apps — safe to ship | anonymous reads + RLS-scoped access; upgrades to the signed-in user when they log in |
45
+ | **Secret** | servers ONLY — never ship to a client | full read/write, bypasses RLS |
46
+
47
+ ```ts
48
+ // server-side admin client (API routes, cron jobs, workers)
49
+ const admin = createClient(url, process.env.AIROSTACK_SECRET_KEY!, {
50
+ auth: { persistSession: false },
51
+ });
52
+ ```
53
+
54
+ ### React Native / Expo
55
+
56
+ Sessions persist to `localStorage` by default, which doesn't exist in React Native — pass AsyncStorage:
21
57
 
22
58
  ```ts
23
59
  import AsyncStorage from '@react-native-async-storage/async-storage';
@@ -25,61 +61,336 @@ import AsyncStorage from '@react-native-async-storage/async-storage';
25
61
  const client = createClient(url, key, { auth: { storage: AsyncStorage } });
26
62
  ```
27
63
 
28
- ## Database
64
+ Everything else is identical across platforms.
65
+
66
+ ---
67
+
68
+ ## 2. Database queries — `client.from(table)`
69
+
70
+ Every chain is *thenable*: `await` it whenever you're done building. The result is always:
29
71
 
30
72
  ```ts
31
- // read
32
- const { data, error } = await client
33
- .from('orders')
34
- .select('id, item, qty')
35
- .eq('status', 'paid')
73
+ {
74
+ data: T[] | T | null, // rows (or a single row after .single())
75
+ error: ApiError | null, // never thrown — always check this
76
+ count: number | null, // total rows when { count: 'exact' } was requested
77
+ status: number, // HTTP status
78
+ }
79
+ ```
80
+
81
+ ### Reading
82
+
83
+ ```ts
84
+ // all columns
85
+ const { data, error } = await client.from('tasks').select();
86
+
87
+ // specific columns
88
+ await client.from('tasks').select('id, title, done');
89
+
90
+ // filters (chainable, ANDed together)
91
+ await client.from('tasks').select().eq('done', false).gte('priority', 3);
92
+
93
+ // sorting + paging
94
+ await client.from('tasks')
95
+ .select()
36
96
  .order('created_at', { ascending: false })
37
97
  .limit(20);
38
98
 
39
- // one row
40
- const { data: order } = await client.from('orders').select().eq('id', id).single();
99
+ // page 2 (rows 20–39)
100
+ await client.from('tasks').select().range(20, 39);
101
+
102
+ // exactly one row — error if none
103
+ const { data: task } = await client.from('tasks').select().eq('id', id).single();
104
+
105
+ // one row or null — no error if none
106
+ const { data: maybe } = await client.from('tasks').select().eq('id', id).maybeSingle();
107
+
108
+ // total count (for pagination UIs)
109
+ const { data: rows, count } = await client
110
+ .from('tasks')
111
+ .select('*', { count: 'exact' })
112
+ .limit(10);
113
+ // count = total matching rows, rows = the first 10
114
+ ```
115
+
116
+ ### All filters
117
+
118
+ | Method | SQL equivalent | Example |
119
+ |---|---|---|
120
+ | `.eq(col, v)` | `col = v` | `.eq('status', 'open')` |
121
+ | `.neq(col, v)` | `col <> v` | `.neq('status', 'done')` |
122
+ | `.gt(col, v)` | `col > v` | `.gt('qty', 10)` |
123
+ | `.gte(col, v)` | `col >= v` | `.gte('price', 100)` |
124
+ | `.lt(col, v)` | `col < v` | `.lt('age', 18)` |
125
+ | `.lte(col, v)` | `col <= v` | `.lte('stock', 5)` |
126
+ | `.like(col, p)` | `col LIKE p` | `.like('name', 'A%')` |
127
+ | `.ilike(col, p)` | `col ILIKE p` (case-insensitive) | `.ilike('email', '%@gmail.com')` |
128
+ | `.filter(col, op, v)` | any operator | `.filter('qty', 'gte', 10)` |
41
129
 
42
- // insert (returns the created rows)
43
- const { data: created } = await client.from('orders').insert({ item: 'Burger', qty: 2 });
130
+ ### Inserting
44
131
 
45
- // update / delete
46
- await client.from('orders').update({ status: 'done' }).eq('id', id);
47
- await client.from('orders').delete().eq('id', id);
132
+ ```ts
133
+ // single row returns the created row(s) in data
134
+ const { data, error } = await client.from('tasks').insert({ title: 'Ship v1', done: false });
48
135
 
49
- // count
50
- const { count } = await client.from('orders').select('*', { count: 'exact' }).limit(1);
136
+ // bulk
137
+ await client.from('tasks').insert([
138
+ { title: 'One' },
139
+ { title: 'Two' },
140
+ ]);
51
141
  ```
52
142
 
53
- Filters: `eq neq gt gte lt lte like ilike`, plus the generic `.filter(col, op, value)`.
54
- Pagination: `.range(from, to)`. Errors never throw — check `error` on the result.
143
+ ### Updating
55
144
 
56
- ## Auth
145
+ ```ts
146
+ // ALWAYS pair .update() with filters — they choose which rows change
147
+ const { data } = await client
148
+ .from('tasks')
149
+ .update({ done: true })
150
+ .eq('id', taskId);
151
+ // data = the updated rows
152
+ ```
153
+
154
+ ### Deleting
57
155
 
58
156
  ```ts
59
- await client.auth.signUp({ email, password, options: { data: { name: 'Ada' } } });
60
- await client.auth.signInWithPassword({ email, password });
61
- const { data: { user } } = await client.auth.getUser();
62
- await client.auth.signOut();
157
+ await client.from('tasks').delete().eq('id', taskId);
158
+ ```
159
+
160
+ ### Typed rows (TypeScript)
161
+
162
+ ```ts
163
+ interface Task { id: string; title: string; done: boolean; created_at: string }
164
+
165
+ const { data } = await client.from<Task>('tasks').select().eq('done', false);
166
+ // data: Task[] | null — fully typed
167
+ ```
168
+
169
+ ### Error handling
170
+
171
+ Nothing throws. Check `error` on every result:
172
+
173
+ ```ts
174
+ const { data, error } = await client.from('tasks').select();
175
+ if (error) {
176
+ console.error(error.message, error.status); // ApiError: message + HTTP status
177
+ return;
178
+ }
179
+ // data is safe to use here
180
+ ```
181
+
182
+ ---
183
+
184
+ ## 3. Authentication — `client.auth`
63
185
 
64
- client.auth.onAuthStateChange((event, session) => {
65
- // 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED'
186
+ End-user accounts for **your app's users** (not dashboard accounts). Sessions persist automatically and access tokens refresh themselves.
187
+
188
+ ### Sign up
189
+
190
+ ```ts
191
+ const { data, error } = await client.auth.signUp({
192
+ email: 'ada@example.com',
193
+ password: 'secret123',
194
+ options: { data: { name: 'Ada Lovelace' } }, // optional user metadata
195
+ });
196
+ // data.user, data.session — signed in immediately
197
+ ```
198
+
199
+ ### Sign in / out
200
+
201
+ ```ts
202
+ const { data, error } = await client.auth.signInWithPassword({
203
+ email: 'ada@example.com',
204
+ password: 'secret123',
66
205
  });
206
+
207
+ await client.auth.signOut(); // drops the stored session
67
208
  ```
68
209
 
69
- Sessions persist automatically and access tokens refresh on demand. While a
70
- user is signed in, every `.from()` query carries their JWT — so your
71
- **Row Level Security** policies (`auth.uid()` etc.) apply per user, exactly
72
- like Supabase.
210
+ ### Current session & user
211
+
212
+ ```ts
213
+ // fast — local session (auto-refreshes the token if it expired)
214
+ const { data: { session } } = await client.auth.getSession();
215
+ if (session) console.log(session.user.email, session.access_token);
216
+
217
+ // verified — validates the token against the server
218
+ const { data: { user }, error } = await client.auth.getUser();
219
+ ```
73
220
 
74
- ## Server-side with the secret key
221
+ ### Reacting to auth changes
75
222
 
76
223
  ```ts
77
- const admin = createClient(url, process.env.AIROSTACK_SECRET_KEY!, {
224
+ const sub = client.auth.onAuthStateChange((event, session) => {
225
+ // event: 'SIGNED_IN' | 'SIGNED_OUT' | 'TOKEN_REFRESHED'
226
+ setUser(session?.user ?? null);
227
+ });
228
+
229
+ sub.unsubscribe(); // e.g. in a useEffect cleanup
230
+ ```
231
+
232
+ ### Auth + RLS: how they connect
233
+
234
+ While a user is signed in, **every `client.from()` query automatically carries their JWT**. On the server, your Row Level Security policies see them via `auth.uid()`, `auth.email()`, `auth.role()`:
235
+
236
+ ```sql
237
+ -- example policy: users only see their own tasks
238
+ create policy "own tasks" on tasks
239
+ for select using (auth.uid() = user_id);
240
+ ```
241
+
242
+ No extra client code — sign in, and the same `.from('tasks').select()` now returns only that user's rows.
243
+
244
+ ---
245
+
246
+ ## 4. Recipes
247
+
248
+ **Next.js — one shared client**
249
+
250
+ ```ts
251
+ // src/lib/airostack.ts
252
+ import { createClient } from '@airostack/client';
253
+
254
+ export const client = createClient(
255
+ process.env.NEXT_PUBLIC_AIROSTACK_URL!,
256
+ process.env.NEXT_PUBLIC_AIROSTACK_KEY!,
257
+ );
258
+ ```
259
+
260
+ **React login form**
261
+
262
+ ```tsx
263
+ async function onSubmit(email: string, password: string) {
264
+ const { error } = await client.auth.signInWithPassword({ email, password });
265
+ if (error) return setMessage(error.message);
266
+ router.push('/dashboard');
267
+ }
268
+ ```
269
+
270
+ **Server route with full access**
271
+
272
+ ```ts
273
+ // app/api/report/route.ts — secret key, server only
274
+ import { createClient } from '@airostack/client';
275
+
276
+ const admin = createClient(process.env.AIROSTACK_URL!, process.env.AIROSTACK_SECRET_KEY!, {
78
277
  auth: { persistSession: false },
79
278
  });
80
- // full access, bypasses RLS — server only, never ship this key to a client
279
+
280
+ export async function GET() {
281
+ const { data } = await admin.from('orders').select('*', { count: 'exact' }).limit(1000);
282
+ return Response.json(data);
283
+ }
284
+ ```
285
+
286
+ **Pagination UI**
287
+
288
+ ```ts
289
+ const PAGE = 25;
290
+ const { data, count } = await client
291
+ .from('orders')
292
+ .select('*', { count: 'exact' })
293
+ .order('created_at', { ascending: false })
294
+ .range(page * PAGE, page * PAGE + PAGE - 1);
295
+ const totalPages = Math.ceil((count ?? 0) / PAGE);
296
+ ```
297
+
298
+ ---
299
+
300
+ ## 5. Exported types
301
+
302
+ ```ts
303
+ import type {
304
+ ApiResult, // { data, error, count, status }
305
+ ApiError, // Error subclass with .status
306
+ User, // { id, email, user_metadata?, created_at? }
307
+ Session, // { access_token, refresh_token, expires_in, expires_at?, user }
308
+ AuthResult, // { data: { user, session }, error }
309
+ StorageAdapter, // getItem/setItem/removeItem — for custom session storage
310
+ ClientOptions, // createClient's third argument
311
+ } from '@airostack/client';
81
312
  ```
82
313
 
314
+ ## 6. RPC — calling Postgres functions
315
+
316
+ Define a function in the SQL editor, then call it by name with named arguments:
317
+
318
+ ```sql
319
+ create or replace function total_revenue(since date)
320
+ returns numeric language sql stable as $$
321
+ select coalesce(sum(amount), 0) from orders where created_at >= since;
322
+ $$;
323
+ ```
324
+
325
+ ```ts
326
+ const { data, error } = await client.rpc('total_revenue', { since: '2026-01-01' });
327
+ // scalar functions return the value directly; set-returning functions return rows
328
+
329
+ const { data: rows } = await client.rpc<Order[]>('orders_by_region', { region: 'EU' });
330
+ ```
331
+
332
+ RPC runs under the caller's role — RLS and `security definer/invoker` behave exactly as in SQL.
333
+
334
+ ## 7. Storage — `client.storage.from(bucket)`
335
+
336
+ Create buckets in the dashboard (project → Storage). Access rules: **reads on
337
+ public buckets** work with the publishable key; **private buckets and all
338
+ writes** need a signed-in user or the secret key.
339
+
340
+ ```ts
341
+ const avatars = client.storage.from('avatars');
342
+
343
+ // upload (Blob, File, ArrayBuffer, Uint8Array, or string)
344
+ const { error } = await avatars.upload('u1/profile.png', file, { contentType: 'image/png' });
345
+
346
+ // download → Blob
347
+ const { data: blob } = await avatars.download('u1/profile.png');
348
+
349
+ // permanent public URL (public buckets) — great for <img src>
350
+ const { data: { publicUrl } } = avatars.getPublicUrl('u1/profile.png');
351
+
352
+ // list + delete
353
+ const { data: files } = await avatars.list('u1/', { limit: 50 });
354
+ await avatars.remove(['u1/profile.png']);
355
+ ```
356
+
357
+ ## 8. Realtime — `client.channel(name)`
358
+
359
+ First enable realtime for the table in the dashboard (project → Realtime).
360
+ Then subscribe to live changes:
361
+
362
+ ```ts
363
+ const channel = client
364
+ .channel('orders-feed')
365
+ .on('postgres_changes', { event: '*', schema: 'public', table: 'orders' }, (payload) => {
366
+ // payload: { eventType: 'INSERT'|'UPDATE'|'DELETE', schema, table, new, old }
367
+ console.log(payload.eventType, payload.new);
368
+ })
369
+ .subscribe((status) => console.log(status)); // 'SUBSCRIBED' | 'CLOSED' | 'CHANNEL_ERROR'
370
+
371
+ // later
372
+ client.removeChannel(channel); // or channel.unsubscribe()
373
+ client.removeAllChannels(); // app teardown
374
+ ```
375
+
376
+ - Filter per event: `{ event: 'INSERT', table: 'orders' }` — or `'*'` for all.
377
+ - Reconnects automatically with backoff.
378
+ - **Node < 22** has no global WebSocket — pass one:
379
+
380
+ ```ts
381
+ import ws from 'ws';
382
+ const client = createClient(url, key, { realtime: { WebSocket: ws as never } });
383
+ ```
384
+
385
+ Browsers, React Native, and Node ≥ 22 need nothing extra.
386
+
387
+ ## 9. Direct SQL (alternative)
388
+
389
+ For complex server-side queries you can always skip the API and connect straight
390
+ to your project's Postgres (`db.airoxlab.com:5432`, connection string in the
391
+ dashboard's Connect menu) with any SQL client — the Data API and direct SQL are
392
+ the same database.
393
+
83
394
  ## License
84
395
 
85
396
  MIT
package/dist/index.d.ts CHANGED
@@ -1,27 +1,49 @@
1
1
  import { AuthClient } from './auth.js';
2
2
  import { QueryBuilder } from './query-builder.js';
3
- import type { ClientOptions } from './types.js';
3
+ import { RealtimeChannel } from './realtime.js';
4
+ import { StorageClient } from './storage.js';
5
+ import { ApiError, type ClientOptions } from './types.js';
4
6
  export { ApiError } from './types.js';
5
7
  export type { ApiResult, AuthResult, ClientOptions, Session, StorageAdapter, User } from './types.js';
6
8
  export { AuthClient } from './auth.js';
7
9
  export { QueryBuilder } from './query-builder.js';
10
+ export { StorageClient, StorageBucket, type StorageObjectInfo } from './storage.js';
11
+ export { RealtimeChannel, type PostgresChangePayload, type PostgresChangesFilter, type WebSocketLike, } from './realtime.js';
8
12
  /**
9
- * AiroStack client — Supabase-style API over your project's Data API + Auth.
13
+ * AiroStack client — Supabase-style API over your project's Data API, Auth,
14
+ * Storage, and Realtime.
10
15
  *
11
16
  * import { createClient } from '@airostack/client';
12
17
  * const client = createClient(process.env.AIROSTACK_URL!, process.env.AIROSTACK_KEY!);
13
18
  *
14
19
  * const { data, error } = await client.from('orders').select().eq('status', 'paid');
20
+ * const { data: result } = await client.rpc('total_revenue', { since: '2026-01-01' });
15
21
  * await client.auth.signInWithPassword({ email, password });
22
+ * await client.storage.from('avatars').upload('u1.png', file);
23
+ * client.channel('feed').on('postgres_changes', { event: '*', schema: 'public', table: 'orders' }, cb).subscribe();
16
24
  */
17
25
  export declare class AiroStackClient {
18
26
  private url;
19
27
  private key;
20
28
  readonly auth: AuthClient;
29
+ readonly storage: StorageClient;
21
30
  private fetcher;
31
+ private channels;
32
+ private wsCtor;
22
33
  constructor(url: string, key: string, options?: ClientOptions);
23
34
  /** Query a table through the Data API (RLS applies per the caller's key/JWT). */
24
35
  from<T = Record<string, unknown>>(table: string): QueryBuilder<T>;
36
+ /** Call a Postgres function in the public schema (PostgREST rpc). */
37
+ rpc<T = unknown>(fn: string, args?: Record<string, unknown>): Promise<{
38
+ data: T | null;
39
+ error: ApiError | null;
40
+ }>;
41
+ /** Create a realtime channel (subscribe to Postgres changes). */
42
+ channel(name: string): RealtimeChannel;
43
+ /** Unsubscribe and drop a channel. */
44
+ removeChannel(channel: RealtimeChannel): void;
45
+ /** Unsubscribe all channels (e.g. on app teardown). */
46
+ removeAllChannels(): void;
25
47
  }
26
48
  export declare function createClient(url: string, key: string, options?: ClientOptions): AiroStackClient;
27
49
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACtG,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;GAQG;AACH,qBAAa,eAAe;IAId,OAAO,CAAC,GAAG;IAAU,OAAO,CAAC,GAAG;IAH5C,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,OAAO,CAAC,OAAO,CAAe;gBAEV,GAAG,EAAE,MAAM,EAAU,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAO7E,iFAAiF;IACjF,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;CAMlE;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CAE/F"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAoB,eAAe,EAAsB,MAAM,eAAe,CAAC;AACtF,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAE1D,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,aAAa,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACtG,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,KAAK,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACpF,OAAO,EACL,eAAe,EACf,KAAK,qBAAqB,EAC1B,KAAK,qBAAqB,EAC1B,KAAK,aAAa,GACnB,MAAM,eAAe,CAAC;AAEvB;;;;;;;;;;;;GAYG;AACH,qBAAa,eAAe;IAOd,OAAO,CAAC,GAAG;IAAU,OAAO,CAAC,GAAG;IAN5C,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC;IAChC,OAAO,CAAC,OAAO,CAAe;IAC9B,OAAO,CAAC,QAAQ,CAA8B;IAC9C,OAAO,CAAC,MAAM,CAA8C;gBAExC,GAAG,EAAE,MAAM,EAAU,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa;IAU7E,iFAAiF;IACjF,IAAI,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAAC,CAAC,CAAC;IAOjE,qEAAqE;IAC/D,GAAG,CAAC,CAAC,GAAG,OAAO,EAAE,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;QAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;KAAE,CAAC;IAsBvH,iEAAiE;IACjE,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,eAAe;IAMtC,sCAAsC;IACtC,aAAa,CAAC,OAAO,EAAE,eAAe,GAAG,IAAI;IAI7C,uDAAuD;IACvD,iBAAiB,IAAI,IAAI;CAG1B;AAED,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,eAAe,CAE/F"}
package/dist/index.js CHANGED
@@ -1,22 +1,34 @@
1
1
  import { AuthClient } from './auth.js';
2
2
  import { QueryBuilder } from './query-builder.js';
3
+ import { defaultWebSocket, RealtimeChannel } from './realtime.js';
4
+ import { StorageClient } from './storage.js';
5
+ import { ApiError } from './types.js';
3
6
  export { ApiError } from './types.js';
4
7
  export { AuthClient } from './auth.js';
5
8
  export { QueryBuilder } from './query-builder.js';
9
+ export { StorageClient, StorageBucket } from './storage.js';
10
+ export { RealtimeChannel, } from './realtime.js';
6
11
  /**
7
- * AiroStack client — Supabase-style API over your project's Data API + Auth.
12
+ * AiroStack client — Supabase-style API over your project's Data API, Auth,
13
+ * Storage, and Realtime.
8
14
  *
9
15
  * import { createClient } from '@airostack/client';
10
16
  * const client = createClient(process.env.AIROSTACK_URL!, process.env.AIROSTACK_KEY!);
11
17
  *
12
18
  * const { data, error } = await client.from('orders').select().eq('status', 'paid');
19
+ * const { data: result } = await client.rpc('total_revenue', { since: '2026-01-01' });
13
20
  * await client.auth.signInWithPassword({ email, password });
21
+ * await client.storage.from('avatars').upload('u1.png', file);
22
+ * client.channel('feed').on('postgres_changes', { event: '*', schema: 'public', table: 'orders' }, cb).subscribe();
14
23
  */
15
24
  export class AiroStackClient {
16
25
  url;
17
26
  key;
18
27
  auth;
28
+ storage;
19
29
  fetcher;
30
+ channels = new Set();
31
+ wsCtor;
20
32
  constructor(url, key, options) {
21
33
  this.url = url;
22
34
  this.key = key;
@@ -25,11 +37,53 @@ export class AiroStackClient {
25
37
  this.url = url.replace(/\/+$/, '');
26
38
  this.fetcher = options?.fetch ?? ((...args) => fetch(...args));
27
39
  this.auth = new AuthClient(this.url, key, this.fetcher, options?.auth);
40
+ const ctx = { url: this.url, key: this.key, fetch: this.fetcher, accessToken: () => this.auth.accessToken() };
41
+ this.storage = new StorageClient(ctx);
42
+ this.wsCtor = options?.realtime?.WebSocket ?? defaultWebSocket();
28
43
  }
29
44
  /** Query a table through the Data API (RLS applies per the caller's key/JWT). */
30
45
  from(table) {
31
46
  return new QueryBuilder({ url: this.url, key: this.key, fetch: this.fetcher, accessToken: () => this.auth.accessToken() }, table);
32
47
  }
48
+ /** Call a Postgres function in the public schema (PostgREST rpc). */
49
+ async rpc(fn, args) {
50
+ try {
51
+ const headers = { apikey: this.key, 'content-type': 'application/json' };
52
+ const token = await this.auth.accessToken();
53
+ if (token)
54
+ headers.authorization = `Bearer ${token}`;
55
+ const res = await this.fetcher(`${this.url}/rest/v1/rpc/${fn}`, {
56
+ method: 'POST',
57
+ headers,
58
+ body: JSON.stringify(args ?? {}),
59
+ });
60
+ if (!res.ok) {
61
+ const body = (await res.json().catch(() => ({})));
62
+ const msg = typeof body.message === 'string' ? body.message : `rpc failed (${res.status})`;
63
+ return { data: null, error: new ApiError(msg, res.status) };
64
+ }
65
+ const data = res.status === 204 ? null : (await res.json());
66
+ return { data, error: null };
67
+ }
68
+ catch (err) {
69
+ return { data: null, error: new ApiError(err.message, 0) };
70
+ }
71
+ }
72
+ /** Create a realtime channel (subscribe to Postgres changes). */
73
+ channel(name) {
74
+ const ch = new RealtimeChannel(name, this.url, this.key, this.wsCtor, (c) => this.channels.delete(c));
75
+ this.channels.add(ch);
76
+ return ch;
77
+ }
78
+ /** Unsubscribe and drop a channel. */
79
+ removeChannel(channel) {
80
+ channel.unsubscribe();
81
+ }
82
+ /** Unsubscribe all channels (e.g. on app teardown). */
83
+ removeAllChannels() {
84
+ for (const ch of [...this.channels])
85
+ ch.unsubscribe();
86
+ }
33
87
  }
34
88
  export function createClient(url, key, options) {
35
89
  return new AiroStackClient(url, key, options);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAGlD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAElD;;;;;;;;GAQG;AACH,MAAM,OAAO,eAAe;IAIN;IAAqB;IAHhC,IAAI,CAAa;IAClB,OAAO,CAAe;IAE9B,YAAoB,GAAW,EAAU,GAAW,EAAE,OAAuB;QAAzD,QAAG,GAAH,GAAG,CAAQ;QAAU,QAAG,GAAH,GAAG,CAAQ;QAClD,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAC;QAChF,IAAI,CAAC,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;QACnC,IAAI,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,IAAI,CAAC,CAAC,GAAG,IAA8B,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,CAAC,CAAC;QACzF,IAAI,CAAC,IAAI,GAAG,IAAI,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;IACzE,CAAC;IAED,iFAAiF;IACjF,IAAI,CAA8B,KAAa;QAC7C,OAAO,IAAI,YAAY,CACrB,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,EAAE,EACjG,KAAK,CACN,CAAC;IACJ,CAAC;CACF;AAED,MAAM,UAAU,YAAY,CAAC,GAAW,EAAE,GAAW,EAAE,OAAuB;IAC5E,OAAO,IAAI,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;AAChD,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,58 @@
1
+ /** Minimal WebSocket surface — satisfied by browsers, React Native, and
2
+ * Node ≥ 22's global WebSocket (or the `ws` package's default export). */
3
+ export interface WebSocketLike {
4
+ send(data: string): void;
5
+ close(code?: number, reason?: string): void;
6
+ addEventListener(type: 'open' | 'message' | 'close' | 'error', cb: (ev: never) => void): void;
7
+ }
8
+ type WebSocketCtor = new (url: string) => WebSocketLike;
9
+ export interface PostgresChangesFilter {
10
+ event: 'INSERT' | 'UPDATE' | 'DELETE' | '*';
11
+ schema?: string;
12
+ table: string;
13
+ }
14
+ export interface PostgresChangePayload<T = Record<string, unknown>> {
15
+ eventType: 'INSERT' | 'UPDATE' | 'DELETE';
16
+ schema: string;
17
+ table: string;
18
+ /** the row after the change (INSERT/UPDATE) */
19
+ new: T | null;
20
+ /** the row before the change (DELETE) */
21
+ old: T | null;
22
+ }
23
+ type ChangeCallback = (payload: PostgresChangePayload) => void;
24
+ type Status = 'SUBSCRIBED' | 'CLOSED' | 'CHANNEL_ERROR';
25
+ /**
26
+ * Live Postgres change subscriptions — supabase-style:
27
+ *
28
+ * const channel = client
29
+ * .channel('orders-feed')
30
+ * .on('postgres_changes', { event: '*', schema: 'public', table: 'orders' }, (payload) => {
31
+ * console.log(payload.eventType, payload.new);
32
+ * })
33
+ * .subscribe();
34
+ * // later: client.removeChannel(channel)
35
+ *
36
+ * Tables must have Realtime enabled in the dashboard (project → Realtime).
37
+ * Reconnects automatically with backoff until unsubscribed.
38
+ */
39
+ export declare class RealtimeChannel {
40
+ readonly name: string;
41
+ private url;
42
+ private key;
43
+ private WS;
44
+ private onRemove;
45
+ private bindings;
46
+ private ws;
47
+ private closedByUser;
48
+ private retry;
49
+ private statusCb;
50
+ constructor(name: string, url: string, key: string, WS: WebSocketCtor | null, onRemove: (ch: RealtimeChannel) => void);
51
+ on(_type: 'postgres_changes', filter: PostgresChangesFilter, callback: ChangeCallback): this;
52
+ subscribe(statusCallback?: (status: Status) => void): this;
53
+ private connect;
54
+ unsubscribe(): void;
55
+ }
56
+ export declare function defaultWebSocket(): WebSocketCtor | null;
57
+ export {};
58
+ //# sourceMappingURL=realtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.d.ts","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAAA;2EAC2E;AAC3E,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5C,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,IAAI,GAAG,IAAI,CAAC;CAC/F;AACD,KAAK,aAAa,GAAG,KAAK,GAAG,EAAE,MAAM,KAAK,aAAa,CAAC;AAExD,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,GAAG,GAAG,CAAC;IAC5C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;IAChE,SAAS,EAAE,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;IAC1C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,+CAA+C;IAC/C,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;IACd,yCAAyC;IACzC,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC;CACf;AAED,KAAK,cAAc,GAAG,CAAC,OAAO,EAAE,qBAAqB,KAAK,IAAI,CAAC;AAC/D,KAAK,MAAM,GAAG,YAAY,GAAG,QAAQ,GAAG,eAAe,CAAC;AAOxD;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAe;aAQR,IAAI,EAAE,MAAM;IAC5B,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,GAAG;IACX,OAAO,CAAC,EAAE;IACV,OAAO,CAAC,QAAQ;IAXlB,OAAO,CAAC,QAAQ,CAAiB;IACjC,OAAO,CAAC,EAAE,CAA8B;IACxC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,KAAK,CAAK;IAClB,OAAO,CAAC,QAAQ,CAA2C;gBAGzC,IAAI,EAAE,MAAM,EACpB,GAAG,EAAE,MAAM,EACX,GAAG,EAAE,MAAM,EACX,EAAE,EAAE,aAAa,GAAG,IAAI,EACxB,QAAQ,EAAE,CAAC,EAAE,EAAE,eAAe,KAAK,IAAI;IAGjD,EAAE,CAAC,KAAK,EAAE,kBAAkB,EAAE,MAAM,EAAE,qBAAqB,EAAE,QAAQ,EAAE,cAAc,GAAG,IAAI;IAK5F,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,GAAG,IAAI;IAY1D,OAAO,CAAC,OAAO;IAmDf,WAAW,IAAI,IAAI;CAKpB;AAED,wBAAgB,gBAAgB,IAAI,aAAa,GAAG,IAAI,CAGvD"}
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Live Postgres change subscriptions — supabase-style:
3
+ *
4
+ * const channel = client
5
+ * .channel('orders-feed')
6
+ * .on('postgres_changes', { event: '*', schema: 'public', table: 'orders' }, (payload) => {
7
+ * console.log(payload.eventType, payload.new);
8
+ * })
9
+ * .subscribe();
10
+ * // later: client.removeChannel(channel)
11
+ *
12
+ * Tables must have Realtime enabled in the dashboard (project → Realtime).
13
+ * Reconnects automatically with backoff until unsubscribed.
14
+ */
15
+ export class RealtimeChannel {
16
+ name;
17
+ url;
18
+ key;
19
+ WS;
20
+ onRemove;
21
+ bindings = [];
22
+ ws = null;
23
+ closedByUser = false;
24
+ retry = 0;
25
+ statusCb = null;
26
+ constructor(name, url, key, WS, onRemove) {
27
+ this.name = name;
28
+ this.url = url;
29
+ this.key = key;
30
+ this.WS = WS;
31
+ this.onRemove = onRemove;
32
+ }
33
+ on(_type, filter, callback) {
34
+ this.bindings.push({ filter, callback });
35
+ return this;
36
+ }
37
+ subscribe(statusCallback) {
38
+ this.statusCb = statusCallback ?? null;
39
+ if (!this.WS) {
40
+ this.statusCb?.('CHANNEL_ERROR');
41
+ throw new Error('No WebSocket available. On Node < 22 pass one: createClient(url, key, { realtime: { WebSocket: require("ws") } })');
42
+ }
43
+ this.connect();
44
+ return this;
45
+ }
46
+ connect() {
47
+ const tables = [...new Set(this.bindings.map((b) => b.filter.table))].join(',');
48
+ const wsUrl = `${this.url.replace(/^http/, 'ws')}/realtime/v1?apikey=${encodeURIComponent(this.key)}${tables ? `&tables=${encodeURIComponent(tables)}` : ''}`;
49
+ const ws = new this.WS(wsUrl);
50
+ this.ws = ws;
51
+ ws.addEventListener('open', () => {
52
+ this.retry = 0;
53
+ this.statusCb?.('SUBSCRIBED');
54
+ });
55
+ ws.addEventListener('message', ((ev) => {
56
+ try {
57
+ const msg = JSON.parse(String(ev.data));
58
+ if (msg.type !== 'change' || !msg.event)
59
+ return;
60
+ const e = msg.event;
61
+ const eventType = (e.type ?? '');
62
+ if (!['INSERT', 'UPDATE', 'DELETE'].includes(eventType))
63
+ return;
64
+ const payload = {
65
+ eventType,
66
+ schema: e.schema ?? 'public',
67
+ table: e.table ?? '',
68
+ new: eventType === 'DELETE' ? null : (e.record ?? null),
69
+ old: eventType === 'DELETE' ? (e.record ?? null) : null,
70
+ };
71
+ for (const b of this.bindings) {
72
+ if (b.filter.table !== payload.table)
73
+ continue;
74
+ if (b.filter.schema && b.filter.schema !== payload.schema)
75
+ continue;
76
+ if (b.filter.event !== '*' && b.filter.event !== payload.eventType)
77
+ continue;
78
+ b.callback(payload);
79
+ }
80
+ }
81
+ catch {
82
+ // ignore malformed frames
83
+ }
84
+ }));
85
+ ws.addEventListener('close', () => {
86
+ if (this.closedByUser) {
87
+ this.statusCb?.('CLOSED');
88
+ return;
89
+ }
90
+ // auto-reconnect with capped backoff
91
+ const delay = Math.min(1_000 * 2 ** this.retry++, 15_000);
92
+ setTimeout(() => { if (!this.closedByUser)
93
+ this.connect(); }, delay);
94
+ });
95
+ ws.addEventListener('error', () => {
96
+ // close handler drives the reconnect
97
+ });
98
+ }
99
+ unsubscribe() {
100
+ this.closedByUser = true;
101
+ try {
102
+ this.ws?.close(1000, 'unsubscribed');
103
+ }
104
+ catch { /* already closed */ }
105
+ this.onRemove(this);
106
+ }
107
+ }
108
+ export function defaultWebSocket() {
109
+ const ws = globalThis.WebSocket;
110
+ return ws ?? null;
111
+ }
112
+ //# sourceMappingURL=realtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ import { ApiError } from './types.js';
2
+ type TokenProvider = () => Promise<string | null>;
3
+ interface Ctx {
4
+ url: string;
5
+ key: string;
6
+ fetch: typeof fetch;
7
+ accessToken: TokenProvider;
8
+ }
9
+ export interface StorageObjectInfo {
10
+ name: string;
11
+ size: number;
12
+ updated_at: string | null;
13
+ }
14
+ type StorageResult<T> = {
15
+ data: T | null;
16
+ error: ApiError | null;
17
+ };
18
+ /** File storage for one bucket — `client.storage.from('avatars')`.
19
+ * Reads on public buckets work with the publishable key; private buckets and
20
+ * all writes need a signed-in user or the secret key. */
21
+ export declare class StorageBucket {
22
+ private ctx;
23
+ private bucket;
24
+ constructor(ctx: Ctx, bucket: string);
25
+ private headers;
26
+ private fail;
27
+ /** Upload (or overwrite) a file. Body: Blob, File, ArrayBuffer, Uint8Array, or string. */
28
+ upload(path: string, body: Blob | ArrayBuffer | Uint8Array | string, options?: {
29
+ contentType?: string;
30
+ }): Promise<StorageResult<{
31
+ path: string;
32
+ }>>;
33
+ /** Download a file. Returns a Blob (browser/RN) — use .arrayBuffer()/.text() as needed. */
34
+ download(path: string): Promise<StorageResult<Blob>>;
35
+ /** Delete files. */
36
+ remove(paths: string[]): Promise<StorageResult<{
37
+ removed: string[];
38
+ }>>;
39
+ /** List objects (optionally under a prefix). */
40
+ list(prefix?: string, options?: {
41
+ limit?: number;
42
+ }): Promise<StorageResult<StorageObjectInfo[]>>;
43
+ /** Permanent unauthenticated URL for an object in a PUBLIC bucket —
44
+ * perfect for <img src>. */
45
+ getPublicUrl(path: string): {
46
+ data: {
47
+ publicUrl: string;
48
+ };
49
+ };
50
+ }
51
+ export declare class StorageClient {
52
+ private ctx;
53
+ constructor(ctx: Ctx);
54
+ from(bucket: string): StorageBucket;
55
+ }
56
+ export {};
57
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,KAAK,aAAa,GAAG,MAAM,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;AAElD,UAAU,GAAG;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,OAAO,KAAK,CAAC;IACpB,WAAW,EAAE,aAAa,CAAC;CAC5B;AAED,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,KAAK,aAAa,CAAC,CAAC,IAAI;IAAE,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IAAC,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAA;CAAE,CAAC;AAEnE;;0DAE0D;AAC1D,qBAAa,aAAa;IACZ,OAAO,CAAC,GAAG;IAAO,OAAO,CAAC,MAAM;gBAAxB,GAAG,EAAE,GAAG,EAAU,MAAM,EAAE,MAAM;YAEtC,OAAO;YAOP,IAAI;IAKlB,0FAA0F;IACpF,MAAM,CACV,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM,EAC9C,OAAO,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GACjC,OAAO,CAAC,aAAa,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAiB3C,2FAA2F;IACrF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;IAY1D,oBAAoB;IACd,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,aAAa,CAAC;QAAE,OAAO,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAiB5E,gDAAgD;IAC1C,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,aAAa,CAAC,iBAAiB,EAAE,CAAC,CAAC;IActG;iCAC6B;IAC7B,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG;QAAE,IAAI,EAAE;YAAE,SAAS,EAAE,MAAM,CAAA;SAAE,CAAA;KAAE;CAI5D;AAED,qBAAa,aAAa;IACZ,OAAO,CAAC,GAAG;gBAAH,GAAG,EAAE,GAAG;IAC5B,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa;CAGpC"}
@@ -0,0 +1,106 @@
1
+ import { ApiError } from './types.js';
2
+ /** File storage for one bucket — `client.storage.from('avatars')`.
3
+ * Reads on public buckets work with the publishable key; private buckets and
4
+ * all writes need a signed-in user or the secret key. */
5
+ export class StorageBucket {
6
+ ctx;
7
+ bucket;
8
+ constructor(ctx, bucket) {
9
+ this.ctx = ctx;
10
+ this.bucket = bucket;
11
+ }
12
+ async headers(extra) {
13
+ const h = { apikey: this.ctx.key, ...extra };
14
+ const token = await this.ctx.accessToken();
15
+ if (token)
16
+ h.authorization = `Bearer ${token}`;
17
+ return h;
18
+ }
19
+ async fail(res) {
20
+ const body = (await res.json().catch(() => ({})));
21
+ return new ApiError(typeof body.message === 'string' ? body.message : `storage error (${res.status})`, res.status);
22
+ }
23
+ /** Upload (or overwrite) a file. Body: Blob, File, ArrayBuffer, Uint8Array, or string. */
24
+ async upload(path, body, options) {
25
+ try {
26
+ const contentType = options?.contentType ??
27
+ (typeof Blob !== 'undefined' && body instanceof Blob && body.type ? body.type : 'application/octet-stream');
28
+ const res = await this.ctx.fetch(`${this.ctx.url}/storage/v1/object/${this.bucket}/${path}`, {
29
+ method: 'POST',
30
+ headers: await this.headers({ 'content-type': contentType }),
31
+ body: body,
32
+ });
33
+ if (!res.ok)
34
+ return { data: null, error: await this.fail(res) };
35
+ return { data: { path }, error: null };
36
+ }
37
+ catch (err) {
38
+ return { data: null, error: new ApiError(err.message, 0) };
39
+ }
40
+ }
41
+ /** Download a file. Returns a Blob (browser/RN) — use .arrayBuffer()/.text() as needed. */
42
+ async download(path) {
43
+ try {
44
+ const res = await this.ctx.fetch(`${this.ctx.url}/storage/v1/object/${this.bucket}/${path}`, {
45
+ headers: await this.headers(),
46
+ });
47
+ if (!res.ok)
48
+ return { data: null, error: await this.fail(res) };
49
+ return { data: await res.blob(), error: null };
50
+ }
51
+ catch (err) {
52
+ return { data: null, error: new ApiError(err.message, 0) };
53
+ }
54
+ }
55
+ /** Delete files. */
56
+ async remove(paths) {
57
+ try {
58
+ const removed = [];
59
+ for (const path of paths) {
60
+ const res = await this.ctx.fetch(`${this.ctx.url}/storage/v1/object/${this.bucket}/${path}`, {
61
+ method: 'DELETE',
62
+ headers: await this.headers(),
63
+ });
64
+ if (!res.ok)
65
+ return { data: null, error: await this.fail(res) };
66
+ removed.push(path);
67
+ }
68
+ return { data: { removed }, error: null };
69
+ }
70
+ catch (err) {
71
+ return { data: null, error: new ApiError(err.message, 0) };
72
+ }
73
+ }
74
+ /** List objects (optionally under a prefix). */
75
+ async list(prefix, options) {
76
+ try {
77
+ const res = await this.ctx.fetch(`${this.ctx.url}/storage/v1/object/list/${this.bucket}`, {
78
+ method: 'POST',
79
+ headers: await this.headers({ 'content-type': 'application/json' }),
80
+ body: JSON.stringify({ prefix, limit: options?.limit }),
81
+ });
82
+ if (!res.ok)
83
+ return { data: null, error: await this.fail(res) };
84
+ return { data: (await res.json()), error: null };
85
+ }
86
+ catch (err) {
87
+ return { data: null, error: new ApiError(err.message, 0) };
88
+ }
89
+ }
90
+ /** Permanent unauthenticated URL for an object in a PUBLIC bucket —
91
+ * perfect for <img src>. */
92
+ getPublicUrl(path) {
93
+ const ref = new URL(this.ctx.url).hostname.split('.')[0];
94
+ return { data: { publicUrl: `${this.ctx.url}/storage/v1/object/public/${ref}/${this.bucket}/${path}` } };
95
+ }
96
+ }
97
+ export class StorageClient {
98
+ ctx;
99
+ constructor(ctx) {
100
+ this.ctx = ctx;
101
+ }
102
+ from(bucket) {
103
+ return new StorageBucket(this.ctx, bucket);
104
+ }
105
+ }
106
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
package/dist/types.d.ts CHANGED
@@ -51,5 +51,10 @@ export interface ClientOptions {
51
51
  /** storage key (default 'airostack.auth.token') */
52
52
  storageKey?: string;
53
53
  };
54
+ realtime?: {
55
+ /** WebSocket implementation — needed on Node < 22 (pass the `ws` package).
56
+ * Browsers, React Native, and Node ≥ 22 use the global automatically. */
57
+ WebSocket?: new (url: string) => import('./realtime.js').WebSocketLike;
58
+ };
54
59
  }
55
60
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;yBACyB;AACzB,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,MAAM,EAAE,MAAM,CAAC;gBACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,QAAQ,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IACrD,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED;iDACiD;AACjD,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,IAAI,CAAC,EAAE;QACL,yCAAyC;QACzC,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,8DAA8D;QAC9D,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,mDAAmD;QACnD,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;CACH"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;yBACyB;AACzB,MAAM,WAAW,SAAS,CAAC,CAAC;IAC1B,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC;IACf,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;IACvB,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,qBAAa,QAAS,SAAQ,KAAK;IACjC,MAAM,EAAE,MAAM,CAAC;gBACH,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,MAAM,WAAW,IAAI;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,OAAO;IACtB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,QAAQ,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,IAAI,CAAC;CACZ;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE;QAAE,IAAI,EAAE,IAAI,GAAG,IAAI,CAAC;QAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAAA;KAAE,CAAC;IACrD,KAAK,EAAE,QAAQ,GAAG,IAAI,CAAC;CACxB;AAED;iDACiD;AACjD,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC;IAC7D,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1D,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAED,MAAM,WAAW,aAAa;IAC5B,iEAAiE;IACjE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IACrB,IAAI,CAAC,EAAE;QACL,yCAAyC;QACzC,cAAc,CAAC,EAAE,OAAO,CAAC;QACzB,8DAA8D;QAC9D,OAAO,CAAC,EAAE,cAAc,CAAC;QACzB,mDAAmD;QACnD,UAAU,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,QAAQ,CAAC,EAAE;QACT;kFAC0E;QAC1E,SAAS,CAAC,EAAE,KAAK,GAAG,EAAE,MAAM,KAAK,OAAO,eAAe,EAAE,aAAa,CAAC;KACxE,CAAC;CACH"}
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@airostack/client",
3
- "version": "0.1.0",
4
- "description": "Isomorphic client for AiroStack projects — Supabase-style Data API queries and Auth. Works in Node.js, browsers, and React Native.",
3
+ "version": "0.2.0",
4
+ "description": "Isomorphic client for AiroStack projects — Supabase-style database queries, auth, RPC, storage, and realtime. Works in Node.js, browsers, and React Native.",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "https://github.com/airoxlab/baseway.git",
8
+ "url": "git+https://github.com/airoxlab/baseway.git",
9
9
  "directory": "packages/client"
10
10
  },
11
+ "publishConfig": {
12
+ "access": "public"
13
+ },
11
14
  "keywords": ["airostack", "postgrest", "database", "auth", "baas", "react-native"],
12
15
  "type": "module",
13
16
  "main": "./dist/index.js",