hydrousdb 2.0.3 → 3.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -1,180 +1,222 @@
1
+ interface RequestOptions {
2
+ method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
3
+ body?: unknown;
4
+ headers?: Record<string, string>;
5
+ rawBody?: string | FormData | Uint8Array<ArrayBuffer>;
6
+ contentType?: string;
7
+ }
8
+ declare class HttpClient {
9
+ private readonly baseUrl;
10
+ constructor(baseUrl: string);
11
+ request<T = unknown>(path: string, apiKeyOrOpts?: string | RequestOptions, opts?: RequestOptions): Promise<T>;
12
+ get<T = unknown>(path: string, apiKey?: string, headers?: Record<string, string>): Promise<T>;
13
+ post<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
14
+ put<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
15
+ patch<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
16
+ delete<T = unknown>(path: string, apiKey?: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
17
+ putToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
18
+ }
19
+
1
20
  /**
2
- * Named storage keys.
3
- * Create as many as you need in the HydrousDB dashboard and give each one a
4
- * meaningful name — you'll reference it by name when calling storage methods.
21
+ * Storage keys map — one named key per storage bucket/permission scope.
22
+ * Keys start with `ssk_`. You can define as many as you need.
5
23
  *
6
24
  * @example
7
- * {
25
+ * ```ts
26
+ * storageKeys: {
8
27
  * main: 'ssk_main_…',
9
28
  * avatars: 'ssk_avatars_…',
10
29
  * documents: 'ssk_docs_…',
11
30
  * }
31
+ * ```
12
32
  */
13
33
  type StorageKeys = Record<string, string>;
14
34
  interface HydrousConfig {
15
- /** Your HydrousDB project base URL */
16
- url: string;
17
- /** Auth service key — used for all auth.* operations */
35
+ /**
36
+ * Auth service key (starts with `hk_auth_`).
37
+ * Used exclusively for all `/auth/*` routes — signup, login, sessions, etc.
38
+ * Obtain from your dashboard at https://hydrousdb.com/dashboard.
39
+ */
18
40
  authKey: string;
19
- /** Bucket security key — used for records.* and analytics.* operations */
41
+ /**
42
+ * Bucket security key (starts with `hk_bucket_`).
43
+ * Used for all `/records/*` and `/analytics/*` routes.
44
+ * Obtain from your dashboard at https://hydrousdb.com/dashboard.
45
+ */
20
46
  bucketSecurityKey: string;
21
47
  /**
22
- * Storage keys object. Each property name is a label you choose; each
23
- * value is a storage key (ssk_…) from your dashboard.
24
- * You can have as many as you need.
48
+ * Named storage keys (each starts with `ssk_`).
49
+ * Define one key per storage bucket/permission scope.
50
+ * Pass the name of the key you want when calling `db.storage('keyName')`.
51
+ *
52
+ * @example
53
+ * ```ts
54
+ * storageKeys: {
55
+ * main: 'ssk_main_…', // default general-purpose bucket
56
+ * avatars: 'ssk_avatars_…', // user avatar uploads
57
+ * documents: 'ssk_docs_…', // private documents
58
+ * }
59
+ * ```
25
60
  */
26
61
  storageKeys: StorageKeys;
27
- /** Optional global request timeout in milliseconds (default: 30 000) */
28
- timeout?: number;
62
+ /**
63
+ * Override the API base URL. Defaults to the official HydrousDB endpoint.
64
+ * You almost never need to set this.
65
+ */
66
+ baseUrl?: string;
29
67
  }
30
- interface HydrousResponse<T = unknown> {
31
- data: T | null;
32
- error: HydrousError | null;
68
+ interface SignupOptions {
69
+ email: string;
70
+ password: string;
71
+ fullName?: string;
72
+ [key: string]: unknown;
33
73
  }
34
- interface HydrousError {
35
- message: string;
36
- code: string;
37
- status?: number;
74
+ interface LoginOptions {
75
+ email: string;
76
+ password: string;
38
77
  }
39
- type FilterOperator = 'eq' | 'neq' | 'gt' | 'gte' | 'lt' | 'lte' | 'like' | 'ilike' | 'in' | 'nin' | 'is' | 'not';
40
- interface Filter {
41
- field: string;
42
- operator: FilterOperator;
43
- value: unknown;
78
+ interface UserRecord {
79
+ id: string;
80
+ email: string;
81
+ fullName?: string | null;
82
+ emailVerified: boolean;
83
+ accountStatus: 'active' | 'locked' | 'suspended';
84
+ role: 'user' | 'admin';
85
+ createdAt: number;
86
+ updatedAt: number;
87
+ metadata?: Record<string, unknown>;
88
+ [key: string]: unknown;
44
89
  }
45
- interface QueryOptions {
46
- where?: Filter | Filter[];
47
- orderBy?: {
48
- field: string;
49
- direction?: 'asc' | 'desc';
50
- };
90
+ interface Session {
91
+ sessionId: string;
92
+ userId: string;
93
+ bucketId: string;
94
+ createdAt: number;
95
+ expiresAt: number;
96
+ refreshToken: string;
97
+ refreshExpiresAt: number;
98
+ }
99
+ interface AuthResult {
100
+ user: UserRecord;
101
+ session: Session;
102
+ }
103
+ interface UpdateUserOptions {
104
+ sessionId: string;
105
+ userId: string;
106
+ data: Partial<Omit<UserRecord, 'id' | 'email' | 'createdAt'>>;
107
+ }
108
+ interface ChangePasswordOptions {
109
+ sessionId: string;
110
+ userId: string;
111
+ currentPassword: string;
112
+ newPassword: string;
113
+ }
114
+ interface ListUsersOptions {
115
+ sessionId: string;
51
116
  limit?: number;
52
117
  offset?: number;
53
- select?: string[];
54
- }
55
- interface RecordResponse<T = Record<string, unknown>> {
56
- data: T[];
57
- count: number;
58
- error: HydrousError | null;
59
118
  }
60
- interface SingleRecordResponse<T = Record<string, unknown>> {
61
- data: T | null;
62
- error: HydrousError | null;
119
+ interface ListUsersResult {
120
+ users: UserRecord[];
121
+ total: number;
122
+ limit: number;
123
+ offset: number;
63
124
  }
64
- interface AuthUser {
125
+ type RecordData = Record<string, unknown>;
126
+ interface RecordResult {
65
127
  id: string;
66
- email: string;
67
- createdAt: string;
68
- updatedAt: string;
69
- metadata?: Record<string, unknown>;
128
+ createdAt?: number;
129
+ updatedAt?: number;
130
+ [key: string]: unknown;
70
131
  }
71
- interface AuthSession {
72
- accessToken: string;
73
- refreshToken: string;
74
- expiresAt: number;
75
- user: AuthUser;
132
+ interface QueryFilter {
133
+ field: string;
134
+ op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'CONTAINS';
135
+ value: string | number | boolean;
76
136
  }
77
- interface SignUpOptions {
78
- email: string;
79
- password: string;
80
- metadata?: Record<string, unknown>;
137
+ interface QueryOptions {
138
+ filters?: QueryFilter[];
139
+ fields?: string;
140
+ limit?: number;
141
+ offset?: number;
142
+ orderBy?: string;
143
+ order?: 'asc' | 'desc';
144
+ startAfter?: string;
145
+ startAt?: string;
146
+ endAt?: string;
147
+ dateRange?: DateRange;
81
148
  }
82
- interface SignInOptions {
83
- email: string;
84
- password: string;
149
+ interface QueryResult<T = RecordData> {
150
+ records: (T & RecordResult)[];
151
+ total?: number;
152
+ hasMore?: boolean;
153
+ nextCursor?: string;
85
154
  }
86
- interface TrackEventOptions {
87
- event: string;
88
- properties?: Record<string, unknown>;
89
- userId?: string;
90
- sessionId?: string;
91
- timestamp?: number;
92
- }
93
- interface AnalyticsQueryOptions {
94
- event?: string;
95
- from?: string;
96
- to?: string;
97
- limit?: number;
98
- groupBy?: string;
155
+ interface PatchRecordOptions {
156
+ merge?: boolean;
99
157
  }
100
- interface AnalyticsEvent {
158
+ interface RecordHistoryEntry {
101
159
  id: string;
102
- event: string;
103
- properties: Record<string, unknown>;
104
- userId?: string;
105
- sessionId?: string;
106
- timestamp: number;
107
- }
108
- type UploadStage = 'pending' | 'compressing' | 'uploading' | 'done' | 'error';
109
- interface UploadProgress {
110
- /** 0-based file index (always 0 for single uploads) */
111
- index: number;
112
- /** Total files in the operation */
113
- total: number;
114
- /** Destination path in the bucket */
115
- path: string;
116
- /** Current lifecycle stage */
117
- stage: UploadStage;
118
- bytesUploaded: number;
119
- totalBytes: number;
120
- /** 0 – 100 */
121
- percent: number;
122
- /** Upload speed in bytes/sec; null until the first tick */
123
- bytesPerSecond: number | null;
124
- /** Estimated seconds remaining; null until speed is known */
125
- eta: number | null;
126
- result?: UploadResult;
127
- error?: string;
128
- code?: string;
129
- }
130
- interface DownloadProgress {
131
- index: number;
132
- total: number;
133
- path: string;
134
- status: 'pending' | 'success' | 'error';
135
- size?: number;
160
+ version: number;
161
+ createdAt: number;
162
+ data: RecordData;
163
+ }
164
+ interface UploadOptions {
165
+ /** Set to true to make the file publicly accessible without authentication. */
166
+ isPublic?: boolean;
167
+ /** Set to true to overwrite an existing file at the same path. */
168
+ overwrite?: boolean;
169
+ /** MIME type of the file. Auto-detected if omitted. */
136
170
  mimeType?: string;
137
- error?: string;
171
+ /** TTL in seconds for the signed upload URL (default 900 = 15 min). */
172
+ expiresInSeconds?: number;
138
173
  }
139
174
  interface UploadResult {
140
175
  path: string;
141
- compressed: boolean;
142
- originalSize: number;
143
- storedSize: number;
144
- spaceSaved: number;
145
176
  mimeType: string;
177
+ size: number;
178
+ isPublic: boolean;
179
+ publicUrl: string | null;
180
+ downloadUrl: string | null;
146
181
  }
147
- interface FileMetadata {
182
+ interface UploadUrlResult {
183
+ uploadUrl: string;
148
184
  path: string;
149
- name: string;
150
- size: number | null;
151
- originalSize: number | null;
152
- mimeType: string | null;
153
- isCompressed: boolean;
154
- md5Hash: string | null;
155
- updatedAt: string | null;
156
- createdAt: string | null;
157
- }
158
- interface StorageItem {
185
+ mimeType: string;
186
+ expiresAt: string;
187
+ expiresIn: number;
188
+ }
189
+ interface ListOptions {
190
+ prefix?: string;
191
+ limit?: number;
192
+ cursor?: string;
193
+ recursive?: boolean;
194
+ }
195
+ interface FileEntry {
159
196
  name: string;
160
197
  path: string;
161
- type: 'file' | 'folder';
162
- size?: number | null;
163
- storedSize?: number | null;
164
- mimeType?: string | null;
165
- isCompressed?: boolean;
166
- updatedAt?: string | null;
167
- createdAt?: string | null;
168
- md5Hash?: string | null;
198
+ size?: number;
199
+ mimeType?: string;
200
+ isPublic?: boolean;
201
+ publicUrl?: string | null;
202
+ downloadUrl?: string | null;
203
+ updatedAt?: string;
169
204
  }
170
205
  interface ListResult {
171
- items: StorageItem[];
172
- pagination: {
173
- limit: number;
174
- count: number;
175
- hasNextPage: boolean;
176
- nextCursor: string | null;
177
- };
206
+ files: FileEntry[];
207
+ folders: string[];
208
+ hasMore: boolean;
209
+ nextCursor?: string;
210
+ }
211
+ interface FileMetadata {
212
+ path: string;
213
+ size: number;
214
+ mimeType: string;
215
+ isPublic: boolean;
216
+ publicUrl: string | null;
217
+ downloadUrl: string | null;
218
+ createdAt?: string;
219
+ updatedAt?: string;
178
220
  }
179
221
  interface SignedUrlResult {
180
222
  signedUrl: string;
@@ -182,276 +224,718 @@ interface SignedUrlResult {
182
224
  expiresIn: number;
183
225
  path: string;
184
226
  }
185
- interface BatchUploadResult {
186
- succeeded: UploadResult[];
187
- failed: Array<{
188
- path: string;
189
- error: string;
190
- code: string;
191
- }>;
227
+ interface StorageStats {
228
+ totalFiles: number;
229
+ totalBytes: number;
230
+ uploadCount: number;
231
+ downloadCount: number;
232
+ deleteCount: number;
192
233
  }
193
- interface BatchDownloadFile {
234
+ interface BatchUploadItem {
194
235
  path: string;
195
- content: ArrayBuffer;
196
236
  mimeType: string;
197
237
  size: number;
238
+ isPublic?: boolean;
239
+ overwrite?: boolean;
198
240
  }
199
- interface StorageStats {
200
- totalFiles: number;
201
- totalSizeBytes: number;
202
- totalOriginalSizeBytes: number;
203
- spaceSavedBytes: number;
204
- uploadsCount: number;
205
- downloadsCount: number;
206
- deletesCount: number;
207
- creditsUsedUpload: number;
208
- creditsUsedDownload: number;
209
- creditsTotalUsed: number;
210
- bytesDelivered: number;
211
- compressionRatio: string;
241
+ interface BatchUploadUrlResult {
242
+ files: Array<UploadUrlResult & {
243
+ index: number;
244
+ }>;
212
245
  }
213
- interface UploadOptions {
214
- /** Destination path in the bucket (defaults to the file's original name) */
215
- path?: string;
216
- /** Replace the file if it already exists (default: false) */
217
- overwrite?: boolean;
218
- /**
219
- * Called on every progress tick.
220
- * Use this to drive progress bars, speed displays, and ETAs.
221
- */
222
- onProgress?: (progress: UploadProgress) => void;
246
+ type QueryType = 'count' | 'distribution' | 'sum' | 'timeSeries' | 'fieldTimeSeries' | 'topN' | 'stats' | 'records' | 'multiMetric' | 'storageStats' | 'crossBucket';
247
+ type Aggregation = 'sum' | 'avg' | 'min' | 'max' | 'count';
248
+ type Granularity = 'hour' | 'day' | 'week' | 'month' | 'year';
249
+ type SortOrder = 'asc' | 'desc';
250
+ interface DateRange {
251
+ /** Start timestamp in milliseconds (Unix epoch). */
252
+ start?: number;
253
+ /** End timestamp in milliseconds (Unix epoch). */
254
+ end?: number;
223
255
  }
224
- interface BatchUploadOptions {
225
- /** Folder prefix prepended to every filename */
226
- prefix?: string;
227
- /** Per-file paths (same order as the `files` array; takes priority over prefix) */
228
- paths?: string[];
229
- overwrite?: boolean;
230
- /** Max simultaneous server-side uploads (1–10, default 5) */
231
- concurrency?: number;
232
- /** Called for every progress tick on every file */
233
- onProgress?: (progress: UploadProgress) => void;
234
- }
235
- interface BatchDownloadOptions {
236
- concurrency?: number;
237
- onProgress?: (progress: DownloadProgress) => void;
238
- /**
239
- * Browser only — automatically triggers a Save dialog for each file as it
240
- * arrives (default: false)
241
- */
242
- autoSave?: boolean;
256
+ interface AnalyticsFilter {
257
+ field: string;
258
+ op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'CONTAINS';
259
+ value: string | number | boolean;
243
260
  }
244
- interface ListOptions {
245
- /** Folder prefix to list (empty = bucket root) */
246
- prefix?: string;
261
+ interface MetricDefinition {
262
+ /** Field name in your records to aggregate. */
263
+ field: string;
264
+ /** Alias used in the result object. Must be a safe identifier. */
265
+ name: string;
266
+ /** Aggregation function. Defaults to 'count'. */
267
+ aggregation?: Aggregation;
268
+ }
269
+ interface AnalyticsQuery {
270
+ queryType: QueryType;
271
+ dateRange?: DateRange;
272
+ field?: string;
273
+ groupBy?: string;
274
+ labelField?: string;
275
+ aggregation?: Aggregation;
276
+ granularity?: Granularity;
277
+ filters?: AnalyticsFilter[];
278
+ selectFields?: string[];
247
279
  limit?: number;
248
- cursor?: string;
280
+ offset?: number;
281
+ orderBy?: string;
282
+ order?: SortOrder;
283
+ n?: number;
284
+ metrics?: MetricDefinition[];
285
+ /** For crossBucket queries: list of bucket names to compare. */
286
+ bucketKeys?: string[];
249
287
  }
250
- interface SignedUrlOptions {
251
- /** How long the URL stays valid in seconds (default: 3600) */
252
- expiresIn?: number;
288
+ interface AnalyticsResult<T = unknown> {
289
+ queryType: QueryType;
290
+ data: T;
291
+ }
292
+ interface CountResult {
293
+ count: number;
294
+ }
295
+ interface DistributionRow {
296
+ value: string | number;
297
+ count: number;
298
+ }
299
+ interface SumRow {
300
+ group?: string | number;
301
+ sum: number;
302
+ }
303
+ interface TimeSeriesRow {
304
+ date: string;
305
+ count: number;
306
+ }
307
+ interface FieldTimeSeriesRow {
308
+ date: string;
309
+ value: number;
310
+ }
311
+ interface TopNRow {
312
+ value: string | number;
313
+ label?: string;
314
+ count: number;
315
+ }
316
+ interface FieldStats {
317
+ min: number;
318
+ max: number;
319
+ avg: number;
320
+ sum: number;
321
+ count: number;
322
+ stddev?: number;
323
+ }
324
+ interface MultiMetricResult {
325
+ [metricName: string]: number;
326
+ }
327
+ interface StorageStatsResult {
328
+ totalRecords: number;
329
+ totalBytes: number;
330
+ avgBytes: number;
331
+ minBytes: number;
332
+ maxBytes: number;
333
+ }
334
+ interface CrossBucketRow {
335
+ bucket: string;
336
+ value: number;
253
337
  }
254
338
 
339
+ /**
340
+ * AuthClient — full user authentication for a single bucket.
341
+ * Uses the `authKey` (`hk_auth_…`) sent via `X-Api-Key` header.
342
+ *
343
+ * @example
344
+ * ```ts
345
+ * const db = createClient({ authKey: 'hk_auth_…', bucketSecurityKey: '…', storageKeys: { main: '…' } });
346
+ * const auth = db.auth('my-app-users');
347
+ * const { user, session } = await auth.signup({ email: 'alice@example.com', password: 'hunter2' });
348
+ * ```
349
+ */
255
350
  declare class AuthClient {
256
- private readonly baseUrl;
257
- private readonly headers;
258
- private session;
259
- constructor(config: HydrousConfig);
260
- /** Create a new user account */
261
- signUp(options: SignUpOptions): Promise<HydrousResponse<AuthSession>>;
262
- /** Sign in with email and password */
263
- signIn(options: SignInOptions): Promise<HydrousResponse<AuthSession>>;
264
- /** Sign out and invalidate the current session */
265
- signOut(): Promise<HydrousResponse<void>>;
266
- /** Get the currently authenticated user */
267
- getUser(): Promise<HydrousResponse<AuthUser>>;
268
- /** Refresh the access token using the stored refresh token */
269
- refreshSession(): Promise<HydrousResponse<AuthSession>>;
270
- /** Return the current in-memory session (may be null) */
271
- getSession(): AuthSession | null;
272
- private _sessionHeader;
351
+ private readonly http;
352
+ private readonly authKey;
353
+ private readonly basePath;
354
+ constructor(http: HttpClient, authKey: string, bucketKey: string);
355
+ private post;
356
+ private get;
357
+ private patch;
358
+ private delete;
359
+ signup(options: SignupOptions): Promise<AuthResult>;
360
+ login(options: LoginOptions): Promise<AuthResult>;
361
+ logout({ sessionId }: {
362
+ sessionId: string;
363
+ }): Promise<void>;
364
+ refreshSession({ refreshToken }: {
365
+ refreshToken: string;
366
+ }): Promise<Session>;
367
+ getUser({ userId }: {
368
+ userId: string;
369
+ }): Promise<UserRecord>;
370
+ updateUser(options: UpdateUserOptions): Promise<UserRecord>;
371
+ deleteUser({ sessionId, userId }: {
372
+ sessionId: string;
373
+ userId: string;
374
+ }): Promise<void>;
375
+ listUsers(options: ListUsersOptions): Promise<ListUsersResult>;
376
+ hardDeleteUser({ sessionId, userId }: {
377
+ sessionId: string;
378
+ userId: string;
379
+ }): Promise<void>;
380
+ bulkDeleteUsers({ sessionId, userIds }: {
381
+ sessionId: string;
382
+ userIds: string[];
383
+ }): Promise<{
384
+ deleted: number;
385
+ failed: string[];
386
+ }>;
387
+ lockAccount({ sessionId, userId, duration }: {
388
+ sessionId: string;
389
+ userId: string;
390
+ duration?: number;
391
+ }): Promise<{
392
+ lockedUntil: number;
393
+ unlockTime: string;
394
+ }>;
395
+ unlockAccount({ sessionId, userId }: {
396
+ sessionId: string;
397
+ userId: string;
398
+ }): Promise<void>;
399
+ changePassword(options: ChangePasswordOptions): Promise<void>;
400
+ requestPasswordReset({ email }: {
401
+ email: string;
402
+ }): Promise<void>;
403
+ confirmPasswordReset({ resetToken, newPassword }: {
404
+ resetToken: string;
405
+ newPassword: string;
406
+ }): Promise<void>;
407
+ requestEmailVerification({ userId }: {
408
+ userId: string;
409
+ }): Promise<void>;
410
+ confirmEmailVerification({ verifyToken }: {
411
+ verifyToken: string;
412
+ }): Promise<void>;
273
413
  }
274
414
 
275
- declare class RecordsClient {
276
- private readonly baseUrl;
277
- private readonly headers;
278
- constructor(config: HydrousConfig);
279
- /** Query records from a collection */
280
- select<T = Record<string, unknown>>(collection: string, options?: QueryOptions): Promise<RecordResponse<T>>;
281
- /** Fetch a single record by ID */
282
- get<T = Record<string, unknown>>(collection: string, id: string): Promise<SingleRecordResponse<T>>;
283
- /** Insert one or more records */
284
- insert<T = Record<string, unknown>>(collection: string, payload: Partial<T> | Partial<T>[]): Promise<RecordResponse<T>>;
285
- /** Update a record by ID */
286
- update<T = Record<string, unknown>>(collection: string, id: string, payload: Partial<T>): Promise<SingleRecordResponse<T>>;
287
- /** Delete a record by ID */
288
- delete(collection: string, id: string): Promise<SingleRecordResponse<void>>;
415
+ /**
416
+ * RecordsClient — CRUD + query for a single bucket.
417
+ * Uses the `bucketSecurityKey` (`hk_bucket_…`) sent via `X-Api-Key` header.
418
+ *
419
+ * @example
420
+ * ```ts
421
+ * const db = createClient({ authKey: '…', bucketSecurityKey: 'hk_bucket_…', storageKeys: { main: '…' } });
422
+ * const posts = db.records('blog-posts');
423
+ * const post = await posts.create({ title: 'Hello World', status: 'draft' });
424
+ * ```
425
+ */
426
+ declare class RecordsClient<T extends RecordData = RecordData> {
427
+ private readonly http;
428
+ private readonly bucketKey;
429
+ private readonly basePath;
430
+ private readonly bucketKey_;
431
+ constructor(http: HttpClient, bucketSecurityKey: string, bucketKey: string);
432
+ private get key();
433
+ create(data: T): Promise<T & RecordResult>;
434
+ get(id: string): Promise<T & RecordResult>;
435
+ set(id: string, data: T): Promise<T & RecordResult>;
436
+ patch(id: string, data: Partial<T>, options?: PatchRecordOptions): Promise<T & RecordResult>;
437
+ delete(id: string): Promise<void>;
438
+ batchCreate(items: T[]): Promise<(T & RecordResult)[]>;
439
+ batchDelete(ids: string[]): Promise<{
440
+ deleted: number;
441
+ failed: string[];
442
+ }>;
443
+ query(options?: QueryOptions): Promise<QueryResult<T>>;
444
+ getAll(options?: Omit<QueryOptions, 'filters'>): Promise<(T & RecordResult)[]>;
445
+ count(filters?: QueryOptions['filters']): Promise<number>;
446
+ getHistory(id: string): Promise<RecordHistoryEntry[]>;
447
+ restoreVersion(id: string, version: number): Promise<T & RecordResult>;
289
448
  }
290
449
 
450
+ /**
451
+ * AnalyticsClient — BigQuery-powered aggregations for a bucket.
452
+ * Uses the `bucketSecurityKey` (`hk_bucket_…`) sent via `X-Api-Key` header.
453
+ *
454
+ * @example
455
+ * ```ts
456
+ * const db = createClient({ authKey: '…', bucketSecurityKey: 'hk_bucket_…', storageKeys: { main: '…' } });
457
+ * const analytics = db.analytics('orders');
458
+ * const { count } = await analytics.count();
459
+ * ```
460
+ */
291
461
  declare class AnalyticsClient {
292
- private readonly baseUrl;
293
- private readonly headers;
294
- constructor(config: HydrousConfig);
295
- /** Track a single analytics event */
296
- track(options: TrackEventOptions): Promise<HydrousResponse<void>>;
297
- /** Track many events in one request */
298
- trackBatch(events: TrackEventOptions[]): Promise<HydrousResponse<void>>;
299
- /** Query recorded analytics events */
300
- query(options?: AnalyticsQueryOptions): Promise<RecordResponse<AnalyticsEvent>>;
462
+ private readonly http;
463
+ private readonly bucketSecurityKey;
464
+ private readonly basePath;
465
+ constructor(http: HttpClient, bucketSecurityKey: string, bucketKey: string);
466
+ private run;
467
+ count(opts?: {
468
+ dateRange?: DateRange;
469
+ }): Promise<CountResult>;
470
+ distribution(opts: {
471
+ field: string;
472
+ limit?: number;
473
+ order?: SortOrder;
474
+ dateRange?: DateRange;
475
+ }): Promise<DistributionRow[]>;
476
+ sum(opts: {
477
+ field: string;
478
+ groupBy?: string;
479
+ limit?: number;
480
+ dateRange?: DateRange;
481
+ }): Promise<SumRow[]>;
482
+ timeSeries(opts?: {
483
+ granularity?: Granularity;
484
+ dateRange?: DateRange;
485
+ }): Promise<TimeSeriesRow[]>;
486
+ fieldTimeSeries(opts: {
487
+ field: string;
488
+ aggregation?: Aggregation;
489
+ granularity?: Granularity;
490
+ dateRange?: DateRange;
491
+ }): Promise<FieldTimeSeriesRow[]>;
492
+ topN(opts: {
493
+ field: string;
494
+ n?: number;
495
+ labelField?: string;
496
+ order?: SortOrder;
497
+ dateRange?: DateRange;
498
+ }): Promise<TopNRow[]>;
499
+ stats(opts: {
500
+ field: string;
501
+ dateRange?: DateRange;
502
+ }): Promise<FieldStats>;
503
+ records<T extends RecordData = RecordData>(opts?: {
504
+ filters?: AnalyticsFilter[];
505
+ selectFields?: string[];
506
+ limit?: number;
507
+ offset?: number;
508
+ orderBy?: string;
509
+ order?: SortOrder;
510
+ dateRange?: DateRange;
511
+ }): Promise<(T & RecordResult)[]>;
512
+ multiMetric(opts: {
513
+ metrics: MetricDefinition[];
514
+ dateRange?: DateRange;
515
+ }): Promise<MultiMetricResult>;
516
+ storageStats(opts?: {
517
+ dateRange?: DateRange;
518
+ }): Promise<StorageStatsResult>;
519
+ crossBucket(opts: {
520
+ bucketKeys: string[];
521
+ field: string;
522
+ aggregation?: Aggregation;
523
+ dateRange?: DateRange;
524
+ }): Promise<CrossBucketRow[]>;
525
+ query<T = unknown>(query: AnalyticsQuery): Promise<AnalyticsResult<T>>;
301
526
  }
302
527
 
303
528
  /**
304
- * A storage client that is already bound to a specific storage key.
305
- * You get one of these by calling `db.storage('keyName')`.
529
+ * StorageManager upload, download, list, move, copy, and delete files.
530
+ * Uses an `X-Storage-Key` (`ssk_…`) header separate from auth and bucket keys.
531
+ * Each key is scoped to a specific storage bucket and permission set.
532
+ *
533
+ * Get a StorageManager via `db.storage('keyName')` where the key name
534
+ * matches one of the keys you defined in `storageKeys` when calling `createClient`.
306
535
  *
307
- * None of the methods on this class require you to pass a bucket key —
308
- * it's already baked in.
536
+ * @example
537
+ * ```ts
538
+ * const db = createClient({ authKey: '…', bucketSecurityKey: '…', storageKeys: { avatars: 'ssk_avatars_…' } });
539
+ * const avatars = db.storage('avatars');
540
+ * const result = await avatars.upload(file, 'alice.jpg', { isPublic: true });
541
+ * ```
309
542
  */
310
- declare class ScopedStorageClient {
311
- private readonly base;
312
- private readonly key;
313
- readonly keyName: string;
314
- constructor(baseUrl: string, keyName: string, bucketKey: string);
543
+ declare class StorageManager {
544
+ private readonly http;
545
+ private readonly storageKey;
546
+ private readonly basePath;
547
+ constructor(http: HttpClient, storageKey: string);
548
+ /** Headers for all storage requests — uses X-Storage-Key, not X-Api-Key. */
549
+ private get authHeaders();
315
550
  /**
316
- * Upload a single file.
551
+ * Upload a file to storage in one step (server-buffered, up to 500 MB).
552
+ * For files >10 MB or when you need upload progress, use `getUploadUrl()` instead.
317
553
  *
318
- * Supply `onProgress` to receive live upload ticks including bytes
319
- * transferred, speed (bytes/sec), ETA, and lifecycle stage.
554
+ * @param data File data as a Blob, Buffer, Uint8Array, or ArrayBuffer.
555
+ * @param path Destination path in your storage (e.g. `"avatars/alice.jpg"`).
556
+ * @param options Upload options: isPublic, overwrite, mimeType.
320
557
  *
321
- * **Stage sequence:**
322
- * `pending → compressing → uploading → done | error`
558
+ * @example
559
+ * ```ts
560
+ * // Upload a public avatar
561
+ * const result = await storage.upload(file, 'avatars/alice.jpg', { isPublic: true });
562
+ * console.log(result.publicUrl); // → https://...
323
563
  *
324
- * In browsers the progress is tracked at the network level via XHR, so
325
- * `percent` reflects actual bytes leaving the device. `done` only fires
326
- * after the server confirms the write to cloud storage, so 100% is real.
564
+ * // Upload a private document
565
+ * const result = await storage.upload(pdfBuffer, 'docs/contract.pdf');
566
+ * console.log(result.downloadUrl); // /storage/download/docs/contract.pdf
567
+ * ```
568
+ */
569
+ upload(data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
570
+ /**
571
+ * Upload raw JSON or plain text data as a file.
327
572
  *
328
573
  * @example
329
- * const { data, error } = await db.storage('avatars').upload(file, {
330
- * path: 'users/alice.jpg',
331
- * overwrite: true,
332
- * onProgress: (p) => {
333
- * setProgress(p.percent); // e.g. drive a <progress> bar
334
- * setSpeed(`${p.bytesPerSecond} B/s`);
335
- * setEta(`${p.eta}s remaining`);
336
- * },
337
- * });
574
+ * ```ts
575
+ * const result = await storage.uploadRaw(
576
+ * { config: { theme: 'dark' } },
577
+ * 'settings/user-config.json',
578
+ * { isPublic: false },
579
+ * );
580
+ * ```
338
581
  */
339
- upload(file: File | Blob | Uint8Array | ArrayBuffer, options?: UploadOptions): Promise<HydrousResponse<UploadResult>>;
582
+ uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
340
583
  /**
341
- * Upload raw text or JSON content directly — no File object needed.
584
+ * Step 1 of the recommended upload flow.
585
+ * Get a signed URL to upload directly to GCS from the client (supports progress).
342
586
  *
343
587
  * @example
344
- * // Save a JSON config
345
- * await db.storage('configs').uploadText(
346
- * 'settings/app.json',
347
- * JSON.stringify({ theme: 'dark' }),
348
- * { mimeType: 'application/json' }
349
- * );
588
+ * ```ts
589
+ * const { uploadUrl, path: confirmedPath } = await storage.getUploadUrl({
590
+ * path: 'videos/intro.mp4',
591
+ * mimeType: 'video/mp4',
592
+ * size: file.size,
593
+ * isPublic: true,
594
+ * });
595
+ *
596
+ * // Upload using XHR for progress tracking
597
+ * await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', (pct) => {
598
+ * console.log(`${pct}% uploaded`);
599
+ * });
600
+ *
601
+ * // Step 3: confirm
602
+ * const result = await storage.confirmUpload({ path: confirmedPath, mimeType: 'video/mp4', isPublic: true });
603
+ * ```
350
604
  */
351
- uploadText(path: string, content: string, options?: {
352
- mimeType?: string;
605
+ getUploadUrl(opts: {
606
+ path: string;
607
+ mimeType: string;
608
+ size: number;
609
+ isPublic?: boolean;
353
610
  overwrite?: boolean;
354
- onProgress?: UploadOptions['onProgress'];
355
- }): Promise<HydrousResponse<UploadResult>>;
611
+ expiresInSeconds?: number;
612
+ }): Promise<UploadUrlResult>;
356
613
  /**
357
- * Upload multiple files in one request.
614
+ * Upload data directly to a signed GCS URL (no auth headers needed).
615
+ * Optionally tracks progress via a callback.
358
616
  *
359
- * `onProgress` fires per file use `p.index` to identify which file.
360
- * All files receive a `pending` event upfront so you can render progress
361
- * bars immediately before any data is sent.
617
+ * @param signedUrl The URL returned by `getUploadUrl()`.
618
+ * @param data File data.
619
+ * @param mimeType Must match what was used in `getUploadUrl()`.
620
+ * @param onProgress Optional callback called with 0–100 progress percentage.
621
+ */
622
+ uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
623
+ /**
624
+ * Step 3 of the recommended upload flow.
625
+ * Confirm a direct upload and register metadata on the server.
362
626
  *
363
627
  * @example
364
- * await db.storage('documents').batchUpload(files, {
365
- * prefix: 'reports/2024/',
366
- * onProgress: (p) => updateBar(p.index, p.percent),
628
+ * ```ts
629
+ * const result = await storage.confirmUpload({
630
+ * path: 'videos/intro.mp4',
631
+ * mimeType: 'video/mp4',
632
+ * isPublic: true,
367
633
  * });
634
+ * console.log(result.publicUrl);
635
+ * ```
368
636
  */
369
- batchUpload(files: File[], options?: BatchUploadOptions): Promise<HydrousResponse<BatchUploadResult>>;
637
+ confirmUpload(opts: {
638
+ path: string;
639
+ mimeType: string;
640
+ isPublic?: boolean;
641
+ }): Promise<UploadResult>;
370
642
  /**
371
- * Download a single file and return its content as an `ArrayBuffer`.
643
+ * Get signed upload URLs for multiple files at once.
372
644
  *
373
645
  * @example
374
- * const { data } = await db.storage('avatars').download('users/alice.jpg');
375
- * const blob = new Blob([data!]);
376
- * img.src = URL.createObjectURL(blob);
646
+ * ```ts
647
+ * const { files } = await storage.getBatchUploadUrls([
648
+ * { path: 'images/photo1.jpg', mimeType: 'image/jpeg', size: 204800 },
649
+ * { path: 'images/photo2.jpg', mimeType: 'image/jpeg', size: 153600 },
650
+ * ]);
651
+ *
652
+ * // Upload each file and confirm
653
+ * for (const f of files) {
654
+ * await storage.uploadToSignedUrl(f.uploadUrl, blobs[f.index], f.mimeType);
655
+ * await storage.confirmUpload({ path: f.path, mimeType: f.mimeType });
656
+ * }
657
+ * ```
377
658
  */
378
- download(filePath: string): Promise<HydrousResponse<ArrayBuffer>>;
659
+ getBatchUploadUrls(files: BatchUploadItem[]): Promise<BatchUploadUrlResult>;
379
660
  /**
380
- * Download multiple files in one request.
661
+ * Confirm multiple direct uploads at once.
662
+ */
663
+ batchConfirmUploads(items: Array<{
664
+ path: string;
665
+ mimeType: string;
666
+ isPublic?: boolean;
667
+ }>): Promise<UploadResult[]>;
668
+ /**
669
+ * Download a private file as an ArrayBuffer.
670
+ * For public files, use the `publicUrl` directly — no SDK needed.
381
671
  *
382
- * Set `autoSave: true` (browser only) to trigger a Save dialog per file.
672
+ * @example
673
+ * ```ts
674
+ * const buffer = await storage.download('docs/contract.pdf');
675
+ * const blob = new Blob([buffer], { type: 'application/pdf' });
676
+ * // Open in browser:
677
+ * window.open(URL.createObjectURL(blob));
678
+ * ```
679
+ */
680
+ download(path: string): Promise<ArrayBuffer>;
681
+ /**
682
+ * Download multiple files at once, returned as a JSON map of `{ path: base64 }`.
383
683
  *
384
684
  * @example
385
- * const { data } = await db.storage('reports').batchDownload(
386
- * ['jan.pdf', 'feb.pdf'],
387
- * { autoSave: true, onProgress: (p) => console.log(p.path, p.status) }
388
- * );
685
+ * ```ts
686
+ * const files = await storage.batchDownload(['docs/a.pdf', 'docs/b.pdf']);
687
+ * ```
389
688
  */
390
- batchDownload(filePaths: string[], options?: BatchDownloadOptions): Promise<HydrousResponse<BatchDownloadFile[]>>;
689
+ batchDownload(paths: string[]): Promise<Record<string, string>>;
391
690
  /**
392
- * List files and folders (paginated).
691
+ * List files and folders at a given path prefix.
393
692
  *
394
693
  * @example
395
- * const { data } = await db.storage('avatars').list({ prefix: 'users/' });
396
- * for (const item of data!.items) {
397
- * console.log(item.type, item.path, item.size);
398
- * }
694
+ * ```ts
695
+ * // List everything in the root
696
+ * const { files, folders } = await storage.list();
697
+ *
698
+ * // List a specific folder
699
+ * const { files, folders, hasMore, nextCursor } = await storage.list({
700
+ * prefix: 'avatars/',
701
+ * limit: 20,
702
+ * });
703
+ *
704
+ * // Next page
705
+ * const page2 = await storage.list({ prefix: 'avatars/', cursor: nextCursor });
706
+ * ```
707
+ */
708
+ list(opts?: ListOptions): Promise<ListResult>;
709
+ /**
710
+ * Get metadata for a file (size, MIME type, visibility, URLs).
711
+ *
712
+ * @example
713
+ * ```ts
714
+ * const meta = await storage.getMetadata('avatars/alice.jpg');
715
+ * console.log(meta.size, meta.isPublic, meta.publicUrl);
716
+ * ```
717
+ */
718
+ getMetadata(path: string): Promise<FileMetadata>;
719
+ /**
720
+ * Generate a time-limited download URL for a private file.
721
+ * The URL can be shared externally without requiring an `X-Storage-Key`.
722
+ *
723
+ * > **Note:** Downloads via signed URLs bypass the server, so download stats
724
+ * > are NOT tracked. Use `downloadUrl` for tracked downloads.
725
+ *
726
+ * @param path Path to the file.
727
+ * @param expiresIn URL lifetime in seconds (default 3600 = 1 hour).
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * const { signedUrl, expiresAt } = await storage.getSignedUrl('docs/invoice.pdf', 1800);
732
+ * // Share signedUrl with the recipient — it expires in 30 minutes.
733
+ * ```
734
+ */
735
+ getSignedUrl(path: string, expiresIn?: number): Promise<SignedUrlResult>;
736
+ /**
737
+ * Change a file's visibility between public and private after upload.
738
+ *
739
+ * @example
740
+ * ```ts
741
+ * // Make a file public
742
+ * const result = await storage.setVisibility('avatars/alice.jpg', true);
743
+ * console.log(result.publicUrl); // CDN URL
744
+ *
745
+ * // Make a file private
746
+ * const result = await storage.setVisibility('avatars/alice.jpg', false);
747
+ * console.log(result.downloadUrl); // Auth-required URL
748
+ * ```
749
+ */
750
+ setVisibility(path: string, isPublic: boolean): Promise<{
751
+ path: string;
752
+ isPublic: boolean;
753
+ publicUrl: string | null;
754
+ downloadUrl: string | null;
755
+ }>;
756
+ /**
757
+ * Create a folder (a GCS prefix placeholder).
758
+ *
759
+ * @example
760
+ * ```ts
761
+ * await storage.createFolder('uploads/2025/');
762
+ * ```
399
763
  */
400
- list(options?: ListOptions): Promise<HydrousResponse<ListResult>>;
764
+ createFolder(path: string): Promise<{
765
+ path: string;
766
+ }>;
401
767
  /**
402
- * Get metadata for a file (size, MIME type, compression, etc.)
768
+ * Delete a single file.
403
769
  *
404
770
  * @example
405
- * const { data: meta } = await db.storage('docs').metadata('report.pdf');
406
- * console.log(meta!.size, meta!.mimeType, meta!.isCompressed);
771
+ * ```ts
772
+ * await storage.deleteFile('avatars/old-avatar.jpg');
773
+ * ```
407
774
  */
408
- metadata(filePath: string): Promise<HydrousResponse<FileMetadata>>;
409
- /** Delete a single file */
410
- deleteFile(filePath: string): Promise<HydrousResponse<void>>;
411
- /** Recursively delete a folder and all its contents */
412
- deleteFolder(folderPath: string): Promise<HydrousResponse<void>>;
413
- /** Create an empty folder */
414
- createFolder(folderPath: string): Promise<HydrousResponse<void>>;
415
- /** Move (rename) a file */
416
- move(fromPath: string, toPath: string): Promise<HydrousResponse<void>>;
417
- /** Copy a file (original is kept) */
418
- copy(fromPath: string, toPath: string): Promise<HydrousResponse<void>>;
775
+ deleteFile(path: string): Promise<void>;
419
776
  /**
420
- * Generate a time-limited public URL for a private file.
777
+ * Delete a folder and all its contents recursively.
421
778
  *
422
779
  * @example
423
- * const { data } = await db.storage('contracts').signedUrl('nda.pdf', { expiresIn: 300 });
424
- * console.log(data!.signedUrl); // share this
780
+ * ```ts
781
+ * await storage.deleteFolder('temp/');
782
+ * ```
783
+ */
784
+ deleteFolder(path: string): Promise<void>;
785
+ /**
786
+ * Move or rename a file.
787
+ *
788
+ * @example
789
+ * ```ts
790
+ * // Rename
791
+ * await storage.move('docs/draft.pdf', 'docs/final.pdf');
792
+ * // Move to a different folder
793
+ * await storage.move('inbox/report.xlsx', 'archive/2025/report.xlsx');
794
+ * ```
795
+ */
796
+ move(from: string, to: string): Promise<{
797
+ from: string;
798
+ to: string;
799
+ }>;
800
+ /**
801
+ * Copy a file to a new path.
802
+ *
803
+ * @example
804
+ * ```ts
805
+ * await storage.copy('templates/base.html', 'sites/my-site/index.html');
806
+ * ```
807
+ */
808
+ copy(from: string, to: string): Promise<{
809
+ from: string;
810
+ to: string;
811
+ }>;
812
+ /**
813
+ * Get storage statistics for your key: total files, bytes, operation counts.
814
+ *
815
+ * @example
816
+ * ```ts
817
+ * const stats = await storage.getStats();
818
+ * console.log(`${stats.totalFiles} files, ${(stats.totalBytes / 1e6).toFixed(1)} MB`);
819
+ * ```
425
820
  */
426
- signedUrl(filePath: string, options?: SignedUrlOptions): Promise<HydrousResponse<SignedUrlResult>>;
821
+ getStats(): Promise<StorageStats>;
427
822
  /**
428
- * Get usage and billing stats for this storage key.
823
+ * Ping the storage service. No authentication required.
429
824
  *
430
825
  * @example
431
- * const { data } = await db.storage('main').stats();
432
- * console.log(data!.totalFiles, data!.totalSizeBytes);
826
+ * ```ts
827
+ * const info = await storage.info();
828
+ * // → { ok: true, storageRoot: 'hydrous-storage' }
829
+ * ```
433
830
  */
434
- stats(): Promise<HydrousResponse<StorageStats>>;
831
+ info(): Promise<{
832
+ ok: boolean;
833
+ storageRoot: string;
834
+ }>;
435
835
  }
436
836
 
437
- interface CallableStorage {
438
- /** Get a scoped storage client by key name */
439
- (keyName: string): ScopedStorageClient;
440
- /** Same as calling db.storage(keyName) more explicit */
441
- use(keyName: string): ScopedStorageClient;
442
- /** Return names of all configured storage keys */
443
- keyNames(): string[];
837
+ /**
838
+ * ScopedStorage — a StorageManager pre-scoped to a specific folder prefix.
839
+ *
840
+ * All paths are automatically prepended with the scope, so you never
841
+ * have to repeat the folder name.
842
+ *
843
+ * @example
844
+ * ```ts
845
+ * const db = createClient({ securityKey: 'sk_...' });
846
+ *
847
+ * // All operations in the "avatars/" folder
848
+ * const avatars = db.storage.scope('avatars');
849
+ *
850
+ * await avatars.upload(file, 'alice.jpg'); // → "avatars/alice.jpg"
851
+ * const list = await avatars.list(); // → files under "avatars/"
852
+ * await avatars.deleteFile('alice.jpg'); // → deletes "avatars/alice.jpg"
853
+ * ```
854
+ */
855
+ declare class ScopedStorage {
856
+ private readonly manager;
857
+ private readonly prefix;
858
+ constructor(manager: StorageManager, prefix: string);
859
+ private scopedPath;
860
+ /** Upload a file within the scoped folder. */
861
+ upload(data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
862
+ /** Upload raw JSON or text within the scoped folder. */
863
+ uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
864
+ /** Get a signed upload URL for a file within the scoped folder. */
865
+ getUploadUrl(opts: {
866
+ path: string;
867
+ mimeType: string;
868
+ size: number;
869
+ isPublic?: boolean;
870
+ overwrite?: boolean;
871
+ }): Promise<UploadUrlResult>;
872
+ /** Confirm a direct upload within the scoped folder. */
873
+ confirmUpload(opts: {
874
+ path: string;
875
+ mimeType: string;
876
+ isPublic?: boolean;
877
+ }): Promise<UploadResult>;
878
+ /** Download a file within the scoped folder. */
879
+ download(path: string): Promise<ArrayBuffer>;
880
+ /**
881
+ * List files within the scoped folder.
882
+ * `prefix` in options is relative to the scope.
883
+ */
884
+ list(opts?: ListOptions): Promise<ListResult>;
885
+ /** Get metadata for a file within the scoped folder. */
886
+ getMetadata(path: string): Promise<FileMetadata>;
887
+ /** Get a time-limited signed URL for a file within the scoped folder. */
888
+ getSignedUrl(path: string, expiresIn?: number): Promise<SignedUrlResult>;
889
+ /** Change visibility of a file within the scoped folder. */
890
+ setVisibility(path: string, isPublic: boolean): Promise<{
891
+ path: string;
892
+ isPublic: boolean;
893
+ publicUrl: string | null;
894
+ downloadUrl: string | null;
895
+ }>;
896
+ /** Delete a file within the scoped folder. */
897
+ deleteFile(path: string): Promise<void>;
898
+ /** Delete a sub-folder within the scoped folder. */
899
+ deleteFolder(path: string): Promise<void>;
900
+ /** Move a file within the scoped folder. */
901
+ move(from: string, to: string): Promise<{
902
+ from: string;
903
+ to: string;
904
+ }>;
905
+ /** Copy a file within the scoped folder. */
906
+ copy(from: string, to: string): Promise<{
907
+ from: string;
908
+ to: string;
909
+ }>;
910
+ /** Create a sub-folder within the scoped folder. */
911
+ createFolder(path: string): Promise<{
912
+ path: string;
913
+ }>;
914
+ /**
915
+ * Create a further-scoped instance nested within this scope.
916
+ *
917
+ * @example
918
+ * ```ts
919
+ * const uploads = db.storage.scope('user-uploads');
920
+ * const images = uploads.scope('images'); // → "user-uploads/images/"
921
+ * ```
922
+ */
923
+ scope(subPrefix: string): ScopedStorage;
444
924
  }
925
+
445
926
  /**
446
- * The HydrousDB root client.
927
+ * HydrousClient — the main entry point for the HydrousDB SDK.
447
928
  *
448
- * Create once and reuse across your app:
929
+ * Each service uses its own dedicated key:
930
+ * - `authKey` (`hk_auth_…`) → `db.auth('bucket')`
931
+ * - `bucketSecurityKey` (`hk_bucket_…`) → `db.records('bucket')` + `db.analytics('bucket')`
932
+ * - `storageKeys` (`ssk_…`) → `db.storage('keyName')`
449
933
  *
934
+ * @example
450
935
  * ```ts
451
936
  * import { createClient } from 'hydrousdb';
452
937
  *
453
938
  * const db = createClient({
454
- * url: 'https://api.myapp.hydrous.app',
455
939
  * authKey: 'hk_auth_…',
456
940
  * bucketSecurityKey: 'hk_bucket_…',
457
941
  * storageKeys: {
@@ -460,95 +944,137 @@ interface CallableStorage {
460
944
  * documents: 'ssk_docs_…',
461
945
  * },
462
946
  * });
947
+ *
948
+ * // Records & Analytics — use bucketSecurityKey automatically
949
+ * const posts = db.records('blog-posts');
950
+ * const analytics = db.analytics('orders');
951
+ *
952
+ * // Auth — uses authKey automatically
953
+ * const auth = db.auth('app-users');
954
+ *
955
+ * // Storage — pick which key to use by name
956
+ * const avatarStorage = db.storage('avatars');
957
+ * const documentStorage = db.storage('documents');
463
958
  * ```
464
959
  */
465
960
  declare class HydrousClient {
466
- /** Auth — sign up, sign in, sign out, session management */
467
- readonly auth: AuthClient;
468
- /** Records — insert, select, update, delete structured data */
469
- readonly records: RecordsClient;
470
- /** Analytics — track events and query history */
471
- readonly analytics: AnalyticsClient;
961
+ private readonly http;
962
+ private readonly authKey_;
963
+ private readonly bucketSecurityKey_;
964
+ private readonly storageKeys_;
965
+ private readonly _recordsCache;
966
+ private readonly _authCache;
967
+ private readonly _analyticsCache;
968
+ private readonly _storageCache;
969
+ constructor(config: HydrousConfig);
472
970
  /**
473
- * Storage call with the name of the key you want.
971
+ * Get a typed records client for the named bucket.
972
+ * Uses your `bucketSecurityKey` automatically.
474
973
  *
974
+ * @example
475
975
  * ```ts
476
- * db.storage('avatars').upload(file, { path: 'user.jpg' })
477
- * db.storage('documents').list({ prefix: 'invoices/' })
478
- * db.storage('main').stats()
976
+ * interface Post { title: string; published: boolean }
977
+ * const posts = db.records<Post>('blog-posts');
978
+ * const post = await posts.create({ title: 'Hello', published: false });
479
979
  * ```
480
980
  */
481
- readonly storage: CallableStorage;
482
- constructor(config: HydrousConfig);
483
- }
484
-
485
- /**
486
- * StorageManager is the object exposed as `db.storage`.
487
- *
488
- * Call `.use(keyName)` or use the shorthand `db.storage('keyName')` which
489
- * the main HydrousClient wires up as a Proxy to get a scoped storage client
490
- * bound to that key.
491
- *
492
- * ```ts
493
- * db.storage('main').upload(file)
494
- * db.storage('avatars').list()
495
- * db.storage('documents').download('report.pdf')
496
- * ```
497
- */
498
- declare class StorageManager {
499
- private readonly baseUrl;
500
- private readonly _keys;
501
- private readonly cache;
502
- constructor(config: HydrousConfig);
981
+ records<T extends RecordData = RecordData>(bucketKey: string): RecordsClient<T>;
982
+ /**
983
+ * Get an auth client for the named user bucket.
984
+ * Uses your `authKey` automatically.
985
+ *
986
+ * @example
987
+ * ```ts
988
+ * const auth = db.auth('app-users');
989
+ * const { user, session } = await auth.login({ email: '…', password: '…' });
990
+ * ```
991
+ */
992
+ auth(bucketKey: string): AuthClient;
503
993
  /**
504
- * Get a storage client scoped to a named key.
994
+ * Get an analytics client for the named bucket.
995
+ * Uses your `bucketSecurityKey` automatically.
505
996
  *
506
- * @param keyName - Must match a property you declared in `storageKeys`
997
+ * @example
998
+ * ```ts
999
+ * const analytics = db.analytics('orders');
1000
+ * const { count } = await analytics.count();
1001
+ * ```
1002
+ */
1003
+ analytics(bucketKey: string): AnalyticsClient;
1004
+ /**
1005
+ * Get a storage manager for the named storage key.
1006
+ * The name must match a key you defined in `storageKeys` when calling `createClient`.
1007
+ * Uses the corresponding `ssk_…` key automatically via `X-Storage-Key` header.
1008
+ *
1009
+ * @param keyName The name of the storage key (e.g. `"avatars"`, `"documents"`, `"main"`).
507
1010
  *
508
1011
  * @example
509
- * const avatarStore = db.storage('avatars');
510
- * const documentStore = db.storage('documents');
1012
+ * ```ts
1013
+ * const avatars = db.storage('avatars');
1014
+ * const documents = db.storage('documents');
1015
+ *
1016
+ * // Upload to avatars bucket
1017
+ * await avatars.upload(file, `${userId}.jpg`, { isPublic: true });
511
1018
  *
512
- * await avatarStore.upload(file, { path: 'users/alice.jpg' });
513
- * const list = await documentStore.list({ prefix: 'invoices/' });
1019
+ * // Scope to a sub-folder
1020
+ * const userDocs = db.storage('documents').scope(`users/${userId}`);
1021
+ * await userDocs.upload(pdfBuffer, 'contract.pdf');
1022
+ * ```
514
1023
  */
515
- use(keyName: string): ScopedStorageClient;
516
- /** Return the names of all configured storage keys */
517
- keyNames(): string[];
518
- }
519
-
520
- declare const eq: (field: string, value: unknown) => Filter;
521
- declare const neq: (field: string, value: unknown) => Filter;
522
- declare const gt: (field: string, value: unknown) => Filter;
523
- declare const lt: (field: string, value: unknown) => Filter;
524
- declare const gte: (field: string, value: unknown) => Filter;
525
- declare const lte: (field: string, value: unknown) => Filter;
526
- declare const inArray: (field: string, value: unknown[]) => Filter;
527
-
528
- declare class HydrousDBError extends Error {
529
- readonly code: string;
530
- readonly status: number | undefined;
531
- constructor(message: string, code?: string, status?: number);
1024
+ storage(keyName: string): StorageManager & {
1025
+ scope: (prefix: string) => ScopedStorage;
1026
+ };
532
1027
  }
533
- declare function isHydrousError(err: unknown): err is HydrousDBError;
534
-
535
1028
  /**
536
- * Create a HydrousDB client.
1029
+ * Create a new HydrousDB client.
537
1030
  *
538
1031
  * @example
1032
+ * ```ts
539
1033
  * import { createClient } from 'hydrousdb';
540
1034
  *
541
1035
  * const db = createClient({
542
- * url: 'https://api.myapp.hydrous.app',
543
- * authKey: 'hk_auth_…',
544
- * bucketSecurityKey: 'hk_bucket_…',
1036
+ * authKey: process.env.HYDROUS_AUTH_KEY!,
1037
+ * bucketSecurityKey: process.env.HYDROUS_BUCKET_KEY!,
545
1038
  * storageKeys: {
546
- * main: 'ssk_main_…',
547
- * avatars: 'ssk_avatars_…',
548
- * documents: 'ssk_docs_…',
1039
+ * main: process.env.HYDROUS_STORAGE_MAIN!,
1040
+ * avatars: process.env.HYDROUS_STORAGE_AVATARS!,
1041
+ * documents: process.env.HYDROUS_STORAGE_DOCS!,
549
1042
  * },
550
1043
  * });
1044
+ * ```
551
1045
  */
552
1046
  declare function createClient(config: HydrousConfig): HydrousClient;
553
1047
 
554
- export { AnalyticsClient, type AnalyticsEvent, type AnalyticsQueryOptions, AuthClient, type AuthSession, type AuthUser, type BatchDownloadFile, type BatchDownloadOptions, type BatchUploadOptions, type BatchUploadResult, type CallableStorage, type DownloadProgress, type FileMetadata, type Filter, type FilterOperator, HydrousClient, type HydrousConfig, HydrousDBError, type HydrousError, type HydrousResponse, type ListOptions, type ListResult, type QueryOptions, type RecordResponse, RecordsClient, ScopedStorageClient, type SignInOptions, type SignUpOptions, type SignedUrlOptions, type SignedUrlResult, type SingleRecordResponse, type StorageItem, type StorageKeys, StorageManager, type StorageStats, type TrackEventOptions, type UploadOptions, type UploadProgress, type UploadResult, type UploadStage, createClient, eq, gt, gte, inArray, isHydrousError, lt, lte, neq };
1048
+ declare class HydrousError extends Error {
1049
+ /** Machine-readable error code returned by the API. */
1050
+ readonly code: string;
1051
+ /** HTTP status code (if applicable). */
1052
+ readonly status?: number;
1053
+ /** The original request ID for support tracing. */
1054
+ readonly requestId?: string;
1055
+ /** Additional validation details. */
1056
+ readonly details?: string[];
1057
+ constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
1058
+ toString(): string;
1059
+ }
1060
+ declare class AuthError extends HydrousError {
1061
+ constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
1062
+ }
1063
+ declare class RecordError extends HydrousError {
1064
+ constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
1065
+ }
1066
+ declare class StorageError extends HydrousError {
1067
+ constructor(message: string, code: string, status?: number, requestId?: string);
1068
+ }
1069
+ declare class AnalyticsError extends HydrousError {
1070
+ constructor(message: string, code: string, status?: number, requestId?: string);
1071
+ }
1072
+ declare class ValidationError extends HydrousError {
1073
+ constructor(message: string, details?: string[]);
1074
+ }
1075
+ declare class NetworkError extends HydrousError {
1076
+ readonly cause?: unknown;
1077
+ constructor(message: string, cause?: unknown);
1078
+ }
1079
+
1080
+ export { type Aggregation, AnalyticsClient, AnalyticsError, type AnalyticsFilter, type AnalyticsQuery, type AnalyticsResult, AuthClient, AuthError, type AuthResult, type BatchUploadItem, type BatchUploadUrlResult, type ChangePasswordOptions, type CountResult, type CrossBucketRow, type DateRange, type DistributionRow, type FieldStats, type FieldTimeSeriesRow, type FileEntry, type FileMetadata, type Granularity, HydrousClient, type HydrousConfig, HydrousError, type ListOptions, type ListResult, type ListUsersOptions, type ListUsersResult, type LoginOptions, type MetricDefinition, type MultiMetricResult, NetworkError, type PatchRecordOptions, type QueryFilter, type QueryOptions, type QueryResult, type QueryType, type RecordData, RecordError, type RecordHistoryEntry, type RecordResult, RecordsClient, ScopedStorage, type Session, type SignedUrlResult, type SignupOptions, type SortOrder, StorageError, StorageManager, type StorageStats, type StorageStatsResult, type SumRow, type TimeSeriesRow, type TopNRow, type UpdateUserOptions, type UploadOptions, type UploadResult, type UploadUrlResult, type UserRecord, ValidationError, createClient };