hydrousdb 2.0.1 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +911 -515
- package/dist/index.cjs +1647 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1540 -0
- package/dist/index.d.ts +1339 -510
- package/dist/index.js +1427 -1100
- package/dist/index.js.map +1 -1
- package/package.json +31 -14
- package/dist/index.d.mts +0 -711
- package/dist/index.mjs +0 -1291
- package/dist/index.mjs.map +0 -1
package/README.md
CHANGED
|
@@ -1,730 +1,1126 @@
|
|
|
1
|
-
#
|
|
1
|
+
# HydrousDB JavaScript / TypeScript SDK
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>The official SDK for <a href="https://hydrousdb.com">HydrousDB</a> — records, auth, file storage, and analytics in one package.</strong>
|
|
5
|
+
</p>
|
|
5
6
|
|
|
6
|
-
|
|
7
|
-
npm
|
|
8
|
-
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://www.npmjs.com/package/hydrousdb"><img src="https://img.shields.io/npm/v/hydrousdb.svg" alt="npm version"></a>
|
|
9
|
+
<a href="https://www.npmjs.com/package/hydrousdb"><img src="https://img.shields.io/npm/dm/hydrousdb.svg" alt="npm downloads"></a>
|
|
10
|
+
<a href="https://github.com/hydrousdb/hydrousdb-js/blob/main/LICENSE"><img src="https://img.shields.io/npm/l/hydrousdb.svg" alt="MIT License"></a>
|
|
11
|
+
<a href="https://hydrousdb.com"><img src="https://img.shields.io/badge/docs-hydrousdb.com-blue" alt="Documentation"></a>
|
|
12
|
+
</p>
|
|
9
13
|
|
|
10
14
|
---
|
|
11
15
|
|
|
12
16
|
## Table of Contents
|
|
13
17
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
18
|
+
- [What is HydrousDB?](#what-is-hydrousdb)
|
|
19
|
+
- [Quick Start (5 minutes)](#quick-start-5-minutes)
|
|
20
|
+
- [Step 1 — Create your account](#step-1--create-your-account)
|
|
21
|
+
- [Step 2 — Create your first bucket](#step-2--create-your-first-bucket)
|
|
22
|
+
- [Step 3 — Grab your Security Key](#step-3--grab-your-security-key)
|
|
23
|
+
- [Step 4 — Install the SDK](#step-4--install-the-sdk)
|
|
24
|
+
- [Step 5 — Your first record](#step-5--your-first-record)
|
|
25
|
+
- [Records](#records)
|
|
26
|
+
- [Create](#create-a-record)
|
|
27
|
+
- [Read](#read-a-record)
|
|
28
|
+
- [Update](#update-a-record)
|
|
29
|
+
- [Delete](#delete-a-record)
|
|
30
|
+
- [Query](#query-records)
|
|
31
|
+
- [Batch Operations](#batch-operations)
|
|
32
|
+
- [Version History](#version-history)
|
|
33
|
+
- [Authentication](#authentication)
|
|
34
|
+
- [Sign Up](#sign-up-users)
|
|
35
|
+
- [Log In / Log Out](#log-in--log-out)
|
|
36
|
+
- [Session Management](#session-management)
|
|
37
|
+
- [Password Reset](#password-reset-flow)
|
|
38
|
+
- [Email Verification](#email-verification)
|
|
39
|
+
- [Admin Operations](#admin-operations)
|
|
40
|
+
- [File Storage](#file-storage)
|
|
41
|
+
- [Simple Upload](#simple-upload)
|
|
42
|
+
- [Large File Upload (with progress)](#large-file-upload-with-progress)
|
|
43
|
+
- [Download](#download-files)
|
|
44
|
+
- [List Files](#list-files)
|
|
45
|
+
- [Scoped Storage](#scoped-storage)
|
|
46
|
+
- [Share & Visibility](#share--visibility)
|
|
47
|
+
- [File Operations](#file-operations)
|
|
48
|
+
- [Analytics](#analytics)
|
|
49
|
+
- [Count](#count)
|
|
50
|
+
- [Distribution](#distribution)
|
|
51
|
+
- [Time Series](#time-series)
|
|
52
|
+
- [Top N](#top-n)
|
|
53
|
+
- [Field Stats](#field-stats)
|
|
54
|
+
- [Multi-Metric Dashboard](#multi-metric-dashboard)
|
|
55
|
+
- [Cross-Bucket Comparison](#cross-bucket-comparison)
|
|
56
|
+
- [TypeScript Support](#typescript-support)
|
|
57
|
+
- [Error Handling](#error-handling)
|
|
58
|
+
- [Security Best Practices](#security-best-practices)
|
|
59
|
+
- [API Reference](#api-reference)
|
|
60
|
+
- [Contributing](#contributing)
|
|
61
|
+
- [License](#license)
|
|
51
62
|
|
|
52
63
|
---
|
|
53
64
|
|
|
54
|
-
##
|
|
65
|
+
## What is HydrousDB?
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
import { createClient } from 'hydrous';
|
|
67
|
+
HydrousDB is a **backend-as-a-service** platform that gives your app a fully managed backend in minutes. Instead of spinning up servers, databases, and storage buckets yourself, you call an API.
|
|
58
68
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
69
|
+
| Feature | What it does |
|
|
70
|
+
|---|---|
|
|
71
|
+
| **Records** | Schemaless JSON document store. Create, read, update, delete and query records in named *buckets*. |
|
|
72
|
+
| **Auth** | Full user authentication — signup, login, sessions, password reset, email verification, and admin controls. |
|
|
73
|
+
| **Storage** | File uploads and downloads backed by Google Cloud Storage. Public and private files, signed share URLs. |
|
|
74
|
+
| **Analytics** | BigQuery-powered aggregations — counts, distributions, time series, top-N, multi-metric dashboards, and cross-bucket comparisons. |
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
{
|
|
69
|
-
path: 'avatars/alice.jpg',
|
|
70
|
-
onProgress: (p) => console.log(`${p.percent}%`),
|
|
71
|
-
}
|
|
72
|
-
);
|
|
73
|
-
```
|
|
76
|
+
Everything is organised around two concepts:
|
|
77
|
+
|
|
78
|
+
- **Security Key (`sk_...`)** — your master credential. Authenticate all API calls. Keep it secret.
|
|
79
|
+
- **Bucket Key** — just the name of your bucket (e.g. `"blog-posts"`, `"app-users"`). Not a secret.
|
|
74
80
|
|
|
75
81
|
---
|
|
76
82
|
|
|
77
|
-
##
|
|
83
|
+
## Quick Start (5 minutes)
|
|
78
84
|
|
|
79
|
-
|
|
80
|
-
import { HydrousClient } from 'hydrous';
|
|
85
|
+
### Step 1 — Create your account
|
|
81
86
|
|
|
82
|
-
|
|
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
|
-
});
|
|
87
|
-
```
|
|
87
|
+
Go to **[https://hydrousdb.com](https://hydrousdb.com)** and sign up for a free account.
|
|
88
88
|
|
|
89
|
-
|
|
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) |
|
|
89
|
+
### Step 2 — Create your first bucket
|
|
94
90
|
|
|
95
|
-
|
|
91
|
+
1. Log in to your dashboard at **[https://hydrousdb.com/dashboard](https://hydrousdb.com/dashboard)**.
|
|
92
|
+
2. Click **"New Bucket"**.
|
|
93
|
+
3. Give it a name — use lowercase letters, numbers, hyphens, or underscores (e.g. `my-first-bucket`).
|
|
94
|
+
4. Click **"Create"**.
|
|
96
95
|
|
|
97
|
-
|
|
96
|
+
> 💡 **What is a bucket?** A bucket is a named collection of JSON records — similar to a table in SQL or a collection in MongoDB.
|
|
98
97
|
|
|
99
|
-
###
|
|
98
|
+
### Step 3 — Grab your Security Key
|
|
100
99
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
password: 'supersecret',
|
|
105
|
-
metadata: { plan: 'pro' }, // optional
|
|
106
|
-
});
|
|
100
|
+
1. In the dashboard, go to **Settings → API Keys**.
|
|
101
|
+
2. Click **"Generate Security Key"**.
|
|
102
|
+
3. Copy the key — it looks like `sk_live_xxxxxxxxxxxxxxxxxxxx`.
|
|
107
103
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
> ⚠️ **Your Security Key is your most important credential.** Treat it like a password. Never commit it to Git. Use environment variables.
|
|
105
|
+
|
|
106
|
+
### Step 4 — Install the SDK
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
npm install hydrousdb
|
|
110
|
+
# or
|
|
111
|
+
yarn add hydrousdb
|
|
112
|
+
# or
|
|
113
|
+
pnpm add hydrousdb
|
|
112
114
|
```
|
|
113
115
|
|
|
114
|
-
|
|
116
|
+
**Requirements:** Node.js 18+ (uses the native `fetch` API).
|
|
115
117
|
|
|
116
|
-
|
|
117
|
-
const { data, error } = await hydrous.auth.signIn({
|
|
118
|
-
email: 'user@example.com',
|
|
119
|
-
password: 'supersecret',
|
|
120
|
-
});
|
|
118
|
+
### Step 5 — Your first record
|
|
121
119
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
}
|
|
125
|
-
```
|
|
120
|
+
```typescript
|
|
121
|
+
import { createClient } from 'hydrousdb';
|
|
126
122
|
|
|
127
|
-
|
|
123
|
+
// Create the client once — reuse it everywhere
|
|
124
|
+
const db = createClient({
|
|
125
|
+
securityKey: process.env.HYDROUS_SECURITY_KEY!, // from Step 3
|
|
126
|
+
});
|
|
128
127
|
|
|
129
|
-
|
|
130
|
-
await
|
|
131
|
-
|
|
128
|
+
// Write a record to your bucket
|
|
129
|
+
const post = await db.records('my-first-bucket').create({
|
|
130
|
+
title: 'Hello, HydrousDB!',
|
|
131
|
+
body: 'My first record.',
|
|
132
|
+
published: false,
|
|
133
|
+
});
|
|
132
134
|
|
|
133
|
-
|
|
135
|
+
console.log(post.id); // "rec_a1b2c3d4"
|
|
136
|
+
console.log(post.createdAt); // 1717200000000
|
|
134
137
|
|
|
135
|
-
|
|
136
|
-
const
|
|
137
|
-
console.log(
|
|
138
|
-
```
|
|
138
|
+
// Read it back
|
|
139
|
+
const fetched = await db.records('my-first-bucket').get(post.id);
|
|
140
|
+
console.log(fetched.title); // "Hello, HydrousDB!"
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
// Update it
|
|
143
|
+
const updated = await db.records('my-first-bucket').patch(post.id, { published: true });
|
|
141
144
|
|
|
142
|
-
|
|
143
|
-
|
|
145
|
+
// Delete it
|
|
146
|
+
await db.records('my-first-bucket').delete(post.id);
|
|
144
147
|
```
|
|
145
148
|
|
|
149
|
+
🎉 **That's it!** You're live with zero configuration beyond your Security Key.
|
|
150
|
+
|
|
146
151
|
---
|
|
147
152
|
|
|
148
|
-
##
|
|
153
|
+
## Records
|
|
154
|
+
|
|
155
|
+
Records are JSON objects stored in named buckets. Every record automatically gets:
|
|
156
|
+
- `id` — unique record identifier (e.g. `"rec_a1b2c3d4"`)
|
|
157
|
+
- `createdAt` — Unix timestamp in milliseconds
|
|
158
|
+
- `updatedAt` — Unix timestamp in milliseconds (updated on every write)
|
|
149
159
|
|
|
150
|
-
###
|
|
160
|
+
### Create a Record
|
|
151
161
|
|
|
152
|
-
|
|
162
|
+
```typescript
|
|
163
|
+
const products = db.records('products');
|
|
153
164
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
165
|
+
const product = await products.create({
|
|
166
|
+
name: 'Wireless Headphones',
|
|
167
|
+
price: 79.99,
|
|
168
|
+
inStock: true,
|
|
169
|
+
tags: ['audio', 'wireless'],
|
|
159
170
|
});
|
|
171
|
+
|
|
172
|
+
// product.id, product.createdAt, product.updatedAt are added automatically
|
|
160
173
|
```
|
|
161
174
|
|
|
162
|
-
|
|
175
|
+
### Read a Record
|
|
163
176
|
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
console.log(`Inserted ${data.length} products`);
|
|
177
|
+
```typescript
|
|
178
|
+
// Get by ID
|
|
179
|
+
const product = await products.get('rec_abc123');
|
|
180
|
+
|
|
181
|
+
// product is null-safe: throws HydrousError with code RECORD_NOT_FOUND if missing
|
|
170
182
|
```
|
|
171
183
|
|
|
172
|
-
###
|
|
184
|
+
### Update a Record
|
|
173
185
|
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
186
|
+
```typescript
|
|
187
|
+
// Patch (merge) — only the listed fields are changed
|
|
188
|
+
const updated = await products.patch('rec_abc123', {
|
|
189
|
+
price: 69.99,
|
|
190
|
+
inStock: false,
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
// Set (full replace) — the entire record is replaced
|
|
194
|
+
const replaced = await products.set('rec_abc123', {
|
|
195
|
+
name: 'Wireless Headphones v2',
|
|
196
|
+
price: 89.99,
|
|
197
|
+
inStock: true,
|
|
198
|
+
tags: ['audio', 'wireless', 'premium'],
|
|
181
199
|
});
|
|
182
200
|
```
|
|
183
201
|
|
|
184
|
-
|
|
202
|
+
### Delete a Record
|
|
203
|
+
|
|
204
|
+
```typescript
|
|
205
|
+
await products.delete('rec_abc123');
|
|
206
|
+
```
|
|
185
207
|
|
|
186
|
-
|
|
187
|
-
import { eq, gt, inArray } from 'hydrous';
|
|
208
|
+
### Query Records
|
|
188
209
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
210
|
+
```typescript
|
|
211
|
+
// Get all records (up to 100 by default)
|
|
212
|
+
const { records } = await products.query();
|
|
213
|
+
|
|
214
|
+
// With filters
|
|
215
|
+
const { records: affordableStock } = await products.query({
|
|
216
|
+
filters: [
|
|
217
|
+
{ field: 'inStock', op: '==', value: true },
|
|
218
|
+
{ field: 'price', op: '<', value: 100 },
|
|
194
219
|
],
|
|
195
220
|
});
|
|
196
|
-
```
|
|
197
221
|
|
|
198
|
-
|
|
222
|
+
// Sort and paginate
|
|
223
|
+
const { records, hasMore, nextCursor } = await products.query({
|
|
224
|
+
orderBy: 'price',
|
|
225
|
+
order: 'asc',
|
|
226
|
+
limit: 20,
|
|
227
|
+
});
|
|
199
228
|
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
229
|
+
// Next page
|
|
230
|
+
if (hasMore) {
|
|
231
|
+
const page2 = await products.query({
|
|
232
|
+
orderBy: 'price',
|
|
233
|
+
order: 'asc',
|
|
234
|
+
limit: 20,
|
|
235
|
+
startAfter: nextCursor,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
203
238
|
|
|
204
|
-
|
|
239
|
+
// Select only specific fields
|
|
240
|
+
const { records: lightRecords } = await products.query({
|
|
241
|
+
fields: 'name,price,inStock',
|
|
242
|
+
});
|
|
205
243
|
|
|
206
|
-
|
|
207
|
-
const {
|
|
208
|
-
|
|
244
|
+
// Filter by date range
|
|
245
|
+
const { records: recent } = await products.query({
|
|
246
|
+
dateRange: {
|
|
247
|
+
start: Date.now() - 7 * 24 * 60 * 60 * 1000, // 7 days ago
|
|
248
|
+
end: Date.now(),
|
|
249
|
+
},
|
|
209
250
|
});
|
|
210
251
|
```
|
|
211
252
|
|
|
212
|
-
|
|
253
|
+
**Available filter operators:**
|
|
254
|
+
|
|
255
|
+
| Operator | Meaning |
|
|
256
|
+
|---|---|
|
|
257
|
+
| `==` | Equal |
|
|
258
|
+
| `!=` | Not equal |
|
|
259
|
+
| `>` | Greater than |
|
|
260
|
+
| `<` | Less than |
|
|
261
|
+
| `>=` | Greater than or equal |
|
|
262
|
+
| `<=` | Less than or equal |
|
|
263
|
+
| `CONTAINS` | String contains (case-sensitive) |
|
|
264
|
+
|
|
265
|
+
### Batch Operations
|
|
266
|
+
|
|
267
|
+
```typescript
|
|
268
|
+
// Create multiple records at once
|
|
269
|
+
const created = await products.batchCreate([
|
|
270
|
+
{ name: 'Item A', price: 10.00, inStock: true },
|
|
271
|
+
{ name: 'Item B', price: 20.00, inStock: false },
|
|
272
|
+
{ name: 'Item C', price: 30.00, inStock: true },
|
|
273
|
+
]);
|
|
274
|
+
// → [{ id: 'rec_1', ... }, { id: 'rec_2', ... }, { id: 'rec_3', ... }]
|
|
275
|
+
|
|
276
|
+
// Count records
|
|
277
|
+
const total = await products.count();
|
|
278
|
+
const inStock = await products.count([{ field: 'inStock', op: '==', value: true }]);
|
|
213
279
|
|
|
214
|
-
|
|
215
|
-
const {
|
|
280
|
+
// Delete multiple records
|
|
281
|
+
const { deleted, failed } = await products.batchDelete(['rec_1', 'rec_2', 'rec_3']);
|
|
216
282
|
```
|
|
217
283
|
|
|
218
|
-
###
|
|
284
|
+
### Version History
|
|
285
|
+
|
|
286
|
+
Every write to a record creates a new version stored in GCS, so you can travel back in time.
|
|
219
287
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
| `gt(field, val)` | `field > val` |
|
|
225
|
-
| `lt(field, val)` | `field < val` |
|
|
226
|
-
| `inArray(field, [])` | `field IN (…)` |
|
|
288
|
+
```typescript
|
|
289
|
+
// Get the full version history of a record
|
|
290
|
+
const history = await products.getHistory('rec_abc123');
|
|
291
|
+
// history[0] is the latest version, history[1] is one write before, etc.
|
|
227
292
|
|
|
228
|
-
|
|
229
|
-
|
|
293
|
+
// Restore to a specific version
|
|
294
|
+
const restored = await products.restoreVersion('rec_abc123', history[2]!.version);
|
|
230
295
|
```
|
|
231
296
|
|
|
232
297
|
---
|
|
233
298
|
|
|
234
|
-
##
|
|
299
|
+
## Authentication
|
|
300
|
+
|
|
301
|
+
HydrousDB has a built-in user auth system. Your users live in a bucket you create
|
|
302
|
+
(e.g. `"app-users"`). You get sessions, refresh tokens, password reset, email
|
|
303
|
+
verification, and admin controls out of the box.
|
|
235
304
|
|
|
236
|
-
|
|
305
|
+
```typescript
|
|
306
|
+
const auth = db.auth('app-users');
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Sign Up Users
|
|
237
310
|
|
|
238
|
-
```
|
|
239
|
-
await
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
311
|
+
```typescript
|
|
312
|
+
const { user, session } = await auth.signup({
|
|
313
|
+
email: 'alice@example.com',
|
|
314
|
+
password: 'hunter2', // min 8 characters, validated server-side
|
|
315
|
+
fullName: 'Alice Wonderland',
|
|
316
|
+
// Any extra fields are stored on the user record:
|
|
317
|
+
plan: 'pro',
|
|
318
|
+
referral: 'friend123',
|
|
244
319
|
});
|
|
320
|
+
|
|
321
|
+
// user.id → "usr_xxxxxxxxxx"
|
|
322
|
+
// session.sessionId → persist this in your app
|
|
323
|
+
// session.refreshToken → persist this for long-lived sessions
|
|
245
324
|
```
|
|
246
325
|
|
|
247
|
-
###
|
|
326
|
+
### Log In / Log Out
|
|
248
327
|
|
|
249
|
-
|
|
328
|
+
```typescript
|
|
329
|
+
// Log in
|
|
330
|
+
const { user, session } = await auth.login({
|
|
331
|
+
email: 'alice@example.com',
|
|
332
|
+
password: 'hunter2',
|
|
333
|
+
});
|
|
250
334
|
|
|
251
|
-
|
|
252
|
-
await
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
335
|
+
// Log out (invalidates the session server-side)
|
|
336
|
+
await auth.logout({ sessionId: session.sessionId });
|
|
337
|
+
```
|
|
338
|
+
|
|
339
|
+
### Session Management
|
|
340
|
+
|
|
341
|
+
Sessions expire after **24 hours**. Use the refresh token to get a new session (refresh tokens last **30 days**).
|
|
342
|
+
|
|
343
|
+
```typescript
|
|
344
|
+
// Refresh the session before it expires
|
|
345
|
+
const newSession = await auth.refreshSession({
|
|
346
|
+
refreshToken: session.refreshToken,
|
|
347
|
+
});
|
|
348
|
+
// Store newSession.sessionId and newSession.refreshToken
|
|
349
|
+
|
|
350
|
+
// Get the current user
|
|
351
|
+
const user = await auth.getUser({ userId: session.userId });
|
|
256
352
|
```
|
|
257
353
|
|
|
258
|
-
###
|
|
354
|
+
### Update User Profile
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
const updated = await auth.updateUser({
|
|
358
|
+
sessionId: session.sessionId,
|
|
359
|
+
userId: user.id,
|
|
360
|
+
data: {
|
|
361
|
+
fullName: 'Alice Smith',
|
|
362
|
+
plan: 'enterprise',
|
|
363
|
+
avatar: 'https://example.com/avatar.jpg',
|
|
364
|
+
},
|
|
365
|
+
});
|
|
366
|
+
```
|
|
259
367
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
368
|
+
### Password Reset Flow
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// 1. User requests a reset (always returns success — prevents email enumeration)
|
|
372
|
+
await auth.requestPasswordReset({ email: 'alice@example.com' });
|
|
373
|
+
|
|
374
|
+
// 2. User receives an email with a reset token (your app handles the email sending)
|
|
375
|
+
|
|
376
|
+
// 3. User submits the new password
|
|
377
|
+
await auth.confirmPasswordReset({
|
|
378
|
+
resetToken: 'tok_from_email',
|
|
379
|
+
newPassword: 'correcthorsebatterystaple',
|
|
267
380
|
});
|
|
381
|
+
// All existing sessions for this user are automatically revoked
|
|
268
382
|
```
|
|
269
383
|
|
|
270
|
-
|
|
384
|
+
### Change Password (authenticated)
|
|
271
385
|
|
|
272
|
-
|
|
386
|
+
```typescript
|
|
387
|
+
await auth.changePassword({
|
|
388
|
+
sessionId: session.sessionId,
|
|
389
|
+
userId: user.id,
|
|
390
|
+
currentPassword: 'hunter2',
|
|
391
|
+
newPassword: 'correcthorsebatterystaple',
|
|
392
|
+
});
|
|
393
|
+
```
|
|
273
394
|
|
|
274
|
-
|
|
395
|
+
### Email Verification
|
|
275
396
|
|
|
276
|
-
|
|
397
|
+
```typescript
|
|
398
|
+
// 1. Send verification email
|
|
399
|
+
await auth.requestEmailVerification({ userId: user.id });
|
|
277
400
|
|
|
278
|
-
|
|
401
|
+
// 2. User clicks link in email, your app extracts the token
|
|
279
402
|
|
|
403
|
+
// 3. Confirm the token
|
|
404
|
+
await auth.confirmEmailVerification({ verifyToken: 'tok_from_email' });
|
|
280
405
|
```
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
406
|
+
|
|
407
|
+
### Admin Operations
|
|
408
|
+
|
|
409
|
+
Admin operations require a valid session from a user with `role: 'admin'`.
|
|
410
|
+
|
|
411
|
+
```typescript
|
|
412
|
+
// List all users
|
|
413
|
+
const { users, total } = await auth.listUsers({
|
|
414
|
+
sessionId: adminSession.sessionId,
|
|
415
|
+
limit: 50,
|
|
416
|
+
offset: 0,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Lock an account (prevents login)
|
|
420
|
+
await auth.lockAccount({
|
|
421
|
+
sessionId: adminSession.sessionId,
|
|
422
|
+
userId: 'usr_abc123',
|
|
423
|
+
duration: 60 * 60 * 1000, // lock for 1 hour (default: 15 minutes)
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
// Unlock an account
|
|
427
|
+
await auth.unlockAccount({
|
|
428
|
+
sessionId: adminSession.sessionId,
|
|
429
|
+
userId: 'usr_abc123',
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Soft-delete a user (marks as deleted, keeps data)
|
|
433
|
+
await auth.deleteUser({
|
|
434
|
+
sessionId: adminSession.sessionId,
|
|
435
|
+
userId: 'usr_abc123',
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
// Hard-delete a user (permanent — irreversible)
|
|
439
|
+
await auth.hardDeleteUser({
|
|
440
|
+
sessionId: adminSession.sessionId,
|
|
441
|
+
userId: 'usr_abc123',
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Bulk delete multiple users
|
|
445
|
+
const { deleted, failed } = await auth.bulkDeleteUsers({
|
|
446
|
+
sessionId: adminSession.sessionId,
|
|
447
|
+
userIds: ['usr_a', 'usr_b', 'usr_c'],
|
|
448
|
+
});
|
|
285
449
|
```
|
|
286
450
|
|
|
287
451
|
---
|
|
288
452
|
|
|
289
|
-
|
|
290
|
-
|
|
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
|
-
);
|
|
453
|
+
## File Storage
|
|
303
454
|
|
|
304
|
-
|
|
305
|
-
console.log('Stored at:', data.path);
|
|
306
|
-
console.log('Space saved:', data.spaceSaved, 'bytes');
|
|
307
|
-
}
|
|
455
|
+
HydrousDB Storage is backed by Google Cloud Storage. Your files live at:
|
|
308
456
|
```
|
|
457
|
+
hydrous-storage/{your-owner-id}/{your-path}
|
|
458
|
+
```
|
|
459
|
+
You never see or specify the owner prefix — the SDK handles it transparently.
|
|
309
460
|
|
|
310
|
-
|
|
461
|
+
### Simple Upload
|
|
311
462
|
|
|
312
|
-
|
|
463
|
+
For files up to **500 MB** when you don't need upload progress:
|
|
313
464
|
|
|
314
|
-
|
|
465
|
+
```typescript
|
|
466
|
+
// Browser: upload from a file input
|
|
467
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
468
|
+
const file = fileInput.files[0];
|
|
315
469
|
|
|
316
|
-
|
|
317
|
-
//
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
);
|
|
470
|
+
const result = await db.storage.upload(file, `uploads/${file.name}`, {
|
|
471
|
+
isPublic: true, // publicly accessible without auth
|
|
472
|
+
overwrite: false, // throw if the file already exists
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
console.log(result.publicUrl); // CDN URL — usable anywhere
|
|
476
|
+
console.log(result.downloadUrl); // null (it's public)
|
|
477
|
+
console.log(result.size); // bytes
|
|
478
|
+
console.log(result.mimeType); // auto-detected from extension
|
|
479
|
+
|
|
480
|
+
// Node.js: upload from a Buffer
|
|
481
|
+
import { readFileSync } from 'fs';
|
|
482
|
+
const buffer = readFileSync('./report.pdf');
|
|
483
|
+
const result = await db.storage.upload(buffer, 'reports/q3.pdf');
|
|
484
|
+
console.log(result.downloadUrl); // requires X-Storage-Key to access
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
### Upload Raw JSON or Text
|
|
324
488
|
|
|
325
|
-
|
|
326
|
-
await
|
|
327
|
-
'
|
|
328
|
-
'
|
|
329
|
-
|
|
330
|
-
{ mimeType: 'application/json' }
|
|
489
|
+
```typescript
|
|
490
|
+
const result = await db.storage.uploadRaw(
|
|
491
|
+
{ theme: 'dark', language: 'en' },
|
|
492
|
+
'user-config/alice.json',
|
|
493
|
+
{ isPublic: false },
|
|
331
494
|
);
|
|
332
495
|
```
|
|
333
496
|
|
|
334
|
-
|
|
497
|
+
### Large File Upload (with progress)
|
|
335
498
|
|
|
336
|
-
|
|
499
|
+
For files over 10 MB or when you need a progress bar. The file goes directly
|
|
500
|
+
to GCS — your server never buffers it.
|
|
337
501
|
|
|
338
|
-
|
|
502
|
+
```typescript
|
|
503
|
+
// Step 1: Get a signed upload URL
|
|
504
|
+
const { uploadUrl, path } = await db.storage.getUploadUrl({
|
|
505
|
+
path: 'videos/product-demo.mp4',
|
|
506
|
+
mimeType: 'video/mp4',
|
|
507
|
+
size: file.size,
|
|
508
|
+
isPublic: true,
|
|
509
|
+
});
|
|
339
510
|
|
|
340
|
-
|
|
341
|
-
await
|
|
342
|
-
|
|
511
|
+
// Step 2: Upload directly to GCS with progress
|
|
512
|
+
await db.storage.uploadToSignedUrl(
|
|
513
|
+
uploadUrl,
|
|
343
514
|
file,
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
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
|
-
}
|
|
515
|
+
'video/mp4',
|
|
516
|
+
(percent) => {
|
|
517
|
+
progressBar.style.width = `${percent}%`;
|
|
518
|
+
console.log(`${percent}% uploaded`);
|
|
519
|
+
},
|
|
374
520
|
);
|
|
375
521
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
}
|
|
382
|
-
```
|
|
383
|
-
|
|
384
|
-
**Stage lifecycle:**
|
|
522
|
+
// Step 3: Confirm the upload (registers metadata)
|
|
523
|
+
const result = await db.storage.confirmUpload({
|
|
524
|
+
path: path,
|
|
525
|
+
mimeType: 'video/mp4',
|
|
526
|
+
isPublic: true,
|
|
527
|
+
});
|
|
385
528
|
|
|
386
|
-
|
|
387
|
-
pending → compressing → uploading → done
|
|
388
|
-
↘ error
|
|
529
|
+
console.log(result.publicUrl); // ready to use
|
|
389
530
|
```
|
|
390
531
|
|
|
391
|
-
|
|
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.
|
|
532
|
+
### Batch Upload
|
|
394
533
|
|
|
395
|
-
|
|
534
|
+
```typescript
|
|
535
|
+
// Get signed URLs for multiple files
|
|
536
|
+
const { files } = await db.storage.getBatchUploadUrls([
|
|
537
|
+
{ path: 'gallery/photo1.jpg', mimeType: 'image/jpeg', size: 204800, isPublic: true },
|
|
538
|
+
{ path: 'gallery/photo2.jpg', mimeType: 'image/jpeg', size: 153600, isPublic: true },
|
|
539
|
+
]);
|
|
396
540
|
|
|
397
|
-
|
|
541
|
+
// Upload each one
|
|
542
|
+
for (const f of files) {
|
|
543
|
+
await db.storage.uploadToSignedUrl(f.uploadUrl, blobs[f.index], f.mimeType);
|
|
544
|
+
}
|
|
398
545
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
```ts
|
|
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
|
-
}
|
|
546
|
+
// Confirm all at once
|
|
547
|
+
const results = await db.storage.batchConfirmUploads(
|
|
548
|
+
files.map(f => ({ path: f.path, mimeType: f.mimeType, isPublic: true }))
|
|
417
549
|
);
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
### Download Files
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
// Private files require authentication — download as ArrayBuffer
|
|
556
|
+
const buffer = await db.storage.download('reports/q3.pdf');
|
|
557
|
+
const blob = new Blob([buffer], { type: 'application/pdf' });
|
|
418
558
|
|
|
419
|
-
|
|
420
|
-
|
|
559
|
+
// In a browser: trigger a file download
|
|
560
|
+
const url = URL.createObjectURL(blob);
|
|
561
|
+
const a = document.createElement('a');
|
|
562
|
+
a.href = url;
|
|
563
|
+
a.download = 'q3.pdf';
|
|
564
|
+
a.click();
|
|
565
|
+
|
|
566
|
+
// Public files: just use the publicUrl directly (no SDK needed)
|
|
567
|
+
// <img src={result.publicUrl} />
|
|
421
568
|
```
|
|
422
569
|
|
|
423
|
-
|
|
570
|
+
### List Files
|
|
424
571
|
|
|
425
|
-
```
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
572
|
+
```typescript
|
|
573
|
+
// List everything at the root
|
|
574
|
+
const { files, folders } = await db.storage.list();
|
|
575
|
+
|
|
576
|
+
// List a specific folder
|
|
577
|
+
const { files, folders, hasMore, nextCursor } = await db.storage.list({
|
|
578
|
+
prefix: 'gallery/',
|
|
579
|
+
limit: 50,
|
|
580
|
+
recursive: false,
|
|
431
581
|
});
|
|
582
|
+
|
|
583
|
+
// Paginate
|
|
584
|
+
if (hasMore) {
|
|
585
|
+
const page2 = await db.storage.list({ prefix: 'gallery/', cursor: nextCursor });
|
|
586
|
+
}
|
|
432
587
|
```
|
|
433
588
|
|
|
434
|
-
|
|
435
|
-
|
|
589
|
+
Each file entry includes:
|
|
590
|
+
```typescript
|
|
591
|
+
{
|
|
592
|
+
name: 'photo1.jpg',
|
|
593
|
+
path: 'gallery/photo1.jpg',
|
|
594
|
+
size: 204800,
|
|
595
|
+
mimeType: 'image/jpeg',
|
|
596
|
+
isPublic: true,
|
|
597
|
+
publicUrl: 'https://storage.googleapis.com/...',
|
|
598
|
+
downloadUrl: null,
|
|
599
|
+
updatedAt: '2025-06-01T12:00:00.000Z',
|
|
600
|
+
}
|
|
601
|
+
```
|
|
436
602
|
|
|
437
|
-
|
|
603
|
+
### Scoped Storage
|
|
438
604
|
|
|
439
|
-
|
|
605
|
+
Working within a specific folder? Use `.scope()` to avoid typing the prefix on every call.
|
|
440
606
|
|
|
441
|
-
|
|
607
|
+
```typescript
|
|
608
|
+
// All operations in the "user-avatars/" folder
|
|
609
|
+
const avatars = db.storage.scope('user-avatars');
|
|
442
610
|
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
'ssk_my_bucket_key',
|
|
446
|
-
'avatars/alice.jpg'
|
|
447
|
-
);
|
|
611
|
+
await avatars.upload(file, `${userId}.jpg`, { isPublic: true });
|
|
612
|
+
// → uploads to "user-avatars/{userId}.jpg"
|
|
448
613
|
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
const blob = new Blob([buffer], { type: 'image/jpeg' });
|
|
452
|
-
img.src = URL.createObjectURL(blob);
|
|
614
|
+
const { files } = await avatars.list();
|
|
615
|
+
// → lists files under "user-avatars/"
|
|
453
616
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
617
|
+
await avatars.deleteFile(`${userId}.jpg`);
|
|
618
|
+
// → deletes "user-avatars/{userId}.jpg"
|
|
619
|
+
|
|
620
|
+
// Nest scopes
|
|
621
|
+
const thumbnails = avatars.scope('thumbnails');
|
|
622
|
+
// → all operations under "user-avatars/thumbnails/"
|
|
458
623
|
```
|
|
459
624
|
|
|
460
|
-
|
|
625
|
+
### Share & Visibility
|
|
461
626
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
onProgress: (p) => {
|
|
472
|
-
console.log(`${p.path}: ${p.status}`); // 'pending' | 'success' | 'error'
|
|
473
|
-
},
|
|
474
|
-
}
|
|
627
|
+
```typescript
|
|
628
|
+
// Get file metadata (sizes, URLs, visibility)
|
|
629
|
+
const meta = await db.storage.getMetadata('reports/q3.pdf');
|
|
630
|
+
|
|
631
|
+
// Generate a time-limited share link for a private file
|
|
632
|
+
// (no auth key needed to use the link)
|
|
633
|
+
const { signedUrl, expiresAt } = await db.storage.getSignedUrl(
|
|
634
|
+
'reports/q3.pdf',
|
|
635
|
+
3600, // expires in 1 hour (default)
|
|
475
636
|
);
|
|
637
|
+
// Share signedUrl with whoever needs it
|
|
476
638
|
|
|
477
|
-
//
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// files[n].size — bytes
|
|
639
|
+
// Toggle visibility after upload
|
|
640
|
+
const result = await db.storage.setVisibility('reports/q3.pdf', true); // make public
|
|
641
|
+
const result2 = await db.storage.setVisibility('reports/q3.pdf', false); // make private
|
|
481
642
|
```
|
|
482
643
|
|
|
483
|
-
|
|
644
|
+
### File Operations
|
|
484
645
|
|
|
485
|
-
|
|
646
|
+
```typescript
|
|
647
|
+
// Rename / move a file
|
|
648
|
+
await db.storage.move('drafts/report.pdf', 'published/report-2025.pdf');
|
|
486
649
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
prefix: 'avatars/', // list inside a folder — omit for root
|
|
490
|
-
limit: 50, // max items per page (1–100)
|
|
491
|
-
});
|
|
650
|
+
// Copy a file
|
|
651
|
+
await db.storage.copy('templates/invoice.html', 'invoices/inv-001.html');
|
|
492
652
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
console.log('📁', item.path);
|
|
496
|
-
} else {
|
|
497
|
-
console.log('📄', item.path, item.size, 'bytes');
|
|
498
|
-
}
|
|
499
|
-
}
|
|
653
|
+
// Create a folder
|
|
654
|
+
await db.storage.createFolder('archive/2025/');
|
|
500
655
|
|
|
501
|
-
//
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
656
|
+
// Delete a file
|
|
657
|
+
await db.storage.deleteFile('temp/scratch.txt');
|
|
658
|
+
|
|
659
|
+
// Delete a folder and all its contents
|
|
660
|
+
await db.storage.deleteFolder('temp/');
|
|
661
|
+
|
|
662
|
+
// Get key-level stats
|
|
663
|
+
const stats = await db.storage.getStats();
|
|
664
|
+
// → { totalFiles: 842, totalBytes: 1073741824, uploadCount: 1200, ... }
|
|
508
665
|
```
|
|
509
666
|
|
|
510
667
|
---
|
|
511
668
|
|
|
512
|
-
|
|
669
|
+
## Analytics
|
|
513
670
|
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
'ssk_my_bucket_key',
|
|
517
|
-
'avatars/alice.jpg'
|
|
518
|
-
);
|
|
671
|
+
HydrousDB Analytics runs your queries against BigQuery, so they're fast even
|
|
672
|
+
on millions of records. All queries accept an optional `dateRange` filter.
|
|
519
673
|
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
console.log(meta.mimeType);
|
|
523
|
-
console.log(meta.isCompressed);
|
|
524
|
-
console.log(meta.updatedAt);
|
|
674
|
+
```typescript
|
|
675
|
+
const analytics = db.analytics('orders');
|
|
525
676
|
```
|
|
526
677
|
|
|
527
|
-
|
|
678
|
+
### Count
|
|
528
679
|
|
|
529
|
-
|
|
680
|
+
```typescript
|
|
681
|
+
// Total records
|
|
682
|
+
const { count } = await analytics.count();
|
|
530
683
|
|
|
531
|
-
|
|
532
|
-
const {
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
)
|
|
684
|
+
// Records in a date range
|
|
685
|
+
const { count: lastWeek } = await analytics.count({
|
|
686
|
+
dateRange: {
|
|
687
|
+
start: Date.now() - 7 * 24 * 60 * 60 * 1000,
|
|
688
|
+
end: Date.now(),
|
|
689
|
+
},
|
|
690
|
+
});
|
|
536
691
|
```
|
|
537
692
|
|
|
538
|
-
|
|
693
|
+
### Distribution
|
|
539
694
|
|
|
540
|
-
|
|
695
|
+
How many records have each unique value for a field?
|
|
541
696
|
|
|
542
|
-
|
|
697
|
+
```typescript
|
|
698
|
+
const rows = await analytics.distribution({ field: 'status', limit: 10, order: 'desc' });
|
|
699
|
+
// → [
|
|
700
|
+
// { value: 'completed', count: 8234 },
|
|
701
|
+
// { value: 'pending', count: 1203 },
|
|
702
|
+
// { value: 'refunded', count: 412 },
|
|
703
|
+
// ]
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
### Sum
|
|
707
|
+
|
|
708
|
+
```typescript
|
|
709
|
+
// Total revenue
|
|
710
|
+
const rows = await analytics.sum({ field: 'amount' });
|
|
711
|
+
// → [{ sum: 198432.50 }]
|
|
543
712
|
|
|
544
|
-
|
|
545
|
-
await
|
|
713
|
+
// Revenue by country
|
|
714
|
+
const byCountry = await analytics.sum({
|
|
715
|
+
field: 'amount',
|
|
716
|
+
groupBy: 'country',
|
|
717
|
+
limit: 10,
|
|
718
|
+
});
|
|
719
|
+
// → [{ group: 'US', sum: 120000 }, { group: 'UK', sum: 45000 }, ...]
|
|
546
720
|
```
|
|
547
721
|
|
|
548
|
-
|
|
722
|
+
### Time Series
|
|
549
723
|
|
|
550
|
-
|
|
724
|
+
Record counts over time — ideal for activity charts.
|
|
551
725
|
|
|
552
|
-
```
|
|
553
|
-
await
|
|
726
|
+
```typescript
|
|
727
|
+
const rows = await analytics.timeSeries({
|
|
728
|
+
granularity: 'day', // 'hour' | 'day' | 'week' | 'month' | 'year'
|
|
729
|
+
dateRange: {
|
|
730
|
+
start: new Date('2025-01-01').getTime(),
|
|
731
|
+
end: new Date('2025-06-01').getTime(),
|
|
732
|
+
},
|
|
733
|
+
});
|
|
734
|
+
// → [{ date: '2025-01-01', count: 42 }, { date: '2025-01-02', count: 67 }, ...]
|
|
554
735
|
```
|
|
555
736
|
|
|
556
|
-
|
|
737
|
+
Aggregate a field over time:
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
const revenue = await analytics.fieldTimeSeries({
|
|
741
|
+
field: 'amount',
|
|
742
|
+
aggregation: 'sum', // 'sum' | 'avg' | 'min' | 'max' | 'count'
|
|
743
|
+
granularity: 'week',
|
|
744
|
+
});
|
|
745
|
+
// → [{ date: '2025-W01', value: 12340.50 }, ...]
|
|
746
|
+
```
|
|
557
747
|
|
|
558
|
-
###
|
|
748
|
+
### Top N
|
|
559
749
|
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
'
|
|
565
|
-
|
|
750
|
+
Most common values for a field:
|
|
751
|
+
|
|
752
|
+
```typescript
|
|
753
|
+
const topProducts = await analytics.topN({
|
|
754
|
+
field: 'productId',
|
|
755
|
+
labelField: 'productName', // optional: include a human-readable label
|
|
756
|
+
n: 5,
|
|
757
|
+
order: 'desc',
|
|
758
|
+
});
|
|
759
|
+
// → [
|
|
760
|
+
// { value: 'prod_123', label: 'Widget Pro', count: 892 },
|
|
761
|
+
// { value: 'prod_456', label: 'Gizmo Plus', count: 743 },
|
|
762
|
+
// ]
|
|
566
763
|
```
|
|
567
764
|
|
|
568
|
-
|
|
765
|
+
### Field Stats
|
|
569
766
|
|
|
570
|
-
|
|
767
|
+
Statistical summary for any numeric field:
|
|
571
768
|
|
|
572
|
-
```
|
|
573
|
-
await
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
769
|
+
```typescript
|
|
770
|
+
const stats = await analytics.stats({ field: 'orderValue' });
|
|
771
|
+
// → {
|
|
772
|
+
// min: 4.99, max: 9999.99, avg: 87.23,
|
|
773
|
+
// sum: 420948.27, count: 4823, stddev: 143.2
|
|
774
|
+
// }
|
|
578
775
|
```
|
|
579
776
|
|
|
580
|
-
|
|
777
|
+
### Multi-Metric Dashboard
|
|
581
778
|
|
|
582
|
-
|
|
779
|
+
Calculate several aggregations in a single BigQuery query:
|
|
583
780
|
|
|
584
|
-
|
|
781
|
+
```typescript
|
|
782
|
+
const dashboard = await analytics.multiMetric({
|
|
783
|
+
metrics: [
|
|
784
|
+
{ field: 'amount', name: 'totalRevenue', aggregation: 'sum' },
|
|
785
|
+
{ field: 'amount', name: 'avgOrderValue', aggregation: 'avg' },
|
|
786
|
+
{ field: 'amount', name: 'maxOrder', aggregation: 'max' },
|
|
787
|
+
{ field: 'userId', name: 'totalOrders', aggregation: 'count' },
|
|
788
|
+
],
|
|
789
|
+
dateRange: { start: new Date('2025-01-01').getTime(), end: Date.now() },
|
|
790
|
+
});
|
|
791
|
+
// → {
|
|
792
|
+
// totalRevenue: 198432.50,
|
|
793
|
+
// avgOrderValue: 87.23,
|
|
794
|
+
// maxOrder: 9999.99,
|
|
795
|
+
// totalOrders: 2275,
|
|
796
|
+
// }
|
|
797
|
+
```
|
|
585
798
|
|
|
586
|
-
|
|
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
|
-
);
|
|
799
|
+
### Filtered Records (BigQuery)
|
|
592
800
|
|
|
593
|
-
|
|
801
|
+
Query raw records with full BigQuery speed:
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
const records = await analytics.records({
|
|
805
|
+
filters: [
|
|
806
|
+
{ field: 'status', op: '==', value: 'refunded' },
|
|
807
|
+
{ field: 'amount', op: '>', value: 100 },
|
|
808
|
+
],
|
|
809
|
+
selectFields: ['orderId', 'amount', 'userId', 'createdAt'],
|
|
810
|
+
orderBy: 'amount',
|
|
811
|
+
order: 'desc',
|
|
812
|
+
limit: 50,
|
|
813
|
+
});
|
|
594
814
|
```
|
|
595
815
|
|
|
596
|
-
|
|
816
|
+
### Cross-Bucket Comparison
|
|
597
817
|
|
|
598
|
-
|
|
818
|
+
Compare the same metric across multiple buckets in one query:
|
|
599
819
|
|
|
600
|
-
```
|
|
601
|
-
const
|
|
820
|
+
```typescript
|
|
821
|
+
const comparison = await analytics.crossBucket({
|
|
822
|
+
bucketKeys: ['orders-us', 'orders-eu', 'orders-apac'],
|
|
823
|
+
field: 'amount',
|
|
824
|
+
aggregation: 'sum',
|
|
825
|
+
});
|
|
826
|
+
// → [
|
|
827
|
+
// { bucket: 'orders-us', value: 120000 },
|
|
828
|
+
// { bucket: 'orders-eu', value: 45000 },
|
|
829
|
+
// { bucket: 'orders-apac', value: 33000 },
|
|
830
|
+
// ]
|
|
831
|
+
```
|
|
832
|
+
|
|
833
|
+
> ⚠️ Your Security Key must have read access to **all** buckets in the list.
|
|
834
|
+
|
|
835
|
+
### Storage Stats
|
|
602
836
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
console.log('Credits used:', stats.creditsTotalUsed);
|
|
607
|
-
console.log('Compression:', stats.compressionRatio);
|
|
837
|
+
```typescript
|
|
838
|
+
const stats = await analytics.storageStats();
|
|
839
|
+
// → { totalRecords: 48210, totalBytes: 921600000, avgBytes: 19112, minBytes: 128, maxBytes: 5242880 }
|
|
608
840
|
```
|
|
609
841
|
|
|
610
842
|
---
|
|
611
843
|
|
|
612
|
-
##
|
|
844
|
+
## TypeScript Support
|
|
613
845
|
|
|
614
|
-
|
|
846
|
+
The SDK is written in TypeScript and ships with full type definitions. Use generic
|
|
847
|
+
type parameters to describe the shape of your records and get autocomplete throughout.
|
|
615
848
|
|
|
616
|
-
```
|
|
617
|
-
|
|
849
|
+
```typescript
|
|
850
|
+
import { createClient } from 'hydrousdb';
|
|
618
851
|
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
852
|
+
// Define your data models
|
|
853
|
+
interface Order {
|
|
854
|
+
customerId: string;
|
|
855
|
+
items: Array<{ productId: string; qty: number; price: number }>;
|
|
856
|
+
total: number;
|
|
857
|
+
status: 'pending' | 'processing' | 'completed' | 'refunded';
|
|
858
|
+
country: string;
|
|
623
859
|
}
|
|
860
|
+
|
|
861
|
+
interface Customer {
|
|
862
|
+
name: string;
|
|
863
|
+
email: string;
|
|
864
|
+
tier: 'free' | 'pro' | 'enterprise';
|
|
865
|
+
credits: number;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
const db = createClient({ securityKey: process.env.HYDROUS_SECURITY_KEY! });
|
|
869
|
+
|
|
870
|
+
// Fully typed clients
|
|
871
|
+
const orders = db.records<Order>('orders');
|
|
872
|
+
const customers = db.records<Customer>('customers');
|
|
873
|
+
|
|
874
|
+
// order.total, order.status, etc. are all type-safe
|
|
875
|
+
const order = await orders.create({
|
|
876
|
+
customerId: 'cust_abc',
|
|
877
|
+
items: [{ productId: 'prod_1', qty: 2, price: 29.99 }],
|
|
878
|
+
total: 59.98,
|
|
879
|
+
status: 'pending',
|
|
880
|
+
country: 'US',
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
// TypeScript catches mistakes at compile time:
|
|
884
|
+
// order.nonExistentField // ← TS error ✓
|
|
885
|
+
// order.status = 'invalid' // ← TS error ✓
|
|
886
|
+
```
|
|
887
|
+
|
|
888
|
+
All exported types are available for import:
|
|
889
|
+
|
|
890
|
+
```typescript
|
|
891
|
+
import type {
|
|
892
|
+
HydrousConfig,
|
|
893
|
+
RecordResult,
|
|
894
|
+
QueryFilter,
|
|
895
|
+
QueryOptions,
|
|
896
|
+
UploadResult,
|
|
897
|
+
AnalyticsQuery,
|
|
898
|
+
DateRange,
|
|
899
|
+
// ... and many more
|
|
900
|
+
} from 'hydrousdb';
|
|
624
901
|
```
|
|
625
902
|
|
|
626
|
-
|
|
903
|
+
---
|
|
904
|
+
|
|
905
|
+
## Error Handling
|
|
906
|
+
|
|
907
|
+
All errors thrown by the SDK extend `HydrousError`, which carries:
|
|
627
908
|
|
|
628
|
-
|
|
909
|
+
| Property | Type | Description |
|
|
910
|
+
|---|---|---|
|
|
911
|
+
| `message` | `string` | Human-readable description |
|
|
912
|
+
| `code` | `string` | Machine-readable error code (e.g. `"RECORD_NOT_FOUND"`) |
|
|
913
|
+
| `status` | `number` | HTTP status code |
|
|
914
|
+
| `requestId` | `string` | Server request ID (for support tracing) |
|
|
915
|
+
| `details` | `string[]` | Validation error details |
|
|
629
916
|
|
|
630
|
-
```
|
|
631
|
-
import {
|
|
917
|
+
```typescript
|
|
918
|
+
import { HydrousError, NetworkError, AuthError } from 'hydrousdb';
|
|
632
919
|
|
|
633
920
|
try {
|
|
634
|
-
|
|
921
|
+
const user = await auth.login({ email: 'a@b.com', password: 'wrong' });
|
|
635
922
|
} catch (err) {
|
|
636
|
-
if (
|
|
637
|
-
|
|
923
|
+
if (err instanceof AuthError) {
|
|
924
|
+
// Authentication-specific error
|
|
925
|
+
console.error(`Auth failed: ${err.code}`);
|
|
926
|
+
// err.code might be: INVALID_CREDENTIALS, ACCOUNT_LOCKED, EMAIL_NOT_VERIFIED, etc.
|
|
927
|
+
} else if (err instanceof NetworkError) {
|
|
928
|
+
// No internet / server unreachable
|
|
929
|
+
console.error('Cannot reach HydrousDB — check your internet connection');
|
|
930
|
+
} else if (err instanceof HydrousError) {
|
|
931
|
+
// Any other API error
|
|
932
|
+
console.error(`API error [${err.code}]: ${err.message}`);
|
|
933
|
+
console.error(`Request ID: ${err.requestId}`); // include this in support tickets
|
|
638
934
|
}
|
|
639
935
|
}
|
|
640
936
|
```
|
|
641
937
|
|
|
642
|
-
|
|
938
|
+
**Common error codes:**
|
|
939
|
+
|
|
940
|
+
| Code | Meaning |
|
|
941
|
+
|---|---|
|
|
942
|
+
| `RECORD_NOT_FOUND` | The requested record ID does not exist |
|
|
943
|
+
| `INVALID_CREDENTIALS` | Wrong email or password |
|
|
944
|
+
| `ACCOUNT_LOCKED` | The account is temporarily locked |
|
|
945
|
+
| `INVALID_SESSION` | Session expired or revoked — re-authenticate |
|
|
946
|
+
| `MISSING_API_KEY` | Security key not provided |
|
|
947
|
+
| `INVALID_SECURITY_KEY` | Security key is wrong or revoked |
|
|
948
|
+
| `FORBIDDEN` | Insufficient permissions |
|
|
949
|
+
| `FILE_EXISTS` | File already exists at path (use `overwrite: true`) |
|
|
950
|
+
| `LIMIT_EXCEEDED` | Storage quota or file size limit reached |
|
|
951
|
+
| `SYSTEM_BUCKET_FORBIDDEN` | Cannot query system buckets via analytics |
|
|
952
|
+
| `VALIDATION_ERROR` | Invalid input — check `err.details` |
|
|
953
|
+
| `NETWORK_ERROR` | Failed to reach the API |
|
|
954
|
+
|
|
955
|
+
---
|
|
956
|
+
|
|
957
|
+
## Security Best Practices
|
|
643
958
|
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
959
|
+
1. **Never hard-code your Security Key.** Use environment variables:
|
|
960
|
+
|
|
961
|
+
```bash
|
|
962
|
+
# .env (add to .gitignore)
|
|
963
|
+
HYDROUS_SECURITY_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
|
|
964
|
+
```
|
|
965
|
+
|
|
966
|
+
```typescript
|
|
967
|
+
const db = createClient({ securityKey: process.env.HYDROUS_SECURITY_KEY! });
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
2. **Never expose your Security Key to browsers.** For browser-side apps,
|
|
971
|
+
route requests through your own backend, or use per-user session tokens.
|
|
972
|
+
|
|
973
|
+
3. **Your Security Key is sent via the `X-Api-Key` header — never in URLs.**
|
|
974
|
+
The SDK enforces this automatically, so keys never appear in server logs or
|
|
975
|
+
browser history.
|
|
976
|
+
|
|
977
|
+
4. **Rotate keys periodically.** Revoke old keys from the dashboard after rotation.
|
|
978
|
+
|
|
979
|
+
5. **Use scoped storage** (`db.storage.scope(...)`) to isolate access by user or
|
|
980
|
+
feature, reducing the blast radius of any misconfiguration.
|
|
981
|
+
|
|
982
|
+
6. **Use `isPublic: false` (the default) for sensitive files.** Use signed URLs
|
|
983
|
+
for time-limited sharing instead of making files permanently public.
|
|
656
984
|
|
|
657
985
|
---
|
|
658
986
|
|
|
659
|
-
##
|
|
660
|
-
|
|
661
|
-
### `UploadProgress`
|
|
662
|
-
|
|
663
|
-
```ts
|
|
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
|
-
}
|
|
987
|
+
## API Reference
|
|
678
988
|
|
|
679
|
-
|
|
680
|
-
```
|
|
989
|
+
### `createClient(config)`
|
|
681
990
|
|
|
682
|
-
|
|
991
|
+
Creates and returns a `HydrousClient` instance. Call this once and reuse the instance.
|
|
683
992
|
|
|
684
|
-
```
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
storedSize: number;
|
|
690
|
-
spaceSaved: number;
|
|
691
|
-
mimeType: string;
|
|
692
|
-
}
|
|
993
|
+
```typescript
|
|
994
|
+
const db = createClient({
|
|
995
|
+
securityKey: 'sk_live_...', // Required — from https://hydrousdb.com/dashboard
|
|
996
|
+
baseUrl: 'https://...', // Optional — defaults to official HydrousDB endpoint
|
|
997
|
+
});
|
|
693
998
|
```
|
|
694
999
|
|
|
695
|
-
### `
|
|
1000
|
+
### `db.records<T>(bucketKey)`
|
|
1001
|
+
|
|
1002
|
+
Returns a `RecordsClient<T>` for the named bucket.
|
|
1003
|
+
|
|
1004
|
+
| Method | Description |
|
|
1005
|
+
|---|---|
|
|
1006
|
+
| `create(data)` | Create a new record |
|
|
1007
|
+
| `get(id)` | Get a record by ID |
|
|
1008
|
+
| `set(id, data)` | Full replace |
|
|
1009
|
+
| `patch(id, data, opts?)` | Partial update (merge by default) |
|
|
1010
|
+
| `delete(id)` | Delete a record |
|
|
1011
|
+
| `query(opts?)` | Query with filters, sort, pagination |
|
|
1012
|
+
| `getAll(opts?)` | Shortcut for query without filters |
|
|
1013
|
+
| `count(filters?)` | Count matching records |
|
|
1014
|
+
| `batchCreate(items)` | Create multiple records |
|
|
1015
|
+
| `batchDelete(ids)` | Delete multiple records |
|
|
1016
|
+
| `getHistory(id)` | Get version history |
|
|
1017
|
+
| `restoreVersion(id, version)` | Restore to a version |
|
|
1018
|
+
|
|
1019
|
+
### `db.auth(bucketKey)`
|
|
1020
|
+
|
|
1021
|
+
Returns an `AuthClient` for the named user bucket.
|
|
1022
|
+
|
|
1023
|
+
| Method | Description |
|
|
1024
|
+
|---|---|
|
|
1025
|
+
| `signup(opts)` | Register a new user |
|
|
1026
|
+
| `login(opts)` | Authenticate and create session |
|
|
1027
|
+
| `logout({ sessionId })` | Invalidate session |
|
|
1028
|
+
| `refreshSession({ refreshToken })` | Extend a session |
|
|
1029
|
+
| `getUser({ userId })` | Get user by ID |
|
|
1030
|
+
| `updateUser(opts)` | Update user fields |
|
|
1031
|
+
| `deleteUser(opts)` | Soft-delete a user |
|
|
1032
|
+
| `hardDeleteUser(opts)` | Permanently delete a user (admin) |
|
|
1033
|
+
| `listUsers(opts)` | List all users (admin) |
|
|
1034
|
+
| `bulkDeleteUsers(opts)` | Bulk delete (admin) |
|
|
1035
|
+
| `lockAccount(opts)` | Lock a user (admin) |
|
|
1036
|
+
| `unlockAccount(opts)` | Unlock a user (admin) |
|
|
1037
|
+
| `changePassword(opts)` | Change password (authenticated) |
|
|
1038
|
+
| `requestPasswordReset(opts)` | Send reset email |
|
|
1039
|
+
| `confirmPasswordReset(opts)` | Apply new password |
|
|
1040
|
+
| `requestEmailVerification(opts)` | Send verification email |
|
|
1041
|
+
| `confirmEmailVerification(opts)` | Verify email with token |
|
|
1042
|
+
|
|
1043
|
+
### `db.analytics(bucketKey)`
|
|
1044
|
+
|
|
1045
|
+
Returns an `AnalyticsClient` for the named bucket.
|
|
1046
|
+
|
|
1047
|
+
| Method | Description |
|
|
1048
|
+
|---|---|
|
|
1049
|
+
| `count(opts?)` | Count records |
|
|
1050
|
+
| `distribution(opts)` | Value distribution for a field |
|
|
1051
|
+
| `sum(opts)` | Sum (with optional groupBy) |
|
|
1052
|
+
| `timeSeries(opts?)` | Records over time |
|
|
1053
|
+
| `fieldTimeSeries(opts)` | Field aggregation over time |
|
|
1054
|
+
| `topN(opts)` | Top N values for a field |
|
|
1055
|
+
| `stats(opts)` | Statistical summary for a field |
|
|
1056
|
+
| `records(opts?)` | Filtered raw records (BigQuery) |
|
|
1057
|
+
| `multiMetric(opts)` | Multiple aggregations in one query |
|
|
1058
|
+
| `storageStats(opts?)` | Bucket storage statistics |
|
|
1059
|
+
| `crossBucket(opts)` | Compare across multiple buckets |
|
|
1060
|
+
| `query(query)` | Raw analytics query |
|
|
1061
|
+
|
|
1062
|
+
### `db.storage`
|
|
1063
|
+
|
|
1064
|
+
The `StorageManager` instance. Also has `.scope(prefix)` for folder-scoped access.
|
|
1065
|
+
|
|
1066
|
+
| Method | Description |
|
|
1067
|
+
|---|---|
|
|
1068
|
+
| `upload(data, path, opts?)` | Simple server-buffered upload |
|
|
1069
|
+
| `uploadRaw(data, path, opts?)` | Upload JSON/text data |
|
|
1070
|
+
| `getUploadUrl(opts)` | Step 1: Get signed GCS upload URL |
|
|
1071
|
+
| `uploadToSignedUrl(url, data, mime, onProgress?)` | Step 2: Upload to GCS directly |
|
|
1072
|
+
| `confirmUpload(opts)` | Step 3: Register upload metadata |
|
|
1073
|
+
| `getBatchUploadUrls(files)` | Batch signed upload URLs |
|
|
1074
|
+
| `batchConfirmUploads(items)` | Confirm batch uploads |
|
|
1075
|
+
| `download(path)` | Download private file |
|
|
1076
|
+
| `batchDownload(paths)` | Batch download |
|
|
1077
|
+
| `list(opts?)` | List files and folders |
|
|
1078
|
+
| `getMetadata(path)` | File metadata |
|
|
1079
|
+
| `getSignedUrl(path, expiresIn?)` | Time-limited share URL |
|
|
1080
|
+
| `setVisibility(path, isPublic)` | Toggle public/private |
|
|
1081
|
+
| `createFolder(path)` | Create a folder |
|
|
1082
|
+
| `deleteFile(path)` | Delete a file |
|
|
1083
|
+
| `deleteFolder(path)` | Delete a folder recursively |
|
|
1084
|
+
| `move(from, to)` | Move/rename |
|
|
1085
|
+
| `copy(from, to)` | Copy |
|
|
1086
|
+
| `getStats()` | Key-level stats |
|
|
1087
|
+
| `info()` | Server ping (no auth) |
|
|
1088
|
+
| `scope(prefix)` | Get a ScopedStorage instance |
|
|
696
1089
|
|
|
697
|
-
|
|
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
|
-
```
|
|
1090
|
+
---
|
|
708
1091
|
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
1092
|
+
## Contributing
|
|
1093
|
+
|
|
1094
|
+
We love contributions! Please see [CONTRIBUTING.md](./CONTRIBUTING.md) for guidelines.
|
|
1095
|
+
|
|
1096
|
+
```bash
|
|
1097
|
+
# Clone the repo
|
|
1098
|
+
git clone https://github.com/hydrousdb/hydrousdb-js.git
|
|
1099
|
+
cd hydrousdb-js
|
|
1100
|
+
|
|
1101
|
+
# Install dependencies
|
|
1102
|
+
npm install
|
|
1103
|
+
|
|
1104
|
+
# Run tests
|
|
1105
|
+
npm test
|
|
1106
|
+
|
|
1107
|
+
# Build
|
|
1108
|
+
npm run build
|
|
1109
|
+
|
|
1110
|
+
# Run tests in watch mode
|
|
1111
|
+
npm run test:watch
|
|
724
1112
|
```
|
|
725
1113
|
|
|
726
1114
|
---
|
|
727
1115
|
|
|
728
1116
|
## License
|
|
729
1117
|
|
|
730
|
-
MIT
|
|
1118
|
+
MIT — see [LICENSE](./LICENSE) for details.
|
|
1119
|
+
|
|
1120
|
+
---
|
|
1121
|
+
|
|
1122
|
+
<p align="center">
|
|
1123
|
+
Built with ❤️ by the <a href="https://hydrousdb.com">HydrousDB</a> team.<br>
|
|
1124
|
+
Questions? <a href="mailto:support@hydrousdb.com">support@hydrousdb.com</a> ·
|
|
1125
|
+
<a href="https://github.com/hydrousdb/hydrousdb-js/issues">Open an issue</a>
|
|
1126
|
+
</p>
|