hydrousdb 3.5.1 → 3.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -179,98 +179,74 @@ var HttpClient = class {
179
179
 
180
180
  // src/routes.ts
181
181
  var RECORDS = {
182
- /** GET|POST|PATCH|DELETE|HEAD /api/:bucketKey */
182
+ /** Base path for a bucket: GET|POST /api/:bucketKey */
183
183
  bucket: (bucketKey) => `/api/${bucketKey}`,
184
- /** POST /api/:bucketKey/batch/insert */
184
+ /** Batch insert: POST /api/:bucketKey/batch/insert */
185
185
  batchInsert: (bucketKey) => `/api/${bucketKey}/batch/insert`,
186
- /** POST /api/:bucketKey/batch/update */
186
+ /** Batch update: POST /api/:bucketKey/batch/update */
187
187
  batchUpdate: (bucketKey) => `/api/${bucketKey}/batch/update`,
188
- /** POST /api/:bucketKey/batch/delete */
188
+ /** Batch delete: POST /api/:bucketKey/batch/delete */
189
189
  batchDelete: (bucketKey) => `/api/${bucketKey}/batch/delete`
190
190
  };
191
191
  var ANALYTICS = {
192
- /** POST /api/analytics/:bucketKey */
192
+ /** Analytics query: POST /api/analytics/:bucketKey */
193
193
  query: (bucketKey) => `/api/analytics/${bucketKey}`
194
194
  };
195
195
  var AUTH = {
196
- /** POST /api/auth/signup body: { email, password, fullName?, ...extra } */
196
+ // ── Core ────────────────────────────────────────────────────────────────
197
197
  signup: "/api/auth/signup",
198
- /** POST /api/auth/signin body: { email, password } */
199
198
  signin: "/api/auth/signin",
200
- /** POST /api/auth/signout body: { sessionId, allDevices? } */
201
199
  signout: "/api/auth/signout",
202
- /** POST /api/auth/session/validate body: { sessionId } */
200
+ // ── Google OAuth ─────────────────────────────────────────────────────────
201
+ // POST /api/auth/google — sign in or create account via Google ID token
202
+ // POST /api/auth/google/link — link Google to an existing email/password account
203
+ // DELETE /api/auth/google/unlink — remove Google (only if a password is set)
204
+ googleSignIn: "/api/auth/google",
205
+ googleLink: "/api/auth/google/link",
206
+ googleUnlink: "/api/auth/google/unlink",
207
+ // ── Session ──────────────────────────────────────────────────────────────
203
208
  sessionValidate: "/api/auth/session/validate",
204
- /** POST /api/auth/session/refresh body: { refreshToken } */
205
209
  sessionRefresh: "/api/auth/session/refresh",
206
- /** GET /api/auth/user?userId=... */
210
+ // ── User ─────────────────────────────────────────────────────────────────
207
211
  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
212
  updateUser: "/api/auth/user",
212
- /** DELETE /api/auth/user?userId=... body: { sessionId } */
213
213
  deleteUser: "/api/auth/user",
214
- /** DELETE /api/auth/user/hard?userId=... body: { sessionId } */
214
+ // ── Admin ─────────────────────────────────────────────────────────────────
215
+ listUsers: "/api/auth/users",
215
216
  hardDeleteUser: "/api/auth/user/hard",
216
- /** DELETE /api/auth/users/bulk body: { userIds, hard?, sessionId } */
217
217
  bulkDeleteUsers: "/api/auth/users/bulk",
218
- /** POST /api/auth/password/change body: { sessionId, userId, oldPassword, newPassword } */
218
+ // ── Account ───────────────────────────────────────────────────────────────
219
+ accountLock: "/api/auth/account/lock",
220
+ accountUnlock: "/api/auth/account/unlock",
221
+ // ── Password ──────────────────────────────────────────────────────────────
219
222
  passwordChange: "/api/auth/password/change",
220
- /** POST /api/auth/password/reset/request body: { email } */
221
223
  passwordResetRequest: "/api/auth/password/reset/request",
222
- /** POST /api/auth/password/reset/confirm body: { resetToken, newPassword } */
223
224
  passwordResetConfirm: "/api/auth/password/reset/confirm",
224
- /** POST /api/auth/email/verify/request body: { userId } */
225
+ // ── Email Verification ────────────────────────────────────────────────────
225
226
  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"
227
+ emailVerifyConfirm: "/api/auth/email/verify/confirm"
232
228
  };
233
229
  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 */
230
+ base: "/storage",
247
231
  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= */
232
+ uploadRaw: "/storage/upload/raw",
233
+ uploadUrl: "/storage/upload/url",
234
+ confirm: "/storage/upload/confirm",
235
+ batchUploadUrls: "/storage/upload/batch/urls",
236
+ batchConfirm: "/storage/upload/batch/confirm",
237
+ download: (encodedPath) => `/storage/download/${encodedPath}`,
238
+ batchDownload: "/storage/download/batch",
251
239
  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? } */
