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