hydrousdb 3.0.0 → 3.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +246 -172
- package/dist/index.cjs +157 -610
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +142 -602
- package/dist/index.d.ts +142 -602
- package/dist/index.js +157 -610
- package/dist/index.js.map +1 -1
- package/package.json +8 -8
package/dist/index.cjs
CHANGED
|
@@ -49,28 +49,36 @@ var NetworkError = class extends HydrousError {
|
|
|
49
49
|
constructor(message, cause) {
|
|
50
50
|
super(message, "NETWORK_ERROR");
|
|
51
51
|
this.name = "NetworkError";
|
|
52
|
-
this.cause = cause;
|
|
52
|
+
if (cause) this.cause = cause;
|
|
53
53
|
}
|
|
54
54
|
};
|
|
55
55
|
|
|
56
56
|
// src/utils/http.ts
|
|
57
57
|
var DEFAULT_BASE_URL = "https://db-api-82687684612.us-central1.run.app";
|
|
58
58
|
var HttpClient = class {
|
|
59
|
-
constructor(baseUrl
|
|
59
|
+
constructor(baseUrl) {
|
|
60
60
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
61
|
-
this.securityKey = securityKey;
|
|
62
61
|
}
|
|
63
|
-
async request(path, opts = {}) {
|
|
62
|
+
async request(path, apiKeyOrOpts, opts = {}) {
|
|
63
|
+
let apiKey;
|
|
64
|
+
let resolvedOpts;
|
|
65
|
+
if (typeof apiKeyOrOpts === "string") {
|
|
66
|
+
apiKey = apiKeyOrOpts;
|
|
67
|
+
resolvedOpts = opts;
|
|
68
|
+
} else {
|
|
69
|
+
apiKey = void 0;
|
|
70
|
+
resolvedOpts = apiKeyOrOpts ?? opts;
|
|
71
|
+
}
|
|
64
72
|
const {
|
|
65
73
|
method = "GET",
|
|
66
74
|
body,
|
|
67
75
|
headers = {},
|
|
68
76
|
rawBody,
|
|
69
77
|
contentType = "application/json"
|
|
70
|
-
} =
|
|
78
|
+
} = resolvedOpts;
|
|
71
79
|
const url = `${this.baseUrl}${path}`;
|
|
72
80
|
const reqHeaders = {
|
|
73
|
-
"X-Api-Key":
|
|
81
|
+
...apiKey ? { "X-Api-Key": apiKey } : {},
|
|
74
82
|
...headers
|
|
75
83
|
};
|
|
76
84
|
let reqBody = null;
|
|
@@ -128,25 +136,26 @@ var HttpClient = class {
|
|
|
128
136
|
}
|
|
129
137
|
return responseData;
|
|
130
138
|
}
|
|
131
|
-
get(path, headers) {
|
|
132
|
-
return this.request(path, { method: "GET", headers });
|
|
139
|
+
get(path, apiKey, headers) {
|
|
140
|
+
return this.request(path, apiKey, { method: "GET", headers });
|
|
133
141
|
}
|
|
134
|
-
post(path, body, headers) {
|
|
135
|
-
return this.request(path, { method: "POST", body, headers });
|
|
142
|
+
post(path, apiKey, body, headers) {
|
|
143
|
+
return this.request(path, apiKey, { method: "POST", body, headers });
|
|
136
144
|
}
|
|
137
|
-
put(path, body, headers) {
|
|
138
|
-
return this.request(path, { method: "PUT", body, headers });
|
|
145
|
+
put(path, apiKey, body, headers) {
|
|
146
|
+
return this.request(path, apiKey, { method: "PUT", body, headers });
|
|
139
147
|
}
|
|
140
|
-
patch(path, body, headers) {
|
|
141
|
-
return this.request(path, { method: "PATCH", body, headers });
|
|
148
|
+
patch(path, apiKey, body, headers) {
|
|
149
|
+
return this.request(path, apiKey, { method: "PATCH", body, headers });
|
|
142
150
|
}
|
|
143
|
-
delete(path, body, headers) {
|
|
144
|
-
return this.request(path, { method: "DELETE", body, headers });
|
|
151
|
+
delete(path, apiKey, body, headers) {
|
|
152
|
+
return this.request(path, apiKey, { method: "DELETE", body, headers });
|
|
145
153
|
}
|
|
146
154
|
async putToSignedUrl(signedUrl, data, mimeType, onProgress) {
|
|
147
|
-
|
|
155
|
+
const XHR = typeof globalThis["XMLHttpRequest"] !== "undefined" ? globalThis["XMLHttpRequest"] : void 0;
|
|
156
|
+
if (XHR && onProgress) {
|
|
148
157
|
return new Promise((resolve, reject) => {
|
|
149
|
-
const xhr = new
|
|
158
|
+
const xhr = new XHR();
|
|
150
159
|
xhr.upload.onprogress = (e) => {
|
|
151
160
|
if (e.lengthComputable) {
|
|
152
161
|
onProgress(Math.round(e.loaded / e.total * 100));
|
|
@@ -180,280 +189,97 @@ var HttpClient = class {
|
|
|
180
189
|
|
|
181
190
|
// src/auth/client.ts
|
|
182
191
|
var AuthClient = class {
|
|
183
|
-
constructor(http, bucketKey) {
|
|
192
|
+
constructor(http, authKey, bucketKey) {
|
|
184
193
|
this.http = http;
|
|
185
|
-
this.
|
|
194
|
+
this.authKey = authKey;
|
|
186
195
|
this.basePath = `/auth/${bucketKey}`;
|
|
187
196
|
}
|
|
197
|
+
post(path, body) {
|
|
198
|
+
return this.http.post(path, this.authKey, body);
|
|
199
|
+
}
|
|
200
|
+
get(path) {
|
|
201
|
+
return this.http.get(path, this.authKey);
|
|
202
|
+
}
|
|
203
|
+
patch(path, body) {
|
|
204
|
+
return this.http.patch(path, this.authKey, body);
|
|
205
|
+
}
|
|
206
|
+
delete(path, body) {
|
|
207
|
+
return this.http.delete(path, this.authKey, body);
|
|
208
|
+
}
|
|
188
209
|
// ─── Registration & Login ─────────────────────────────────────────────────
|
|
189
|
-
/**
|
|
190
|
-
* Register a new user in this bucket.
|
|
191
|
-
*
|
|
192
|
-
* @example
|
|
193
|
-
* ```ts
|
|
194
|
-
* const { user, session } = await auth.signup({
|
|
195
|
-
* email: 'alice@example.com',
|
|
196
|
-
* password: 'hunter2',
|
|
197
|
-
* fullName: 'Alice Wonderland',
|
|
198
|
-
* // Any extra fields are stored on the user record:
|
|
199
|
-
* plan: 'pro',
|
|
200
|
-
* });
|
|
201
|
-
* ```
|
|
202
|
-
*/
|
|
203
210
|
async signup(options) {
|
|
204
|
-
const result = await this.
|
|
211
|
+
const result = await this.post(`${this.basePath}/signup`, options);
|
|
205
212
|
return { user: result.user, session: result.session };
|
|
206
213
|
}
|
|
207
|
-
/**
|
|
208
|
-
* Authenticate an existing user and create a session.
|
|
209
|
-
* Sessions are valid for 24 hours; use `refreshSession` to extend.
|
|
210
|
-
*
|
|
211
|
-
* @example
|
|
212
|
-
* ```ts
|
|
213
|
-
* const { user, session } = await auth.login({
|
|
214
|
-
* email: 'alice@example.com',
|
|
215
|
-
* password: 'hunter2',
|
|
216
|
-
* });
|
|
217
|
-
* // Store session.sessionId safely — you'll need it for other calls.
|
|
218
|
-
* ```
|
|
219
|
-
*/
|
|
220
214
|
async login(options) {
|
|
221
|
-
const result = await this.
|
|
215
|
+
const result = await this.post(`${this.basePath}/login`, options);
|
|
222
216
|
return { user: result.user, session: result.session };
|
|
223
217
|
}
|
|
224
|
-
/**
|
|
225
|
-
* Invalidate a session (sign out).
|
|
226
|
-
*
|
|
227
|
-
* @example
|
|
228
|
-
* ```ts
|
|
229
|
-
* await auth.logout({ sessionId: session.sessionId });
|
|
230
|
-
* ```
|
|
231
|
-
*/
|
|
232
218
|
async logout({ sessionId }) {
|
|
233
|
-
await this.
|
|
219
|
+
await this.post(`${this.basePath}/logout`, { sessionId });
|
|
234
220
|
}
|
|
235
|
-
/**
|
|
236
|
-
* Extend a session using its refresh token.
|
|
237
|
-
* Returns a new session object.
|
|
238
|
-
*
|
|
239
|
-
* @example
|
|
240
|
-
* ```ts
|
|
241
|
-
* const newSession = await auth.refreshSession({ refreshToken: session.refreshToken });
|
|
242
|
-
* ```
|
|
243
|
-
*/
|
|
244
221
|
async refreshSession({ refreshToken }) {
|
|
245
|
-
const result = await this.
|
|
246
|
-
`${this.basePath}/session/refresh`,
|
|
247
|
-
{ refreshToken }
|
|
248
|
-
);
|
|
222
|
+
const result = await this.post(`${this.basePath}/session/refresh`, { refreshToken });
|
|
249
223
|
return result.session;
|
|
250
224
|
}
|
|
251
225
|
// ─── User Profile ─────────────────────────────────────────────────────────
|
|
252
|
-
/**
|
|
253
|
-
* Fetch a user by their ID.
|
|
254
|
-
*
|
|
255
|
-
* @example
|
|
256
|
-
* ```ts
|
|
257
|
-
* const user = await auth.getUser({ userId: 'usr_abc123' });
|
|
258
|
-
* ```
|
|
259
|
-
*/
|
|
260
226
|
async getUser({ userId }) {
|
|
261
|
-
const result = await this.
|
|
227
|
+
const result = await this.get(`${this.basePath}/user/${userId}`);
|
|
262
228
|
return result.user;
|
|
263
229
|
}
|
|
264
|
-
/**
|
|
265
|
-
* Update fields on a user record. Requires a valid session.
|
|
266
|
-
*
|
|
267
|
-
* @example
|
|
268
|
-
* ```ts
|
|
269
|
-
* const updated = await auth.updateUser({
|
|
270
|
-
* sessionId: session.sessionId,
|
|
271
|
-
* userId: user.id,
|
|
272
|
-
* data: { fullName: 'Alice Smith', plan: 'enterprise' },
|
|
273
|
-
* });
|
|
274
|
-
* ```
|
|
275
|
-
*/
|
|
276
230
|
async updateUser(options) {
|
|
277
231
|
const { sessionId, userId, data } = options;
|
|
278
|
-
const result = await this.
|
|
279
|
-
`${this.basePath}/user`,
|
|
280
|
-
{ sessionId, userId, ...data }
|
|
281
|
-
);
|
|
232
|
+
const result = await this.patch(`${this.basePath}/user`, { sessionId, userId, ...data });
|
|
282
233
|
return result.user;
|
|
283
234
|
}
|
|
284
|
-
/**
|
|
285
|
-
* Soft-delete a user. The record is marked deleted but not removed from storage.
|
|
286
|
-
* Requires a valid session (the user can delete themselves, or an admin can delete any user).
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* ```ts
|
|
290
|
-
* await auth.deleteUser({ sessionId: session.sessionId, userId: user.id });
|
|
291
|
-
* ```
|
|
292
|
-
*/
|
|
293
235
|
async deleteUser({ sessionId, userId }) {
|
|
294
|
-
await this.
|
|
236
|
+
await this.delete(`${this.basePath}/user`, { sessionId, userId });
|
|
295
237
|
}
|
|
296
238
|
// ─── Admin Operations ─────────────────────────────────────────────────────
|
|
297
|
-
/**
|
|
298
|
-
* List all users in the bucket. **Admin session required.**
|
|
299
|
-
*
|
|
300
|
-
* @example
|
|
301
|
-
* ```ts
|
|
302
|
-
* const { users, total } = await auth.listUsers({
|
|
303
|
-
* sessionId: adminSession.sessionId,
|
|
304
|
-
* limit: 50,
|
|
305
|
-
* offset: 0,
|
|
306
|
-
* });
|
|
307
|
-
* ```
|
|
308
|
-
*/
|
|
309
239
|
async listUsers(options) {
|
|
310
240
|
const { sessionId, limit = 50, offset = 0 } = options;
|
|
311
|
-
const result = await this.
|
|
241
|
+
const result = await this.post(
|
|
312
242
|
`${this.basePath}/users/list`,
|
|
313
243
|
{ sessionId, limit, offset }
|
|
314
244
|
);
|
|
315
245
|
return { users: result.users, total: result.total, limit: result.limit, offset: result.offset };
|
|
316
246
|
}
|
|
317
|
-
/**
|
|
318
|
-
* Permanently hard-delete a user and all their data. **Admin session required.**
|
|
319
|
-
* This action is irreversible.
|
|
320
|
-
*
|
|
321
|
-
* @example
|
|
322
|
-
* ```ts
|
|
323
|
-
* await auth.hardDeleteUser({ sessionId: adminSession.sessionId, userId: user.id });
|
|
324
|
-
* ```
|
|
325
|
-
*/
|
|
326
247
|
async hardDeleteUser({ sessionId, userId }) {
|
|
327
|
-
await this.
|
|
248
|
+
await this.delete(`${this.basePath}/user/hard`, { sessionId, userId });
|
|
328
249
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
*
|
|
332
|
-
* @example
|
|
333
|
-
* ```ts
|
|
334
|
-
* const result = await auth.bulkDeleteUsers({
|
|
335
|
-
* sessionId: adminSession.sessionId,
|
|
336
|
-
* userIds: ['usr_a', 'usr_b'],
|
|
337
|
-
* });
|
|
338
|
-
* ```
|
|
339
|
-
*/
|
|
340
|
-
async bulkDeleteUsers({
|
|
341
|
-
sessionId,
|
|
342
|
-
userIds
|
|
343
|
-
}) {
|
|
344
|
-
const result = await this.http.post(
|
|
250
|
+
async bulkDeleteUsers({ sessionId, userIds }) {
|
|
251
|
+
const result = await this.post(
|
|
345
252
|
`${this.basePath}/users/bulk-delete`,
|
|
346
253
|
{ sessionId, userIds }
|
|
347
254
|
);
|
|
348
255
|
return { deleted: result.deleted, failed: result.failed };
|
|
349
256
|
}
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
* @example
|
|
356
|
-
* ```ts
|
|
357
|
-
* await auth.lockAccount({
|
|
358
|
-
* sessionId: adminSession.sessionId,
|
|
359
|
-
* userId: user.id,
|
|
360
|
-
* duration: 60 * 60 * 1000, // 1 hour
|
|
361
|
-
* });
|
|
362
|
-
* ```
|
|
363
|
-
*/
|
|
364
|
-
async lockAccount({
|
|
365
|
-
sessionId,
|
|
366
|
-
userId,
|
|
367
|
-
duration
|
|
368
|
-
}) {
|
|
369
|
-
const result = await this.http.post(`${this.basePath}/account/lock`, { sessionId, userId, duration });
|
|
257
|
+
async lockAccount({ sessionId, userId, duration }) {
|
|
258
|
+
const result = await this.post(
|
|
259
|
+
`${this.basePath}/account/lock`,
|
|
260
|
+
{ sessionId, userId, duration }
|
|
261
|
+
);
|
|
370
262
|
return result.data;
|
|
371
263
|
}
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
*
|
|
375
|
-
* @example
|
|
376
|
-
* ```ts
|
|
377
|
-
* await auth.unlockAccount({ sessionId: adminSession.sessionId, userId: user.id });
|
|
378
|
-
* ```
|
|
379
|
-
*/
|
|
380
|
-
async unlockAccount({
|
|
381
|
-
sessionId,
|
|
382
|
-
userId
|
|
383
|
-
}) {
|
|
384
|
-
await this.http.post(`${this.basePath}/account/unlock`, { sessionId, userId });
|
|
264
|
+
async unlockAccount({ sessionId, userId }) {
|
|
265
|
+
await this.post(`${this.basePath}/account/unlock`, { sessionId, userId });
|
|
385
266
|
}
|
|
386
267
|
// ─── Password Management ──────────────────────────────────────────────────
|
|
387
|
-
/**
|
|
388
|
-
* Change a user's password. The user must supply their current password.
|
|
389
|
-
*
|
|
390
|
-
* @example
|
|
391
|
-
* ```ts
|
|
392
|
-
* await auth.changePassword({
|
|
393
|
-
* sessionId: session.sessionId,
|
|
394
|
-
* userId: user.id,
|
|
395
|
-
* currentPassword: 'hunter2',
|
|
396
|
-
* newPassword: 'correcthorsebatterystaple',
|
|
397
|
-
* });
|
|
398
|
-
* ```
|
|
399
|
-
*/
|
|
400
268
|
async changePassword(options) {
|
|
401
|
-
await this.
|
|
269
|
+
await this.post(`${this.basePath}/password/change`, options);
|
|
402
270
|
}
|
|
403
|
-
/**
|
|
404
|
-
* Request a password reset email for a user.
|
|
405
|
-
* Always returns success to prevent user enumeration.
|
|
406
|
-
*
|
|
407
|
-
* @example
|
|
408
|
-
* ```ts
|
|
409
|
-
* await auth.requestPasswordReset({ email: 'alice@example.com' });
|
|
410
|
-
* ```
|
|
411
|
-
*/
|
|
412
271
|
async requestPasswordReset({ email }) {
|
|
413
|
-
await this.
|
|
272
|
+
await this.post(`${this.basePath}/password/reset/request`, { email });
|
|
414
273
|
}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
*
|
|
418
|
-
* @example
|
|
419
|
-
* ```ts
|
|
420
|
-
* await auth.confirmPasswordReset({
|
|
421
|
-
* resetToken: 'tok_from_email',
|
|
422
|
-
* newPassword: 'correcthorsebatterystaple',
|
|
423
|
-
* });
|
|
424
|
-
* ```
|
|
425
|
-
*/
|
|
426
|
-
async confirmPasswordReset({
|
|
427
|
-
resetToken,
|
|
428
|
-
newPassword
|
|
429
|
-
}) {
|
|
430
|
-
await this.http.post(`${this.basePath}/password/reset/confirm`, {
|
|
431
|
-
resetToken,
|
|
432
|
-
newPassword
|
|
433
|
-
});
|
|
274
|
+
async confirmPasswordReset({ resetToken, newPassword }) {
|
|
275
|
+
await this.post(`${this.basePath}/password/reset/confirm`, { resetToken, newPassword });
|
|
434
276
|
}
|
|
435
277
|
// ─── Email Verification ───────────────────────────────────────────────────
|
|
436
|
-
/**
|
|
437
|
-
* Send (or resend) an email verification message to a user.
|
|
438
|
-
*
|
|
439
|
-
* @example
|
|
440
|
-
* ```ts
|
|
441
|
-
* await auth.requestEmailVerification({ userId: user.id });
|
|
442
|
-
* ```
|
|
443
|
-
*/
|
|
444
278
|
async requestEmailVerification({ userId }) {
|
|
445
|
-
await this.
|
|
279
|
+
await this.post(`${this.basePath}/email/verify/request`, { userId });
|
|
446
280
|
}
|
|
447
|
-
/**
|
|
448
|
-
* Confirm an email address using the token from the verification email.
|
|
449
|
-
*
|
|
450
|
-
* @example
|
|
451
|
-
* ```ts
|
|
452
|
-
* await auth.confirmEmailVerification({ verifyToken: 'tok_from_email' });
|
|
453
|
-
* ```
|
|
454
|
-
*/
|
|
455
281
|
async confirmEmailVerification({ verifyToken }) {
|
|
456
|
-
await this.
|
|
282
|
+
await this.post(`${this.basePath}/email/verify/confirm`, { verifyToken });
|
|
457
283
|
}
|
|
458
284
|
};
|
|
459
285
|
|
|
@@ -513,149 +339,62 @@ function assertSafeName(name, label = "name") {
|
|
|
513
339
|
|
|
514
340
|
// src/records/client.ts
|
|
515
341
|
var RecordsClient = class {
|
|
516
|
-
constructor(http, bucketKey) {
|
|
342
|
+
constructor(http, bucketSecurityKey, bucketKey) {
|
|
517
343
|
assertSafeName(bucketKey, "bucketKey");
|
|
518
344
|
this.http = http;
|
|
519
345
|
this.bucketKey = bucketKey;
|
|
346
|
+
this.bucketKey_ = bucketSecurityKey;
|
|
520
347
|
this.basePath = `/records/${bucketKey}`;
|
|
521
348
|
}
|
|
349
|
+
get key() {
|
|
350
|
+
return this.bucketKey_;
|
|
351
|
+
}
|
|
522
352
|
// ─── Single Record Operations ─────────────────────────────────────────────
|
|
523
|
-
/**
|
|
524
|
-
* Create a new record.
|
|
525
|
-
*
|
|
526
|
-
* @example
|
|
527
|
-
* ```ts
|
|
528
|
-
* const user = await records.create({
|
|
529
|
-
* name: 'Alice',
|
|
530
|
-
* email: 'alice@example.com',
|
|
531
|
-
* score: 100,
|
|
532
|
-
* });
|
|
533
|
-
* console.log(user.id); // "rec_xxxxxxxx"
|
|
534
|
-
* ```
|
|
535
|
-
*/
|
|
536
353
|
async create(data) {
|
|
537
|
-
const result = await this.http.post(this.basePath, data);
|
|
354
|
+
const result = await this.http.post(this.basePath, this.key, data);
|
|
538
355
|
return result.record ?? result.data;
|
|
539
356
|
}
|
|
540
|
-
/**
|
|
541
|
-
* Fetch a single record by ID.
|
|
542
|
-
*
|
|
543
|
-
* @example
|
|
544
|
-
* ```ts
|
|
545
|
-
* const post = await records.get('rec_abc123');
|
|
546
|
-
* ```
|
|
547
|
-
*/
|
|
548
357
|
async get(id) {
|
|
549
|
-
const result = await this.http.get(`${this.basePath}/${id}
|
|
358
|
+
const result = await this.http.get(`${this.basePath}/${id}`, this.key);
|
|
550
359
|
return result.record ?? result.data;
|
|
551
360
|
}
|
|
552
|
-
/**
|
|
553
|
-
* Overwrite a record entirely (full replace).
|
|
554
|
-
*
|
|
555
|
-
* @example
|
|
556
|
-
* ```ts
|
|
557
|
-
* const updated = await records.set('rec_abc123', {
|
|
558
|
-
* name: 'Alice Updated',
|
|
559
|
-
* email: 'alice2@example.com',
|
|
560
|
-
* });
|
|
561
|
-
* ```
|
|
562
|
-
*/
|
|
563
361
|
async set(id, data) {
|
|
564
|
-
const result = await this.http.put(`${this.basePath}/${id}`, data);
|
|
362
|
+
const result = await this.http.put(`${this.basePath}/${id}`, this.key, data);
|
|
565
363
|
return result.record ?? result.data;
|
|
566
364
|
}
|
|
567
|
-
/**
|
|
568
|
-
* Partially update a record (merge by default).
|
|
569
|
-
*
|
|
570
|
-
* @example
|
|
571
|
-
* ```ts
|
|
572
|
-
* // Merge: only the provided fields are updated
|
|
573
|
-
* const updated = await records.patch('rec_abc123', { score: 200 });
|
|
574
|
-
*
|
|
575
|
-
* // Replace: equivalent to set()
|
|
576
|
-
* const replaced = await records.patch('rec_abc123', { score: 200 }, { merge: false });
|
|
577
|
-
* ```
|
|
578
|
-
*/
|
|
579
365
|
async patch(id, data, options = {}) {
|
|
580
366
|
const { merge = true } = options;
|
|
581
367
|
const result = await this.http.patch(
|
|
582
368
|
`${this.basePath}/${id}`,
|
|
369
|
+
this.key,
|
|
583
370
|
{ ...data, _merge: merge }
|
|
584
371
|
);
|
|
585
372
|
return result.record ?? result.data;
|
|
586
373
|
}
|
|
587
|
-
/**
|
|
588
|
-
* Delete a record permanently.
|
|
589
|
-
*
|
|
590
|
-
* @example
|
|
591
|
-
* ```ts
|
|
592
|
-
* await records.delete('rec_abc123');
|
|
593
|
-
* ```
|
|
594
|
-
*/
|
|
595
374
|
async delete(id) {
|
|
596
|
-
await this.http.delete(`${this.basePath}/${id}
|
|
375
|
+
await this.http.delete(`${this.basePath}/${id}`, this.key);
|
|
597
376
|
}
|
|
598
377
|
// ─── Batch Operations ─────────────────────────────────────────────────────
|
|
599
|
-
/**
|
|
600
|
-
* Create multiple records in one request.
|
|
601
|
-
*
|
|
602
|
-
* @example
|
|
603
|
-
* ```ts
|
|
604
|
-
* const created = await records.batchCreate([
|
|
605
|
-
* { name: 'Alice', score: 100 },
|
|
606
|
-
* { name: 'Bob', score: 200 },
|
|
607
|
-
* ]);
|
|
608
|
-
* ```
|
|
609
|
-
*/
|
|
610
378
|
async batchCreate(items) {
|
|
611
379
|
const result = await this.http.post(
|
|
612
380
|
`${this.basePath}/batch`,
|
|
381
|
+
this.key,
|
|
613
382
|
{ records: items }
|
|
614
383
|
);
|
|
615
384
|
return result.records;
|
|
616
385
|
}
|
|
617
|
-
/**
|
|
618
|
-
* Delete multiple records by ID in one request.
|
|
619
|
-
*
|
|
620
|
-
* @example
|
|
621
|
-
* ```ts
|
|
622
|
-
* await records.batchDelete(['rec_a', 'rec_b', 'rec_c']);
|
|
623
|
-
* ```
|
|
624
|
-
*/
|
|
625
386
|
async batchDelete(ids) {
|
|
626
|
-
const result = await this.http.post(
|
|
387
|
+
const result = await this.http.post(
|
|
388
|
+
`${this.basePath}/batch/delete`,
|
|
389
|
+
this.key,
|
|
390
|
+
{ ids }
|
|
391
|
+
);
|
|
627
392
|
return { deleted: result.deleted, failed: result.failed };
|
|
628
393
|
}
|
|
629
394
|
// ─── Querying ─────────────────────────────────────────────────────────────
|
|
630
|
-
/**
|
|
631
|
-
* Query records with optional filters, sorting, and pagination.
|
|
632
|
-
*
|
|
633
|
-
* @example
|
|
634
|
-
* ```ts
|
|
635
|
-
* // Simple query
|
|
636
|
-
* const { records } = await posts.query({ limit: 10 });
|
|
637
|
-
*
|
|
638
|
-
* // Filtered query with cursor pagination
|
|
639
|
-
* const page1 = await posts.query({
|
|
640
|
-
* filters: [
|
|
641
|
-
* { field: 'status', op: '==', value: 'published' },
|
|
642
|
-
* { field: 'views', op: '>', value: 1000 },
|
|
643
|
-
* ],
|
|
644
|
-
* orderBy: 'createdAt',
|
|
645
|
-
* order: 'desc',
|
|
646
|
-
* limit: 20,
|
|
647
|
-
* });
|
|
648
|
-
*
|
|
649
|
-
* const page2 = await posts.query({
|
|
650
|
-
* filters: [{ field: 'status', op: '==', value: 'published' }],
|
|
651
|
-
* limit: 20,
|
|
652
|
-
* startAfter: page1.nextCursor,
|
|
653
|
-
* });
|
|
654
|
-
* ```
|
|
655
|
-
*/
|
|
656
395
|
async query(options = {}) {
|
|
657
396
|
const qs = buildQueryParams(options);
|
|
658
|
-
const result = await this.http.get(`${this.basePath}${qs}
|
|
397
|
+
const result = await this.http.get(`${this.basePath}${qs}`, this.key);
|
|
659
398
|
return {
|
|
660
399
|
records: result.records,
|
|
661
400
|
total: result.total,
|
|
@@ -663,60 +402,27 @@ var RecordsClient = class {
|
|
|
663
402
|
nextCursor: result.nextCursor
|
|
664
403
|
};
|
|
665
404
|
}
|
|
666
|
-
/**
|
|
667
|
-
* Convenience alias: get all records up to `limit` (default 100).
|
|
668
|
-
*
|
|
669
|
-
* @example
|
|
670
|
-
* ```ts
|
|
671
|
-
* const allPosts = await posts.getAll({ limit: 500 });
|
|
672
|
-
* ```
|
|
673
|
-
*/
|
|
674
405
|
async getAll(options = {}) {
|
|
675
406
|
const { records } = await this.query(options);
|
|
676
407
|
return records;
|
|
677
408
|
}
|
|
678
|
-
/**
|
|
679
|
-
* Count records matching optional filters.
|
|
680
|
-
*
|
|
681
|
-
* @example
|
|
682
|
-
* ```ts
|
|
683
|
-
* const total = await posts.count([{ field: 'status', op: '==', value: 'published' }]);
|
|
684
|
-
* ```
|
|
685
|
-
*/
|
|
686
409
|
async count(filters = []) {
|
|
687
410
|
const result = await this.http.post(
|
|
688
411
|
`${this.basePath}/count`,
|
|
412
|
+
this.key,
|
|
689
413
|
{ filters }
|
|
690
414
|
);
|
|
691
415
|
return result.count;
|
|
692
416
|
}
|
|
693
417
|
// ─── Version History ──────────────────────────────────────────────────────
|
|
694
|
-
/**
|
|
695
|
-
* Retrieve the full version history of a record.
|
|
696
|
-
* Each write creates a new version stored in GCS.
|
|
697
|
-
*
|
|
698
|
-
* @example
|
|
699
|
-
* ```ts
|
|
700
|
-
* const history = await records.getHistory('rec_abc123');
|
|
701
|
-
* console.log(history[0].data); // latest version
|
|
702
|
-
* ```
|
|
703
|
-
*/
|
|
704
418
|
async getHistory(id) {
|
|
705
|
-
const result = await this.http.get(`${this.basePath}/${id}/history
|
|
419
|
+
const result = await this.http.get(`${this.basePath}/${id}/history`, this.key);
|
|
706
420
|
return result.history;
|
|
707
421
|
}
|
|
708
|
-
/**
|
|
709
|
-
* Restore a record to a specific historical version.
|
|
710
|
-
*
|
|
711
|
-
* @example
|
|
712
|
-
* ```ts
|
|
713
|
-
* const history = await records.getHistory('rec_abc123');
|
|
714
|
-
* const restored = await records.restoreVersion('rec_abc123', history[2].version);
|
|
715
|
-
* ```
|
|
716
|
-
*/
|
|
717
422
|
async restoreVersion(id, version) {
|
|
718
423
|
const result = await this.http.post(
|
|
719
424
|
`${this.basePath}/${id}/restore`,
|
|
425
|
+
this.key,
|
|
720
426
|
{ version }
|
|
721
427
|
);
|
|
722
428
|
return result.record ?? result.data;
|
|
@@ -725,187 +431,53 @@ var RecordsClient = class {
|
|
|
725
431
|
|
|
726
432
|
// src/analytics/client.ts
|
|
727
433
|
var AnalyticsClient = class {
|
|
728
|
-
constructor(http, bucketKey) {
|
|
434
|
+
constructor(http, bucketSecurityKey, bucketKey) {
|
|
729
435
|
assertSafeName(bucketKey, "bucketKey");
|
|
730
436
|
this.http = http;
|
|
731
|
-
this.
|
|
437
|
+
this.bucketSecurityKey = bucketSecurityKey;
|
|
732
438
|
this.basePath = `/analytics/${bucketKey}`;
|
|
733
439
|
}
|
|
734
|
-
/** Internal dispatcher — all queries POST to the same endpoint. */
|
|
735
440
|
async run(query) {
|
|
736
|
-
const result = await this.http.post(
|
|
441
|
+
const result = await this.http.post(
|
|
442
|
+
this.basePath,
|
|
443
|
+
this.bucketSecurityKey,
|
|
444
|
+
query
|
|
445
|
+
);
|
|
737
446
|
return result.data;
|
|
738
447
|
}
|
|
739
|
-
// ─── Count ────────────────────────────────────────────────────────────────
|
|
740
|
-
/**
|
|
741
|
-
* Count the total number of records in the bucket, with optional date filter.
|
|
742
|
-
*
|
|
743
|
-
* @example
|
|
744
|
-
* ```ts
|
|
745
|
-
* const { count } = await analytics.count();
|
|
746
|
-
* // → { count: 4821 }
|
|
747
|
-
*
|
|
748
|
-
* // Count only this month's records
|
|
749
|
-
* const { count } = await analytics.count({
|
|
750
|
-
* dateRange: { start: new Date('2025-01-01').getTime(), end: Date.now() },
|
|
751
|
-
* });
|
|
752
|
-
* ```
|
|
753
|
-
*/
|
|
754
448
|
async count(opts = {}) {
|
|
755
449
|
return this.run({ queryType: "count", ...opts });
|
|
756
450
|
}
|
|
757
|
-
// ─── Distribution ─────────────────────────────────────────────────────────
|
|
758
|
-
/**
|
|
759
|
-
* Count how many records have each unique value for a given field.
|
|
760
|
-
* Great for pie charts and bar charts.
|
|
761
|
-
*
|
|
762
|
-
* @example
|
|
763
|
-
* ```ts
|
|
764
|
-
* const rows = await analytics.distribution({
|
|
765
|
-
* field: 'status',
|
|
766
|
-
* limit: 10,
|
|
767
|
-
* order: 'desc',
|
|
768
|
-
* });
|
|
769
|
-
* // → [{ value: 'completed', count: 312 }, { value: 'pending', count: 88 }, ...]
|
|
770
|
-
* ```
|
|
771
|
-
*/
|
|
772
451
|
async distribution(opts) {
|
|
773
452
|
assertSafeName(opts.field, "field");
|
|
774
453
|
return this.run({ queryType: "distribution", ...opts });
|
|
775
454
|
}
|
|
776
|
-
// ─── Sum ──────────────────────────────────────────────────────────────────
|
|
777
|
-
/**
|
|
778
|
-
* Sum a numeric field, optionally grouped by another field.
|
|
779
|
-
*
|
|
780
|
-
* @example
|
|
781
|
-
* ```ts
|
|
782
|
-
* // Total revenue
|
|
783
|
-
* const rows = await analytics.sum({ field: 'amount' });
|
|
784
|
-
* // → [{ sum: 198432.50 }]
|
|
785
|
-
*
|
|
786
|
-
* // Revenue by country
|
|
787
|
-
* const rows = await analytics.sum({ field: 'amount', groupBy: 'country', limit: 10 });
|
|
788
|
-
* // → [{ group: 'US', sum: 120000 }, { group: 'UK', sum: 45000 }, ...]
|
|
789
|
-
* ```
|
|
790
|
-
*/
|
|
791
455
|
async sum(opts) {
|
|
792
456
|
assertSafeName(opts.field, "field");
|
|
793
457
|
if (opts.groupBy) assertSafeName(opts.groupBy, "groupBy");
|
|
794
458
|
return this.run({ queryType: "sum", ...opts });
|
|
795
459
|
}
|
|
796
|
-
// ─── Time Series ──────────────────────────────────────────────────────────
|
|
797
|
-
/**
|
|
798
|
-
* Count records created over time, grouped by a time granularity.
|
|
799
|
-
* Perfect for line charts and activity graphs.
|
|
800
|
-
*
|
|
801
|
-
* @example
|
|
802
|
-
* ```ts
|
|
803
|
-
* const rows = await analytics.timeSeries({
|
|
804
|
-
* granularity: 'day',
|
|
805
|
-
* dateRange: { start: Date.now() - 7 * 86400000, end: Date.now() },
|
|
806
|
-
* });
|
|
807
|
-
* // → [{ date: '2025-06-01', count: 42 }, { date: '2025-06-02', count: 67 }, ...]
|
|
808
|
-
* ```
|
|
809
|
-
*/
|
|
810
460
|
async timeSeries(opts = {}) {
|
|
811
461
|
return this.run({ queryType: "timeSeries", granularity: "day", ...opts });
|
|
812
462
|
}
|
|
813
|
-
// ─── Field Time Series ────────────────────────────────────────────────────
|
|
814
|
-
/**
|
|
815
|
-
* Aggregate a numeric field over time (e.g. daily revenue, hourly signups).
|
|
816
|
-
*
|
|
817
|
-
* @example
|
|
818
|
-
* ```ts
|
|
819
|
-
* const rows = await analytics.fieldTimeSeries({
|
|
820
|
-
* field: 'amount',
|
|
821
|
-
* aggregation: 'sum',
|
|
822
|
-
* granularity: 'week',
|
|
823
|
-
* });
|
|
824
|
-
* // → [{ date: '2025-W22', value: 14230.50 }, ...]
|
|
825
|
-
* ```
|
|
826
|
-
*/
|
|
827
463
|
async fieldTimeSeries(opts) {
|
|
828
464
|
assertSafeName(opts.field, "field");
|
|
829
|
-
return this.run({
|
|
830
|
-
queryType: "fieldTimeSeries",
|
|
831
|
-
aggregation: "sum",
|
|
832
|
-
granularity: "day",
|
|
833
|
-
...opts
|
|
834
|
-
});
|
|
465
|
+
return this.run({ queryType: "fieldTimeSeries", aggregation: "sum", granularity: "day", ...opts });
|
|
835
466
|
}
|
|
836
|
-
// ─── Top N ────────────────────────────────────────────────────────────────
|
|
837
|
-
/**
|
|
838
|
-
* Get the top N values by frequency for a field.
|
|
839
|
-
* Optionally pair with a `labelField` for human-readable labels.
|
|
840
|
-
*
|
|
841
|
-
* @example
|
|
842
|
-
* ```ts
|
|
843
|
-
* // Top 5 most purchased products
|
|
844
|
-
* const rows = await analytics.topN({
|
|
845
|
-
* field: 'productId',
|
|
846
|
-
* labelField: 'productName',
|
|
847
|
-
* n: 5,
|
|
848
|
-
* });
|
|
849
|
-
* // → [{ value: 'prod_123', label: 'Widget Pro', count: 892 }, ...]
|
|
850
|
-
* ```
|
|
851
|
-
*/
|
|
852
467
|
async topN(opts) {
|
|
853
468
|
assertSafeName(opts.field, "field");
|
|
854
469
|
if (opts.labelField) assertSafeName(opts.labelField, "labelField");
|
|
855
470
|
return this.run({ queryType: "topN", n: 10, order: "desc", ...opts });
|
|
856
471
|
}
|
|
857
|
-
// ─── Field Stats ──────────────────────────────────────────────────────────
|
|
858
|
-
/**
|
|
859
|
-
* Get statistical summary (min, max, avg, sum, count, stddev) for a numeric field.
|
|
860
|
-
*
|
|
861
|
-
* @example
|
|
862
|
-
* ```ts
|
|
863
|
-
* const stats = await analytics.stats({ field: 'orderValue' });
|
|
864
|
-
* // → { min: 4.99, max: 9999.99, avg: 87.23, sum: 420948.27, count: 4823, stddev: 143.2 }
|
|
865
|
-
* ```
|
|
866
|
-
*/
|
|
867
472
|
async stats(opts) {
|
|
868
473
|
assertSafeName(opts.field, "field");
|
|
869
474
|
return this.run({ queryType: "stats", ...opts });
|
|
870
475
|
}
|
|
871
|
-
// ─── Filtered Records ─────────────────────────────────────────────────────
|
|
872
|
-
/**
|
|
873
|
-
* Query raw records with filters, field selection, and pagination.
|
|
874
|
-
* This is the analytics version of `records.query()` but powered by BigQuery.
|
|
875
|
-
*
|
|
876
|
-
* @example
|
|
877
|
-
* ```ts
|
|
878
|
-
* const { records } = await analytics.records({
|
|
879
|
-
* filters: [{ field: 'status', op: '==', value: 'refunded' }],
|
|
880
|
-
* selectFields: ['orderId', 'amount', 'createdAt'],
|
|
881
|
-
* limit: 50,
|
|
882
|
-
* orderBy: 'amount',
|
|
883
|
-
* order: 'desc',
|
|
884
|
-
* });
|
|
885
|
-
* ```
|
|
886
|
-
*/
|
|
887
476
|
async records(opts = {}) {
|
|
888
477
|
if (opts.orderBy) assertSafeName(opts.orderBy, "orderBy");
|
|
889
478
|
if (opts.selectFields) opts.selectFields.forEach((f) => assertSafeName(f, "selectField"));
|
|
890
479
|
return this.run({ queryType: "records", limit: 100, order: "desc", ...opts });
|
|
891
480
|
}
|
|
892
|
-
// ─── Multi Metric ─────────────────────────────────────────────────────────
|
|
893
|
-
/**
|
|
894
|
-
* Calculate multiple aggregations in a single query.
|
|
895
|
-
* Ideal for dashboards that need several numbers at once.
|
|
896
|
-
*
|
|
897
|
-
* @example
|
|
898
|
-
* ```ts
|
|
899
|
-
* const result = await analytics.multiMetric({
|
|
900
|
-
* metrics: [
|
|
901
|
-
* { field: 'amount', name: 'totalRevenue', aggregation: 'sum' },
|
|
902
|
-
* { field: 'amount', name: 'avgOrderValue', aggregation: 'avg' },
|
|
903
|
-
* { field: 'userId', name: 'uniqueCustomers', aggregation: 'count' },
|
|
904
|
-
* ],
|
|
905
|
-
* });
|
|
906
|
-
* // → { totalRevenue: 198432.50, avgOrderValue: 87.23, uniqueCustomers: 2275 }
|
|
907
|
-
* ```
|
|
908
|
-
*/
|
|
909
481
|
async multiMetric(opts) {
|
|
910
482
|
opts.metrics.forEach((m) => {
|
|
911
483
|
assertSafeName(m.field, "metric.field");
|
|
@@ -913,58 +485,14 @@ var AnalyticsClient = class {
|
|
|
913
485
|
});
|
|
914
486
|
return this.run({ queryType: "multiMetric", ...opts });
|
|
915
487
|
}
|
|
916
|
-
// ─── Storage Stats ────────────────────────────────────────────────────────
|
|
917
|
-
/**
|
|
918
|
-
* Get storage statistics for this bucket: record counts, byte sizes.
|
|
919
|
-
*
|
|
920
|
-
* @example
|
|
921
|
-
* ```ts
|
|
922
|
-
* const stats = await analytics.storageStats();
|
|
923
|
-
* // → { totalRecords: 4821, totalBytes: 48293820, avgBytes: 10015, ... }
|
|
924
|
-
* ```
|
|
925
|
-
*/
|
|
926
488
|
async storageStats(opts = {}) {
|
|
927
489
|
return this.run({ queryType: "storageStats", ...opts });
|
|
928
490
|
}
|
|
929
|
-
// ─── Cross-Bucket Comparison ──────────────────────────────────────────────
|
|
930
|
-
/**
|
|
931
|
-
* Compare the same field aggregation across multiple buckets in one query.
|
|
932
|
-
* Your security key must have read access to ALL listed buckets.
|
|
933
|
-
*
|
|
934
|
-
* @example
|
|
935
|
-
* ```ts
|
|
936
|
-
* const rows = await analytics.crossBucket({
|
|
937
|
-
* bucketKeys: ['orders-us', 'orders-eu', 'orders-apac'],
|
|
938
|
-
* field: 'amount',
|
|
939
|
-
* aggregation: 'sum',
|
|
940
|
-
* });
|
|
941
|
-
* // → [
|
|
942
|
-
* // { bucket: 'orders-us', value: 120000 },
|
|
943
|
-
* // { bucket: 'orders-eu', value: 45000 },
|
|
944
|
-
* // { bucket: 'orders-apac', value: 33000 },
|
|
945
|
-
* // ]
|
|
946
|
-
* ```
|
|
947
|
-
*/
|
|
948
491
|
async crossBucket(opts) {
|
|
949
492
|
assertSafeName(opts.field, "field");
|
|
950
493
|
opts.bucketKeys.forEach((k) => assertSafeName(k, "bucketKey"));
|
|
951
494
|
return this.run({ queryType: "crossBucket", aggregation: "sum", ...opts });
|
|
952
495
|
}
|
|
953
|
-
// ─── Raw Query ────────────────────────────────────────────────────────────
|
|
954
|
-
/**
|
|
955
|
-
* Send a raw analytics query object. Use this when you need full control
|
|
956
|
-
* over the query shape or want to use a queryType not covered by the helpers.
|
|
957
|
-
*
|
|
958
|
-
* @example
|
|
959
|
-
* ```ts
|
|
960
|
-
* const result = await analytics.query({
|
|
961
|
-
* queryType: 'topN',
|
|
962
|
-
* field: 'category',
|
|
963
|
-
* n: 3,
|
|
964
|
-
* order: 'asc',
|
|
965
|
-
* });
|
|
966
|
-
* ```
|
|
967
|
-
*/
|
|
968
496
|
async query(query) {
|
|
969
497
|
const data = await this.run(query);
|
|
970
498
|
return { queryType: query.queryType, data };
|
|
@@ -1459,10 +987,6 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1459
987
|
getUploadUrl(opts) {
|
|
1460
988
|
return this.manager.getUploadUrl({ ...opts, path: this.scopedPath(opts.path) });
|
|
1461
989
|
}
|
|
1462
|
-
/** Upload data directly to a signed GCS URL with optional progress tracking. */
|
|
1463
|
-
uploadToSignedUrl(signedUrl, data, mimeType, onProgress) {
|
|
1464
|
-
return this.manager.uploadToSignedUrl(signedUrl, data, mimeType, onProgress);
|
|
1465
|
-
}
|
|
1466
990
|
/** Confirm a direct upload within the scoped folder. */
|
|
1467
991
|
confirmUpload(opts) {
|
|
1468
992
|
return this.manager.confirmUpload({ ...opts, path: this.scopedPath(opts.path) });
|
|
@@ -1471,9 +995,16 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1471
995
|
download(path) {
|
|
1472
996
|
return this.manager.download(this.scopedPath(path));
|
|
1473
997
|
}
|
|
1474
|
-
/**
|
|
998
|
+
/**
|
|
999
|
+
* List files within the scoped folder.
|
|
1000
|
+
* `prefix` in options is relative to the scope.
|
|
1001
|
+
*/
|
|
1475
1002
|
list(opts = {}) {
|
|
1476
|
-
|
|
1003
|
+
const scopedOpts = {
|
|
1004
|
+
...opts,
|
|
1005
|
+
prefix: this.scopedPath(opts.prefix ?? "")
|
|
1006
|
+
};
|
|
1007
|
+
return this.manager.list(scopedOpts);
|
|
1477
1008
|
}
|
|
1478
1009
|
/** Get metadata for a file within the scoped folder. */
|
|
1479
1010
|
getMetadata(path) {
|
|
@@ -1513,7 +1044,7 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1513
1044
|
* @example
|
|
1514
1045
|
* ```ts
|
|
1515
1046
|
* const uploads = db.storage.scope('user-uploads');
|
|
1516
|
-
* const images
|
|
1047
|
+
* const images = uploads.scope('images'); // → "user-uploads/images/"
|
|
1517
1048
|
* ```
|
|
1518
1049
|
*/
|
|
1519
1050
|
scope(subPrefix) {
|
|
@@ -1524,66 +1055,67 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1524
1055
|
// src/client.ts
|
|
1525
1056
|
var HydrousClient = class {
|
|
1526
1057
|
constructor(config) {
|
|
1527
|
-
this._storage = null;
|
|
1528
1058
|
this._recordsCache = /* @__PURE__ */ new Map();
|
|
1529
1059
|
this._authCache = /* @__PURE__ */ new Map();
|
|
1530
1060
|
this._analyticsCache = /* @__PURE__ */ new Map();
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1061
|
+
this._storageCache = /* @__PURE__ */ new Map();
|
|
1062
|
+
if (!config.authKey) {
|
|
1063
|
+
throw new Error("[HydrousDB] authKey is required. Get yours from https://hydrousdb.com/dashboard.");
|
|
1064
|
+
}
|
|
1065
|
+
if (!config.bucketSecurityKey) {
|
|
1066
|
+
throw new Error("[HydrousDB] bucketSecurityKey is required. Get yours from https://hydrousdb.com/dashboard.");
|
|
1067
|
+
}
|
|
1068
|
+
if (!config.storageKeys || Object.keys(config.storageKeys).length === 0) {
|
|
1069
|
+
throw new Error("[HydrousDB] storageKeys is required. Define at least one storage key from https://hydrousdb.com/dashboard.");
|
|
1535
1070
|
}
|
|
1536
1071
|
const baseUrl = config.baseUrl ?? DEFAULT_BASE_URL;
|
|
1537
|
-
this.http = new HttpClient(baseUrl
|
|
1538
|
-
this.
|
|
1072
|
+
this.http = new HttpClient(baseUrl);
|
|
1073
|
+
this.authKey_ = config.authKey;
|
|
1074
|
+
this.bucketSecurityKey_ = config.bucketSecurityKey;
|
|
1075
|
+
this.storageKeys_ = config.storageKeys;
|
|
1539
1076
|
}
|
|
1540
1077
|
// ─── Records ─────────────────────────────────────────────────────────────
|
|
1541
1078
|
/**
|
|
1542
|
-
* Get a typed records client for the
|
|
1543
|
-
*
|
|
1544
|
-
* The generic type parameter `T` describes the shape of records in this
|
|
1545
|
-
* bucket. Leave it unset for a generic `Record<string, unknown>` shape.
|
|
1546
|
-
*
|
|
1547
|
-
* @param bucketKey The name of your bucket (must match what you created in the dashboard).
|
|
1079
|
+
* Get a typed records client for the named bucket.
|
|
1080
|
+
* Uses your `bucketSecurityKey` automatically.
|
|
1548
1081
|
*
|
|
1549
1082
|
* @example
|
|
1550
1083
|
* ```ts
|
|
1551
|
-
* interface Post { title: string;
|
|
1084
|
+
* interface Post { title: string; published: boolean }
|
|
1552
1085
|
* const posts = db.records<Post>('blog-posts');
|
|
1553
|
-
*
|
|
1554
|
-
* const post = await posts.create({ title: 'Hello', body: '...', published: false });
|
|
1555
|
-
* // post.id, post.createdAt, post.updatedAt are added automatically
|
|
1086
|
+
* const post = await posts.create({ title: 'Hello', published: false });
|
|
1556
1087
|
* ```
|
|
1557
1088
|
*/
|
|
1558
1089
|
records(bucketKey) {
|
|
1559
1090
|
if (!this._recordsCache.has(bucketKey)) {
|
|
1560
|
-
this._recordsCache.set(
|
|
1091
|
+
this._recordsCache.set(
|
|
1092
|
+
bucketKey,
|
|
1093
|
+
new RecordsClient(this.http, this.bucketSecurityKey_, bucketKey)
|
|
1094
|
+
);
|
|
1561
1095
|
}
|
|
1562
1096
|
return this._recordsCache.get(bucketKey);
|
|
1563
1097
|
}
|
|
1564
1098
|
// ─── Auth ─────────────────────────────────────────────────────────────────
|
|
1565
1099
|
/**
|
|
1566
|
-
* Get an auth client for the
|
|
1567
|
-
*
|
|
1568
|
-
* @param bucketKey The name of your user bucket (e.g. `"app-users"`).
|
|
1100
|
+
* Get an auth client for the named user bucket.
|
|
1101
|
+
* Uses your `authKey` automatically.
|
|
1569
1102
|
*
|
|
1570
1103
|
* @example
|
|
1571
1104
|
* ```ts
|
|
1572
1105
|
* const auth = db.auth('app-users');
|
|
1573
|
-
* const { user, session } = await auth.login({ email: '
|
|
1106
|
+
* const { user, session } = await auth.login({ email: '…', password: '…' });
|
|
1574
1107
|
* ```
|
|
1575
1108
|
*/
|
|
1576
1109
|
auth(bucketKey) {
|
|
1577
1110
|
if (!this._authCache.has(bucketKey)) {
|
|
1578
|
-
this._authCache.set(bucketKey, new AuthClient(this.http, bucketKey));
|
|
1111
|
+
this._authCache.set(bucketKey, new AuthClient(this.http, this.authKey_, bucketKey));
|
|
1579
1112
|
}
|
|
1580
1113
|
return this._authCache.get(bucketKey);
|
|
1581
1114
|
}
|
|
1582
1115
|
// ─── Analytics ────────────────────────────────────────────────────────────
|
|
1583
1116
|
/**
|
|
1584
|
-
* Get an analytics client for the
|
|
1585
|
-
*
|
|
1586
|
-
* @param bucketKey The name of the bucket to analyse.
|
|
1117
|
+
* Get an analytics client for the named bucket.
|
|
1118
|
+
* Uses your `bucketSecurityKey` automatically.
|
|
1587
1119
|
*
|
|
1588
1120
|
* @example
|
|
1589
1121
|
* ```ts
|
|
@@ -1593,31 +1125,46 @@ var HydrousClient = class {
|
|
|
1593
1125
|
*/
|
|
1594
1126
|
analytics(bucketKey) {
|
|
1595
1127
|
if (!this._analyticsCache.has(bucketKey)) {
|
|
1596
|
-
this._analyticsCache.set(
|
|
1128
|
+
this._analyticsCache.set(
|
|
1129
|
+
bucketKey,
|
|
1130
|
+
new AnalyticsClient(this.http, this.bucketSecurityKey_, bucketKey)
|
|
1131
|
+
);
|
|
1597
1132
|
}
|
|
1598
1133
|
return this._analyticsCache.get(bucketKey);
|
|
1599
1134
|
}
|
|
1600
1135
|
// ─── Storage ──────────────────────────────────────────────────────────────
|
|
1601
1136
|
/**
|
|
1602
|
-
*
|
|
1137
|
+
* Get a storage manager for the named storage key.
|
|
1138
|
+
* The name must match a key you defined in `storageKeys` when calling `createClient`.
|
|
1139
|
+
* Uses the corresponding `ssk_…` key automatically via `X-Storage-Key` header.
|
|
1603
1140
|
*
|
|
1604
|
-
*
|
|
1141
|
+
* @param keyName The name of the storage key (e.g. `"avatars"`, `"documents"`, `"main"`).
|
|
1605
1142
|
*
|
|
1606
1143
|
* @example
|
|
1607
1144
|
* ```ts
|
|
1608
|
-
*
|
|
1609
|
-
* const
|
|
1145
|
+
* const avatars = db.storage('avatars');
|
|
1146
|
+
* const documents = db.storage('documents');
|
|
1610
1147
|
*
|
|
1611
|
-
* //
|
|
1612
|
-
*
|
|
1613
|
-
*
|
|
1148
|
+
* // Upload to avatars bucket
|
|
1149
|
+
* await avatars.upload(file, `${userId}.jpg`, { isPublic: true });
|
|
1150
|
+
*
|
|
1151
|
+
* // Scope to a sub-folder
|
|
1152
|
+
* const userDocs = db.storage('documents').scope(`users/${userId}`);
|
|
1153
|
+
* await userDocs.upload(pdfBuffer, 'contract.pdf');
|
|
1614
1154
|
* ```
|
|
1615
1155
|
*/
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1156
|
+
storage(keyName) {
|
|
1157
|
+
const ssk = this.storageKeys_[keyName];
|
|
1158
|
+
if (!ssk) {
|
|
1159
|
+
const available = Object.keys(this.storageKeys_).join(", ");
|
|
1160
|
+
throw new Error(
|
|
1161
|
+
`[HydrousDB] Unknown storage key name "${keyName}". Available keys: ${available}. Add it to storageKeys in your createClient() config.`
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
if (!this._storageCache.has(keyName)) {
|
|
1165
|
+
this._storageCache.set(keyName, new StorageManager(this.http, ssk));
|
|
1619
1166
|
}
|
|
1620
|
-
const mgr = this.
|
|
1167
|
+
const mgr = this._storageCache.get(keyName);
|
|
1621
1168
|
const extended = mgr;
|
|
1622
1169
|
if (!extended.scope) {
|
|
1623
1170
|
extended.scope = (prefix) => new ScopedStorage(mgr, prefix);
|