hydrousdb 3.2.0 → 3.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +832 -1124
- package/dist/index.cjs +618 -321
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +519 -222
- package/dist/index.d.ts +519 -222
- package/dist/index.mjs +613 -322
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -59,7 +59,7 @@ var HttpClient = class {
|
|
|
59
59
|
constructor(baseUrl) {
|
|
60
60
|
this.baseUrl = baseUrl.replace(/\/$/, "");
|
|
61
61
|
}
|
|
62
|
-
// ─── Core request
|
|
62
|
+
// ─── Core request ──────────────────────────────────────────────────────────
|
|
63
63
|
async request(path, options) {
|
|
64
64
|
const url = `${this.baseUrl}${path}`;
|
|
65
65
|
const headers = { ...options.headers };
|
|
@@ -98,11 +98,7 @@ var HttpClient = class {
|
|
|
98
98
|
try {
|
|
99
99
|
data = await response.json();
|
|
100
100
|
} catch {
|
|
101
|
-
throw new HydrousError(
|
|
102
|
-
`Failed to parse response JSON`,
|
|
103
|
-
"PARSE_ERROR",
|
|
104
|
-
response.status
|
|
105
|
-
);
|
|
101
|
+
throw new HydrousError("Failed to parse response JSON", "PARSE_ERROR", response.status);
|
|
106
102
|
}
|
|
107
103
|
if (!response.ok) {
|
|
108
104
|
throw new HydrousError(
|
|
@@ -151,8 +147,8 @@ var HttpClient = class {
|
|
|
151
147
|
});
|
|
152
148
|
}
|
|
153
149
|
/**
|
|
154
|
-
*
|
|
155
|
-
*
|
|
150
|
+
* PUT directly to a signed GCS URL.
|
|
151
|
+
* Uses XHR in browsers for onprogress support; fetch in Node.
|
|
156
152
|
*/
|
|
157
153
|
async putToSignedUrl(signedUrl, data, mimeType, onProgress) {
|
|
158
154
|
const body = data instanceof Blob ? data : data instanceof Uint8Array ? data.buffer : data;
|
|
@@ -181,135 +177,262 @@ var HttpClient = class {
|
|
|
181
177
|
}
|
|
182
178
|
};
|
|
183
179
|
|
|
180
|
+
// src/routes.ts
|
|
181
|
+
var RECORDS = {
|
|
182
|
+
/** GET|POST|PATCH|DELETE|HEAD /api/:bucketKey */
|
|
183
|
+
bucket: (bucketKey) => `/api/${bucketKey}`,
|
|
184
|
+
/** POST /api/:bucketKey/batch/insert */
|
|
185
|
+
batchInsert: (bucketKey) => `/api/${bucketKey}/batch/insert`,
|
|
186
|
+
/** POST /api/:bucketKey/batch/update */
|
|
187
|
+
batchUpdate: (bucketKey) => `/api/${bucketKey}/batch/update`,
|
|
188
|
+
/** POST /api/:bucketKey/batch/delete */
|
|
189
|
+
batchDelete: (bucketKey) => `/api/${bucketKey}/batch/delete`
|
|
190
|
+
};
|
|
191
|
+
var ANALYTICS = {
|
|
192
|
+
/** POST /api/analytics/:bucketKey */
|
|
193
|
+
query: (bucketKey) => `/api/analytics/${bucketKey}`
|
|
194
|
+
};
|
|
195
|
+
var AUTH = {
|
|
196
|
+
/** POST /api/auth/signup body: { email, password, fullName?, ...extra } */
|
|
197
|
+
signup: "/api/auth/signup",
|
|
198
|
+
/** POST /api/auth/signin body: { email, password } */
|
|
199
|
+
signin: "/api/auth/signin",
|
|
200
|
+
/** POST /api/auth/signout body: { sessionId, allDevices? } */
|
|
201
|
+
signout: "/api/auth/signout",
|
|
202
|
+
/** POST /api/auth/session/validate body: { sessionId } */
|
|
203
|
+
sessionValidate: "/api/auth/session/validate",
|
|
204
|
+
/** POST /api/auth/session/refresh body: { refreshToken } */
|
|
205
|
+
sessionRefresh: "/api/auth/session/refresh",
|
|
206
|
+
/** GET /api/auth/user?userId=... */
|
|
207
|
+
getUser: "/api/auth/user",
|
|
208
|
+
/** GET /api/auth/users?limit=&cursor= */
|
|
209
|
+
listUsers: "/api/auth/users",
|
|
210
|
+
/** PATCH /api/auth/user body: { sessionId, userId, updates: {...} } */
|
|
211
|
+
updateUser: "/api/auth/user",
|
|
212
|
+
/** DELETE /api/auth/user?userId=... body: { sessionId } */
|
|
213
|
+
deleteUser: "/api/auth/user",
|
|
214
|
+
/** DELETE /api/auth/user/hard?userId=... body: { sessionId } */
|
|
215
|
+
hardDeleteUser: "/api/auth/user/hard",
|
|
216
|
+
/** DELETE /api/auth/users/bulk body: { userIds, hard?, sessionId } */
|
|
217
|
+
bulkDeleteUsers: "/api/auth/users/bulk",
|
|
218
|
+
/** POST /api/auth/password/change body: { sessionId, userId, oldPassword, newPassword } */
|
|
219
|
+
passwordChange: "/api/auth/password/change",
|
|
220
|
+
/** POST /api/auth/password/reset/request body: { email } */
|
|
221
|
+
passwordResetRequest: "/api/auth/password/reset/request",
|
|
222
|
+
/** POST /api/auth/password/reset/confirm body: { resetToken, newPassword } */
|
|
223
|
+
passwordResetConfirm: "/api/auth/password/reset/confirm",
|
|
224
|
+
/** POST /api/auth/email/verify/request body: { userId } */
|
|
225
|
+
emailVerifyRequest: "/api/auth/email/verify/request",
|
|
226
|
+
/** POST /api/auth/email/verify/confirm body: { verifyToken } */
|
|
227
|
+
emailVerifyConfirm: "/api/auth/email/verify/confirm",
|
|
228
|
+
/** POST /api/auth/account/lock body: { sessionId, userId, duration? } */
|
|
229
|
+
accountLock: "/api/auth/account/lock",
|
|
230
|
+
/** POST /api/auth/account/unlock body: { sessionId, userId } */
|
|
231
|
+
accountUnlock: "/api/auth/account/unlock"
|
|
232
|
+
};
|
|
233
|
+
var STORAGE = {
|
|
234
|
+
/** GET /storage/info — no auth required */
|
|
235
|
+
info: "/storage/info",
|
|
236
|
+
/** GET /storage/public/:fullScopedPath — no auth required */
|
|
237
|
+
publicFile: (fullScopedPath) => `/storage/public/${fullScopedPath}`,
|
|
238
|
+
/** POST /storage/upload-url body: { path, mimeType, size, isPublic?, overwrite?, expiresIn? } */
|
|
239
|
+
uploadUrl: "/storage/upload-url",
|
|
240
|
+
/** POST /storage/batch-upload-urls body: { files: [...], expiresIn? } */
|
|
241
|
+
batchUploadUrls: "/storage/batch-upload-urls",
|
|
242
|
+
/** POST /storage/confirm body: { path, mimeType, isPublic? } */
|
|
243
|
+
confirm: "/storage/confirm",
|
|
244
|
+
/** POST /storage/batch-confirm body: { files: [...] } */
|
|
245
|
+
batchConfirm: "/storage/batch-confirm",
|
|
246
|
+
/** POST /storage/upload multipart/form-data: file, path, mimeType, isPublic, overwrite */
|
|
247
|
+
upload: "/storage/upload",
|
|
248
|
+
/** POST /storage/upload-raw body: { path, content, mimeType?, isPublic?, overwrite? } */
|
|
249
|
+
uploadRaw: "/storage/upload-raw",
|
|
250
|
+
/** GET /storage/list?prefix=&limit=&cursor= */
|
|
251
|
+
list: "/storage/list",
|
|
252
|
+
/** GET /storage/download/:path — requires X-Storage-Key */
|
|
253
|
+
download: (filePath) => `/storage/download/${filePath}`,
|
|
254
|
+
/** POST /storage/batch-download body: { paths: [...], concurrency? } */
|
|
255
|
+
batchDownload: "/storage/batch-download",
|
|
256
|
+
/** GET /storage/metadata/:path */
|
|
257
|
+
metadata: (filePath) => `/storage/metadata/${filePath}`,
|
|
258
|
+
/** POST /storage/signed-url body: { path, expiresIn? } */
|
|
259
|
+
signedUrl: "/storage/signed-url",
|
|
260
|
+
/** PATCH /storage/visibility body: { path, isPublic } */
|
|
261
|
+
visibility: "/storage/visibility",
|
|
262
|
+
/** POST /storage/folder body: { path } */
|
|
263
|
+
folder: "/storage/folder",
|
|
264
|
+
/** DELETE /storage/file body: { path } */
|
|
265
|
+
file: "/storage/file",
|
|
266
|
+
/** DELETE /storage/folder body: { path } */
|
|
267
|
+
folderDelete: "/storage/folder",
|
|
268
|
+
/** POST /storage/move body: { from, to } */
|
|
269
|
+
move: "/storage/move",
|
|
270
|
+
/** POST /storage/copy body: { from, to } */
|
|
271
|
+
copy: "/storage/copy",
|
|
272
|
+
/** GET /storage/stats */
|
|
273
|
+
stats: "/storage/stats"
|
|
274
|
+
};
|
|
275
|
+
|
|
184
276
|
// src/auth/client.ts
|
|
185
277
|
var AuthClient = class {
|
|
186
|
-
constructor(http, authKey
|
|
278
|
+
constructor(http, authKey) {
|
|
187
279
|
this.http = http;
|
|
188
280
|
this.authKey = authKey;
|
|
189
|
-
this.basePath = `/auth/${bucketKey}`;
|
|
190
281
|
}
|
|
191
282
|
post(path, body) {
|
|
192
283
|
return this.http.post(path, this.authKey, body);
|
|
193
284
|
}
|
|
194
|
-
get(path, query) {
|
|
285
|
+
get(path, query, extraHeaders) {
|
|
195
286
|
const qs = query && Object.keys(query).length ? "?" + new URLSearchParams(query).toString() : "";
|
|
196
|
-
return this.http.get(`${path}${qs}`, this.authKey);
|
|
287
|
+
return this.http.get(`${path}${qs}`, this.authKey, extraHeaders);
|
|
197
288
|
}
|
|
198
289
|
patch(path, body) {
|
|
199
290
|
return this.http.patch(path, this.authKey, body);
|
|
200
291
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
292
|
+
// ─── Signup / Login / Logout ──────────────────────────────────────────────
|
|
293
|
+
/**
|
|
294
|
+
* Register a new user account.
|
|
295
|
+
*
|
|
296
|
+
* Server: POST /api/auth/signup
|
|
297
|
+
* Body: { email, password, fullName?, ...extraData }
|
|
298
|
+
*/
|
|
208
299
|
async signup(options) {
|
|
209
|
-
const result = await this.post(
|
|
210
|
-
const user = result.
|
|
211
|
-
return
|
|
212
|
-
user,
|
|
213
|
-
session: {
|
|
214
|
-
sessionId: result.session.sessionId,
|
|
215
|
-
userId: user.id,
|
|
216
|
-
bucketId: this.basePath.split("/")[2],
|
|
217
|
-
createdAt: Date.now(),
|
|
218
|
-
expiresAt: result.session.expiresAt,
|
|
219
|
-
refreshToken: result.session.refreshToken,
|
|
220
|
-
refreshExpiresAt: result.session.expiresAt
|
|
221
|
-
}
|
|
222
|
-
};
|
|
300
|
+
const result = await this.post(AUTH.signup, options);
|
|
301
|
+
const user = result.data;
|
|
302
|
+
return this._buildAuthResult(user, result.session);
|
|
223
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Sign in with email + password.
|
|
306
|
+
*
|
|
307
|
+
* Server: POST /api/auth/signin (NOT /login)
|
|
308
|
+
* Body: { email, password }
|
|
309
|
+
*/
|
|
224
310
|
async login(options) {
|
|
225
|
-
const result = await this.post(
|
|
226
|
-
const user = result.
|
|
227
|
-
return
|
|
228
|
-
user,
|
|
229
|
-
session: {
|
|
230
|
-
sessionId: result.session.sessionId,
|
|
231
|
-
userId: user.id,
|
|
232
|
-
bucketId: this.basePath.split("/")[2],
|
|
233
|
-
createdAt: Date.now(),
|
|
234
|
-
expiresAt: result.session.expiresAt,
|
|
235
|
-
refreshToken: result.session.refreshToken,
|
|
236
|
-
refreshExpiresAt: result.session.expiresAt
|
|
237
|
-
}
|
|
238
|
-
};
|
|
311
|
+
const result = await this.post(AUTH.signin, options);
|
|
312
|
+
const user = result.data;
|
|
313
|
+
return this._buildAuthResult(user, result.session);
|
|
239
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Sign out — revoke a session (or all sessions with `allDevices: true`).
|
|
317
|
+
*
|
|
318
|
+
* Server: POST /api/auth/signout
|
|
319
|
+
* Body: { sessionId, allDevices? }
|
|
320
|
+
*/
|
|
240
321
|
async logout(options) {
|
|
241
|
-
await this.post(
|
|
322
|
+
await this.post(AUTH.signout, options);
|
|
242
323
|
}
|
|
243
|
-
|
|
324
|
+
// ─── Session Management ───────────────────────────────────────────────────
|
|
325
|
+
/**
|
|
326
|
+
* Validate an existing session and retrieve the current user.
|
|
327
|
+
*
|
|
328
|
+
* Server: POST /api/auth/session/validate
|
|
329
|
+
* Body: { sessionId }
|
|
330
|
+
*/
|
|
331
|
+
async validateSession(sessionId) {
|
|
244
332
|
const result = await this.post(
|
|
245
|
-
|
|
333
|
+
AUTH.sessionValidate,
|
|
334
|
+
{ sessionId }
|
|
335
|
+
);
|
|
336
|
+
return { user: result.data, session: result.session };
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Rotate a refresh token to get a new session.
|
|
340
|
+
*
|
|
341
|
+
* Server: POST /api/auth/session/refresh
|
|
342
|
+
* Body: { refreshToken }
|
|
343
|
+
*/
|
|
344
|
+
async refreshSession(refreshToken) {
|
|
345
|
+
const result = await this.post(
|
|
346
|
+
AUTH.sessionRefresh,
|
|
246
347
|
{ refreshToken }
|
|
247
348
|
);
|
|
248
349
|
const s = result.session;
|
|
249
350
|
return {
|
|
250
351
|
sessionId: s.sessionId,
|
|
251
352
|
userId: result.data?.id ?? "",
|
|
252
|
-
bucketId:
|
|
353
|
+
bucketId: "",
|
|
253
354
|
createdAt: Date.now(),
|
|
254
355
|
expiresAt: s.expiresAt,
|
|
255
356
|
refreshToken: s.refreshToken,
|
|
256
357
|
refreshExpiresAt: s.expiresAt
|
|
257
358
|
};
|
|
258
359
|
}
|
|
259
|
-
async validateSession({ sessionId }) {
|
|
260
|
-
const result = await this.post(
|
|
261
|
-
`${this.basePath}/session/validate`,
|
|
262
|
-
{ sessionId }
|
|
263
|
-
);
|
|
264
|
-
return { user: result.data, session: result.session };
|
|
265
|
-
}
|
|
266
360
|
// ─── User Profile ─────────────────────────────────────────────────────────
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
361
|
+
/**
|
|
362
|
+
* Fetch a user by ID.
|
|
363
|
+
*
|
|
364
|
+
* Server: GET /api/auth/user?userId=:userId
|
|
365
|
+
*/
|
|
366
|
+
async getUser(userId) {
|
|
367
|
+
const result = await this.get(AUTH.getUser, { userId });
|
|
272
368
|
return result.data;
|
|
273
369
|
}
|
|
370
|
+
/**
|
|
371
|
+
* Update a user's profile fields.
|
|
372
|
+
* Regular users can only update themselves; admins can update any user.
|
|
373
|
+
*
|
|
374
|
+
* Server: PATCH /api/auth/user
|
|
375
|
+
* Body: { sessionId, userId, updates: { ...fields } }
|
|
376
|
+
*/
|
|
274
377
|
async updateUser(options) {
|
|
275
378
|
const { sessionId, userId, updates } = options;
|
|
276
379
|
const result = await this.patch(
|
|
277
|
-
|
|
380
|
+
AUTH.updateUser,
|
|
278
381
|
{ sessionId, userId, updates }
|
|
279
382
|
);
|
|
280
383
|
return result.data;
|
|
281
384
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
385
|
+
/**
|
|
386
|
+
* Soft-delete a user account.
|
|
387
|
+
* Regular users can only delete themselves; admins can delete any user.
|
|
388
|
+
*
|
|
389
|
+
* Server: DELETE /api/auth/user?userId=:userId
|
|
390
|
+
* Body: { sessionId }
|
|
391
|
+
*/
|
|
392
|
+
async deleteUser(sessionId, userId) {
|
|
393
|
+
await this.http.request(
|
|
394
|
+
`${AUTH.deleteUser}?userId=${encodeURIComponent(userId)}`,
|
|
395
|
+
{
|
|
396
|
+
method: "DELETE",
|
|
397
|
+
body: { sessionId },
|
|
398
|
+
headers: { "X-Api-Key": this.authKey }
|
|
399
|
+
}
|
|
400
|
+
);
|
|
288
401
|
}
|
|
289
402
|
// ─── Admin Operations ─────────────────────────────────────────────────────
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
403
|
+
/**
|
|
404
|
+
* List all users (paginated). Admin only.
|
|
405
|
+
*
|
|
406
|
+
* Server: GET /api/auth/users?limit=&cursor=
|
|
407
|
+
* The sessionId is passed via the X-Session-Id header (GET body is unreliable).
|
|
408
|
+
*/
|
|
293
409
|
async listUsers(options) {
|
|
294
410
|
const { sessionId, limit = 50, cursor } = options;
|
|
295
411
|
const params = { limit: String(limit) };
|
|
296
412
|
if (cursor) params["cursor"] = cursor;
|
|
297
413
|
const result = await this.http.request(
|
|
298
|
-
`${
|
|
414
|
+
`${AUTH.listUsers}?${new URLSearchParams(params)}`,
|
|
299
415
|
{
|
|
300
416
|
method: "GET",
|
|
301
417
|
headers: { "X-Api-Key": this.authKey, "X-Session-Id": sessionId }
|
|
302
418
|
}
|
|
303
419
|
);
|
|
304
420
|
return {
|
|
305
|
-
users: result.data ??
|
|
421
|
+
users: result.data ?? [],
|
|
306
422
|
hasMore: result.meta?.hasMore ?? result.hasMore ?? false,
|
|
307
423
|
nextCursor: result.meta?.nextCursor ?? result.nextCursor ?? null
|
|
308
424
|
};
|
|
309
425
|
}
|
|
310
|
-
|
|
426
|
+
/**
|
|
427
|
+
* Permanently delete a user (hard delete — cannot be undone).
|
|
428
|
+
* Admin only. Charged at 1 HC per deletion.
|
|
429
|
+
*
|
|
430
|
+
* Server: DELETE /api/auth/user/hard?userId=:userId
|
|
431
|
+
* Body: { sessionId }
|
|
432
|
+
*/
|
|
433
|
+
async hardDeleteUser(sessionId, userId) {
|
|
311
434
|
await this.http.request(
|
|
312
|
-
`${
|
|
435
|
+
`${AUTH.hardDeleteUser}?userId=${encodeURIComponent(userId)}`,
|
|
313
436
|
{
|
|
314
437
|
method: "DELETE",
|
|
315
438
|
body: { sessionId },
|
|
@@ -317,54 +440,114 @@ var AuthClient = class {
|
|
|
317
440
|
}
|
|
318
441
|
);
|
|
319
442
|
}
|
|
443
|
+
/**
|
|
444
|
+
* Delete multiple users at once (soft or hard). Admin only.
|
|
445
|
+
*
|
|
446
|
+
* Server: DELETE /api/auth/users/bulk
|
|
447
|
+
* Body: { userIds, hard?, sessionId }
|
|
448
|
+
*/
|
|
320
449
|
async bulkDeleteUsers(options) {
|
|
321
450
|
const result = await this.http.request(
|
|
322
|
-
|
|
451
|
+
AUTH.bulkDeleteUsers,
|
|
323
452
|
{
|
|
324
453
|
method: "DELETE",
|
|
325
|
-
body: {
|
|
454
|
+
body: {
|
|
455
|
+
userIds: options.userIds,
|
|
456
|
+
hard: options.hard ?? false,
|
|
457
|
+
sessionId: options.sessionId
|
|
458
|
+
},
|
|
326
459
|
headers: { "X-Api-Key": this.authKey }
|
|
327
460
|
}
|
|
328
461
|
);
|
|
329
462
|
return { succeeded: result.meta.succeeded, failed: result.meta.failed };
|
|
330
463
|
}
|
|
464
|
+
/**
|
|
465
|
+
* Lock a user account for a specified duration. Admin only.
|
|
466
|
+
*
|
|
467
|
+
* Server: POST /api/auth/account/lock
|
|
468
|
+
* Body: { sessionId, userId, duration? } — duration in ms, default 15 min
|
|
469
|
+
*/
|
|
331
470
|
async lockAccount(options) {
|
|
332
|
-
const result = await this.post(
|
|
333
|
-
`${this.basePath}/account/lock`,
|
|
334
|
-
options
|
|
335
|
-
);
|
|
471
|
+
const result = await this.post(AUTH.accountLock, options);
|
|
336
472
|
return result.data;
|
|
337
473
|
}
|
|
338
|
-
|
|
339
|
-
|
|
474
|
+
/**
|
|
475
|
+
* Unlock a locked user account. Admin only.
|
|
476
|
+
*
|
|
477
|
+
* Server: POST /api/auth/account/unlock
|
|
478
|
+
* Body: { sessionId, userId }
|
|
479
|
+
*/
|
|
480
|
+
async unlockAccount(sessionId, userId) {
|
|
481
|
+
await this.post(AUTH.accountUnlock, { sessionId, userId });
|
|
340
482
|
}
|
|
341
483
|
// ─── Password Management ──────────────────────────────────────────────────
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
484
|
+
/**
|
|
485
|
+
* Change a user's password. Requires both a valid session AND the old password.
|
|
486
|
+
*
|
|
487
|
+
* Server: POST /api/auth/password/change
|
|
488
|
+
* Body: { sessionId, userId, oldPassword, newPassword }
|
|
489
|
+
*/
|
|
345
490
|
async changePassword(options) {
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
oldPassword
|
|
351
|
-
newPassword
|
|
491
|
+
await this.post(AUTH.passwordChange, {
|
|
492
|
+
sessionId: options.sessionId,
|
|
493
|
+
userId: options.userId,
|
|
494
|
+
oldPassword: options.currentPassword,
|
|
495
|
+
// server field is `oldPassword`
|
|
496
|
+
newPassword: options.newPassword
|
|
352
497
|
});
|
|
353
498
|
}
|
|
354
|
-
|
|
355
|
-
|
|
499
|
+
/**
|
|
500
|
+
* Request a password reset email.
|
|
501
|
+
* Always returns success regardless of whether the email exists (prevents enumeration).
|
|
502
|
+
*
|
|
503
|
+
* Server: POST /api/auth/password/reset/request
|
|
504
|
+
* Body: { email }
|
|
505
|
+
*/
|
|
506
|
+
async requestPasswordReset(email) {
|
|
507
|
+
await this.post(AUTH.passwordResetRequest, { email });
|
|
356
508
|
}
|
|
357
|
-
|
|
358
|
-
|
|
509
|
+
/**
|
|
510
|
+
* Confirm a password reset using the token from the reset email.
|
|
511
|
+
*
|
|
512
|
+
* Server: POST /api/auth/password/reset/confirm
|
|
513
|
+
* Body: { resetToken, newPassword }
|
|
514
|
+
*/
|
|
515
|
+
async confirmPasswordReset(resetToken, newPassword) {
|
|
516
|
+
await this.post(AUTH.passwordResetConfirm, { resetToken, newPassword });
|
|
359
517
|
}
|
|
360
518
|
// ─── Email Verification ───────────────────────────────────────────────────
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
519
|
+
/**
|
|
520
|
+
* Request a verification email be sent to a user.
|
|
521
|
+
*
|
|
522
|
+
* Server: POST /api/auth/email/verify/request
|
|
523
|
+
* Body: { userId }
|
|
524
|
+
*/
|
|
525
|
+
async requestEmailVerification(userId) {
|
|
526
|
+
await this.post(AUTH.emailVerifyRequest, { userId });
|
|
365
527
|
}
|
|
366
|
-
|
|
367
|
-
|
|
528
|
+
/**
|
|
529
|
+
* Confirm email ownership using the token from the verification email.
|
|
530
|
+
*
|
|
531
|
+
* Server: POST /api/auth/email/verify/confirm
|
|
532
|
+
* Body: { verifyToken }
|
|
533
|
+
*/
|
|
534
|
+
async confirmEmailVerification(verifyToken) {
|
|
535
|
+
await this.post(AUTH.emailVerifyConfirm, { verifyToken });
|
|
536
|
+
}
|
|
537
|
+
// ─── Private helpers ──────────────────────────────────────────────────────
|
|
538
|
+
_buildAuthResult(user, session) {
|
|
539
|
+
return {
|
|
540
|
+
user,
|
|
541
|
+
session: {
|
|
542
|
+
sessionId: session.sessionId,
|
|
543
|
+
userId: user.id,
|
|
544
|
+
bucketId: "",
|
|
545
|
+
createdAt: Date.now(),
|
|
546
|
+
expiresAt: session.expiresAt,
|
|
547
|
+
refreshToken: session.refreshToken,
|
|
548
|
+
refreshExpiresAt: session.expiresAt
|
|
549
|
+
}
|
|
550
|
+
};
|
|
368
551
|
}
|
|
369
552
|
};
|
|
370
553
|
|
|
@@ -373,18 +556,22 @@ function buildQueryParams(options = {}) {
|
|
|
373
556
|
const params = new URLSearchParams();
|
|
374
557
|
if (options.limit !== void 0) params.set("limit", String(options.limit));
|
|
375
558
|
if (options.offset !== void 0) params.set("offset", String(options.offset));
|
|
376
|
-
if (options.orderBy !== void 0) params.set("
|
|
377
|
-
if (options.order !== void 0) params.set("
|
|
559
|
+
if (options.orderBy !== void 0) params.set("sortBy", options.orderBy);
|
|
560
|
+
if (options.order !== void 0) params.set("sortOrder", options.order);
|
|
378
561
|
if (options.fields !== void 0) params.set("fields", options.fields);
|
|
379
|
-
if (options.startAfter !== void 0) params.set("
|
|
380
|
-
if (options.startAt !== void 0) params.set("
|
|
381
|
-
if (options.
|
|
562
|
+
if (options.startAfter !== void 0) params.set("cursor", options.startAfter);
|
|
563
|
+
if (options.startAt !== void 0) params.set("cursor", options.startAt);
|
|
564
|
+
if (options.timeScope !== void 0) params.set("timeScope", options.timeScope);
|
|
382
565
|
if (options.dateRange?.start !== void 0)
|
|
383
566
|
params.set("startDate", new Date(options.dateRange.start).toISOString().split("T")[0]);
|
|
384
567
|
if (options.dateRange?.end !== void 0)
|
|
385
568
|
params.set("endDate", new Date(options.dateRange.end).toISOString().split("T")[0]);
|
|
386
|
-
if (options.filters && options.filters.length > 0)
|
|
387
|
-
|
|
569
|
+
if (options.filters && options.filters.length > 0) {
|
|
570
|
+
for (const f of options.filters) {
|
|
571
|
+
const key = f.op === "==" ? f.field : `${f.field}[${f.op}]`;
|
|
572
|
+
params.set(key, String(f.value));
|
|
573
|
+
}
|
|
574
|
+
}
|
|
388
575
|
const str = params.toString();
|
|
389
576
|
return str ? `?${str}` : "";
|
|
390
577
|
}
|
|
@@ -406,10 +593,13 @@ function guessMimeType(filename) {
|
|
|
406
593
|
html: "text/html",
|
|
407
594
|
css: "text/css",
|
|
408
595
|
js: "application/javascript",
|
|
596
|
+
ts: "application/typescript",
|
|
409
597
|
json: "application/json",
|
|
410
598
|
xml: "application/xml",
|
|
411
599
|
zip: "application/zip",
|
|
412
|
-
csv: "text/csv"
|
|
600
|
+
csv: "text/csv",
|
|
601
|
+
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
602
|
+
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
|
413
603
|
};
|
|
414
604
|
return map[ext ?? ""] ?? "application/octet-stream";
|
|
415
605
|
}
|
|
@@ -428,15 +618,14 @@ var RecordsClient = class {
|
|
|
428
618
|
this.http = http;
|
|
429
619
|
this.bucketKey = bucketKey;
|
|
430
620
|
this.apiKey = bucketSecurityKey;
|
|
431
|
-
this.basePath = `/records/${bucketKey}`;
|
|
432
621
|
}
|
|
433
622
|
// ─── Single Record Operations ─────────────────────────────────────────────
|
|
434
623
|
/**
|
|
435
|
-
* Create a new record
|
|
624
|
+
* Create a new record (auto-generated ID) or upsert by `customRecordId`.
|
|
436
625
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
*
|
|
626
|
+
* Server: POST /api/:bucketKey
|
|
627
|
+
* Body: { values, queryableFields?, userEmail?, customRecordId? }
|
|
628
|
+
* Returns 201 for new records, 200 for upserts.
|
|
440
629
|
*
|
|
441
630
|
* @example
|
|
442
631
|
* ```ts
|
|
@@ -452,63 +641,110 @@ var RecordsClient = class {
|
|
|
452
641
|
if (queryableFields?.length) body["queryableFields"] = queryableFields;
|
|
453
642
|
if (userEmail) body["userEmail"] = userEmail;
|
|
454
643
|
if (customRecordId) body["customRecordId"] = customRecordId;
|
|
455
|
-
const result = await this.http.post(
|
|
456
|
-
|
|
644
|
+
const result = await this.http.post(
|
|
645
|
+
RECORDS.bucket(this.bucketKey),
|
|
646
|
+
this.apiKey,
|
|
647
|
+
body
|
|
648
|
+
);
|
|
649
|
+
return result.data;
|
|
457
650
|
}
|
|
458
651
|
/**
|
|
459
652
|
* Get a single record by ID.
|
|
653
|
+
*
|
|
654
|
+
* Server: GET /api/:bucketKey?recordId=:id
|
|
460
655
|
*/
|
|
461
656
|
async get(id) {
|
|
462
657
|
const result = await this.http.get(
|
|
463
|
-
`${this.
|
|
658
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
|
|
464
659
|
this.apiKey
|
|
465
660
|
);
|
|
466
|
-
return result.data
|
|
467
|
-
}
|
|
468
|
-
/**
|
|
469
|
-
* Fully replace a record (PUT semantics).
|
|
470
|
-
*/
|
|
471
|
-
async set(id, data) {
|
|
472
|
-
const result = await this.http.put(
|
|
473
|
-
`${this.basePath}/${encodeURIComponent(id)}`,
|
|
474
|
-
this.apiKey,
|
|
475
|
-
data
|
|
476
|
-
);
|
|
477
|
-
return result.data ?? result.record;
|
|
661
|
+
return result.data;
|
|
478
662
|
}
|
|
479
663
|
/**
|
|
480
|
-
* Partially update
|
|
481
|
-
* By default merges with existing fields; set `merge: false` to replace.
|
|
664
|
+
* Partially update an existing record (PATCH semantics).
|
|
482
665
|
* Supports write-filter sentinels: `{ __op: 'increment', delta: 1 }` etc.
|
|
666
|
+
*
|
|
667
|
+
* Server: PATCH /api/:bucketKey
|
|
668
|
+
* Body: { recordId, values, userEmail?, track_record_history? }
|
|
483
669
|
*/
|
|
484
670
|
async patch(id, data, options = {}) {
|
|
485
|
-
const {
|
|
671
|
+
const { userEmail, trackHistory } = options;
|
|
672
|
+
const body = { recordId: id, values: data };
|
|
673
|
+
if (userEmail) body["userEmail"] = userEmail;
|
|
674
|
+
if (trackHistory) body["track_record_history"] = true;
|
|
486
675
|
const result = await this.http.patch(
|
|
487
|
-
|
|
676
|
+
RECORDS.bucket(this.bucketKey),
|
|
488
677
|
this.apiKey,
|
|
489
|
-
|
|
678
|
+
body
|
|
490
679
|
);
|
|
491
|
-
return result.
|
|
680
|
+
return { id: result.meta?.id ?? id, updatedAt: result.meta?.updatedAt };
|
|
492
681
|
}
|
|
493
682
|
/**
|
|
494
683
|
* Delete a record permanently.
|
|
684
|
+
*
|
|
685
|
+
* Server: DELETE /api/:bucketKey?recordId=:id
|
|
495
686
|
*/
|
|
496
687
|
async delete(id) {
|
|
497
688
|
await this.http.delete(
|
|
498
|
-
`${this.
|
|
689
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
|
|
690
|
+
this.apiKey
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Check whether a record exists (HEAD request — very lightweight).
|
|
695
|
+
*
|
|
696
|
+
* Server: HEAD /api/:bucketKey?recordId=:id
|
|
697
|
+
* Returns true if the record exists, false if 404.
|
|
698
|
+
*/
|
|
699
|
+
async exists(id) {
|
|
700
|
+
try {
|
|
701
|
+
await this.http.request(
|
|
702
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}`,
|
|
703
|
+
{ method: "HEAD", headers: { "X-Api-Key": this.apiKey } }
|
|
704
|
+
);
|
|
705
|
+
return true;
|
|
706
|
+
} catch (err) {
|
|
707
|
+
if (err && typeof err === "object" && "status" in err && err.status === 404) return false;
|
|
708
|
+
throw err;
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
/**
|
|
712
|
+
* Get a historical snapshot of a record at a specific generation.
|
|
713
|
+
*
|
|
714
|
+
* Server: GET /api/:bucketKey?recordId=:id&generation=:gen
|
|
715
|
+
*/
|
|
716
|
+
async getVersion(id, generation) {
|
|
717
|
+
const result = await this.http.get(
|
|
718
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}&generation=${encodeURIComponent(String(generation))}`,
|
|
719
|
+
this.apiKey
|
|
720
|
+
);
|
|
721
|
+
return result.data;
|
|
722
|
+
}
|
|
723
|
+
/**
|
|
724
|
+
* Get the version history of a record.
|
|
725
|
+
*
|
|
726
|
+
* Server: GET /api/:bucketKey?recordId=:id&showHistory=true
|
|
727
|
+
*/
|
|
728
|
+
async getHistory(id) {
|
|
729
|
+
const result = await this.http.get(
|
|
730
|
+
`${RECORDS.bucket(this.bucketKey)}?recordId=${encodeURIComponent(id)}&showHistory=true`,
|
|
499
731
|
this.apiKey
|
|
500
732
|
);
|
|
733
|
+
return result.history ?? [];
|
|
501
734
|
}
|
|
502
735
|
// ─── Batch Operations ─────────────────────────────────────────────────────
|
|
503
736
|
/**
|
|
504
|
-
* Create
|
|
505
|
-
* Each record
|
|
737
|
+
* Create up to 500 records in one request.
|
|
738
|
+
* Each record may include `_customRecordId` for upsert behaviour.
|
|
739
|
+
*
|
|
740
|
+
* Server: POST /api/:bucketKey/batch/insert
|
|
741
|
+
* Body: { records, queryableFields?, userEmail? }
|
|
506
742
|
*
|
|
507
743
|
* @example
|
|
508
744
|
* ```ts
|
|
509
|
-
* const
|
|
745
|
+
* const results = await posts.batchCreate(
|
|
510
746
|
* [{ title: 'A' }, { title: 'B' }],
|
|
511
|
-
* { queryableFields: ['title']
|
|
747
|
+
* { queryableFields: ['title'] },
|
|
512
748
|
* );
|
|
513
749
|
* ```
|
|
514
750
|
*/
|
|
@@ -518,29 +754,60 @@ var RecordsClient = class {
|
|
|
518
754
|
if (queryableFields?.length) body["queryableFields"] = queryableFields;
|
|
519
755
|
if (userEmail) body["userEmail"] = userEmail;
|
|
520
756
|
const result = await this.http.post(
|
|
521
|
-
|
|
757
|
+
RECORDS.batchInsert(this.bucketKey),
|
|
522
758
|
this.apiKey,
|
|
523
759
|
body
|
|
524
760
|
);
|
|
525
|
-
return
|
|
761
|
+
return {
|
|
762
|
+
results: result.data ?? [],
|
|
763
|
+
errors: result.errors ?? [],
|
|
764
|
+
successful: result.meta?.successful ?? (result.data?.length ?? 0),
|
|
765
|
+
failed: result.meta?.failed ?? 0
|
|
766
|
+
};
|
|
526
767
|
}
|
|
527
768
|
/**
|
|
528
|
-
*
|
|
769
|
+
* Update up to 500 records in one request.
|
|
770
|
+
*
|
|
771
|
+
* Server: POST /api/:bucketKey/batch/update
|
|
772
|
+
* Body: { updates: [{ recordId, values }], userEmail? }
|
|
529
773
|
*/
|
|
530
|
-
async
|
|
774
|
+
async batchUpdate(updates, userEmail) {
|
|
775
|
+
const body = { updates };
|
|
776
|
+
if (userEmail) body["userEmail"] = userEmail;
|
|
531
777
|
const result = await this.http.post(
|
|
532
|
-
|
|
778
|
+
RECORDS.batchUpdate(this.bucketKey),
|
|
533
779
|
this.apiKey,
|
|
534
|
-
|
|
780
|
+
body
|
|
535
781
|
);
|
|
536
782
|
return {
|
|
537
|
-
|
|
538
|
-
failed: result.data?.failed ??
|
|
783
|
+
successful: result.data?.successful ?? result.meta?.count ?? 0,
|
|
784
|
+
failed: result.data?.failed ?? []
|
|
785
|
+
};
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Delete up to 500 records in one request.
|
|
789
|
+
*
|
|
790
|
+
* Server: POST /api/:bucketKey/batch/delete
|
|
791
|
+
* Body: { recordIds, userEmail? }
|
|
792
|
+
*/
|
|
793
|
+
async batchDelete(ids, userEmail) {
|
|
794
|
+
const body = { recordIds: ids };
|
|
795
|
+
if (userEmail) body["userEmail"] = userEmail;
|
|
796
|
+
const result = await this.http.post(
|
|
797
|
+
RECORDS.batchDelete(this.bucketKey),
|
|
798
|
+
this.apiKey,
|
|
799
|
+
body
|
|
800
|
+
);
|
|
801
|
+
return {
|
|
802
|
+
successful: result.data?.successful ?? result.meta?.count ?? 0,
|
|
803
|
+
failed: result.data?.failed ?? []
|
|
539
804
|
};
|
|
540
805
|
}
|
|
541
806
|
// ─── Querying ─────────────────────────────────────────────────────────────
|
|
542
807
|
/**
|
|
543
|
-
* Query records with
|
|
808
|
+
* Query records with filters, sorting, and cursor-based pagination.
|
|
809
|
+
*
|
|
810
|
+
* Server: GET /api/:bucketKey?field=value&field[op]=value&limit=&sortBy=&sortOrder=&cursor=
|
|
544
811
|
*
|
|
545
812
|
* @example
|
|
546
813
|
* ```ts
|
|
@@ -554,52 +821,23 @@ var RecordsClient = class {
|
|
|
554
821
|
*/
|
|
555
822
|
async query(options = {}) {
|
|
556
823
|
const qs = buildQueryParams(options);
|
|
557
|
-
const result = await this.http.get(`${this.basePath}${qs}`, this.apiKey);
|
|
558
|
-
const records = result.data ?? result.records ?? [];
|
|
559
|
-
const hasMore = result.meta?.hasMore ?? result.hasMore ?? false;
|
|
560
|
-
const total = result.meta?.total ?? result.total;
|
|
561
|
-
const nextCursor = result.meta?.nextCursor ?? result.nextCursor ?? void 0;
|
|
562
|
-
return { records, total, hasMore, nextCursor: nextCursor ?? void 0 };
|
|
563
|
-
}
|
|
564
|
-
/**
|
|
565
|
-
* Get all records matching the given options (no filter support — use `query()` for filters).
|
|
566
|
-
*/
|
|
567
|
-
async getAll(options = {}) {
|
|
568
|
-
const { records } = await this.query(options);
|
|
569
|
-
return records;
|
|
570
|
-
}
|
|
571
|
-
/**
|
|
572
|
-
* Count records matching the given filters.
|
|
573
|
-
*/
|
|
574
|
-
async count(filters = []) {
|
|
575
|
-
const result = await this.http.post(
|
|
576
|
-
`${this.basePath}/count`,
|
|
577
|
-
this.apiKey,
|
|
578
|
-
{ filters }
|
|
579
|
-
);
|
|
580
|
-
return result.data?.count ?? result.count ?? 0;
|
|
581
|
-
}
|
|
582
|
-
// ─── Version History ──────────────────────────────────────────────────────
|
|
583
|
-
/**
|
|
584
|
-
* Get the version history of a record.
|
|
585
|
-
*/
|
|
586
|
-
async getHistory(id) {
|
|
587
824
|
const result = await this.http.get(
|
|
588
|
-
`${this.
|
|
825
|
+
`${RECORDS.bucket(this.bucketKey)}${qs}`,
|
|
589
826
|
this.apiKey
|
|
590
827
|
);
|
|
591
|
-
return
|
|
828
|
+
return {
|
|
829
|
+
records: result.data ?? [],
|
|
830
|
+
total: result.meta?.total ?? result.total,
|
|
831
|
+
hasMore: result.meta?.hasMore ?? result.hasMore ?? false,
|
|
832
|
+
nextCursor: result.meta?.nextCursor ?? result.nextCursor ?? void 0
|
|
833
|
+
};
|
|
592
834
|
}
|
|
593
835
|
/**
|
|
594
|
-
*
|
|
836
|
+
* Retrieve all records matching options (no filter support — use `query()` for filters).
|
|
595
837
|
*/
|
|
596
|
-
async
|
|
597
|
-
const
|
|
598
|
-
|
|
599
|
-
this.apiKey,
|
|
600
|
-
{ version }
|
|
601
|
-
);
|
|
602
|
-
return result.data ?? result.record;
|
|
838
|
+
async getAll(options = {}) {
|
|
839
|
+
const { records } = await this.query(options);
|
|
840
|
+
return records;
|
|
603
841
|
}
|
|
604
842
|
};
|
|
605
843
|
|
|
@@ -609,11 +847,11 @@ var AnalyticsClient = class {
|
|
|
609
847
|
assertSafeName(bucketKey, "bucketKey");
|
|
610
848
|
this.http = http;
|
|
611
849
|
this.bucketSecurityKey = bucketSecurityKey;
|
|
612
|
-
this.
|
|
850
|
+
this.bucketKey = bucketKey;
|
|
613
851
|
}
|
|
614
852
|
async run(query) {
|
|
615
853
|
const result = await this.http.post(
|
|
616
|
-
this.
|
|
854
|
+
ANALYTICS.query(this.bucketKey),
|
|
617
855
|
this.bucketSecurityKey,
|
|
618
856
|
query
|
|
619
857
|
);
|
|
@@ -624,7 +862,10 @@ var AnalyticsClient = class {
|
|
|
624
862
|
async count(opts = {}) {
|
|
625
863
|
return this.run({ queryType: "count", ...opts });
|
|
626
864
|
}
|
|
627
|
-
/**
|
|
865
|
+
/**
|
|
866
|
+
* Get value distribution for a field (e.g. records per status).
|
|
867
|
+
* `order` maps to `sortBy` on the wire (the server param name).
|
|
868
|
+
*/
|
|
628
869
|
async distribution(opts) {
|
|
629
870
|
assertSafeName(opts.field, "field");
|
|
630
871
|
return this.run({ queryType: "distribution", ...opts });
|
|
@@ -660,13 +901,19 @@ var AnalyticsClient = class {
|
|
|
660
901
|
assertSafeName(opts.field, "field");
|
|
661
902
|
return this.run({ queryType: "stats", ...opts });
|
|
662
903
|
}
|
|
663
|
-
/**
|
|
904
|
+
/**
|
|
905
|
+
* Fetch filtered records via the analytics engine (BigQuery).
|
|
906
|
+
* Bypasses Firestore pagination — useful for large result sets.
|
|
907
|
+
*/
|
|
664
908
|
async records(opts = {}) {
|
|
665
909
|
if (opts.orderBy) assertSafeName(opts.orderBy, "orderBy");
|
|
666
910
|
if (opts.selectFields) opts.selectFields.forEach((f) => assertSafeName(f, "selectField"));
|
|
667
911
|
return this.run({ queryType: "records", limit: 100, order: "desc", ...opts });
|
|
668
912
|
}
|
|
669
|
-
/**
|
|
913
|
+
/**
|
|
914
|
+
* Compute multiple aggregations in a single request.
|
|
915
|
+
* `metrics` must have valid `field` and `name` (used as BigQuery column aliases).
|
|
916
|
+
*/
|
|
670
917
|
async multiMetric(opts) {
|
|
671
918
|
opts.metrics.forEach((m) => {
|
|
672
919
|
assertSafeName(m.field, "metric.field");
|
|
@@ -680,7 +927,8 @@ var AnalyticsClient = class {
|
|
|
680
927
|
}
|
|
681
928
|
/**
|
|
682
929
|
* Compare a metric across multiple buckets in one query.
|
|
683
|
-
* The caller's key must have read access to
|
|
930
|
+
* The caller's key must have read access to EVERY bucket in `bucketKeys`.
|
|
931
|
+
* System buckets (`users`, `_sys_*`) are blocked server-side.
|
|
684
932
|
*/
|
|
685
933
|
async crossBucket(opts) {
|
|
686
934
|
assertSafeName(opts.field, "field");
|
|
@@ -688,7 +936,7 @@ var AnalyticsClient = class {
|
|
|
688
936
|
return this.run({ queryType: "crossBucket", aggregation: "sum", ...opts });
|
|
689
937
|
}
|
|
690
938
|
/**
|
|
691
|
-
* Raw query —
|
|
939
|
+
* Raw query — escape hatch when the typed helpers don't cover your case.
|
|
692
940
|
*/
|
|
693
941
|
async query(query) {
|
|
694
942
|
const data = await this.run(query);
|
|
@@ -709,17 +957,19 @@ function toUploadResult(r) {
|
|
|
709
957
|
}
|
|
710
958
|
var StorageManager = class {
|
|
711
959
|
constructor(http, storageKey) {
|
|
712
|
-
this.basePath = "/storage";
|
|
713
960
|
this.http = http;
|
|
714
961
|
this.storageKey = storageKey;
|
|
715
962
|
}
|
|
716
963
|
get authHeaders() {
|
|
717
964
|
return { "X-Storage-Key": this.storageKey };
|
|
718
965
|
}
|
|
719
|
-
// ─── Upload:
|
|
966
|
+
// ─── Upload: Server-buffered ──────────────────────────────────────────────
|
|
720
967
|
/**
|
|
721
|
-
* Upload a file
|
|
722
|
-
* For files >10 MB or when upload progress is needed, use
|
|
968
|
+
* Upload a file in one step (server-buffered, up to 500 MB).
|
|
969
|
+
* For files >10 MB or when upload progress tracking is needed, use the
|
|
970
|
+
* signed URL flow: `getUploadUrl()` → `uploadToSignedUrl()` → `confirmUpload()`.
|
|
971
|
+
*
|
|
972
|
+
* Server: POST /storage/upload (multipart/form-data)
|
|
723
973
|
*
|
|
724
974
|
* @example
|
|
725
975
|
* ```ts
|
|
@@ -737,7 +987,7 @@ var StorageManager = class {
|
|
|
737
987
|
formData.append("mimeType", mime);
|
|
738
988
|
formData.append("isPublic", String(isPublic));
|
|
739
989
|
formData.append("overwrite", String(overwrite));
|
|
740
|
-
const result = await this.http.request(
|
|
990
|
+
const result = await this.http.request(STORAGE.upload, {
|
|
741
991
|
method: "POST",
|
|
742
992
|
rawBody: formData,
|
|
743
993
|
headers: this.authHeaders
|
|
@@ -745,7 +995,10 @@ var StorageManager = class {
|
|
|
745
995
|
return toUploadResult(result);
|
|
746
996
|
}
|
|
747
997
|
/**
|
|
748
|
-
* Upload raw
|
|
998
|
+
* Upload raw string or JSON data directly as a file.
|
|
999
|
+
*
|
|
1000
|
+
* Server: POST /storage/upload-raw
|
|
1001
|
+
* Body: { path, content, mimeType?, isPublic?, overwrite? }
|
|
749
1002
|
*
|
|
750
1003
|
* @example
|
|
751
1004
|
* ```ts
|
|
@@ -755,33 +1008,32 @@ var StorageManager = class {
|
|
|
755
1008
|
async uploadRaw(data, path, options = {}) {
|
|
756
1009
|
const { isPublic = false, overwrite = false, mimeType = "application/json" } = options;
|
|
757
1010
|
const content = typeof data === "string" ? data : JSON.stringify(data);
|
|
758
|
-
const result = await this.http.request(
|
|
1011
|
+
const result = await this.http.request(STORAGE.uploadRaw, {
|
|
759
1012
|
method: "POST",
|
|
760
1013
|
body: { path, content, mimeType, isPublic, overwrite },
|
|
761
1014
|
headers: this.authHeaders
|
|
762
1015
|
});
|
|
763
1016
|
return toUploadResult(result);
|
|
764
1017
|
}
|
|
765
|
-
// ─── Upload: Direct-to-GCS (recommended for large files)
|
|
1018
|
+
// ─── Upload: Direct-to-GCS (recommended for large files / progress) ───────
|
|
766
1019
|
/**
|
|
767
|
-
* Step 1 — get a signed GCS URL to upload
|
|
768
|
-
*
|
|
1020
|
+
* Step 1 — get a signed GCS PUT URL for direct client-to-GCS upload.
|
|
1021
|
+
*
|
|
1022
|
+
* Server: POST /storage/upload-url
|
|
1023
|
+
* Body: { path, mimeType, size, isPublic?, overwrite?, expiresIn? }
|
|
769
1024
|
*
|
|
770
1025
|
* @example
|
|
771
1026
|
* ```ts
|
|
772
|
-
* const { uploadUrl, path:
|
|
773
|
-
* path:
|
|
774
|
-
* mimeType: 'video/mp4',
|
|
775
|
-
* size: file.size,
|
|
776
|
-
* isPublic: true,
|
|
1027
|
+
* const { uploadUrl, path: p } = await storage.getUploadUrl({
|
|
1028
|
+
* path: 'videos/intro.mp4', mimeType: 'video/mp4', size: file.size,
|
|
777
1029
|
* });
|
|
778
|
-
* await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', pct =>
|
|
779
|
-
* const result = await storage.confirmUpload({ path:
|
|
1030
|
+
* await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', pct => setProgress(pct));
|
|
1031
|
+
* const result = await storage.confirmUpload({ path: p, mimeType: 'video/mp4' });
|
|
780
1032
|
* ```
|
|
781
1033
|
*/
|
|
782
1034
|
async getUploadUrl(opts) {
|
|
783
1035
|
const { expiresInSeconds, ...rest } = opts;
|
|
784
|
-
const result = await this.http.request(
|
|
1036
|
+
const result = await this.http.request(STORAGE.uploadUrl, {
|
|
785
1037
|
method: "POST",
|
|
786
1038
|
body: { isPublic: false, overwrite: false, expiresIn: expiresInSeconds ?? 900, ...rest },
|
|
787
1039
|
headers: this.authHeaders
|
|
@@ -795,17 +1047,20 @@ var StorageManager = class {
|
|
|
795
1047
|
};
|
|
796
1048
|
}
|
|
797
1049
|
/**
|
|
798
|
-
* Step 2 — upload data directly to
|
|
799
|
-
* Supports progress tracking in browser environments.
|
|
1050
|
+
* Step 2 — upload data directly to the signed GCS URL.
|
|
1051
|
+
* Supports progress tracking in browser environments via XHR.
|
|
800
1052
|
*/
|
|
801
1053
|
async uploadToSignedUrl(signedUrl, data, mimeType, onProgress) {
|
|
802
1054
|
await this.http.putToSignedUrl(signedUrl, data, mimeType, onProgress);
|
|
803
1055
|
}
|
|
804
1056
|
/**
|
|
805
|
-
* Step 3 — confirm a direct upload and register metadata
|
|
1057
|
+
* Step 3 — confirm a direct upload and register metadata server-side.
|
|
1058
|
+
*
|
|
1059
|
+
* Server: POST /storage/confirm
|
|
1060
|
+
* Body: { path, mimeType, isPublic? }
|
|
806
1061
|
*/
|
|
807
1062
|
async confirmUpload(opts) {
|
|
808
|
-
const result = await this.http.request(
|
|
1063
|
+
const result = await this.http.request(STORAGE.confirm, {
|
|
809
1064
|
method: "POST",
|
|
810
1065
|
body: { isPublic: false, ...opts },
|
|
811
1066
|
headers: this.authHeaders
|
|
@@ -814,26 +1069,17 @@ var StorageManager = class {
|
|
|
814
1069
|
}
|
|
815
1070
|
// ─── Batch Upload ─────────────────────────────────────────────────────────
|
|
816
1071
|
/**
|
|
817
|
-
* Get signed upload URLs for
|
|
818
|
-
* Server returns `{ succeeded, failed }` — only succeeded items have URLs.
|
|
1072
|
+
* Get signed upload URLs for up to 50 files at once.
|
|
819
1073
|
*
|
|
820
|
-
*
|
|
821
|
-
*
|
|
822
|
-
* const { files } = await storage.getBatchUploadUrls([
|
|
823
|
-
* { path: 'images/a.jpg', mimeType: 'image/jpeg', size: 204800 },
|
|
824
|
-
* { path: 'images/b.jpg', mimeType: 'image/jpeg', size: 153600 },
|
|
825
|
-
* ]);
|
|
826
|
-
* for (const f of files) {
|
|
827
|
-
* await storage.uploadToSignedUrl(f.uploadUrl, blobs[f.index], f.mimeType);
|
|
828
|
-
* await storage.confirmUpload({ path: f.path, mimeType: f.mimeType });
|
|
829
|
-
* }
|
|
830
|
-
* ```
|
|
1074
|
+
* Server: POST /storage/batch-upload-urls
|
|
1075
|
+
* Body: { files: [...], expiresIn? }
|
|
831
1076
|
*/
|
|
832
1077
|
async getBatchUploadUrls(files) {
|
|
833
|
-
const result = await this.http.request(
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
1078
|
+
const result = await this.http.request(STORAGE.batchUploadUrls, {
|
|
1079
|
+
method: "POST",
|
|
1080
|
+
body: { files },
|
|
1081
|
+
headers: this.authHeaders
|
|
1082
|
+
});
|
|
837
1083
|
return {
|
|
838
1084
|
files: result.succeeded.map((f) => ({
|
|
839
1085
|
index: f.index,
|
|
@@ -847,13 +1093,16 @@ var StorageManager = class {
|
|
|
847
1093
|
}
|
|
848
1094
|
/**
|
|
849
1095
|
* Confirm multiple direct uploads at once.
|
|
850
|
-
*
|
|
1096
|
+
*
|
|
1097
|
+
* Server: POST /storage/batch-confirm
|
|
1098
|
+
* Body: { files: [{ path, mimeType, isPublic? }] }
|
|
851
1099
|
*/
|
|
852
1100
|
async batchConfirmUploads(items) {
|
|
853
|
-
const result = await this.http.request(
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1101
|
+
const result = await this.http.request(STORAGE.batchConfirm, {
|
|
1102
|
+
method: "POST",
|
|
1103
|
+
body: { files: items },
|
|
1104
|
+
headers: this.authHeaders
|
|
1105
|
+
});
|
|
857
1106
|
return {
|
|
858
1107
|
succeeded: result.succeeded.map(toUploadResult),
|
|
859
1108
|
failed: result.failed
|
|
@@ -863,32 +1112,28 @@ var StorageManager = class {
|
|
|
863
1112
|
/**
|
|
864
1113
|
* Download a private file as an ArrayBuffer.
|
|
865
1114
|
* For public files, use the `publicUrl` directly — no SDK needed.
|
|
1115
|
+
*
|
|
1116
|
+
* Server: GET /storage/download/:path (requires X-Storage-Key)
|
|
866
1117
|
*/
|
|
867
1118
|
async download(path) {
|
|
868
1119
|
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
869
|
-
return this.http.request(
|
|
1120
|
+
return this.http.request(STORAGE.download(encoded), {
|
|
870
1121
|
method: "GET",
|
|
871
1122
|
headers: this.authHeaders
|
|
872
1123
|
});
|
|
873
1124
|
}
|
|
874
1125
|
/**
|
|
875
|
-
* Download
|
|
876
|
-
* Returns a structured result with succeeded and failed items.
|
|
877
|
-
* Each succeeded item includes the file content as a base64 string.
|
|
1126
|
+
* Download up to 20 files at once. Returns base64-encoded content.
|
|
878
1127
|
*
|
|
879
|
-
*
|
|
880
|
-
*
|
|
881
|
-
* const { succeeded, failed } = await storage.batchDownload(['docs/a.pdf', 'docs/b.pdf']);
|
|
882
|
-
* for (const f of succeeded) {
|
|
883
|
-
* const bytes = Buffer.from(f.content, 'base64');
|
|
884
|
-
* }
|
|
885
|
-
* ```
|
|
1128
|
+
* Server: POST /storage/batch-download
|
|
1129
|
+
* Body: { paths, concurrency? }
|
|
886
1130
|
*/
|
|
887
|
-
async batchDownload(paths) {
|
|
888
|
-
const result = await this.http.request(
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1131
|
+
async batchDownload(paths, concurrency = 5) {
|
|
1132
|
+
const result = await this.http.request(STORAGE.batchDownload, {
|
|
1133
|
+
method: "POST",
|
|
1134
|
+
body: { paths, concurrency },
|
|
1135
|
+
headers: this.authHeaders
|
|
1136
|
+
});
|
|
892
1137
|
return {
|
|
893
1138
|
succeeded: result.succeeded.map((f) => ({
|
|
894
1139
|
index: f.index,
|
|
@@ -908,7 +1153,8 @@ var StorageManager = class {
|
|
|
908
1153
|
// ─── List ─────────────────────────────────────────────────────────────────
|
|
909
1154
|
/**
|
|
910
1155
|
* List files and folders at a given path prefix.
|
|
911
|
-
*
|
|
1156
|
+
*
|
|
1157
|
+
* Server: GET /storage/list?prefix=&limit=&cursor=
|
|
912
1158
|
*
|
|
913
1159
|
* @example
|
|
914
1160
|
* ```ts
|
|
@@ -922,7 +1168,7 @@ var StorageManager = class {
|
|
|
922
1168
|
if (opts.limit) params.set("limit", String(opts.limit));
|
|
923
1169
|
if (opts.cursor) params.set("cursor", opts.cursor);
|
|
924
1170
|
const qs = params.toString() ? `?${params}` : "";
|
|
925
|
-
const result = await this.http.request(`${
|
|
1171
|
+
const result = await this.http.request(`${STORAGE.list}${qs}`, {
|
|
926
1172
|
method: "GET",
|
|
927
1173
|
headers: this.authHeaders
|
|
928
1174
|
});
|
|
@@ -930,11 +1176,8 @@ var StorageManager = class {
|
|
|
930
1176
|
const files = [];
|
|
931
1177
|
const folders = [];
|
|
932
1178
|
for (const item of result.items) {
|
|
933
|
-
if (item.type === "folder")
|
|
934
|
-
|
|
935
|
-
} else {
|
|
936
|
-
files.push(item);
|
|
937
|
-
}
|
|
1179
|
+
if (item.type === "folder") folders.push(item.path);
|
|
1180
|
+
else files.push(item);
|
|
938
1181
|
}
|
|
939
1182
|
return {
|
|
940
1183
|
files,
|
|
@@ -952,14 +1195,16 @@ var StorageManager = class {
|
|
|
952
1195
|
}
|
|
953
1196
|
// ─── Metadata ─────────────────────────────────────────────────────────────
|
|
954
1197
|
/**
|
|
955
|
-
* Get metadata
|
|
1198
|
+
* Get file metadata: size, MIME type, visibility, URLs.
|
|
1199
|
+
*
|
|
1200
|
+
* Server: GET /storage/metadata/:path
|
|
956
1201
|
*/
|
|
957
1202
|
async getMetadata(path) {
|
|
958
1203
|
const encoded = path.split("/").map(encodeURIComponent).join("/");
|
|
959
|
-
const result = await this.http.request(
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
);
|
|
1204
|
+
const result = await this.http.request(STORAGE.metadata(encoded), {
|
|
1205
|
+
method: "GET",
|
|
1206
|
+
headers: this.authHeaders
|
|
1207
|
+
});
|
|
963
1208
|
return {
|
|
964
1209
|
path: result.path,
|
|
965
1210
|
size: result.size,
|
|
@@ -974,15 +1219,18 @@ var StorageManager = class {
|
|
|
974
1219
|
// ─── Signed URL ───────────────────────────────────────────────────────────
|
|
975
1220
|
/**
|
|
976
1221
|
* Generate a time-limited download URL for a private file.
|
|
977
|
-
* Can be shared externally
|
|
1222
|
+
* Can be shared externally — no X-Storage-Key required to access.
|
|
978
1223
|
*
|
|
979
|
-
*
|
|
1224
|
+
* Note: Downloads via signed URLs bypass the server — stats are NOT tracked.
|
|
1225
|
+
*
|
|
1226
|
+
* Server: POST /storage/signed-url
|
|
1227
|
+
* Body: { path, expiresIn? }
|
|
980
1228
|
*
|
|
981
1229
|
* @param path File path.
|
|
982
1230
|
* @param expiresIn URL lifetime in seconds (default 3600 = 1 hour).
|
|
983
1231
|
*/
|
|
984
1232
|
async getSignedUrl(path, expiresIn = 3600) {
|
|
985
|
-
const result = await this.http.request(
|
|
1233
|
+
const result = await this.http.request(STORAGE.signedUrl, {
|
|
986
1234
|
method: "POST",
|
|
987
1235
|
body: { path, expiresIn },
|
|
988
1236
|
headers: this.authHeaders
|
|
@@ -996,10 +1244,13 @@ var StorageManager = class {
|
|
|
996
1244
|
}
|
|
997
1245
|
// ─── Visibility ───────────────────────────────────────────────────────────
|
|
998
1246
|
/**
|
|
999
|
-
* Change a file's visibility between public and private
|
|
1247
|
+
* Change a file's visibility between public and private.
|
|
1248
|
+
*
|
|
1249
|
+
* Server: PATCH /storage/visibility
|
|
1250
|
+
* Body: { path, isPublic }
|
|
1000
1251
|
*/
|
|
1001
1252
|
async setVisibility(path, isPublic) {
|
|
1002
|
-
const result = await this.http.request(
|
|
1253
|
+
const result = await this.http.request(STORAGE.visibility, {
|
|
1003
1254
|
method: "PATCH",
|
|
1004
1255
|
body: { path, isPublic },
|
|
1005
1256
|
headers: this.authHeaders
|
|
@@ -1012,53 +1263,95 @@ var StorageManager = class {
|
|
|
1012
1263
|
};
|
|
1013
1264
|
}
|
|
1014
1265
|
// ─── Folder Operations ────────────────────────────────────────────────────
|
|
1266
|
+
/**
|
|
1267
|
+
* Create a folder (empty GCS object used as a prefix marker).
|
|
1268
|
+
*
|
|
1269
|
+
* Server: POST /storage/folder
|
|
1270
|
+
* Body: { path }
|
|
1271
|
+
*/
|
|
1015
1272
|
async createFolder(path) {
|
|
1016
|
-
const result = await this.http.request(
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1273
|
+
const result = await this.http.request(STORAGE.folder, {
|
|
1274
|
+
method: "POST",
|
|
1275
|
+
body: { path },
|
|
1276
|
+
headers: this.authHeaders
|
|
1277
|
+
});
|
|
1020
1278
|
return { path: result.path };
|
|
1021
1279
|
}
|
|
1022
1280
|
// ─── File Operations ──────────────────────────────────────────────────────
|
|
1281
|
+
/**
|
|
1282
|
+
* Permanently delete a file.
|
|
1283
|
+
*
|
|
1284
|
+
* Server: DELETE /storage/file
|
|
1285
|
+
* Body: { path }
|
|
1286
|
+
*/
|
|
1023
1287
|
async deleteFile(path) {
|
|
1024
|
-
await this.http.request(
|
|
1288
|
+
await this.http.request(STORAGE.file, {
|
|
1025
1289
|
method: "DELETE",
|
|
1026
1290
|
body: { path },
|
|
1027
1291
|
headers: this.authHeaders
|
|
1028
1292
|
});
|
|
1029
1293
|
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Recursively delete a folder and all its contents.
|
|
1296
|
+
*
|
|
1297
|
+
* Server: DELETE /storage/folder
|
|
1298
|
+
* Body: { path }
|
|
1299
|
+
*/
|
|
1030
1300
|
async deleteFolder(path) {
|
|
1031
|
-
await this.http.request(
|
|
1301
|
+
await this.http.request(STORAGE.folderDelete, {
|
|
1032
1302
|
method: "DELETE",
|
|
1033
1303
|
body: { path },
|
|
1034
1304
|
headers: this.authHeaders
|
|
1035
1305
|
});
|
|
1036
1306
|
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Move (rename) a file.
|
|
1309
|
+
*
|
|
1310
|
+
* Server: POST /storage/move
|
|
1311
|
+
* Body: { from, to }
|
|
1312
|
+
*/
|
|
1037
1313
|
async move(from, to) {
|
|
1038
|
-
const result = await this.http.request(
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1314
|
+
const result = await this.http.request(STORAGE.move, {
|
|
1315
|
+
method: "POST",
|
|
1316
|
+
body: { from, to },
|
|
1317
|
+
headers: this.authHeaders
|
|
1318
|
+
});
|
|
1042
1319
|
return { from: result.from, to: result.to };
|
|
1043
1320
|
}
|
|
1321
|
+
/**
|
|
1322
|
+
* Copy a file to a new path.
|
|
1323
|
+
*
|
|
1324
|
+
* Server: POST /storage/copy
|
|
1325
|
+
* Body: { from, to }
|
|
1326
|
+
*/
|
|
1044
1327
|
async copy(from, to) {
|
|
1045
|
-
const result = await this.http.request(
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1328
|
+
const result = await this.http.request(STORAGE.copy, {
|
|
1329
|
+
method: "POST",
|
|
1330
|
+
body: { from, to },
|
|
1331
|
+
headers: this.authHeaders
|
|
1332
|
+
});
|
|
1049
1333
|
return { from: result.from, to: result.to };
|
|
1050
1334
|
}
|
|
1051
1335
|
// ─── Stats ────────────────────────────────────────────────────────────────
|
|
1336
|
+
/**
|
|
1337
|
+
* Get usage statistics for this storage key.
|
|
1338
|
+
*
|
|
1339
|
+
* Server: GET /storage/stats
|
|
1340
|
+
*/
|
|
1052
1341
|
async getStats() {
|
|
1053
|
-
const result = await this.http.request(
|
|
1342
|
+
const result = await this.http.request(STORAGE.stats, {
|
|
1054
1343
|
method: "GET",
|
|
1055
1344
|
headers: this.authHeaders
|
|
1056
1345
|
});
|
|
1057
1346
|
return result.stats;
|
|
1058
1347
|
}
|
|
1059
|
-
|
|
1348
|
+
/**
|
|
1349
|
+
* Get server info (no auth required).
|
|
1350
|
+
*
|
|
1351
|
+
* Server: GET /storage/info
|
|
1352
|
+
*/
|
|
1060
1353
|
async info() {
|
|
1061
|
-
return this.http.request(
|
|
1354
|
+
return this.http.request(STORAGE.info, {
|
|
1062
1355
|
method: "GET"
|
|
1063
1356
|
});
|
|
1064
1357
|
}
|
|
@@ -1108,7 +1401,8 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1108
1401
|
}
|
|
1109
1402
|
// ─── List ─────────────────────────────────────────────────────────────────
|
|
1110
1403
|
/**
|
|
1111
|
-
* List files within this scope.
|
|
1404
|
+
* List files within this scope.
|
|
1405
|
+
* The `prefix` option is relative to the scope root.
|
|
1112
1406
|
*
|
|
1113
1407
|
* @example
|
|
1114
1408
|
* ```ts
|
|
@@ -1160,7 +1454,7 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1160
1454
|
*
|
|
1161
1455
|
* @example
|
|
1162
1456
|
* ```ts
|
|
1163
|
-
* const user
|
|
1457
|
+
* const user = storage.scope('users/alice/');
|
|
1164
1458
|
* const userDocs = user.scope('docs/');
|
|
1165
1459
|
* // Effective prefix: users/alice/docs/
|
|
1166
1460
|
* ```
|
|
@@ -1174,17 +1468,16 @@ var ScopedStorage = class _ScopedStorage {
|
|
|
1174
1468
|
var HydrousClient = class {
|
|
1175
1469
|
constructor(config) {
|
|
1176
1470
|
this._recordsCache = /* @__PURE__ */ new Map();
|
|
1177
|
-
this._authCache = /* @__PURE__ */ new Map();
|
|
1178
1471
|
this._analyticsCache = /* @__PURE__ */ new Map();
|
|
1179
1472
|
this._storageCache = /* @__PURE__ */ new Map();
|
|
1180
1473
|
if (!config.authKey) {
|
|
1181
|
-
throw new Error("[HydrousDB] authKey is required.
|
|
1474
|
+
throw new Error("[HydrousDB] authKey is required.");
|
|
1182
1475
|
}
|
|
1183
1476
|
if (!config.bucketSecurityKey) {
|
|
1184
|
-
throw new Error("[HydrousDB] bucketSecurityKey is required.
|
|
1477
|
+
throw new Error("[HydrousDB] bucketSecurityKey is required.");
|
|
1185
1478
|
}
|
|
1186
1479
|
if (!config.storageKeys || Object.keys(config.storageKeys).length === 0) {
|
|
1187
|
-
throw new Error("[HydrousDB] storageKeys
|
|
1480
|
+
throw new Error("[HydrousDB] storageKeys must define at least one key.");
|
|
1188
1481
|
}
|
|
1189
1482
|
this.http = new HttpClient(config.baseUrl ?? DEFAULT_BASE_URL);
|
|
1190
1483
|
this.authKey_ = config.authKey;
|
|
@@ -1193,16 +1486,13 @@ var HydrousClient = class {
|
|
|
1193
1486
|
}
|
|
1194
1487
|
// ─── Records ──────────────────────────────────────────────────────────────
|
|
1195
1488
|
/**
|
|
1196
|
-
* Get a typed
|
|
1489
|
+
* Get a typed RecordsClient for a bucket.
|
|
1197
1490
|
*
|
|
1198
1491
|
* @example
|
|
1199
1492
|
* ```ts
|
|
1200
1493
|
* interface Post { title: string; published: boolean }
|
|
1201
1494
|
* const posts = db.records<Post>('blog-posts');
|
|
1202
|
-
* const post = await posts.create(
|
|
1203
|
-
* { title: 'Hello', published: false },
|
|
1204
|
-
* { queryableFields: ['published'] },
|
|
1205
|
-
* );
|
|
1495
|
+
* const post = await posts.create({ title: 'Hello', published: false });
|
|
1206
1496
|
* ```
|
|
1207
1497
|
*/
|
|
1208
1498
|
records(bucketKey) {
|
|
@@ -1216,28 +1506,28 @@ var HydrousClient = class {
|
|
|
1216
1506
|
}
|
|
1217
1507
|
// ─── Auth ─────────────────────────────────────────────────────────────────
|
|
1218
1508
|
/**
|
|
1219
|
-
* Get
|
|
1509
|
+
* Get the AuthClient.
|
|
1510
|
+
* Auth routes are NOT bucket-scoped in the URL — the bucket is determined
|
|
1511
|
+
* server-side from the API key itself.
|
|
1220
1512
|
*
|
|
1221
1513
|
* @example
|
|
1222
1514
|
* ```ts
|
|
1223
|
-
* const
|
|
1224
|
-
* const { user, session } = await auth.login({ email: '…', password: '…' });
|
|
1515
|
+
* const { user, session } = await db.auth().signup({ email: 'alice@example.com', password: 'pw' });
|
|
1225
1516
|
* ```
|
|
1226
1517
|
*/
|
|
1227
|
-
auth(
|
|
1228
|
-
if (!this.
|
|
1229
|
-
this.
|
|
1518
|
+
auth() {
|
|
1519
|
+
if (!this._authClient) {
|
|
1520
|
+
this._authClient = new AuthClient(this.http, this.authKey_);
|
|
1230
1521
|
}
|
|
1231
|
-
return this.
|
|
1522
|
+
return this._authClient;
|
|
1232
1523
|
}
|
|
1233
1524
|
// ─── Analytics ────────────────────────────────────────────────────────────
|
|
1234
1525
|
/**
|
|
1235
|
-
* Get an
|
|
1526
|
+
* Get an AnalyticsClient for a bucket.
|
|
1236
1527
|
*
|
|
1237
1528
|
* @example
|
|
1238
1529
|
* ```ts
|
|
1239
|
-
* const
|
|
1240
|
-
* const { count } = await analytics.count();
|
|
1530
|
+
* const { count } = await db.analytics('orders').count();
|
|
1241
1531
|
* ```
|
|
1242
1532
|
*/
|
|
1243
1533
|
analytics(bucketKey) {
|
|
@@ -1251,18 +1541,19 @@ var HydrousClient = class {
|
|
|
1251
1541
|
}
|
|
1252
1542
|
// ─── Storage ──────────────────────────────────────────────────────────────
|
|
1253
1543
|
/**
|
|
1254
|
-
* Get a
|
|
1255
|
-
* The name must match a key
|
|
1544
|
+
* Get a StorageManager for a named storage key.
|
|
1545
|
+
* The name must match a key in the `storageKeys` config object.
|
|
1256
1546
|
*
|
|
1257
|
-
* Attach `.scope(prefix)` to
|
|
1547
|
+
* Attach `.scope('prefix/')` to get a path-prefixed ScopedStorage.
|
|
1258
1548
|
*
|
|
1259
1549
|
* @example
|
|
1260
1550
|
* ```ts
|
|
1261
|
-
* const avatars
|
|
1262
|
-
* const
|
|
1551
|
+
* const avatars = db.storage('avatars');
|
|
1552
|
+
* const result = await avatars.upload(file, 'alice.jpg', { isPublic: true });
|
|
1263
1553
|
*
|
|
1264
|
-
*
|
|
1265
|
-
*
|
|
1554
|
+
* // Scoped:
|
|
1555
|
+
* const userFiles = db.storage('documents').scope(`users/${userId}/`);
|
|
1556
|
+
* await userFiles.upload(pdf, 'contract.pdf');
|
|
1266
1557
|
* ```
|
|
1267
1558
|
*/
|
|
1268
1559
|
storage(keyName) {
|
|
@@ -1288,15 +1579,21 @@ function createClient(config) {
|
|
|
1288
1579
|
return new HydrousClient(config);
|
|
1289
1580
|
}
|
|
1290
1581
|
|
|
1582
|
+
exports.ANALYTICS = ANALYTICS;
|
|
1583
|
+
exports.AUTH = AUTH;
|
|
1291
1584
|
exports.AnalyticsClient = AnalyticsClient;
|
|
1292
1585
|
exports.AnalyticsError = AnalyticsError;
|
|
1293
1586
|
exports.AuthClient = AuthClient;
|
|
1294
1587
|
exports.AuthError = AuthError;
|
|
1588
|
+
exports.DEFAULT_BASE_URL = DEFAULT_BASE_URL;
|
|
1589
|
+
exports.HttpClient = HttpClient;
|
|
1295
1590
|
exports.HydrousClient = HydrousClient;
|
|
1296
1591
|
exports.HydrousError = HydrousError;
|
|
1297
1592
|
exports.NetworkError = NetworkError;
|
|
1593
|
+
exports.RECORDS = RECORDS;
|
|
1298
1594
|
exports.RecordError = RecordError;
|
|
1299
1595
|
exports.RecordsClient = RecordsClient;
|
|
1596
|
+
exports.STORAGE = STORAGE;
|
|
1300
1597
|
exports.ScopedStorage = ScopedStorage;
|
|
1301
1598
|
exports.StorageError = StorageError;
|
|
1302
1599
|
exports.StorageManager = StorageManager;
|