hydrousdb 1.1.1 → 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 +528 -484
- package/dist/index.d.mts +689 -51
- package/dist/index.d.ts +689 -51
- package/dist/index.js +1114 -619
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1107 -617
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -37
- package/dist/analytics/index.d.mts +0 -178
- package/dist/analytics/index.d.ts +0 -178
- package/dist/analytics/index.js +0 -221
- package/dist/analytics/index.js.map +0 -1
- package/dist/analytics/index.mjs +0 -219
- 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-DbiqdKlw.d.mts +0 -466
- package/dist/http-DbiqdKlw.d.ts +0 -466
- package/dist/records/index.d.mts +0 -124
- package/dist/records/index.d.ts +0 -124
- package/dist/records/index.js +0 -226
- package/dist/records/index.js.map +0 -1
- package/dist/records/index.mjs +0 -224
- package/dist/records/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,686 +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
|
-
|
|
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)
|
|
33
51
|
|
|
34
52
|
---
|
|
35
53
|
|
|
36
|
-
##
|
|
37
|
-
|
|
38
|
-
- ✅ **Full TypeScript** — every method and response is fully typed
|
|
39
|
-
- ✅ **Modular** — import only what you need (`hydrousdb/records`, `hydrousdb/auth`, `hydrousdb/analytics`)
|
|
40
|
-
- ✅ **Tree-shakeable** — ships ESM + CJS with dual exports
|
|
41
|
-
- ✅ **Auto-retry** — retries on transient 5xx / network errors with linear back-off
|
|
42
|
-
- ✅ **Timeout control** — configurable per-client request timeout
|
|
43
|
-
- ✅ **Cursor pagination helpers** — `queryAll()` / `listAllUsers()` handle cursor following for you
|
|
44
|
-
- ✅ **Zero dependencies** — uses the native `fetch` API (Node 18+, all modern browsers)
|
|
54
|
+
## 1. Quick Start
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
```ts
|
|
57
|
+
import { createClient } from 'hydrous';
|
|
47
58
|
|
|
48
|
-
|
|
59
|
+
const hydrous = createClient({
|
|
60
|
+
url: 'https://api.yourapp.hydrous.app',
|
|
61
|
+
apiKey: 'hk_live_…',
|
|
62
|
+
});
|
|
49
63
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
+
);
|
|
56
73
|
```
|
|
57
74
|
|
|
58
|
-
> **Node.js ≥ 18** is required (uses native `fetch`). For Node 16/17, polyfill `fetch` with `node-fetch` or `undici`.
|
|
59
|
-
|
|
60
75
|
---
|
|
61
76
|
|
|
62
|
-
##
|
|
77
|
+
## 2. Configuration
|
|
63
78
|
|
|
64
79
|
```ts
|
|
65
|
-
import {
|
|
66
|
-
|
|
67
|
-
const db = createClient({
|
|
68
|
-
authKey: 'your-auth-key',
|
|
69
|
-
bucketKey: 'your-bucket-key',
|
|
70
|
-
});
|
|
80
|
+
import { HydrousClient } from 'hydrous';
|
|
71
81
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
});
|
|
77
|
-
console.log('Created record:', meta.id);
|
|
78
|
-
|
|
79
|
-
// Query records
|
|
80
|
-
const { data: records } = await db.records.query({
|
|
81
|
-
filters: [{ field: 'score', op: '>=', value: 90 }],
|
|
82
|
-
limit: 20,
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
// Auth — sign a user in
|
|
86
|
-
const { data: user, session } = await db.auth.signIn({
|
|
87
|
-
email: 'alice@example.com',
|
|
88
|
-
password: 'Str0ng@Pass!',
|
|
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)
|
|
89
86
|
});
|
|
90
87
|
```
|
|
91
88
|
|
|
92
|
-
|
|
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) |
|
|
93
94
|
|
|
94
|
-
|
|
95
|
+
---
|
|
95
96
|
|
|
96
|
-
|
|
97
|
+
## 3. Auth
|
|
97
98
|
|
|
98
|
-
|
|
99
|
+
### Sign Up
|
|
99
100
|
|
|
100
101
|
```ts
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
export const db = createClient({
|
|
106
|
-
authKey: process.env.HYDROUS_AUTH_KEY!,
|
|
107
|
-
bucketKey: process.env.HYDROUS_BUCKET_KEY!,
|
|
102
|
+
const { data, error } = await hydrous.auth.signUp({
|
|
103
|
+
email: 'user@example.com',
|
|
104
|
+
password: 'supersecret',
|
|
105
|
+
metadata: { plan: 'pro' }, // optional
|
|
108
106
|
});
|
|
109
|
-
```
|
|
110
107
|
|
|
111
|
-
|
|
108
|
+
if (data) {
|
|
109
|
+
console.log('New user:', data.user.id);
|
|
110
|
+
console.log('Access token:', data.accessToken);
|
|
111
|
+
}
|
|
112
|
+
```
|
|
112
113
|
|
|
113
|
-
|
|
114
|
-
// app/products/page.tsx
|
|
115
|
-
import { db } from '@/lib/db';
|
|
114
|
+
### Sign In
|
|
116
115
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
116
|
+
```ts
|
|
117
|
+
const { data, error } = await hydrous.auth.signIn({
|
|
118
|
+
email: 'user@example.com',
|
|
119
|
+
password: 'supersecret',
|
|
120
|
+
});
|
|
123
121
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
{products.map(p => (
|
|
127
|
-
<li key={p.id}>{String(p.name)}</li>
|
|
128
|
-
))}
|
|
129
|
-
</ul>
|
|
130
|
-
);
|
|
122
|
+
if (data) {
|
|
123
|
+
console.log('Welcome back,', data.user.email);
|
|
131
124
|
}
|
|
132
125
|
```
|
|
133
126
|
|
|
134
|
-
|
|
127
|
+
### Sign Out
|
|
135
128
|
|
|
136
129
|
```ts
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
import { db } from '@/lib/db';
|
|
140
|
-
import { HydrousError } from 'hydrousdb';
|
|
130
|
+
await hydrous.auth.signOut();
|
|
131
|
+
```
|
|
141
132
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
queryableFields: ['email', 'name'],
|
|
148
|
-
});
|
|
149
|
-
return NextResponse.json(result, { status: 201 });
|
|
150
|
-
} catch (err) {
|
|
151
|
-
if (err instanceof HydrousError) {
|
|
152
|
-
return NextResponse.json({ error: err.message }, { status: err.status });
|
|
153
|
-
}
|
|
154
|
-
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 });
|
|
155
|
-
}
|
|
156
|
-
}
|
|
133
|
+
### Get Current User
|
|
134
|
+
|
|
135
|
+
```ts
|
|
136
|
+
const { data: user } = await hydrous.auth.getUser();
|
|
137
|
+
console.log(user?.email);
|
|
157
138
|
```
|
|
158
139
|
|
|
159
|
-
|
|
140
|
+
### Refresh Session
|
|
160
141
|
|
|
161
142
|
```ts
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
import { db } from '@/lib/db';
|
|
143
|
+
const { data: session } = await hydrous.auth.refreshSession();
|
|
144
|
+
```
|
|
165
145
|
|
|
166
|
-
|
|
167
|
-
const sessionId = req.cookies.get('sessionId')?.value;
|
|
168
|
-
if (!sessionId) return NextResponse.redirect(new URL('/login', req.url));
|
|
146
|
+
---
|
|
169
147
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
}
|
|
176
|
-
}
|
|
148
|
+
## 4. Records
|
|
149
|
+
|
|
150
|
+
### Insert
|
|
151
|
+
|
|
152
|
+
Single record:
|
|
177
153
|
|
|
178
|
-
|
|
154
|
+
```ts
|
|
155
|
+
const { data, error } = await hydrous.records.insert('users', {
|
|
156
|
+
name: 'Alice',
|
|
157
|
+
email: 'alice@example.com',
|
|
158
|
+
role: 'admin',
|
|
159
|
+
});
|
|
179
160
|
```
|
|
180
161
|
|
|
181
|
-
|
|
162
|
+
Bulk insert (array):
|
|
182
163
|
|
|
183
|
-
```
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
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`);
|
|
187
170
|
```
|
|
188
171
|
|
|
189
|
-
|
|
172
|
+
### Select / Query
|
|
173
|
+
|
|
174
|
+
```ts
|
|
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
|
+
});
|
|
182
|
+
```
|
|
190
183
|
|
|
191
|
-
|
|
184
|
+
Multiple filters:
|
|
192
185
|
|
|
193
186
|
```ts
|
|
194
|
-
|
|
195
|
-
import type { NextApiRequest, NextApiResponse } from 'next';
|
|
196
|
-
import { createClient, HydrousError } from 'hydrousdb';
|
|
187
|
+
import { eq, gt, inArray } from 'hydrous';
|
|
197
188
|
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
189
|
+
const { data } = await hydrous.records.select('orders', {
|
|
190
|
+
where: [
|
|
191
|
+
eq('status', 'shipped'),
|
|
192
|
+
gt('total', 100),
|
|
193
|
+
inArray('tag', ['vip', 'priority']),
|
|
194
|
+
],
|
|
201
195
|
});
|
|
196
|
+
```
|
|
202
197
|
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
return res.status(200).json(result);
|
|
208
|
-
} catch (err) {
|
|
209
|
-
if (err instanceof HydrousError) return res.status(err.status).json({ error: err.message });
|
|
210
|
-
return res.status(500).json({ error: 'Internal Server Error' });
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
res.setHeader('Allow', ['GET']);
|
|
214
|
-
res.status(405).end();
|
|
215
|
-
}
|
|
198
|
+
### Get by ID
|
|
199
|
+
|
|
200
|
+
```ts
|
|
201
|
+
const { data: user } = await hydrous.records.get('users', 'user_abc123');
|
|
216
202
|
```
|
|
217
203
|
|
|
218
|
-
|
|
204
|
+
### Update
|
|
219
205
|
|
|
220
|
-
|
|
206
|
+
```ts
|
|
207
|
+
const { data } = await hydrous.records.update('users', 'user_abc123', {
|
|
208
|
+
name: 'Alice Smith',
|
|
209
|
+
});
|
|
210
|
+
```
|
|
221
211
|
|
|
222
|
-
|
|
212
|
+
### Delete
|
|
223
213
|
|
|
224
214
|
```ts
|
|
225
|
-
|
|
226
|
-
|
|
215
|
+
const { error } = await hydrous.records.delete('users', 'user_abc123');
|
|
216
|
+
```
|
|
227
217
|
|
|
228
|
-
|
|
229
|
-
const config = useRuntimeConfig();
|
|
218
|
+
### Query Helpers
|
|
230
219
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
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 (…)` |
|
|
235
227
|
|
|
236
|
-
|
|
237
|
-
}
|
|
228
|
+
```ts
|
|
229
|
+
import { eq, gt, inArray } from 'hydrous';
|
|
238
230
|
```
|
|
239
231
|
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## 5. Analytics
|
|
235
|
+
|
|
236
|
+
### Track an Event
|
|
237
|
+
|
|
240
238
|
```ts
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
},
|
|
239
|
+
await hydrous.analytics.track({
|
|
240
|
+
event: 'page_view',
|
|
241
|
+
properties: { page: '/home', referrer: 'google.com' },
|
|
242
|
+
userId: 'user_abc123',
|
|
243
|
+
sessionId: 'sess_xyz',
|
|
247
244
|
});
|
|
248
245
|
```
|
|
249
246
|
|
|
250
|
-
|
|
247
|
+
### Batch Track
|
|
251
248
|
|
|
252
|
-
|
|
253
|
-
<script setup lang="ts">
|
|
254
|
-
const { $db } = useNuxtApp();
|
|
249
|
+
More efficient than calling `track()` in a loop:
|
|
255
250
|
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
<template>
|
|
262
|
-
<div v-if="pending">Loading…</div>
|
|
263
|
-
<ul v-else>
|
|
264
|
-
<li v-for="r in records?.data" :key="r.id">{{ r.name }}</li>
|
|
265
|
-
</ul>
|
|
266
|
-
</template>
|
|
251
|
+
```ts
|
|
252
|
+
await hydrous.analytics.trackBatch([
|
|
253
|
+
{ event: 'signup', userId: 'u1' },
|
|
254
|
+
{ event: 'onboarded', userId: 'u1', properties: { step: 'profile' } },
|
|
255
|
+
]);
|
|
267
256
|
```
|
|
268
257
|
|
|
269
|
-
|
|
258
|
+
### Query Events
|
|
270
259
|
|
|
271
260
|
```ts
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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',
|
|
278
267
|
});
|
|
279
|
-
|
|
280
|
-
export function useHydrous() {
|
|
281
|
-
return db;
|
|
282
|
-
}
|
|
283
268
|
```
|
|
284
269
|
|
|
285
270
|
---
|
|
286
271
|
|
|
287
|
-
|
|
272
|
+
## 6. Storage
|
|
288
273
|
|
|
289
|
-
|
|
290
|
-
// src/hooks/useRecords.ts
|
|
291
|
-
import { useEffect, useState } from 'react';
|
|
292
|
-
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_`.
|
|
293
275
|
|
|
294
|
-
|
|
295
|
-
authKey: import.meta.env.VITE_HYDROUS_AUTH_KEY,
|
|
296
|
-
bucketKey: import.meta.env.VITE_HYDROUS_BUCKET_KEY,
|
|
297
|
-
});
|
|
276
|
+
### How Bucket Keys Work
|
|
298
277
|
|
|
299
|
-
|
|
300
|
-
const [records, setRecords] = useState<HydrousRecord[]>([]);
|
|
301
|
-
const [loading, setLoading] = useState(true);
|
|
302
|
-
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.
|
|
303
279
|
|
|
304
|
-
|
|
305
|
-
|
|
280
|
+
```
|
|
281
|
+
hydrous.storage.<method>(
|
|
282
|
+
'ssk_your_bucket_key', // ← first, always a string
|
|
283
|
+
...args
|
|
284
|
+
)
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
---
|
|
306
288
|
|
|
307
|
-
|
|
308
|
-
.then(r => setRecords(r.data))
|
|
309
|
-
.catch(e => { if (!(e instanceof DOMException)) setError(String(e)); })
|
|
310
|
-
.finally(() => setLoading(false));
|
|
289
|
+
### Upload a File
|
|
311
290
|
|
|
312
|
-
|
|
313
|
-
|
|
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
|
+
);
|
|
314
303
|
|
|
315
|
-
|
|
304
|
+
if (data) {
|
|
305
|
+
console.log('Stored at:', data.path);
|
|
306
|
+
console.log('Space saved:', data.spaceSaved, 'bytes');
|
|
316
307
|
}
|
|
317
308
|
```
|
|
318
309
|
|
|
319
310
|
---
|
|
320
311
|
|
|
321
|
-
###
|
|
312
|
+
### Upload Raw Text / JSON
|
|
322
313
|
|
|
323
|
-
|
|
324
|
-
// server.ts
|
|
325
|
-
import express from 'express';
|
|
326
|
-
import { createClient, HydrousError } from 'hydrousdb';
|
|
314
|
+
No `File` object needed — pass any string directly.
|
|
327
315
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
try {
|
|
337
|
-
const result = await db.records.query({ limit: 50 });
|
|
338
|
-
res.json(result);
|
|
339
|
-
} catch (err) {
|
|
340
|
-
if (err instanceof HydrousError) return res.status(err.status).json({ error: err.message });
|
|
341
|
-
res.status(500).json({ error: 'Internal Server Error' });
|
|
342
|
-
}
|
|
343
|
-
});
|
|
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
|
+
);
|
|
344
324
|
|
|
345
|
-
|
|
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
|
+
);
|
|
346
332
|
```
|
|
347
333
|
|
|
348
334
|
---
|
|
349
335
|
|
|
350
|
-
|
|
336
|
+
### Track Upload Progress
|
|
351
337
|
|
|
352
|
-
|
|
338
|
+
`onProgress` is called on every progress tick with a rich `UploadProgress` object.
|
|
353
339
|
|
|
354
340
|
```ts
|
|
355
|
-
|
|
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
|
+
);
|
|
356
375
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
});
|
|
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
|
+
}
|
|
364
382
|
```
|
|
365
383
|
|
|
366
|
-
|
|
384
|
+
**Stage lifecycle:**
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
pending → compressing → uploading → done
|
|
388
|
+
↘ error
|
|
389
|
+
```
|
|
367
390
|
|
|
368
|
-
|
|
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.
|
|
369
394
|
|
|
370
|
-
|
|
395
|
+
---
|
|
371
396
|
|
|
372
|
-
|
|
373
|
-
|---|---|
|
|
374
|
-
| `get(recordId, opts?)` | Fetch a single record |
|
|
375
|
-
| `getSnapshot(recordId, generation, opts?)` | Fetch a historical version |
|
|
376
|
-
| `query(opts?)` | Query a collection with filters / pagination |
|
|
377
|
-
| `queryAll(opts?)` | Fetch all pages automatically |
|
|
378
|
-
| `insert(payload, opts?)` | Create a new record |
|
|
379
|
-
| `update(payload, opts?)` | Update an existing record |
|
|
380
|
-
| `delete(recordId, opts?)` | Delete a record |
|
|
381
|
-
| `exists(recordId, opts?)` | HEAD check — returns metadata or `null` |
|
|
382
|
-
| `batchInsert(payload, opts?)` | Insert up to 500 records |
|
|
383
|
-
| `batchUpdate(payload, opts?)` | Update up to 500 records |
|
|
384
|
-
| `batchDelete(payload, opts?)` | Delete up to 500 records |
|
|
397
|
+
### Batch Upload
|
|
385
398
|
|
|
386
|
-
|
|
399
|
+
Upload many files in one request. Progress fires per-file so you can render
|
|
400
|
+
individual progress bars.
|
|
387
401
|
|
|
388
402
|
```ts
|
|
389
|
-
const
|
|
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);
|
|
390
421
|
```
|
|
391
422
|
|
|
392
|
-
|
|
423
|
+
Per-file paths override:
|
|
393
424
|
|
|
394
425
|
```ts
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
cursor: meta.nextCursor ?? undefined, // for pagination
|
|
401
|
-
fields: 'id,name,status', // select specific fields
|
|
426
|
+
await hydrous.storage.batchUpload('ssk_key', files, {
|
|
427
|
+
paths: [
|
|
428
|
+
'documents/report-q1.pdf',
|
|
429
|
+
'documents/report-q2.pdf',
|
|
430
|
+
],
|
|
402
431
|
});
|
|
403
432
|
```
|
|
404
433
|
|
|
405
|
-
|
|
406
|
-
|
|
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.
|
|
436
|
+
|
|
437
|
+
---
|
|
407
438
|
|
|
408
|
-
|
|
439
|
+
### Download a File
|
|
440
|
+
|
|
441
|
+
Returns the file as an `ArrayBuffer`.
|
|
409
442
|
|
|
410
443
|
```ts
|
|
411
|
-
const { data,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
444
|
+
const { data: buffer, error } = await hydrous.storage.download(
|
|
445
|
+
'ssk_my_bucket_key',
|
|
446
|
+
'avatars/alice.jpg'
|
|
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
|
+
}
|
|
417
458
|
```
|
|
418
459
|
|
|
419
|
-
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
### Batch Download
|
|
420
463
|
|
|
421
464
|
```ts
|
|
422
|
-
await
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
465
|
+
const { data: files } = await hydrous.storage.batchDownload(
|
|
466
|
+
'ssk_my_bucket_key',
|
|
467
|
+
['reports/jan.pdf', 'reports/feb.pdf', 'reports/mar.pdf'],
|
|
468
|
+
{
|
|
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
|
+
}
|
|
475
|
+
);
|
|
476
|
+
|
|
477
|
+
// files[n].content — ArrayBuffer
|
|
478
|
+
// files[n].mimeType — string
|
|
479
|
+
// files[n].path — original path
|
|
480
|
+
// files[n].size — bytes
|
|
428
481
|
```
|
|
429
482
|
|
|
430
|
-
|
|
483
|
+
---
|
|
484
|
+
|
|
485
|
+
### List Files & Folders
|
|
431
486
|
|
|
432
487
|
```ts
|
|
433
|
-
const
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
{ name: 'Bob', score: 82 },
|
|
437
|
-
],
|
|
438
|
-
queryableFields: ['name'],
|
|
439
|
-
userEmail: 'import@example.com',
|
|
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)
|
|
440
491
|
});
|
|
441
|
-
|
|
492
|
+
|
|
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
|
+
}
|
|
500
|
+
|
|
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
|
+
}
|
|
442
508
|
```
|
|
443
509
|
|
|
444
510
|
---
|
|
445
511
|
|
|
446
|
-
###
|
|
447
|
-
|
|
448
|
-
All methods live on `db.auth`.
|
|
449
|
-
|
|
450
|
-
| Method | Description |
|
|
451
|
-
|---|---|
|
|
452
|
-
| `signUp(payload, opts?)` | Register a new user |
|
|
453
|
-
| `signIn(payload, opts?)` | Authenticate with email + password |
|
|
454
|
-
| `signOut(payload, opts?)` | Revoke session(s) |
|
|
455
|
-
| `validateSession(sessionId, opts?)` | Verify a session is valid |
|
|
456
|
-
| `refreshSession(refreshToken, opts?)` | Rotate session using refresh token |
|
|
457
|
-
| `getUser(userId, opts?)` | Fetch user by ID |
|
|
458
|
-
| `listUsers(opts?)` | Paginated user list |
|
|
459
|
-
| `listAllUsers(opts?)` | All users (auto-paginates) |
|
|
460
|
-
| `updateUser(payload, opts?)` | Update user profile |
|
|
461
|
-
| `deleteUser(userId, opts?)` | Soft-delete a user |
|
|
462
|
-
| `changePassword(payload, opts?)` | Change password (requires old password) |
|
|
463
|
-
| `requestPasswordReset(payload, opts?)` | Trigger reset email |
|
|
464
|
-
| `confirmPasswordReset(payload, opts?)` | Confirm reset with token |
|
|
465
|
-
| `requestEmailVerification(payload, opts?)` | Trigger verification email |
|
|
466
|
-
| `confirmEmailVerification(payload, opts?)` | Confirm email with token |
|
|
467
|
-
| `lockAccount(payload, opts?)` | Lock account for a duration |
|
|
468
|
-
| `unlockAccount(userId, opts?)` | Unlock account |
|
|
469
|
-
|
|
470
|
-
#### Auth flow example
|
|
471
|
-
|
|
472
|
-
```ts
|
|
473
|
-
// 1. Sign up
|
|
474
|
-
const { data: user, session } = await db.auth.signUp({
|
|
475
|
-
email: 'alice@example.com',
|
|
476
|
-
password: 'Str0ng@Pass!',
|
|
477
|
-
fullName: 'Alice Smith',
|
|
478
|
-
});
|
|
512
|
+
### File Metadata
|
|
479
513
|
|
|
480
|
-
|
|
481
|
-
const {
|
|
514
|
+
```ts
|
|
515
|
+
const { data: meta } = await hydrous.storage.metadata(
|
|
516
|
+
'ssk_my_bucket_key',
|
|
517
|
+
'avatars/alice.jpg'
|
|
518
|
+
);
|
|
519
|
+
|
|
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
|
+
```
|
|
482
526
|
|
|
483
|
-
|
|
484
|
-
const { data: currentUser } = await db.auth.validateSession(sessionId);
|
|
527
|
+
---
|
|
485
528
|
|
|
486
|
-
|
|
487
|
-
const { session: newSession } = await db.auth.refreshSession(refreshToken);
|
|
529
|
+
### Delete a File
|
|
488
530
|
|
|
489
|
-
|
|
490
|
-
await
|
|
531
|
+
```ts
|
|
532
|
+
const { error } = await hydrous.storage.deleteFile(
|
|
533
|
+
'ssk_my_bucket_key',
|
|
534
|
+
'avatars/old-photo.jpg'
|
|
535
|
+
);
|
|
491
536
|
```
|
|
492
537
|
|
|
493
538
|
---
|
|
494
539
|
|
|
495
|
-
###
|
|
540
|
+
### Delete a Folder
|
|
496
541
|
|
|
497
|
-
|
|
542
|
+
Recursively deletes the folder and everything inside it.
|
|
498
543
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
| `distribution(field, opts?)` | `"distribution"` | Value histogram for a field |
|
|
503
|
-
| `sum(field, opts?)` | `"sum"` | Sum a numeric field, with optional group-by |
|
|
504
|
-
| `timeSeries(opts?)` | `"timeSeries"` | Record count over time |
|
|
505
|
-
| `fieldTimeSeries(field, opts?)` | `"fieldTimeSeries"` | Numeric field aggregated over time |
|
|
506
|
-
| `topN(field, n, opts?)` | `"topN"` | Top N field values by frequency |
|
|
507
|
-
| `stats(field, opts?)` | `"stats"` | min/max/avg/stddev/p50/p90/p99 |
|
|
508
|
-
| `records(opts?)` | `"records"` | Filtered + paginated raw records |
|
|
509
|
-
| `multiMetric(metrics, opts?)` | `"multiMetric"` | Multiple aggregations in one call |
|
|
510
|
-
| `storageStats(opts?)` | `"storageStats"` | Bucket storage usage |
|
|
511
|
-
| `crossBucket(opts)` | `"crossBucket"` | Compare metric across buckets |
|
|
512
|
-
| `query(payload, opts?)` | any | Raw query — full control |
|
|
544
|
+
```ts
|
|
545
|
+
await hydrous.storage.deleteFolder('ssk_my_bucket_key', 'temp/');
|
|
546
|
+
```
|
|
513
547
|
|
|
514
|
-
|
|
548
|
+
---
|
|
515
549
|
|
|
516
|
-
|
|
550
|
+
### Create a Folder
|
|
517
551
|
|
|
518
552
|
```ts
|
|
519
|
-
|
|
520
|
-
const dateRange = { startDate: '2025-01-01', endDate: '2025-12-31' };
|
|
521
|
-
|
|
522
|
-
// Or scope to a year / month / day
|
|
523
|
-
const dateRange = { year: '2025', month: '03' };
|
|
553
|
+
await hydrous.storage.createFolder('ssk_my_bucket_key', 'avatars/2024/');
|
|
524
554
|
```
|
|
525
555
|
|
|
526
|
-
|
|
556
|
+
---
|
|
557
|
+
|
|
558
|
+
### Move a File
|
|
527
559
|
|
|
528
560
|
```ts
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
561
|
+
await hydrous.storage.move(
|
|
562
|
+
'ssk_my_bucket_key',
|
|
563
|
+
'drafts/report.pdf',
|
|
564
|
+
'published/report.pdf'
|
|
565
|
+
);
|
|
566
|
+
```
|
|
532
567
|
|
|
533
|
-
|
|
534
|
-
const { data } = await db.analytics.distribution('status');
|
|
535
|
-
// [{ value: 'active', count: 80 }, { value: 'archived', count: 20 }]
|
|
568
|
+
---
|
|
536
569
|
|
|
537
|
-
|
|
538
|
-
const { data } = await db.analytics.sum('amount', {
|
|
539
|
-
groupBy: 'region',
|
|
540
|
-
dateRange: { startDate: '2025-01-01', endDate: '2025-03-31' },
|
|
541
|
-
});
|
|
570
|
+
### Copy a File
|
|
542
571
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
572
|
+
```ts
|
|
573
|
+
await hydrous.storage.copy(
|
|
574
|
+
'ssk_my_bucket_key',
|
|
575
|
+
'templates/invoice.pdf',
|
|
576
|
+
'invoices/invoice-001.pdf'
|
|
577
|
+
);
|
|
578
|
+
```
|
|
548
579
|
|
|
549
|
-
|
|
550
|
-
const { data } = await db.analytics.stats('score');
|
|
551
|
-
console.log(data.avg, data.p90, data.p99);
|
|
580
|
+
---
|
|
552
581
|
|
|
553
|
-
|
|
554
|
-
const { data } = await db.analytics.topN('country', 5);
|
|
582
|
+
### Signed URLs
|
|
555
583
|
|
|
556
|
-
|
|
557
|
-
const { data } = await db.analytics.multiMetric([
|
|
558
|
-
{ name: 'totalRevenue', field: 'amount', aggregation: 'sum' },
|
|
559
|
-
{ name: 'avgScore', field: 'score', aggregation: 'avg' },
|
|
560
|
-
{ name: 'userCount', field: 'userId', aggregation: 'count' },
|
|
561
|
-
]);
|
|
562
|
-
console.log(data.totalRevenue, data.avgScore);
|
|
584
|
+
Generate a time-limited public URL for a private file.
|
|
563
585
|
|
|
564
|
-
|
|
565
|
-
const { data } = await
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
//
|
|
569
|
-
|
|
570
|
-
bucketKeys: ['sales-2024', 'sales-2025'],
|
|
571
|
-
field: 'amount',
|
|
572
|
-
aggregation: 'sum',
|
|
573
|
-
});
|
|
586
|
+
```ts
|
|
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
|
+
);
|
|
574
592
|
|
|
575
|
-
//
|
|
576
|
-
const { data } = await db.analytics.records({
|
|
577
|
-
filters: [{ field: 'role', op: '==', value: 'admin' }],
|
|
578
|
-
selectFields: ['email', 'createdAt'],
|
|
579
|
-
limit: 50,
|
|
580
|
-
offset: 0,
|
|
581
|
-
});
|
|
582
|
-
console.log(data.data, data.hasMore);
|
|
593
|
+
console.log(data.signedUrl); // share this — expires at data.expiresAt
|
|
583
594
|
```
|
|
584
595
|
|
|
585
596
|
---
|
|
586
597
|
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
The SDK throws typed errors you can `instanceof` check:
|
|
598
|
+
### Bucket Stats
|
|
590
599
|
|
|
591
600
|
```ts
|
|
592
|
-
|
|
601
|
+
const { data: stats } = await hydrous.storage.stats('ssk_my_bucket_key');
|
|
593
602
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
console.error(err.status); // HTTP status code (e.g. 404)
|
|
600
|
-
console.error(err.code); // machine-readable code (e.g. 'NOT_FOUND')
|
|
601
|
-
console.error(err.details); // validation error details array
|
|
602
|
-
console.error(err.requestId); // server request ID for support
|
|
603
|
-
} else if (err instanceof HydrousTimeoutError) {
|
|
604
|
-
console.error('Request timed out');
|
|
605
|
-
} else if (err instanceof HydrousNetworkError) {
|
|
606
|
-
console.error('Network failure:', err.cause);
|
|
607
|
-
}
|
|
608
|
-
}
|
|
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);
|
|
609
608
|
```
|
|
610
609
|
|
|
611
610
|
---
|
|
612
611
|
|
|
613
|
-
##
|
|
612
|
+
## 7. Error Handling
|
|
614
613
|
|
|
615
|
-
|
|
614
|
+
Every method returns `{ data, error }`. `error` is `null` on success.
|
|
616
615
|
|
|
617
616
|
```ts
|
|
618
|
-
|
|
619
|
-
let cursor: string | null = null;
|
|
617
|
+
const { data, error } = await hydrous.storage.upload('ssk_key', file);
|
|
620
618
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
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
|
+
}
|
|
624
|
+
```
|
|
626
625
|
|
|
627
|
-
|
|
628
|
-
cursor = meta.nextCursor;
|
|
629
|
-
} while (cursor);
|
|
626
|
+
### Catching thrown errors
|
|
630
627
|
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
628
|
+
If you prefer `try/catch`, import `isHydrousError`:
|
|
629
|
+
|
|
630
|
+
```ts
|
|
631
|
+
import { isHydrousError } from 'hydrous';
|
|
632
|
+
|
|
633
|
+
try {
|
|
634
|
+
// ...
|
|
635
|
+
} catch (err) {
|
|
636
|
+
if (isHydrousError(err)) {
|
|
637
|
+
console.error(err.code, err.message);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
635
640
|
```
|
|
636
641
|
|
|
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 |
|
|
656
|
+
|
|
637
657
|
---
|
|
638
658
|
|
|
639
|
-
## TypeScript
|
|
659
|
+
## 8. TypeScript Types Reference
|
|
640
660
|
|
|
641
|
-
|
|
661
|
+
### `UploadProgress`
|
|
642
662
|
|
|
643
663
|
```ts
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
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
|
+
}
|
|
678
|
+
|
|
679
|
+
type UploadStage = 'pending' | 'compressing' | 'uploading' | 'done' | 'error';
|
|
653
680
|
```
|
|
654
681
|
|
|
655
|
-
|
|
682
|
+
### `UploadResult`
|
|
656
683
|
|
|
657
684
|
```ts
|
|
658
|
-
interface
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
685
|
+
interface UploadResult {
|
|
686
|
+
path: string;
|
|
687
|
+
compressed: boolean;
|
|
688
|
+
originalSize: number;
|
|
689
|
+
storedSize: number;
|
|
690
|
+
spaceSaved: number;
|
|
691
|
+
mimeType: string;
|
|
662
692
|
}
|
|
693
|
+
```
|
|
663
694
|
|
|
664
|
-
|
|
665
|
-
filters: [{ field: 'category', op: '==', value: 'shoes' }],
|
|
666
|
-
});
|
|
695
|
+
### `StorageItem` (from `list()`)
|
|
667
696
|
|
|
668
|
-
|
|
669
|
-
|
|
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
|
+
}
|
|
670
707
|
```
|
|
671
708
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
## Contributing
|
|
709
|
+
### `StorageStats`
|
|
675
710
|
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
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
|
+
```
|
|
681
725
|
|
|
682
726
|
---
|
|
683
727
|
|
|
684
728
|
## License
|
|
685
729
|
|
|
686
|
-
MIT ©
|
|
730
|
+
MIT © Hydrous
|