240
+ metadata: (encodedPath) => `/storage/metadata/${encodedPath}`,
259
241
  signedUrl: "/storage/signed-url",
260
- /** PATCH /storage/visibility body: { path, isPublic } */
261
242
  visibility: "/storage/visibility",
262
- /** POST /storage/folder body: { path } */
263
243
  folder: "/storage/folder",
264
- /** DELETE /storage/file body: { path } */
265
244
  file: "/storage/file",
266
- /** DELETE /storage/folder body: { path } */
267
- folderDelete: "/storage/folder",
268
- /** POST /storage/move body: { from, to } */
245
+ folderDelete: "/storage/folder/delete",
269
246
  move: "/storage/move",
270
- /** POST /storage/copy body: { from, to } */
271
247
  copy: "/storage/copy",
272
- /** GET /storage/stats */
273
- stats: "/storage/stats"
248
+ stats: "/storage/stats",
249
+ info: "/storage/info"
274
250
  };
275
251
 
276
252
  // src/auth/client.ts
@@ -298,8 +274,7 @@ var AuthClient = class {
298
274
  */
299
275
  async signup(options) {
300
276
  const result = await this.post(AUTH.signup, options);
301
- const user = result.data;
302
- return this._buildAuthResult(user, result.session);
277
+ return this._buildAuthResult(result.data, result.session, result.isNew);
303
278
  }
304
279
  /**
305
280
  * Sign in with email + password.
@@ -309,8 +284,7 @@ var AuthClient = class {
309
284
  */
310
285
  async login(options) {
311
286
  const result = await this.post(AUTH.signin, options);
312
- const user = result.data;
313
- return this._buildAuthResult(user, result.session);
287
+ return this._buildAuthResult(result.data, result.session, result.isNew);
314
288
  }
315
289
  /**
316
290
  * Sign out — revoke a session (or all sessions with `allDevices: true`).
@@ -321,6 +295,111 @@ var AuthClient = class {
321
295
  async logout(options) {
322
296
  await this.post(AUTH.signout, options);
323
297
  }
298
+ // ─── Google Sign-In ───────────────────────────────────────────────────────
299
+ /**
300
+ * Sign in or create an account using a Google ID token.
301
+ *
302
+ * Pass the `idToken` from the Google Sign-In SDK. Your server verifies the
303
+ * token against Google's public keys, then either creates a new account or
304
+ * signs in the returning user. No password is ever set or required.
305
+ *
306
+ * The returned `AuthResult` is identical in shape to `login()` and `signup()`.
307
+ * Sessions from Google sign-in work with `validateSession`, `refreshSession`,
308
+ * and `logout` — nothing changes after the initial sign-in.
309
+ *
310
+ * Use `isNew` to drive onboarding flows.
311
+ *
312
+ * @example
313
+ * ```ts
314
+ * // Web — Google Identity Services
315
+ * google.accounts.id.initialize({
316
+ * client_id: 'YOUR_GOOGLE_CLIENT_ID',
317
+ * callback: async ({ credential }) => {
318
+ * const { user, session, isNew } = await db.auth().continueWithGoogle({
319
+ * idToken: credential,
320
+ * });
321
+ * if (isNew) router.push('/onboarding');
322
+ * else router.push('/dashboard');
323
+ * },
324
+ * });
325
+ *
326
+ * // React Native
327
+ * const { idToken } = await GoogleSignin.signIn();
328
+ * const { user, session, isNew } = await db.auth().continueWithGoogle({ idToken });
329
+ * ```
330
+ *
331
+ * Server: POST /api/auth/google
332
+ * Body: { idToken }
333
+ */
334
+ async continueWithGoogle(options) {
335
+ if (!options.idToken || typeof options.idToken !== "string") {
336
+ throw new Error("[HydrousDB] continueWithGoogle: idToken is required and must be a string.");
337
+ }
338
+ const result = await this.post(AUTH.googleSignIn, {
339
+ idToken: options.idToken
340
+ });
341
+ return this._buildAuthResult(result.data, result.session, result.isNew);
342
+ }
343
+ /**
344
+ * Link a Google account to the currently signed-in user.
345
+ *
346
+ * After linking, the user can sign in with either their email + password
347
+ * or with Google — both produce valid sessions.
348
+ *
349
+ * The server is idempotent — calling this when Google is already linked
350
+ * returns the updated user without creating duplicate entries.
351
+ *
352
+ * @example
353
+ * ```ts
354
+ * // User is signed in with email. They click "Connect Google" in settings.
355
+ * const { idToken } = await GoogleSignin.signIn();
356
+ * const updatedUser = await db.auth().linkGoogle({
357
+ * sessionId: currentSession.sessionId,
358
+ * idToken,
359
+ * });
360
+ * ```
361
+ *
362
+ * Server: POST /api/auth/google/link
363
+ * Body: { sessionId, idToken }
364
+ */
365
+ async linkGoogle(options) {
366
+ if (!options.sessionId) {
367
+ throw new Error("[HydrousDB] linkGoogle: sessionId is required.");
368
+ }
369
+ if (!options.idToken || typeof options.idToken !== "string") {
370
+ throw new Error("[HydrousDB] linkGoogle: idToken is required and must be a string.");
371
+ }
372
+ const result = await this.post(AUTH.googleLink, {
373
+ sessionId: options.sessionId,
374
+ idToken: options.idToken
375
+ });
376
+ return result.data;
377
+ }
378
+ /**
379
+ * Unlink Google from the currently signed-in user's account.
380
+ *
381
+ * Only succeeds if the user has a password set — you cannot remove the only
382
+ * sign-in method. If the account was created via Google and has no password,
383
+ * call `changePassword` first.
384
+ *
385
+ * @example
386
+ * ```ts
387
+ * await db.auth().unlinkGoogle({ sessionId: currentSession.sessionId });
388
+ * ```
389
+ *
390
+ * Server: DELETE /api/auth/google/unlink
391
+ * Body: { sessionId }
392
+ */
393
+ async unlinkGoogle(options) {
394
+ if (!options.sessionId) {
395
+ throw new Error("[HydrousDB] unlinkGoogle: sessionId is required.");
396
+ }
397
+ await this.http.request(AUTH.googleUnlink, {
398
+ method: "DELETE",
399
+ body: { sessionId: options.sessionId },
400
+ headers: { "X-Api-Key": this.authKey }
401
+ });
402
+ }
324
403
  // ─── Session Management ───────────────────────────────────────────────────
325
404
  /**
326
405
  * Validate an existing session and retrieve the current user.
@@ -535,7 +614,7 @@ var AuthClient = class {
535
614
  await this.post(AUTH.emailVerifyConfirm, { verifyToken });
536
615
  }
537
616
  // ─── Private helpers ──────────────────────────────────────────────────────
538
- _buildAuthResult(user, session) {
617
+ _buildAuthResult(user, session, isNew) {
539
618
  return {
540
619
  user,
541
620
  session: {
@@ -546,7 +625,8 @@ var AuthClient = class {
546
625
  expiresAt: session.expiresAt,
547
626
  refreshToken: session.refreshToken,
548
627
  refreshExpiresAt: session.expiresAt
549
- }
628
+ },
629
+ isNew: isNew ?? false
550
630
  };
551
631
  }
552
632
  };
@@ -556,16 +636,21 @@ function buildQueryParams(options = {}) {
556
636
  const params = new URLSearchParams();
557
637
  if (options.limit !== void 0) params.set("limit", String(options.limit));
558
638
  if (options.offset !== void 0) params.set("offset", String(options.offset));
559
- if (options.orderBy !== void 0) params.set("sortBy", options.orderBy);
560
639
  if (options.order !== void 0) params.set("sortOrder", options.order);
561
640
  if (options.fields !== void 0) params.set("fields", options.fields);
641
+ if (options.orderBy !== void 0) params.set("sortBy", options.orderBy);
642
+ if (options.sortBy !== void 0) params.set("sortBy", options.sortBy);
562
643
  if (options.startAfter !== void 0) params.set("cursor", options.startAfter);
563
644
  if (options.startAt !== void 0) params.set("cursor", options.startAt);
645
+ if (options.endAt !== void 0) params.set("endAt", options.endAt);
564
646
  if (options.timeScope !== void 0) params.set("timeScope", options.timeScope);
565
- if (options.dateRange?.start !== void 0)
647
+ if (options.startDate !== void 0) params.set("startDate", options.startDate);
648
+ if (options.endDate !== void 0) params.set("endDate", options.endDate);
649
+ if (options.dateRange?.start !== void 0 && options.startDate === void 0)
566
650
  params.set("startDate", new Date(options.dateRange.start).toISOString().split("T")[0]);
567
- if (options.dateRange?.end !== void 0)
651
+ if (options.dateRange?.end !== void 0 && options.endDate === void 0)
568
652
  params.set("endDate", new Date(options.dateRange.end).toISOString().split("T")[0]);
653
+ if (options.year !== void 0) params.set("year", options.year);
569
654
  if (options.filters && options.filters.length > 0) {
570
655
  for (const f of options.filters) {
571
656
  const key = f.op === "==" ? f.field : `${f.field}[${f.op}]`;