hydrousdb 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +512 -559
- package/dist/index.d.mts +688 -56
- package/dist/index.d.ts +688 -56
- package/dist/index.js +1112 -582
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1105 -580
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -37
- package/dist/analytics/index.d.mts +0 -185
- package/dist/analytics/index.d.ts +0 -185
- package/dist/analytics/index.js +0 -184
- package/dist/analytics/index.js.map +0 -1
- package/dist/analytics/index.mjs +0 -182
- package/dist/analytics/index.mjs.map +0 -1
- package/dist/auth/index.d.mts +0 -180
- package/dist/auth/index.d.ts +0 -180
- package/dist/auth/index.js +0 -220
- package/dist/auth/index.js.map +0 -1
- package/dist/auth/index.mjs +0 -218
- package/dist/auth/index.mjs.map +0 -1
- package/dist/http-DTukpdAU.d.mts +0 -470
- package/dist/http-DTukpdAU.d.ts +0 -470
- package/dist/records/index.d.mts +0 -137
- package/dist/records/index.d.ts +0 -137
- package/dist/records/index.js +0 -228
- package/dist/records/index.js.map +0 -1
- package/dist/records/index.mjs +0 -226
- package/dist/records/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,777 +1,730 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Hydrous SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Official JavaScript / TypeScript SDK for the **Hydrous** platform.
|
|
4
|
+
One package — auth, records, analytics, and storage.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
```bash
|
|
7
|
+
npm install hydrous
|
|
8
|
+
```
|
|
9
9
|
|
|
10
10
|
---
|
|
11
11
|
|
|
12
12
|
## Table of Contents
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
- [
|
|
18
|
-
- [
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- [
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
- [
|
|
30
|
-
- [
|
|
31
|
-
- [
|
|
32
|
-
|
|
33
|
-
- [
|
|
34
|
-
- [
|
|
14
|
+
1. [Quick Start](#1-quick-start)
|
|
15
|
+
2. [Configuration](#2-configuration)
|
|
16
|
+
3. [Auth](#3-auth)
|
|
17
|
+
- [Sign Up](#sign-up)
|
|
18
|
+
- [Sign In](#sign-in)
|
|
19
|
+
- [Sign Out](#sign-out)
|
|
20
|
+
- [Get Current User](#get-current-user)
|
|
21
|
+
4. [Records](#4-records)
|
|
22
|
+
- [Insert](#insert)
|
|
23
|
+
- [Select / Query](#select--query)
|
|
24
|
+
- [Get by ID](#get-by-id)
|
|
25
|
+
- [Update](#update)
|
|
26
|
+
- [Delete](#delete)
|
|
27
|
+
- [Query Helpers](#query-helpers)
|
|
28
|
+
5. [Analytics](#5-analytics)
|
|
29
|
+
- [Track an Event](#track-an-event)
|
|
30
|
+
- [Batch Track](#batch-track)
|
|
31
|
+
- [Query Events](#query-events)
|
|
32
|
+
6. [Storage](#6-storage)
|
|
33
|
+
- [How Bucket Keys Work](#how-bucket-keys-work)
|
|
34
|
+
- [Upload a File](#upload-a-file)
|
|
35
|
+
- [Upload Raw Text / JSON](#upload-raw-text--json)
|
|
36
|
+
- [Track Upload Progress](#track-upload-progress)
|
|
37
|
+
- [Batch Upload](#batch-upload)
|
|
38
|
+
- [Download a File](#download-a-file)
|
|
39
|
+
- [Batch Download](#batch-download)
|
|
40
|
+
- [List Files & Folders](#list-files--folders)
|
|
41
|
+
- [File Metadata](#file-metadata)
|
|
42
|
+
- [Delete a File](#delete-a-file)
|
|
43
|
+
- [Delete a Folder](#delete-a-folder)
|
|
44
|
+
- [Create a Folder](#create-a-folder)
|
|
45
|
+
- [Move a File](#move-a-file)
|
|
46
|
+
- [Copy a File](#copy-a-file)
|
|
47
|
+
- [Signed URLs](#signed-urls)
|
|
48
|
+
- [Bucket Stats](#bucket-stats)
|
|
49
|
+
7. [Error Handling](#7-error-handling)
|
|
50
|
+
8. [TypeScript Types Reference](#8-typescript-types-reference)
|
|
35
51
|
|
|
36
52
|
---
|
|
37
53
|
|
|
38
|
-
##
|
|
54
|
+
## 1. Quick Start
|
|
55
|
+
|
|
56
|
+
```ts
|
|
57
|
+
import { createClient } from 'hydrous';
|
|
58
|
+
|
|
59
|
+
const hydrous = createClient({
|
|
60
|
+
url: 'https://api.yourapp.hydrous.app',
|
|
61
|
+
apiKey: 'hk_live_…',
|
|
62
|
+
});
|
|
39
63
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
64
|
+
// Upload a file
|
|
65
|
+
const { data, error } = await hydrous.storage.upload(
|
|
66
|
+
'ssk_my_bucket_key', // ← bucket key always comes first
|
|
67
|
+
file,
|
|
68
|
+
{
|
|
69
|
+
path: 'avatars/alice.jpg',
|
|
70
|
+
onProgress: (p) => console.log(`${p.percent}%`),
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
```
|
|
48
74
|
|
|
49
75
|
---
|
|
50
76
|
|
|
51
|
-
##
|
|
77
|
+
## 2. Configuration
|
|
52
78
|
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
79
|
+
```ts
|
|
80
|
+
import { HydrousClient } from 'hydrous';
|
|
81
|
+
|
|
82
|
+
const hydrous = new HydrousClient({
|
|
83
|
+
url: 'https://api.yourapp.hydrous.app', // your project URL
|
|
84
|
+
apiKey: 'hk_live_…', // your project API key
|
|
85
|
+
timeout: 30_000, // optional — ms (default 30s)
|
|
86
|
+
});
|
|
59
87
|
```
|
|
60
88
|
|
|
61
|
-
|
|
89
|
+
| Option | Type | Required | Description |
|
|
90
|
+
|-----------|----------|----------|--------------------------------------|
|
|
91
|
+
| `url` | `string` | ✅ | Your Hydrous project base URL |
|
|
92
|
+
| `apiKey` | `string` | ✅ | Your project API key |
|
|
93
|
+
| `timeout` | `number` | ✗ | Request timeout in ms (default 30000) |
|
|
62
94
|
|
|
63
95
|
---
|
|
64
96
|
|
|
65
|
-
##
|
|
66
|
-
|
|
67
|
-
### Two keys, one client
|
|
97
|
+
## 3. Auth
|
|
68
98
|
|
|
69
|
-
|
|
99
|
+
### Sign Up
|
|
70
100
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
101
|
+
```ts
|
|
102
|
+
const { data, error } = await hydrous.auth.signUp({
|
|
103
|
+
email: 'user@example.com',
|
|
104
|
+
password: 'supersecret',
|
|
105
|
+
metadata: { plan: 'pro' }, // optional
|
|
106
|
+
});
|
|
75
107
|
|
|
76
|
-
|
|
108
|
+
if (data) {
|
|
109
|
+
console.log('New user:', data.user.id);
|
|
110
|
+
console.log('Access token:', data.accessToken);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
77
113
|
|
|
78
|
-
|
|
114
|
+
### Sign In
|
|
79
115
|
|
|
80
116
|
```ts
|
|
81
|
-
const
|
|
117
|
+
const { data, error } = await hydrous.auth.signIn({
|
|
118
|
+
email: 'user@example.com',
|
|
119
|
+
password: 'supersecret',
|
|
120
|
+
});
|
|
82
121
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
await db.analytics.count({ bucketKey: 'users' });
|
|
87
|
-
await db.analytics.count({ bucketKey: 'orders' });
|
|
122
|
+
if (data) {
|
|
123
|
+
console.log('Welcome back,', data.user.email);
|
|
124
|
+
}
|
|
88
125
|
```
|
|
89
126
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
## Quick Start
|
|
127
|
+
### Sign Out
|
|
93
128
|
|
|
94
129
|
```ts
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const db = createClient({
|
|
98
|
-
authKey: 'your-auth-key',
|
|
99
|
-
securityKey: 'your-security-key',
|
|
100
|
-
});
|
|
130
|
+
await hydrous.auth.signOut();
|
|
131
|
+
```
|
|
101
132
|
|
|
102
|
-
|
|
103
|
-
const { data, meta } = await db.records.insert(
|
|
104
|
-
{ values: { name: 'Alice', score: 99 }, queryableFields: ['name'] },
|
|
105
|
-
{ bucketKey: 'users' }
|
|
106
|
-
);
|
|
107
|
-
console.log('Created record:', meta.id);
|
|
133
|
+
### Get Current User
|
|
108
134
|
|
|
109
|
-
|
|
110
|
-
const { data:
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
limit: 20,
|
|
114
|
-
});
|
|
135
|
+
```ts
|
|
136
|
+
const { data: user } = await hydrous.auth.getUser();
|
|
137
|
+
console.log(user?.email);
|
|
138
|
+
```
|
|
115
139
|
|
|
116
|
-
|
|
117
|
-
const { data: user, session } = await db.auth.signIn({
|
|
118
|
-
email: 'alice@example.com',
|
|
119
|
-
password: 'Str0ng@Pass!',
|
|
120
|
-
});
|
|
140
|
+
### Refresh Session
|
|
121
141
|
|
|
122
|
-
|
|
123
|
-
const { data:
|
|
124
|
-
console.log(stats.count);
|
|
142
|
+
```ts
|
|
143
|
+
const { data: session } = await hydrous.auth.refreshSession();
|
|
125
144
|
```
|
|
126
145
|
|
|
127
146
|
---
|
|
128
147
|
|
|
129
|
-
##
|
|
148
|
+
## 4. Records
|
|
130
149
|
|
|
131
|
-
###
|
|
150
|
+
### Insert
|
|
132
151
|
|
|
133
|
-
|
|
152
|
+
Single record:
|
|
134
153
|
|
|
135
154
|
```ts
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
authKey: process.env.HYDROUS_AUTH_KEY!,
|
|
141
|
-
securityKey: process.env.HYDROUS_SECURITY_KEY!,
|
|
155
|
+
const { data, error } = await hydrous.records.insert('users', {
|
|
156
|
+
name: 'Alice',
|
|
157
|
+
email: 'alice@example.com',
|
|
158
|
+
role: 'admin',
|
|
142
159
|
});
|
|
143
160
|
```
|
|
144
161
|
|
|
145
|
-
|
|
162
|
+
Bulk insert (array):
|
|
146
163
|
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
bucketKey: 'products',
|
|
154
|
-
filters: [{ field: 'category', op: '==', value: 'shoes' }],
|
|
155
|
-
limit: 24,
|
|
156
|
-
sortOrder: 'desc',
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
return (
|
|
160
|
-
<ul>
|
|
161
|
-
{products.map(p => (
|
|
162
|
-
<li key={p.id}>{String(p.name)}</li>
|
|
163
|
-
))}
|
|
164
|
-
</ul>
|
|
165
|
-
);
|
|
166
|
-
}
|
|
164
|
+
```ts
|
|
165
|
+
const { data } = await hydrous.records.insert('products', [
|
|
166
|
+
{ name: 'Widget A', price: 9.99 },
|
|
167
|
+
{ name: 'Widget B', price: 14.99 },
|
|
168
|
+
]);
|
|
169
|
+
console.log(`Inserted ${data.length} products`);
|
|
167
170
|
```
|
|
168
171
|
|
|
169
|
-
|
|
172
|
+
### Select / Query
|
|
170
173
|
|
|
171
174
|
```ts
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
const body = await req.json();
|
|
180
|
-
const result = await db.records.insert(
|
|
181
|
-
{ values: body, queryableFields: ['email', 'name'] },
|
|
182
|
-
{ bucketKey: 'users' }
|
|
183
|
-
);
|
|
184
|
-
return NextResponse.json(result, { status: 201 });
|
|
185
|
-
} catch (err) {
|
|
186
|
-
if (err instanceof HydrousError) {
|
|
187
|
-
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
188
|
-
}
|
|
189
|
-
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
190
|
-
}
|
|
191
|
-
}
|
|
175
|
+
const { data, count } = await hydrous.records.select('users', {
|
|
176
|
+
where: { field: 'role', operator: 'eq', value: 'admin' },
|
|
177
|
+
orderBy: { field: 'createdAt', direction: 'desc' },
|
|
178
|
+
limit: 20,
|
|
179
|
+
offset: 0,
|
|
180
|
+
select: ['id', 'name', 'email'], // optional column projection
|
|
181
|
+
});
|
|
192
182
|
```
|
|
193
183
|
|
|
194
|
-
|
|
184
|
+
Multiple filters:
|
|
195
185
|
|
|
196
186
|
```ts
|
|
197
|
-
|
|
198
|
-
import { NextRequest, NextResponse } from 'next/server';
|
|
199
|
-
import { db } from '@/lib/db';
|
|
187
|
+
import { eq, gt, inArray } from 'hydrous';
|
|
200
188
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
} catch {
|
|
209
|
-
return NextResponse.redirect(new URL('/login', req.url));
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export const config = { matcher: ['/dashboard/:path*'] };
|
|
189
|
+
const { data } = await hydrous.records.select('orders', {
|
|
190
|
+
where: [
|
|
191
|
+
eq('status', 'shipped'),
|
|
192
|
+
gt('total', 100),
|
|
193
|
+
inArray('tag', ['vip', 'priority']),
|
|
194
|
+
],
|
|
195
|
+
});
|
|
214
196
|
```
|
|
215
197
|
|
|
216
|
-
|
|
198
|
+
### Get by ID
|
|
217
199
|
|
|
218
|
-
```
|
|
219
|
-
|
|
220
|
-
HYDROUS_AUTH_KEY=your-auth-key
|
|
221
|
-
HYDROUS_SECURITY_KEY=your-security-key
|
|
200
|
+
```ts
|
|
201
|
+
const { data: user } = await hydrous.records.get('users', 'user_abc123');
|
|
222
202
|
```
|
|
223
203
|
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
### Next.js (Pages Router)
|
|
204
|
+
### Update
|
|
227
205
|
|
|
228
206
|
```ts
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
import { createClient, HydrousError } from 'hydrousdb';
|
|
232
|
-
|
|
233
|
-
const db = createClient({
|
|
234
|
-
authKey: process.env.HYDROUS_AUTH_KEY!,
|
|
235
|
-
securityKey: process.env.HYDROUS_SECURITY_KEY!,
|
|
207
|
+
const { data } = await hydrous.records.update('users', 'user_abc123', {
|
|
208
|
+
name: 'Alice Smith',
|
|
236
209
|
});
|
|
237
|
-
|
|
238
|
-
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|
239
|
-
if (req.method === 'GET') {
|
|
240
|
-
try {
|
|
241
|
-
const result = await db.records.query({ bucketKey: 'orders', limit: 50 });
|
|
242
|
-
return res.status(200).json(result);
|
|
243
|
-
} catch (err) {
|
|
244
|
-
if (err instanceof HydrousError) return res.status(err.status).json({ error: err.message });
|
|
245
|
-
return res.status(500).json({ error: 'Internal Server Error' });
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
res.setHeader('Allow', ['GET']);
|
|
249
|
-
res.status(405).end();
|
|
250
|
-
}
|
|
251
210
|
```
|
|
252
211
|
|
|
253
|
-
|
|
212
|
+
### Delete
|
|
254
213
|
|
|
255
|
-
|
|
214
|
+
```ts
|
|
215
|
+
const { error } = await hydrous.records.delete('users', 'user_abc123');
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Query Helpers
|
|
256
219
|
|
|
257
|
-
|
|
220
|
+
| Helper | SQL equivalent |
|
|
221
|
+
|---------------------|------------------------|
|
|
222
|
+
| `eq(field, val)` | `field = val` |
|
|
223
|
+
| `neq(field, val)` | `field != val` |
|
|
224
|
+
| `gt(field, val)` | `field > val` |
|
|
225
|
+
| `lt(field, val)` | `field < val` |
|
|
226
|
+
| `inArray(field, [])` | `field IN (…)` |
|
|
258
227
|
|
|
259
228
|
```ts
|
|
260
|
-
|
|
261
|
-
|
|
229
|
+
import { eq, gt, inArray } from 'hydrous';
|
|
230
|
+
```
|
|
262
231
|
|
|
263
|
-
|
|
264
|
-
const config = useRuntimeConfig();
|
|
232
|
+
---
|
|
265
233
|
|
|
266
|
-
|
|
267
|
-
authKey: config.hydrousAuthKey as string,
|
|
268
|
-
securityKey: config.hydrousSecurityKey as string,
|
|
269
|
-
});
|
|
234
|
+
## 5. Analytics
|
|
270
235
|
|
|
271
|
-
|
|
272
|
-
});
|
|
273
|
-
```
|
|
236
|
+
### Track an Event
|
|
274
237
|
|
|
275
238
|
```ts
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
},
|
|
239
|
+
await hydrous.analytics.track({
|
|
240
|
+
event: 'page_view',
|
|
241
|
+
properties: { page: '/home', referrer: 'google.com' },
|
|
242
|
+
userId: 'user_abc123',
|
|
243
|
+
sessionId: 'sess_xyz',
|
|
282
244
|
});
|
|
283
245
|
```
|
|
284
246
|
|
|
285
|
-
|
|
247
|
+
### Batch Track
|
|
286
248
|
|
|
287
|
-
|
|
288
|
-
<script setup lang="ts">
|
|
289
|
-
const { $db } = useNuxtApp();
|
|
249
|
+
More efficient than calling `track()` in a loop:
|
|
290
250
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
<template>
|
|
297
|
-
<div v-if="pending">Loading…</div>
|
|
298
|
-
<ul v-else>
|
|
299
|
-
<li v-for="r in records?.data" :key="r.id">{{ r.name }}</li>
|
|
300
|
-
</ul>
|
|
301
|
-
</template>
|
|
251
|
+
```ts
|
|
252
|
+
await hydrous.analytics.trackBatch([
|
|
253
|
+
{ event: 'signup', userId: 'u1' },
|
|
254
|
+
{ event: 'onboarded', userId: 'u1', properties: { step: 'profile' } },
|
|
255
|
+
]);
|
|
302
256
|
```
|
|
303
257
|
|
|
304
|
-
|
|
258
|
+
### Query Events
|
|
305
259
|
|
|
306
260
|
```ts
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
261
|
+
const { data, count } = await hydrous.analytics.query({
|
|
262
|
+
event: 'page_view',
|
|
263
|
+
from: '2024-01-01',
|
|
264
|
+
to: '2024-01-31',
|
|
265
|
+
limit: 500,
|
|
266
|
+
groupBy: 'properties.page',
|
|
313
267
|
});
|
|
314
|
-
|
|
315
|
-
export function useHydrous() {
|
|
316
|
-
return db;
|
|
317
|
-
}
|
|
318
268
|
```
|
|
319
269
|
|
|
320
270
|
---
|
|
321
271
|
|
|
322
|
-
|
|
272
|
+
## 6. Storage
|
|
323
273
|
|
|
324
|
-
|
|
325
|
-
// src/hooks/useRecords.ts
|
|
326
|
-
import { useEffect, useState } from 'react';
|
|
327
|
-
import { createClient, HydrousRecord, HydrousError } from 'hydrousdb';
|
|
274
|
+
The storage module handles all file operations. Every method takes a **bucket key** as its **first argument** — a string that begins with `ssk_`.
|
|
328
275
|
|
|
329
|
-
|
|
330
|
-
authKey: import.meta.env.VITE_HYDROUS_AUTH_KEY,
|
|
331
|
-
securityKey: import.meta.env.VITE_HYDROUS_SECURITY_KEY,
|
|
332
|
-
});
|
|
276
|
+
### How Bucket Keys Work
|
|
333
277
|
|
|
334
|
-
|
|
335
|
-
const [records, setRecords] = useState<HydrousRecord[]>([]);
|
|
336
|
-
const [loading, setLoading] = useState(true);
|
|
337
|
-
const [error, setError] = useState<string | null>(null);
|
|
278
|
+
A bucket key (`ssk_…`) is a scoped credential that grants specific permissions (read / write / delete) to a bucket. You create them in the Hydrous dashboard.
|
|
338
279
|
|
|
339
|
-
|
|
340
|
-
|
|
280
|
+
```
|
|
281
|
+
hydrous.storage.<method>(
|
|
282
|
+
'ssk_your_bucket_key', // ← first, always a string
|
|
283
|
+
...args
|
|
284
|
+
)
|
|
285
|
+
```
|
|
341
286
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
.finally(() => setLoading(false));
|
|
287
|
+
---
|
|
288
|
+
|
|
289
|
+
### Upload a File
|
|
346
290
|
|
|
347
|
-
|
|
348
|
-
|
|
291
|
+
```ts
|
|
292
|
+
const { data, error } = await hydrous.storage.upload(
|
|
293
|
+
'ssk_my_bucket_key',
|
|
294
|
+
file, // File | Blob | Uint8Array | ArrayBuffer
|
|
295
|
+
{
|
|
296
|
+
path: 'avatars/alice.jpg', // destination path in your bucket
|
|
297
|
+
overwrite: true, // replace if exists (default: false)
|
|
298
|
+
onProgress: (progress) => {
|
|
299
|
+
console.log(progress.stage, progress.percent + '%');
|
|
300
|
+
},
|
|
301
|
+
}
|
|
302
|
+
);
|
|
349
303
|
|
|
350
|
-
|
|
304
|
+
if (data) {
|
|
305
|
+
console.log('Stored at:', data.path);
|
|
306
|
+
console.log('Space saved:', data.spaceSaved, 'bytes');
|
|
351
307
|
}
|
|
352
308
|
```
|
|
353
309
|
|
|
354
310
|
---
|
|
355
311
|
|
|
356
|
-
###
|
|
312
|
+
### Upload Raw Text / JSON
|
|
357
313
|
|
|
358
|
-
|
|
359
|
-
// server.ts
|
|
360
|
-
import express from 'express';
|
|
361
|
-
import { createClient, HydrousError } from 'hydrousdb';
|
|
314
|
+
No `File` object needed — pass any string directly.
|
|
362
315
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
try {
|
|
372
|
-
const result = await db.records.query({ bucketKey: 'orders', limit: 50 });
|
|
373
|
-
res.json(result);
|
|
374
|
-
} catch (err) {
|
|
375
|
-
if (err instanceof HydrousError) return res.status(err.status).json({ error: err.message });
|
|
376
|
-
res.status(500).json({ error: 'Internal Server Error' });
|
|
377
|
-
}
|
|
378
|
-
});
|
|
316
|
+
```ts
|
|
317
|
+
// Upload a plain text file
|
|
318
|
+
await hydrous.storage.uploadText(
|
|
319
|
+
'ssk_my_bucket_key',
|
|
320
|
+
'reports/summary.txt',
|
|
321
|
+
'Monthly report content…',
|
|
322
|
+
{ mimeType: 'text/plain' }
|
|
323
|
+
);
|
|
379
324
|
|
|
380
|
-
|
|
325
|
+
// Upload JSON
|
|
326
|
+
await hydrous.storage.uploadText(
|
|
327
|
+
'ssk_my_bucket_key',
|
|
328
|
+
'data/config.json',
|
|
329
|
+
JSON.stringify({ theme: 'dark', lang: 'en' }),
|
|
330
|
+
{ mimeType: 'application/json' }
|
|
331
|
+
);
|
|
381
332
|
```
|
|
382
333
|
|
|
383
334
|
---
|
|
384
335
|
|
|
385
|
-
|
|
336
|
+
### Track Upload Progress
|
|
386
337
|
|
|
387
|
-
|
|
338
|
+
`onProgress` is called on every progress tick with a rich `UploadProgress` object.
|
|
388
339
|
|
|
389
340
|
```ts
|
|
390
|
-
|
|
341
|
+
await hydrous.storage.upload(
|
|
342
|
+
'ssk_my_bucket_key',
|
|
343
|
+
file,
|
|
344
|
+
{
|
|
345
|
+
onProgress: (p) => {
|
|
346
|
+
// p.stage — 'pending' | 'compressing' | 'uploading' | 'done' | 'error'
|
|
347
|
+
// p.percent — 0–100 integer
|
|
348
|
+
// p.bytesUploaded — bytes sent so far
|
|
349
|
+
// p.totalBytes — total bytes to send
|
|
350
|
+
// p.bytesPerSecond — current speed (null before first tick)
|
|
351
|
+
// p.eta — estimated seconds remaining (null until speed is known)
|
|
352
|
+
// p.index — file index (always 0 for single uploads)
|
|
353
|
+
// p.total — total files (always 1 for single uploads)
|
|
354
|
+
|
|
355
|
+
switch (p.stage) {
|
|
356
|
+
case 'pending':
|
|
357
|
+
console.log('Queued');
|
|
358
|
+
break;
|
|
359
|
+
case 'compressing':
|
|
360
|
+
console.log('Compressing…');
|
|
361
|
+
break;
|
|
362
|
+
case 'uploading':
|
|
363
|
+
console.log(`Uploading ${p.percent}% — ${formatSpeed(p.bytesPerSecond)} — ETA ${p.eta}s`);
|
|
364
|
+
break;
|
|
365
|
+
case 'done':
|
|
366
|
+
console.log('✅ Done!', p.result);
|
|
367
|
+
break;
|
|
368
|
+
case 'error':
|
|
369
|
+
console.error('❌ Error:', p.error);
|
|
370
|
+
break;
|
|
371
|
+
}
|
|
372
|
+
},
|
|
373
|
+
}
|
|
374
|
+
);
|
|
391
375
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
});
|
|
376
|
+
function formatSpeed(bps: number | null): string {
|
|
377
|
+
if (!bps) return '—';
|
|
378
|
+
if (bps > 1_000_000) return `${(bps / 1_000_000).toFixed(1)} MB/s`;
|
|
379
|
+
if (bps > 1_000) return `${(bps / 1_000).toFixed(0)} KB/s`;
|
|
380
|
+
return `${bps} B/s`;
|
|
381
|
+
}
|
|
399
382
|
```
|
|
400
383
|
|
|
401
|
-
|
|
384
|
+
**Stage lifecycle:**
|
|
402
385
|
|
|
403
|
-
|
|
386
|
+
```
|
|
387
|
+
pending → compressing → uploading → done
|
|
388
|
+
↘ error
|
|
389
|
+
```
|
|
404
390
|
|
|
405
|
-
|
|
391
|
+
> **Note:** In browsers, upload progress (bytes leaving the NIC) is tracked via
|
|
392
|
+
> `XMLHttpRequest`. The final `done` stage fires only after the server confirms
|
|
393
|
+
> the write to cloud storage — so `100%` means the file is truly saved.
|
|
406
394
|
|
|
407
|
-
|
|
395
|
+
---
|
|
408
396
|
|
|
409
|
-
|
|
410
|
-
|---|---|
|
|
411
|
-
| `get(recordId, { bucketKey, ...opts })` | Fetch a single record |
|
|
412
|
-
| `getSnapshot(recordId, generation, { bucketKey, ...opts })` | Fetch a historical version |
|
|
413
|
-
| `query({ bucketKey, ...opts })` | Query a collection with filters / pagination |
|
|
414
|
-
| `queryAll({ bucketKey, ...opts })` | Fetch all pages automatically |
|
|
415
|
-
| `insert(payload, { bucketKey, ...opts })` | Create a new record |
|
|
416
|
-
| `update(payload, { bucketKey, ...opts })` | Update an existing record |
|
|
417
|
-
| `delete(recordId, { bucketKey, ...opts })` | Delete a record |
|
|
418
|
-
| `exists(recordId, { bucketKey, ...opts })` | HEAD check — returns metadata or `null` |
|
|
419
|
-
| `batchInsert(payload, { bucketKey, ...opts })` | Insert up to 500 records |
|
|
420
|
-
| `batchUpdate(payload, { bucketKey, ...opts })` | Update up to 500 records |
|
|
421
|
-
| `batchDelete(payload, { bucketKey, ...opts })` | Delete up to 500 records |
|
|
397
|
+
### Batch Upload
|
|
422
398
|
|
|
423
|
-
|
|
399
|
+
Upload many files in one request. Progress fires per-file so you can render
|
|
400
|
+
individual progress bars.
|
|
424
401
|
|
|
425
402
|
```ts
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
403
|
+
const inputFiles = Array.from(fileInput.files); // FileList → array
|
|
404
|
+
|
|
405
|
+
const { data, error } = await hydrous.storage.batchUpload(
|
|
406
|
+
'ssk_my_bucket_key',
|
|
407
|
+
inputFiles,
|
|
408
|
+
{
|
|
409
|
+
prefix: 'uploads/2024/', // prepended to each filename
|
|
410
|
+
overwrite: false,
|
|
411
|
+
concurrency: 5, // max parallel uploads on the server (1–10)
|
|
412
|
+
onProgress: (p) => {
|
|
413
|
+
// p.index identifies WHICH file this event is for (0-based)
|
|
414
|
+
console.log(`File ${p.index} (${p.path}): ${p.stage} ${p.percent}%`);
|
|
415
|
+
},
|
|
416
|
+
}
|
|
417
|
+
);
|
|
418
|
+
|
|
419
|
+
console.log('Succeeded:', data.succeeded.length);
|
|
420
|
+
console.log('Failed:', data.failed.length);
|
|
430
421
|
```
|
|
431
422
|
|
|
432
|
-
|
|
423
|
+
Per-file paths override:
|
|
433
424
|
|
|
434
425
|
```ts
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
limit: 50,
|
|
441
|
-
cursor: meta.nextCursor ?? undefined,
|
|
442
|
-
fields: 'id,name,status',
|
|
426
|
+
await hydrous.storage.batchUpload('ssk_key', files, {
|
|
427
|
+
paths: [
|
|
428
|
+
'documents/report-q1.pdf',
|
|
429
|
+
'documents/report-q2.pdf',
|
|
430
|
+
],
|
|
443
431
|
});
|
|
444
432
|
```
|
|
445
433
|
|
|
446
|
-
|
|
447
|
-
|
|
434
|
+
> All files are validated upfront before any uploads begin — if your quota
|
|
435
|
+
> would be exceeded the entire batch is rejected cleanly with no partial writes.
|
|
448
436
|
|
|
449
|
-
|
|
437
|
+
---
|
|
450
438
|
|
|
451
|
-
|
|
452
|
-
const { data, meta } = await db.records.insert(
|
|
453
|
-
{
|
|
454
|
-
values: { name: 'Alice', role: 'admin', score: 100 },
|
|
455
|
-
queryableFields: ['name', 'role'],
|
|
456
|
-
userEmail: 'system@example.com',
|
|
457
|
-
},
|
|
458
|
-
{ bucketKey: 'users' }
|
|
459
|
-
);
|
|
460
|
-
// meta.id — the new record's ID
|
|
461
|
-
```
|
|
439
|
+
### Download a File
|
|
462
440
|
|
|
463
|
-
|
|
441
|
+
Returns the file as an `ArrayBuffer`.
|
|
464
442
|
|
|
465
443
|
```ts
|
|
466
|
-
await
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
values: { score: 150 },
|
|
470
|
-
track_record_history: true,
|
|
471
|
-
reason: 'Manual score adjustment',
|
|
472
|
-
},
|
|
473
|
-
{ bucketKey: 'users' }
|
|
444
|
+
const { data: buffer, error } = await hydrous.storage.download(
|
|
445
|
+
'ssk_my_bucket_key',
|
|
446
|
+
'avatars/alice.jpg'
|
|
474
447
|
);
|
|
448
|
+
|
|
449
|
+
if (buffer) {
|
|
450
|
+
// Browser: display image
|
|
451
|
+
const blob = new Blob([buffer], { type: 'image/jpeg' });
|
|
452
|
+
img.src = URL.createObjectURL(blob);
|
|
453
|
+
|
|
454
|
+
// Node: write to disk
|
|
455
|
+
import { writeFileSync } from 'fs';
|
|
456
|
+
writeFileSync('alice.jpg', Buffer.from(buffer));
|
|
457
|
+
}
|
|
475
458
|
```
|
|
476
459
|
|
|
477
|
-
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
### Batch Download
|
|
478
463
|
|
|
479
464
|
```ts
|
|
480
|
-
const
|
|
465
|
+
const { data: files } = await hydrous.storage.batchDownload(
|
|
466
|
+
'ssk_my_bucket_key',
|
|
467
|
+
['reports/jan.pdf', 'reports/feb.pdf', 'reports/mar.pdf'],
|
|
481
468
|
{
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
469
|
+
concurrency: 3,
|
|
470
|
+
autoSave: true, // browser: auto-triggers Save dialog per file
|
|
471
|
+
onProgress: (p) => {
|
|
472
|
+
console.log(`${p.path}: ${p.status}`); // 'pending' | 'success' | 'error'
|
|
473
|
+
},
|
|
474
|
+
}
|
|
487
475
|
);
|
|
488
|
-
|
|
476
|
+
|
|
477
|
+
// files[n].content — ArrayBuffer
|
|
478
|
+
// files[n].mimeType — string
|
|
479
|
+
// files[n].path — original path
|
|
480
|
+
// files[n].size — bytes
|
|
489
481
|
```
|
|
490
482
|
|
|
491
483
|
---
|
|
492
484
|
|
|
493
|
-
###
|
|
494
|
-
|
|
495
|
-
All methods live on `db.auth`. Auth routes use `authKey` and do **not** require a `bucketKey`.
|
|
496
|
-
|
|
497
|
-
| Method | Description |
|
|
498
|
-
|---|---|
|
|
499
|
-
| `signUp(payload, opts?)` | Register a new user |
|
|
500
|
-
| `signIn(payload, opts?)` | Authenticate with email + password |
|
|
501
|
-
| `signOut(payload, opts?)` | Revoke session(s) |
|
|
502
|
-
| `validateSession(sessionId, opts?)` | Verify a session is valid |
|
|
503
|
-
| `refreshSession(refreshToken, opts?)` | Rotate session using refresh token |
|
|
504
|
-
| `getUser(userId, opts?)` | Fetch user by ID |
|
|
505
|
-
| `listUsers(opts?)` | Paginated user list |
|
|
506
|
-
| `listAllUsers(opts?)` | All users (auto-paginates) |
|
|
507
|
-
| `updateUser(payload, opts?)` | Update user profile |
|
|
508
|
-
| `deleteUser(userId, opts?)` | Soft-delete a user |
|
|
509
|
-
| `changePassword(payload, opts?)` | Change password (requires old password) |
|
|
510
|
-
| `requestPasswordReset(payload, opts?)` | Trigger reset email |
|
|
511
|
-
| `confirmPasswordReset(payload, opts?)` | Confirm reset with token |
|
|
512
|
-
| `requestEmailVerification(payload, opts?)` | Trigger verification email |
|
|
513
|
-
| `confirmEmailVerification(payload, opts?)` | Confirm email with token |
|
|
514
|
-
| `lockAccount(payload, opts?)` | Lock account for a duration |
|
|
515
|
-
| `unlockAccount(userId, opts?)` | Unlock account |
|
|
516
|
-
|
|
517
|
-
#### Auth flow example
|
|
518
|
-
|
|
519
|
-
```ts
|
|
520
|
-
// 1. Sign up
|
|
521
|
-
const { data: user, session } = await db.auth.signUp({
|
|
522
|
-
email: 'alice@example.com',
|
|
523
|
-
password: 'Str0ng@Pass!',
|
|
524
|
-
fullName: 'Alice Smith',
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
// 2. Store session tokens
|
|
528
|
-
const { sessionId, refreshToken, expiresAt } = session;
|
|
485
|
+
### List Files & Folders
|
|
529
486
|
|
|
530
|
-
|
|
531
|
-
const { data
|
|
487
|
+
```ts
|
|
488
|
+
const { data } = await hydrous.storage.list('ssk_my_bucket_key', {
|
|
489
|
+
prefix: 'avatars/', // list inside a folder — omit for root
|
|
490
|
+
limit: 50, // max items per page (1–100)
|
|
491
|
+
});
|
|
532
492
|
|
|
533
|
-
|
|
534
|
-
|
|
493
|
+
for (const item of data.items) {
|
|
494
|
+
if (item.type === 'folder') {
|
|
495
|
+
console.log('📁', item.path);
|
|
496
|
+
} else {
|
|
497
|
+
console.log('📄', item.path, item.size, 'bytes');
|
|
498
|
+
}
|
|
499
|
+
}
|
|
535
500
|
|
|
536
|
-
//
|
|
537
|
-
|
|
501
|
+
// Paginate
|
|
502
|
+
if (data.pagination.hasNextPage) {
|
|
503
|
+
const page2 = await hydrous.storage.list('ssk_my_bucket_key', {
|
|
504
|
+
prefix: 'avatars/',
|
|
505
|
+
cursor: data.pagination.nextCursor,
|
|
506
|
+
});
|
|
507
|
+
}
|
|
538
508
|
```
|
|
539
509
|
|
|
540
510
|
---
|
|
541
511
|
|
|
542
|
-
###
|
|
512
|
+
### File Metadata
|
|
543
513
|
|
|
544
|
-
|
|
514
|
+
```ts
|
|
515
|
+
const { data: meta } = await hydrous.storage.metadata(
|
|
516
|
+
'ssk_my_bucket_key',
|
|
517
|
+
'avatars/alice.jpg'
|
|
518
|
+
);
|
|
545
519
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
| `fieldTimeSeries(field, { bucketKey, ...opts })` | `"fieldTimeSeries"` | Numeric field aggregated over time |
|
|
553
|
-
| `topN(field, n, { bucketKey, ...opts })` | `"topN"` | Top N field values by frequency |
|
|
554
|
-
| `stats(field, { bucketKey, ...opts })` | `"stats"` | min/max/avg/stddev/p50/p90/p99 |
|
|
555
|
-
| `records({ bucketKey, ...opts })` | `"records"` | Filtered + paginated raw records |
|
|
556
|
-
| `multiMetric(metrics, { bucketKey, ...opts })` | `"multiMetric"` | Multiple aggregations in one call |
|
|
557
|
-
| `storageStats({ bucketKey, ...opts })` | `"storageStats"` | Bucket storage usage |
|
|
558
|
-
| `crossBucket({ bucketKey, bucketKeys, ...opts })` | `"crossBucket"` | Compare metric across buckets |
|
|
559
|
-
| `query(payload, { bucketKey, ...opts })` | any | Raw query — full control |
|
|
520
|
+
console.log(meta.size); // stored bytes
|
|
521
|
+
console.log(meta.originalSize); // bytes before compression
|
|
522
|
+
console.log(meta.mimeType);
|
|
523
|
+
console.log(meta.isCompressed);
|
|
524
|
+
console.log(meta.updatedAt);
|
|
525
|
+
```
|
|
560
526
|
|
|
561
|
-
|
|
527
|
+
---
|
|
562
528
|
|
|
563
|
-
|
|
529
|
+
### Delete a File
|
|
564
530
|
|
|
565
531
|
```ts
|
|
566
|
-
const
|
|
567
|
-
|
|
568
|
-
|
|
532
|
+
const { error } = await hydrous.storage.deleteFile(
|
|
533
|
+
'ssk_my_bucket_key',
|
|
534
|
+
'avatars/old-photo.jpg'
|
|
535
|
+
);
|
|
569
536
|
```
|
|
570
537
|
|
|
571
|
-
|
|
538
|
+
---
|
|
539
|
+
|
|
540
|
+
### Delete a Folder
|
|
541
|
+
|
|
542
|
+
Recursively deletes the folder and everything inside it.
|
|
572
543
|
|
|
573
544
|
```ts
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
console.log(data.count);
|
|
545
|
+
await hydrous.storage.deleteFolder('ssk_my_bucket_key', 'temp/');
|
|
546
|
+
```
|
|
577
547
|
|
|
578
|
-
|
|
579
|
-
const { data } = await db.analytics.distribution('status', { bucketKey: 'orders' });
|
|
580
|
-
// [{ value: 'active', count: 80 }, { value: 'archived', count: 20 }]
|
|
548
|
+
---
|
|
581
549
|
|
|
582
|
-
|
|
583
|
-
const { data } = await db.analytics.sum('amount', {
|
|
584
|
-
bucketKey: 'orders',
|
|
585
|
-
groupBy: 'region',
|
|
586
|
-
dateRange: { startDate: '2025-01-01', endDate: '2025-03-31' },
|
|
587
|
-
});
|
|
550
|
+
### Create a Folder
|
|
588
551
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
granularity: 'day',
|
|
593
|
-
dateRange: { startDate: '2025-03-01' },
|
|
594
|
-
});
|
|
552
|
+
```ts
|
|
553
|
+
await hydrous.storage.createFolder('ssk_my_bucket_key', 'avatars/2024/');
|
|
554
|
+
```
|
|
595
555
|
|
|
596
|
-
|
|
597
|
-
const { data } = await db.analytics.stats('score', { bucketKey: 'users' });
|
|
598
|
-
console.log(data.avg, data.p90, data.p99);
|
|
556
|
+
---
|
|
599
557
|
|
|
600
|
-
|
|
601
|
-
const { data } = await db.analytics.topN('country', 5, { bucketKey: 'users' });
|
|
558
|
+
### Move a File
|
|
602
559
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
{ name: 'userCount', field: 'userId', aggregation: 'count' },
|
|
609
|
-
],
|
|
610
|
-
{ bucketKey: 'orders' }
|
|
560
|
+
```ts
|
|
561
|
+
await hydrous.storage.move(
|
|
562
|
+
'ssk_my_bucket_key',
|
|
563
|
+
'drafts/report.pdf',
|
|
564
|
+
'published/report.pdf'
|
|
611
565
|
);
|
|
612
|
-
console.log(data.totalRevenue, data.avgScore);
|
|
613
|
-
|
|
614
|
-
// Storage usage
|
|
615
|
-
const { data } = await db.analytics.storageStats({ bucketKey: 'orders' });
|
|
616
|
-
console.log(data.totalRecords, data.totalBytes);
|
|
617
|
-
|
|
618
|
-
// Cross-bucket revenue comparison
|
|
619
|
-
const { data } = await db.analytics.crossBucket({
|
|
620
|
-
bucketKey: 'sales-2025',
|
|
621
|
-
bucketKeys: ['sales-2024', 'sales-2025'],
|
|
622
|
-
field: 'amount',
|
|
623
|
-
aggregation: 'sum',
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
// Raw records with filters
|
|
627
|
-
const { data } = await db.analytics.records({
|
|
628
|
-
bucketKey: 'users',
|
|
629
|
-
filters: [{ field: 'role', op: '==', value: 'admin' }],
|
|
630
|
-
selectFields: ['email', 'createdAt'],
|
|
631
|
-
limit: 50,
|
|
632
|
-
});
|
|
633
|
-
console.log(data.data, data.hasMore);
|
|
634
566
|
```
|
|
635
567
|
|
|
636
568
|
---
|
|
637
569
|
|
|
638
|
-
|
|
570
|
+
### Copy a File
|
|
639
571
|
|
|
640
572
|
```ts
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
if (err instanceof HydrousError) {
|
|
647
|
-
console.error(err.message); // human-readable message
|
|
648
|
-
console.error(err.status); // HTTP status code (e.g. 404)
|
|
649
|
-
console.error(err.code); // machine-readable code (e.g. 'NOT_FOUND')
|
|
650
|
-
console.error(err.details); // validation error details array
|
|
651
|
-
console.error(err.requestId); // server request ID for support
|
|
652
|
-
} else if (err instanceof HydrousTimeoutError) {
|
|
653
|
-
console.error('Request timed out');
|
|
654
|
-
} else if (err instanceof HydrousNetworkError) {
|
|
655
|
-
console.error('Network failure:', err.cause);
|
|
656
|
-
}
|
|
657
|
-
}
|
|
573
|
+
await hydrous.storage.copy(
|
|
574
|
+
'ssk_my_bucket_key',
|
|
575
|
+
'templates/invoice.pdf',
|
|
576
|
+
'invoices/invoice-001.pdf'
|
|
577
|
+
);
|
|
658
578
|
```
|
|
659
579
|
|
|
660
580
|
---
|
|
661
581
|
|
|
662
|
-
|
|
582
|
+
### Signed URLs
|
|
663
583
|
|
|
664
|
-
|
|
584
|
+
Generate a time-limited public URL for a private file.
|
|
665
585
|
|
|
666
586
|
```ts
|
|
667
|
-
|
|
668
|
-
|
|
587
|
+
const { data } = await hydrous.storage.signedUrl(
|
|
588
|
+
'ssk_my_bucket_key',
|
|
589
|
+
'contracts/agreement.pdf',
|
|
590
|
+
{ expiresIn: 300 } // 5 minutes (default: 3600 = 1 hour)
|
|
591
|
+
);
|
|
669
592
|
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
bucketKey: 'orders',
|
|
673
|
-
limit: 100,
|
|
674
|
-
cursor: cursor ?? undefined,
|
|
675
|
-
});
|
|
593
|
+
console.log(data.signedUrl); // share this — expires at data.expiresAt
|
|
594
|
+
```
|
|
676
595
|
|
|
677
|
-
|
|
678
|
-
cursor = meta.nextCursor;
|
|
679
|
-
} while (cursor);
|
|
596
|
+
---
|
|
680
597
|
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
598
|
+
### Bucket Stats
|
|
599
|
+
|
|
600
|
+
```ts
|
|
601
|
+
const { data: stats } = await hydrous.storage.stats('ssk_my_bucket_key');
|
|
602
|
+
|
|
603
|
+
console.log('Files:', stats.totalFiles);
|
|
604
|
+
console.log('Stored:', stats.totalSizeBytes, 'bytes');
|
|
605
|
+
console.log('Space saved:', stats.spaceSavedBytes, 'bytes');
|
|
606
|
+
console.log('Credits used:', stats.creditsTotalUsed);
|
|
607
|
+
console.log('Compression:', stats.compressionRatio);
|
|
686
608
|
```
|
|
687
609
|
|
|
688
610
|
---
|
|
689
611
|
|
|
690
|
-
##
|
|
612
|
+
## 7. Error Handling
|
|
691
613
|
|
|
692
|
-
|
|
614
|
+
Every method returns `{ data, error }`. `error` is `null` on success.
|
|
693
615
|
|
|
694
616
|
```ts
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
BucketOptions,
|
|
703
|
-
Filter,
|
|
704
|
-
} from 'hydrousdb';
|
|
617
|
+
const { data, error } = await hydrous.storage.upload('ssk_key', file);
|
|
618
|
+
|
|
619
|
+
if (error) {
|
|
620
|
+
console.error(error.message); // human-readable message
|
|
621
|
+
console.error(error.code); // machine-readable code e.g. 'QUOTA_EXCEEDED'
|
|
622
|
+
console.error(error.status); // HTTP status code (if applicable)
|
|
623
|
+
}
|
|
705
624
|
```
|
|
706
625
|
|
|
707
|
-
|
|
626
|
+
### Catching thrown errors
|
|
708
627
|
|
|
709
|
-
|
|
710
|
-
interface Product {
|
|
711
|
-
name: string;
|
|
712
|
-
price: number;
|
|
713
|
-
category: string;
|
|
714
|
-
}
|
|
628
|
+
If you prefer `try/catch`, import `isHydrousError`:
|
|
715
629
|
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
filters: [{ field: 'category', op: '==', value: 'shoes' }],
|
|
719
|
-
});
|
|
630
|
+
```ts
|
|
631
|
+
import { isHydrousError } from 'hydrous';
|
|
720
632
|
|
|
721
|
-
|
|
722
|
-
|
|
633
|
+
try {
|
|
634
|
+
// ...
|
|
635
|
+
} catch (err) {
|
|
636
|
+
if (isHydrousError(err)) {
|
|
637
|
+
console.error(err.code, err.message);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
723
640
|
```
|
|
724
641
|
|
|
725
|
-
|
|
642
|
+
### Common error codes
|
|
643
|
+
|
|
644
|
+
| Code | Meaning |
|
|
645
|
+
|---------------------|----------------------------------------------|
|
|
646
|
+
| `HTTP_ERROR` | Non-2xx HTTP response from the server |
|
|
647
|
+
| `NETWORK_ERROR` | Failed to reach the server |
|
|
648
|
+
| `QUOTA_EXCEEDED` | Storage quota has been reached |
|
|
649
|
+
| `FILE_TOO_LARGE` | File exceeds the per-file size limit (50 MB) |
|
|
650
|
+
| `INVALID_MIME` | MIME type not permitted |
|
|
651
|
+
| `FILE_EXISTS` | File already exists and `overwrite` is false |
|
|
652
|
+
| `UPLOAD_ABORTED` | Upload was cancelled by the client |
|
|
653
|
+
| `UPLOAD_TIMEOUT` | Upload timed out |
|
|
654
|
+
| `NO_SESSION` | Auth operation requires a session |
|
|
655
|
+
| `UNKNOWN_ERROR` | An unexpected error occurred |
|
|
726
656
|
|
|
727
|
-
|
|
657
|
+
---
|
|
728
658
|
|
|
729
|
-
|
|
659
|
+
## 8. TypeScript Types Reference
|
|
730
660
|
|
|
731
|
-
###
|
|
661
|
+
### `UploadProgress`
|
|
732
662
|
|
|
733
663
|
```ts
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
664
|
+
interface UploadProgress {
|
|
665
|
+
index: number; // file index (0-based)
|
|
666
|
+
total: number; // total files in the operation
|
|
667
|
+
path: string; // destination path
|
|
668
|
+
stage: UploadStage; // see below
|
|
669
|
+
bytesUploaded: number;
|
|
670
|
+
totalBytes: number;
|
|
671
|
+
percent: number; // 0–100
|
|
672
|
+
bytesPerSecond: number | null; // null before first tick
|
|
673
|
+
eta: number | null; // seconds remaining, null until speed known
|
|
674
|
+
result?: UploadResult; // set when stage === 'done'
|
|
675
|
+
error?: string; // set when stage === 'error'
|
|
676
|
+
code?: string;
|
|
677
|
+
}
|
|
737
678
|
|
|
738
|
-
|
|
739
|
-
const db = createClient({ authKey: 'ak_...', securityKey: 'sk_...' });
|
|
740
|
-
await db.records.query({ bucketKey: 'users', limit: 50 });
|
|
679
|
+
type UploadStage = 'pending' | 'compressing' | 'uploading' | 'done' | 'error';
|
|
741
680
|
```
|
|
742
681
|
|
|
743
|
-
###
|
|
744
|
-
|
|
745
|
-
In v1 the SDK incorrectly used `bucketKey` as both the bucket identifier and the security credential in the URL. v2 adds an explicit `securityKey` that maps to the server's `:key` parameter.
|
|
682
|
+
### `UploadResult`
|
|
746
683
|
|
|
747
684
|
```ts
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
685
|
+
interface UploadResult {
|
|
686
|
+
path: string;
|
|
687
|
+
compressed: boolean;
|
|
688
|
+
originalSize: number;
|
|
689
|
+
storedSize: number;
|
|
690
|
+
spaceSaved: number;
|
|
691
|
+
mimeType: string;
|
|
692
|
+
}
|
|
755
693
|
```
|
|
756
694
|
|
|
757
|
-
|
|
758
|
-
1. Add `securityKey` to your `createClient` call
|
|
759
|
-
2. Remove `bucketKey` from `createClient`
|
|
760
|
-
3. Add `{ bucketKey: '...' }` to every `db.records.*` and `db.analytics.*` call
|
|
761
|
-
4. Auth calls (`db.auth.*`) are unchanged
|
|
695
|
+
### `StorageItem` (from `list()`)
|
|
762
696
|
|
|
763
|
-
|
|
697
|
+
```ts
|
|
698
|
+
interface StorageItem {
|
|
699
|
+
name: string;
|
|
700
|
+
path: string;
|
|
701
|
+
type: 'file' | 'folder';
|
|
702
|
+
size?: number | null;
|
|
703
|
+
mimeType?: string | null;
|
|
704
|
+
isCompressed?: boolean;
|
|
705
|
+
updatedAt?: string | null;
|
|
706
|
+
}
|
|
707
|
+
```
|
|
764
708
|
|
|
765
|
-
|
|
709
|
+
### `StorageStats`
|
|
766
710
|
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
711
|
+
```ts
|
|
712
|
+
interface StorageStats {
|
|
713
|
+
totalFiles: number;
|
|
714
|
+
totalSizeBytes: number;
|
|
715
|
+
totalOriginalSizeBytes: number;
|
|
716
|
+
spaceSavedBytes: number;
|
|
717
|
+
uploadsCount: number;
|
|
718
|
+
downloadsCount: number;
|
|
719
|
+
creditsUsedUpload: number;
|
|
720
|
+
creditsUsedDownload: number;
|
|
721
|
+
creditsTotalUsed: number;
|
|
722
|
+
compressionRatio: string; // e.g. "34.2%"
|
|
723
|
+
}
|
|
724
|
+
```
|
|
772
725
|
|
|
773
726
|
---
|
|
774
727
|
|
|
775
728
|
## License
|
|
776
729
|
|
|
777
|
-
MIT ©
|
|
730
|
+
MIT © Hydrous
|