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