@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 +347 -36
- package/dist/index.d.ts +24 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +55 -1
- package/dist/index.js.map +1 -1
- package/dist/realtime.d.ts +58 -0
- package/dist/realtime.d.ts.map +1 -0
- package/dist/realtime.js +112 -0
- package/dist/realtime.js.map +1 -0
- package/dist/storage.d.ts +57 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +106 -0
- package/dist/storage.js.map +1 -0
- package/dist/types.d.ts +5 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +6 -3
package/README.md
CHANGED
|
@@ -1,23 +1,59 @@
|
|
|
1
1
|
# @airostack/client
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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', //
|
|
16
|
-
'<publishable-key>', //
|
|
22
|
+
'https://<project-ref>.db.airoxlab.com', // Project URL → dashboard → project → Connect
|
|
23
|
+
'<publishable-key>', // Publishable key → project → API settings
|
|
17
24
|
);
|
|
18
25
|
```
|
|
19
26
|
|
|
20
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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
|
-
//
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
const { data: created } = await client.from('orders').insert({ item: 'Burger', qty: 2 });
|
|
130
|
+
### Inserting
|
|
44
131
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
await client.from('
|
|
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
|
-
//
|
|
50
|
-
|
|
136
|
+
// bulk
|
|
137
|
+
await client.from('tasks').insert([
|
|
138
|
+
{ title: 'One' },
|
|
139
|
+
{ title: 'Two' },
|
|
140
|
+
]);
|
|
51
141
|
```
|
|
52
142
|
|
|
53
|
-
|
|
54
|
-
Pagination: `.range(from, to)`. Errors never throw — check `error` on the result.
|
|
143
|
+
### Updating
|
|
55
144
|
|
|
56
|
-
|
|
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.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
65
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
-
|
|
221
|
+
### Reacting to auth changes
|
|
75
222
|
|
|
76
223
|
```ts
|
|
77
|
-
const
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
|
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;
|
|
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"}
|
package/dist/realtime.js
ADDED
|
@@ -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"}
|
package/dist/storage.js
ADDED
|
@@ -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
|
package/dist/types.d.ts.map
CHANGED
|
@@ -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.
|
|
4
|
-
"description": "Isomorphic client for AiroStack projects — Supabase-style
|
|
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",
|