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