hydrousdb 3.5.1 → 3.5.3
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 +222 -31
- package/dist/index.cjs +153 -68
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +184 -73
- package/dist/index.d.ts +184 -73
- package/dist/index.mjs +153 -68
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# HydrousDB JS/TS SDK
|
|
2
2
|
|
|
3
|
-
**A database that doesn't choke on big JSON.** Store, query, and analyse massive records — with auth and file storage built in.
|
|
3
|
+
**A database that doesn't choke on big JSON.** Store, query, and analyse massive records — with auth, Google sign-in, and file storage built in.
|
|
4
4
|
|
|
5
5
|
```bash
|
|
6
6
|
npm install hydrousdb
|
|
@@ -17,6 +17,7 @@ npm install hydrousdb
|
|
|
17
17
|
- [Create, Read, Update, Delete](#create-read-update-delete)
|
|
18
18
|
- [Querying & Filtering](#querying--filtering)
|
|
19
19
|
- [Time Scope on Queries](#time-scope-on-queries)
|
|
20
|
+
- [Date Range Queries](#date-range-queries)
|
|
20
21
|
- [Pagination](#pagination)
|
|
21
22
|
- [Atomic Field Updates](#atomic-field-updates)
|
|
22
23
|
- [Batch Operations](#batch-operations)
|
|
@@ -26,6 +27,7 @@ npm install hydrousdb
|
|
|
26
27
|
- [Get All Records](#get-all-records)
|
|
27
28
|
- [Auth](#auth)
|
|
28
29
|
- [Sign Up & Log In](#sign-up--log-in)
|
|
30
|
+
- [Google Sign-In](#google-sign-in)
|
|
29
31
|
- [Session Management](#session-management)
|
|
30
32
|
- [User Profile](#user-profile)
|
|
31
33
|
- [Password & Email](#password--email)
|
|
@@ -143,11 +145,37 @@ const { records, hasMore, nextCursor } = await posts.query({
|
|
|
143
145
|
|
|
144
146
|
**Supported filter operators:** `==` `!=` `>` `<` `>=` `<=` `contains`
|
|
145
147
|
|
|
148
|
+
You can combine multiple filters. The first equality filter (`==`) drives the GCS index — additional filters are applied in-memory after hydration. This means you get fast indexed lookups for equality filters and flexible in-memory filtering for everything else.
|
|
149
|
+
|
|
150
|
+
**Filter combinations:**
|
|
151
|
+
```ts
|
|
152
|
+
// Equality only — fast index path
|
|
153
|
+
await posts.query({ filters: [{ field: 'status', op: '==', value: 'published' }] });
|
|
154
|
+
|
|
155
|
+
// Equality + range — index on status, range applied in-memory
|
|
156
|
+
await posts.query({ filters: [
|
|
157
|
+
{ field: 'status', op: '==', value: 'published' },
|
|
158
|
+
{ field: 'views', op: '>=', value: 100 },
|
|
159
|
+
]});
|
|
160
|
+
|
|
161
|
+
// Equality + contains — index on status, contains applied in-memory
|
|
162
|
+
await posts.query({ filters: [
|
|
163
|
+
{ field: 'status', op: '==', value: 'published' },
|
|
164
|
+
{ field: 'title', op: 'contains', value: 'hello' },
|
|
165
|
+
]});
|
|
166
|
+
|
|
167
|
+
// Multiple equality — first drives index, rest applied in-memory
|
|
168
|
+
await posts.query({ filters: [
|
|
169
|
+
{ field: 'status', op: '==', value: 'published' },
|
|
170
|
+
{ field: 'category', op: '==', value: 'tech' },
|
|
171
|
+
]});
|
|
172
|
+
```
|
|
173
|
+
|
|
146
174
|
---
|
|
147
175
|
|
|
148
176
|
### Time Scope on Queries
|
|
149
177
|
|
|
150
|
-
Pass `timeScope` to restrict records to a specific **day, month, or year
|
|
178
|
+
Pass `timeScope` to restrict records to a specific **day, month, or year**. This is the fastest way to scope a query by time — no timestamp arithmetic needed.
|
|
151
179
|
|
|
152
180
|
| Scope | Format | Example | Matches |
|
|
153
181
|
|---|---|---|---|
|
|
@@ -185,7 +213,7 @@ const { records: yearRecords } = await posts.query({
|
|
|
185
213
|
limit: 100,
|
|
186
214
|
});
|
|
187
215
|
|
|
188
|
-
// Fully composable with filters
|
|
216
|
+
// Fully composable with filters — this is the recommended pattern for large buckets
|
|
189
217
|
const { records: published } = await posts.query({
|
|
190
218
|
timeScope: '_month_2603',
|
|
191
219
|
filters: [{ field: 'status', op: '==', value: 'published' }],
|
|
@@ -199,6 +227,49 @@ const all = await posts.getAll({ timeScope: '_year_26' });
|
|
|
199
227
|
|
|
200
228
|
---
|
|
201
229
|
|
|
230
|
+
### Date Range Queries
|
|
231
|
+
|
|
232
|
+
Pass `startDate` and/or `endDate` (ISO date strings) to walk records within a calendar range. Both fields are optional — omit either for an open-ended range.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
// Records from a specific month
|
|
236
|
+
const { records } = await posts.query({
|
|
237
|
+
startDate: '2026-04-01',
|
|
238
|
+
endDate: '2026-04-30',
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Records since a specific date (no end bound)
|
|
242
|
+
const { records: recent } = await posts.query({
|
|
243
|
+
startDate: '2026-01-01',
|
|
244
|
+
orderBy: 'createdAt',
|
|
245
|
+
order: 'desc',
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
// Walk a specific year using the year shorthand
|
|
249
|
+
const { records: yearRecords } = await posts.query({
|
|
250
|
+
year: '26', // two-digit year
|
|
251
|
+
order: 'desc',
|
|
252
|
+
limit: 100,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Combine with filters
|
|
256
|
+
const { records: paidOrders } = await orders.query({
|
|
257
|
+
startDate: '2026-04-01',
|
|
258
|
+
endDate: '2026-04-30',
|
|
259
|
+
filters: [{ field: 'status', op: '==', value: 'paid' }],
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
// Sort by any field
|
|
263
|
+
const { records: byPrice } = await rooms.query({
|
|
264
|
+
timeScope: '_month_2604',
|
|
265
|
+
filters: [{ field: 'status', op: '==', value: 'available' }],
|
|
266
|
+
sortBy: 'pricePerNight',
|
|
267
|
+
order: 'asc',
|
|
268
|
+
});
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
202
273
|
### Pagination
|
|
203
274
|
|
|
204
275
|
`query()` returns a cursor you can pass straight into the next call.
|
|
@@ -229,15 +300,16 @@ Avoid race conditions with server-side sentinels inside `patch()`:
|
|
|
229
300
|
|
|
230
301
|
```ts
|
|
231
302
|
await posts.patch(post.id, {
|
|
232
|
-
views: { __op: 'increment',
|
|
233
|
-
credits: { __op: 'decrement',
|
|
234
|
-
slug: { __op: 'setOnce',
|
|
235
|
-
tags: { __op: 'appendUnique',
|
|
236
|
-
|
|
237
|
-
rating: { __op: 'clamp',
|
|
238
|
-
price: { __op: 'multiplyBy',
|
|
239
|
-
active: { __op: 'toggleBool' },
|
|
240
|
-
syncedAt: { __op: 'serverTimestamp' },
|
|
303
|
+
views: { __op: 'increment', delta: 1 }, // add N
|
|
304
|
+
credits: { __op: 'decrement', delta: 5 }, // subtract N
|
|
305
|
+
slug: { __op: 'setOnce', value: 'my-post' }, // set only if currently empty
|
|
306
|
+
tags: { __op: 'appendUnique', item: 'featured' }, // add to array, no duplicates
|
|
307
|
+
oldTag: { __op: 'removeFromArray', item: 'draft' }, // remove from array
|
|
308
|
+
rating: { __op: 'clamp', value: 6, min: 0, max: 5 }, // clamp to range
|
|
309
|
+
price: { __op: 'multiplyBy', factor: 1.1 }, // multiply
|
|
310
|
+
active: { __op: 'toggleBool' }, // flip boolean
|
|
311
|
+
syncedAt: { __op: 'serverTimestamp' }, // set to server time
|
|
312
|
+
score: { __op: 'setIf', value: 99, cond: { op: '>=', value: 50 } }, // conditional set
|
|
241
313
|
} as any);
|
|
242
314
|
```
|
|
243
315
|
|
|
@@ -341,7 +413,7 @@ const all = await posts.getAll({
|
|
|
341
413
|
|
|
342
414
|
## Auth
|
|
343
415
|
|
|
344
|
-
A complete user system — signup, login, sessions, password reset, email verification, and admin controls.
|
|
416
|
+
A complete user system — signup, login, Google sign-in, sessions, password reset, email verification, and admin controls.
|
|
345
417
|
|
|
346
418
|
```ts
|
|
347
419
|
const auth = db.auth();
|
|
@@ -380,6 +452,118 @@ Store `session.sessionId` and `session.refreshToken` in your app.
|
|
|
380
452
|
|
|
381
453
|
---
|
|
382
454
|
|
|
455
|
+
### Google Sign-In
|
|
456
|
+
|
|
457
|
+
Allow users to sign in or create an account with one tap — no password required.
|
|
458
|
+
|
|
459
|
+
The flow is the same regardless of platform: get a Google ID token on the client, pass it to `continueWithGoogle`. The server verifies the token and returns a session identical in shape to `login()` and `signup()`.
|
|
460
|
+
|
|
461
|
+
**Web (Google Identity Services)**
|
|
462
|
+
|
|
463
|
+
```html
|
|
464
|
+
<!-- Add to your HTML <head> -->
|
|
465
|
+
<script src="https://accounts.google.com/gsi/client" async defer></script>
|
|
466
|
+
```
|
|
467
|
+
|
|
468
|
+
```ts
|
|
469
|
+
google.accounts.id.initialize({
|
|
470
|
+
client_id: 'YOUR_GOOGLE_CLIENT_ID', // from Google Cloud Console
|
|
471
|
+
callback: async ({ credential }) => {
|
|
472
|
+
const { user, session, isNew } = await db.auth().continueWithGoogle({
|
|
473
|
+
idToken: credential,
|
|
474
|
+
});
|
|
475
|
+
if (isNew) router.push('/onboarding'); // brand-new account
|
|
476
|
+
else router.push('/dashboard'); // returning user
|
|
477
|
+
},
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
// Render the button anywhere in your page
|
|
481
|
+
google.accounts.id.renderButton(
|
|
482
|
+
document.getElementById('google-btn'),
|
|
483
|
+
{ theme: 'outline', size: 'large', text: 'continue_with' },
|
|
484
|
+
);
|
|
485
|
+
```
|
|
486
|
+
|
|
487
|
+
**React Native**
|
|
488
|
+
|
|
489
|
+
```ts
|
|
490
|
+
import { GoogleSignin } from '@react-native-google-signin/google-signin';
|
|
491
|
+
|
|
492
|
+
GoogleSignin.configure({ webClientId: 'YOUR_GOOGLE_CLIENT_ID' });
|
|
493
|
+
|
|
494
|
+
const { idToken } = await GoogleSignin.signIn();
|
|
495
|
+
const { user, session, isNew } = await db.auth().continueWithGoogle({ idToken });
|
|
496
|
+
if (isNew) navigation.navigate('Onboarding');
|
|
497
|
+
```
|
|
498
|
+
|
|
499
|
+
**Flutter**
|
|
500
|
+
|
|
501
|
+
```dart
|
|
502
|
+
final googleUser = await GoogleSignIn().signIn();
|
|
503
|
+
final auth = await googleUser!.authentication;
|
|
504
|
+
// Send auth.idToken to your backend which calls the HydrousDB SDK
|
|
505
|
+
```
|
|
506
|
+
|
|
507
|
+
**What `isNew` tells you**
|
|
508
|
+
|
|
509
|
+
```ts
|
|
510
|
+
const { user, session, isNew } = await auth.continueWithGoogle({ idToken });
|
|
511
|
+
|
|
512
|
+
if (isNew) {
|
|
513
|
+
// Account was just created — show onboarding, collect extra info, etc.
|
|
514
|
+
console.log('Welcome!', user.fullName);
|
|
515
|
+
} else {
|
|
516
|
+
// Returning user — go straight to the app
|
|
517
|
+
console.log('Welcome back!', user.email);
|
|
518
|
+
}
|
|
519
|
+
```
|
|
520
|
+
|
|
521
|
+
**Link Google to an existing email/password account**
|
|
522
|
+
|
|
523
|
+
```ts
|
|
524
|
+
// User is signed in with email. They click "Connect Google" in settings.
|
|
525
|
+
const { idToken } = await GoogleSignin.signIn();
|
|
526
|
+
const updatedUser = await auth.linkGoogle({
|
|
527
|
+
sessionId: currentSession.sessionId,
|
|
528
|
+
idToken,
|
|
529
|
+
});
|
|
530
|
+
// After linking, the user can sign in with either method
|
|
531
|
+
```
|
|
532
|
+
|
|
533
|
+
**Unlink Google**
|
|
534
|
+
|
|
535
|
+
```ts
|
|
536
|
+
// Only works if the user has a password set — prevents account lockout
|
|
537
|
+
await auth.unlinkGoogle({ sessionId: currentSession.sessionId });
|
|
538
|
+
```
|
|
539
|
+
|
|
540
|
+
**Setting a password on a Google-only account**
|
|
541
|
+
|
|
542
|
+
```ts
|
|
543
|
+
// Google users have no password by default.
|
|
544
|
+
// Pass an empty string for currentPassword to set the first one.
|
|
545
|
+
await auth.changePassword({
|
|
546
|
+
sessionId: session.sessionId,
|
|
547
|
+
userId: user.id,
|
|
548
|
+
currentPassword: '', // empty — no password set yet
|
|
549
|
+
newPassword: 'newpassword123',
|
|
550
|
+
});
|
|
551
|
+
// After this, the user can sign in with email+password AND Google
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
The `UserRecord` for Google users includes:
|
|
555
|
+
|
|
556
|
+
```ts
|
|
557
|
+
interface UserRecord {
|
|
558
|
+
authProvider?: 'email' | 'google'; // 'google' for Google sign-in users
|
|
559
|
+
picture?: string | null; // profile photo URL from Google
|
|
560
|
+
googleId?: string; // stable Google identifier
|
|
561
|
+
// ... all other standard fields
|
|
562
|
+
}
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
---
|
|
566
|
+
|
|
383
567
|
### Session Management
|
|
384
568
|
|
|
385
569
|
```ts
|
|
@@ -423,7 +607,9 @@ interface UserRecord {
|
|
|
423
607
|
createdAt: number; // Unix ms
|
|
424
608
|
updatedAt: number; // Unix ms
|
|
425
609
|
metadata?: Record<string, unknown>;
|
|
426
|
-
|
|
610
|
+
authProvider?: 'email' | 'google'; // how the account was created
|
|
611
|
+
picture?: string | null; // Google profile photo URL
|
|
612
|
+
[key: string]: unknown; // custom fields from signup
|
|
427
613
|
}
|
|
428
614
|
```
|
|
429
615
|
|
|
@@ -433,6 +619,7 @@ interface UserRecord {
|
|
|
433
619
|
|
|
434
620
|
```ts
|
|
435
621
|
// Change password — requires an active session AND the current password
|
|
622
|
+
// Pass empty string for currentPassword to SET a first password (Google users)
|
|
436
623
|
await auth.changePassword({
|
|
437
624
|
sessionId: session.sessionId,
|
|
438
625
|
userId: user.id,
|
|
@@ -632,7 +819,6 @@ const meta = await storage.getMetadata('avatars/alice.jpg');
|
|
|
632
819
|
// → { path, size, mimeType, isPublic, publicUrl, downloadUrl, createdAt, updatedAt }
|
|
633
820
|
|
|
634
821
|
// Generate a time-limited link — anyone with the URL can download (no key needed)
|
|
635
|
-
// Note: downloads via signed URL bypass the server, so download stats are NOT tracked
|
|
636
822
|
const { signedUrl, expiresAt, expiresIn } = await storage.getSignedUrl(
|
|
637
823
|
'private/report.pdf',
|
|
638
824
|
3600, // lifetime in seconds (default: 3600)
|
|
@@ -690,7 +876,7 @@ const reports = userFiles.scope('reports/'); // → users/{userId}/reports
|
|
|
690
876
|
await reports.upload(file, 'q1.pdf'); // → users/{userId}/reports/q1.pdf
|
|
691
877
|
|
|
692
878
|
// All StorageManager methods are available on ScopedStorage
|
|
693
|
-
const meta
|
|
879
|
+
const meta = await userFiles.getMetadata('contract.pdf');
|
|
694
880
|
const { signedUrl } = await userFiles.getSignedUrl('contract.pdf', 900);
|
|
695
881
|
await userFiles.move('old.pdf', 'new.pdf');
|
|
696
882
|
await userFiles.deleteFile('contract.pdf');
|
|
@@ -723,7 +909,7 @@ const analytics = db.analytics('orders');
|
|
|
723
909
|
|
|
724
910
|
### Date Range (Time Scope)
|
|
725
911
|
|
|
726
|
-
Almost every analytics method accepts an optional `dateRange` to restrict results to a time window. Both `start` and `end` are Unix timestamps **in milliseconds
|
|
912
|
+
Almost every analytics method accepts an optional `dateRange` to restrict results to a time window. Both `start` and `end` are Unix timestamps **in milliseconds**. Both fields are optional; omit either for an open-ended range.
|
|
727
913
|
|
|
728
914
|
```ts
|
|
729
915
|
interface DateRange {
|
|
@@ -779,7 +965,7 @@ How many records have each value of a field.
|
|
|
779
965
|
const dist = await analytics.distribution({
|
|
780
966
|
field: 'status',
|
|
781
967
|
limit: 10,
|
|
782
|
-
order: 'desc',
|
|
968
|
+
order: 'desc',
|
|
783
969
|
dateRange: { start: new Date('2025-01-01').getTime() },
|
|
784
970
|
});
|
|
785
971
|
// → [{ value: 'published', count: 320 }, { value: 'draft', count: 80 }, …]
|
|
@@ -846,7 +1032,6 @@ const revTrend = await analytics.fieldTimeSeries({
|
|
|
846
1032
|
end: Date.now(),
|
|
847
1033
|
},
|
|
848
1034
|
});
|
|
849
|
-
// → [{ date: '2025-01-01', value: 4820.5 }, …]
|
|
850
1035
|
|
|
851
1036
|
// Monthly average order value
|
|
852
1037
|
const avgTrend = await analytics.fieldTimeSeries({
|
|
@@ -863,11 +1048,10 @@ const avgTrend = await analytics.fieldTimeSeries({
|
|
|
863
1048
|
Most frequent values for a field by record count.
|
|
864
1049
|
|
|
865
1050
|
```ts
|
|
866
|
-
// Top 10 countries
|
|
867
1051
|
const top10 = await analytics.topN({
|
|
868
1052
|
field: 'countryCode',
|
|
869
1053
|
n: 10,
|
|
870
|
-
labelField: 'countryName',
|
|
1054
|
+
labelField: 'countryName',
|
|
871
1055
|
order: 'desc',
|
|
872
1056
|
dateRange: { start: new Date('2025-01-01').getTime() },
|
|
873
1057
|
});
|
|
@@ -892,7 +1076,7 @@ const priceStats = await analytics.stats({
|
|
|
892
1076
|
|
|
893
1077
|
### Records via BigQuery
|
|
894
1078
|
|
|
895
|
-
Fetch filtered records through the BigQuery engine instead of
|
|
1079
|
+
Fetch filtered records through the BigQuery engine instead of the GCS index. Useful for large result sets or complex server-side filtering.
|
|
896
1080
|
|
|
897
1081
|
```ts
|
|
898
1082
|
const records = await analytics.records<Order>({
|
|
@@ -901,7 +1085,7 @@ const records = await analytics.records<Order>({
|
|
|
901
1085
|
{ field: 'amount', op: '>=', value: 100 },
|
|
902
1086
|
{ field: 'country', op: 'CONTAINS', value: 'US' },
|
|
903
1087
|
],
|
|
904
|
-
selectFields: ['id', 'amount', 'country', 'createdAt'],
|
|
1088
|
+
selectFields: ['id', 'amount', 'country', 'createdAt'],
|
|
905
1089
|
orderBy: 'createdAt',
|
|
906
1090
|
order: 'desc',
|
|
907
1091
|
limit: 1000,
|
|
@@ -951,7 +1135,7 @@ const storageInfo = await analytics.storageStats({
|
|
|
951
1135
|
|
|
952
1136
|
### Cross-Bucket
|
|
953
1137
|
|
|
954
|
-
Compare the same metric across multiple buckets in a single query.
|
|
1138
|
+
Compare the same metric across multiple buckets in a single query.
|
|
955
1139
|
|
|
956
1140
|
```ts
|
|
957
1141
|
const compare = await analytics.crossBucket({
|
|
@@ -1069,6 +1253,7 @@ import type {
|
|
|
1069
1253
|
DateRange, Granularity, Aggregation, SortOrder,
|
|
1070
1254
|
AnalyticsQuery, AnalyticsResult, AnalyticsFilter,
|
|
1071
1255
|
UserRecord, AuthResult, Session,
|
|
1256
|
+
GoogleSignInOptions, GoogleLinkOptions,
|
|
1072
1257
|
UploadOptions, UploadResult,
|
|
1073
1258
|
ListOptions, ListResult, FileMetadata, SignedUrlResult,
|
|
1074
1259
|
StorageStats,
|
|
@@ -1084,6 +1269,7 @@ import type {
|
|
|
1084
1269
|
- **Keys travel in headers only** — the SDK enforces this. They never appear in URLs, query strings, access logs, or browser history.
|
|
1085
1270
|
- **Files are private by default.** `isPublic` defaults to `false`. Use `getSignedUrl()` for temporary external sharing.
|
|
1086
1271
|
- **Use scoped storage** (`storage.scope('prefix/')`) to isolate files per user and prevent path-traversal bugs.
|
|
1272
|
+
- **Google ID tokens are verified server-side** — the SDK never sends tokens to Google directly after initial sign-in.
|
|
1087
1273
|
|
|
1088
1274
|
---
|
|
1089
1275
|
|
|
@@ -1165,7 +1351,7 @@ const db = createClient({
|
|
|
1165
1351
|
| `patch(id, data, opts?)` | `{ id, updatedAt? }` | Partial update. `opts.merge` for deep merge. `opts.trackHistory` to save a version. |
|
|
1166
1352
|
| `delete(id)` | `void` | Permanently delete a record. |
|
|
1167
1353
|
| `exists(id)` | `boolean` | Lightweight existence check (HEAD request). |
|
|
1168
|
-
| `query(opts?)` | `QueryResult<T>` | Filter, sort, paginate. Supports `dateRange`. |
|
|
1354
|
+
| `query(opts?)` | `QueryResult<T>` | Filter, sort, paginate. Supports `dateRange`, `timeScope`, `startDate`, `endDate`, `year`, `sortBy`. |
|
|
1169
1355
|
| `getAll(opts?)` | `(T & RecordResult)[]` | Fetch all records (no filter support — use `query` for filters). |
|
|
1170
1356
|
| `batchCreate(items, opts?)` | `{ results, errors, successful, failed }` | Up to 500 records at once. |
|
|
1171
1357
|
| `batchUpdate(updates, userEmail?)` | `{ successful, failed }` | Up to 500 records at once. |
|
|
@@ -1179,7 +1365,8 @@ const db = createClient({
|
|
|
1179
1365
|
|---|---|---|
|
|
1180
1366
|
| `filters` | `QueryFilter[]` | Array of `{ field, op, value }` |
|
|
1181
1367
|
| `fields` | `string` | Comma-separated list of fields to return |
|
|
1182
|
-
| `orderBy` | `string` | Field to sort by |
|
|
1368
|
+
| `orderBy` | `string` | Field to sort by (server maps to `sortBy`) |
|
|
1369
|
+
| `sortBy` | `string` | Alias for `orderBy` — maps directly to `?sortBy=` |
|
|
1183
1370
|
| `order` | `'asc' \| 'desc'` | Sort direction |
|
|
1184
1371
|
| `limit` | `number` | Max records to return |
|
|
1185
1372
|
| `offset` | `number` | Skip N records |
|
|
@@ -1188,6 +1375,9 @@ const db = createClient({
|
|
|
1188
1375
|
| `endAt` | `string` | Cursor — stop at this cursor |
|
|
1189
1376
|
| `dateRange` | `DateRange` | `{ start?, end? }` in Unix ms |
|
|
1190
1377
|
| `timeScope` | `string` | Prefix-based time filter: `_day_YYMMDD`, `_month_YYMM`, or `_year_YY` |
|
|
1378
|
+
| `startDate` | `string` | ISO date string e.g. `'2026-01-01'` — GCS day-range walk start |
|
|
1379
|
+
| `endDate` | `string` | ISO date string e.g. `'2026-12-31'` — GCS day-range walk end |
|
|
1380
|
+
| `year` | `string` | Two-digit year e.g. `'26'` — restricts monthly walk to that year |
|
|
1191
1381
|
|
|
1192
1382
|
---
|
|
1193
1383
|
|
|
@@ -1197,17 +1387,20 @@ const db = createClient({
|
|
|
1197
1387
|
|---|---|
|
|
1198
1388
|
| `signup(opts)` | Register + create session. Extra fields on `opts` are stored on the user. |
|
|
1199
1389
|
| `login(opts)` | Authenticate + create session. |
|
|
1390
|
+
| `continueWithGoogle({ idToken })` | Sign in or create account via Google ID token. Returns `isNew` flag. |
|
|
1391
|
+
| `linkGoogle({ sessionId, idToken })` | Add Google sign-in to an existing email/password account. |
|
|
1392
|
+
| `unlinkGoogle({ sessionId })` | Remove Google sign-in (only if a password is set). |
|
|
1200
1393
|
| `logout({ sessionId, allDevices? })` | Revoke one session or all sessions. |
|
|
1201
1394
|
| `validateSession(sessionId)` | Check if a session is active; returns current user. |
|
|
1202
1395
|
| `refreshSession(refreshToken)` | Get a new session from a refresh token. |
|
|
1203
1396
|
| `getUser(userId)` | Fetch a user by ID. |
|
|
1204
1397
|
| `updateUser(opts)` | Update profile fields. |
|
|
1205
|
-
| `
|
|
1398
|
+
| `deleteUser(sessionId, userId)` | Soft-delete a user. |
|
|
1399
|
+
| `changePassword(opts)` | Authenticated password change. Pass `currentPassword: ''` to set first password on Google accounts. |
|
|
1206
1400
|
| `requestPasswordReset(email)` | Send reset email (always succeeds to prevent enumeration). |
|
|
1207
1401
|
| `confirmPasswordReset(token, newPw)` | Apply new password from reset token. |
|
|
1208
|
-
| `requestEmailVerification(userId)` | Send verification email. |
|
|
1402
|
+
| `requestEmailVerification(userId)` | Send verification email. Not needed for Google users. |
|
|
1209
1403
|
| `confirmEmailVerification(token)` | Mark email verified from token. |
|
|
1210
|
-
| `getUser(userId)` | Fetch a user by ID. |
|
|
1211
1404
|
| `listUsers(opts)` | Paginated user list. Admin only. |
|
|
1212
1405
|
| `lockAccount(opts)` | Lock a user account. Admin only. |
|
|
1213
1406
|
| `unlockAccount(sessionId, userId)` | Unlock a user account. Admin only. |
|
|
@@ -1242,8 +1435,6 @@ const db = createClient({
|
|
|
1242
1435
|
| `info()` | Server info — no auth required. |
|
|
1243
1436
|
| `scope(prefix)` | Get a `ScopedStorage` that auto-prefixes all paths. |
|
|
1244
1437
|
|
|
1245
|
-
`ScopedStorage` exposes every method above (except `info`) plus `scope(subPrefix)` for nesting deeper.
|
|
1246
|
-
|
|
1247
1438
|
---
|
|
1248
1439
|
|
|
1249
1440
|
### `db.analytics(bucket)` — all methods
|