hydrousdb 2.0.3 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +857 -1016
- package/dist/index.cjs +1647 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1540 -0
- package/dist/index.d.ts +1423 -437
- package/dist/index.js +1528 -951
- package/dist/index.js.map +1 -1
- package/package.json +48 -19
- package/dist/index.d.mts +0 -554
- package/dist/index.mjs +0 -1038
- package/dist/index.mjs.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,180 +1,188 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* {
|
|
8
|
-
* main: 'ssk_main_…',
|
|
9
|
-
* avatars: 'ssk_avatars_…',
|
|
10
|
-
* documents: 'ssk_docs_…',
|
|
11
|
-
* }
|
|
12
|
-
*/
|
|
13
|
-
type StorageKeys = Record<string, string>;
|
|
14
|
-
interface HydrousConfig {
|
|
15
|
-
/** Your HydrousDB project base URL */
|
|
16
|
-
url: string;
|
|
17
|
-
/** Auth service key — used for all auth.* operations */
|
|
18
|
-
authKey: string;
|
|
19
|
-
/** Bucket security key — used for records.* and analytics.* operations */
|
|
20
|
-
bucketSecurityKey: string;
|
|
21
|
-
/**
|
|
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.
|
|
25
|
-
*/
|
|
26
|
-
storageKeys: StorageKeys;
|
|
27
|
-
/** Optional global request timeout in milliseconds (default: 30 000) */
|
|
28
|
-
timeout?: number;
|
|
29
|
-
}
|
|
30
|
-
interface HydrousResponse<T = unknown> {
|
|
31
|
-
data: T | null;
|
|
32
|
-
error: HydrousError | null;
|
|
33
|
-
}
|
|
34
|
-
interface HydrousError {
|
|
35
|
-
message: string;
|
|
36
|
-
code: string;
|
|
37
|
-
status?: number;
|
|
38
|
-
}
|
|
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;
|
|
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;
|
|
44
7
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
8
|
+
declare class HttpClient {
|
|
9
|
+
private readonly baseUrl;
|
|
10
|
+
private readonly securityKey;
|
|
11
|
+
constructor(baseUrl: string, securityKey: string);
|
|
12
|
+
request<T = unknown>(path: string, opts?: RequestOptions): Promise<T>;
|
|
13
|
+
get<T = unknown>(path: string, headers?: Record<string, string>): Promise<T>;
|
|
14
|
+
post<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
15
|
+
put<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
16
|
+
patch<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
17
|
+
delete<T = unknown>(path: string, body?: unknown, headers?: Record<string, string>): Promise<T>;
|
|
18
|
+
putToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
|
|
54
19
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
20
|
+
|
|
21
|
+
interface HydrousConfig {
|
|
22
|
+
/**
|
|
23
|
+
* Your project's Security Key (starts with `sk_`).
|
|
24
|
+
* Obtain this from your dashboard at https://hydrousdb.com/dashboard.
|
|
25
|
+
* This is your most important credential — keep it secret.
|
|
26
|
+
*/
|
|
27
|
+
securityKey: string;
|
|
28
|
+
/**
|
|
29
|
+
* Override the API base URL. Defaults to the official HydrousDB endpoint.
|
|
30
|
+
* You almost never need to set this.
|
|
31
|
+
*/
|
|
32
|
+
baseUrl?: string;
|
|
33
|
+
}
|
|
34
|
+
interface SignupOptions {
|
|
35
|
+
email: string;
|
|
36
|
+
password: string;
|
|
37
|
+
fullName?: string;
|
|
38
|
+
[key: string]: unknown;
|
|
59
39
|
}
|
|
60
|
-
interface
|
|
61
|
-
|
|
62
|
-
|
|
40
|
+
interface LoginOptions {
|
|
41
|
+
email: string;
|
|
42
|
+
password: string;
|
|
63
43
|
}
|
|
64
|
-
interface
|
|
44
|
+
interface UserRecord {
|
|
65
45
|
id: string;
|
|
66
46
|
email: string;
|
|
67
|
-
|
|
68
|
-
|
|
47
|
+
fullName?: string | null;
|
|
48
|
+
emailVerified: boolean;
|
|
49
|
+
accountStatus: 'active' | 'locked' | 'suspended';
|
|
50
|
+
role: 'user' | 'admin';
|
|
51
|
+
createdAt: number;
|
|
52
|
+
updatedAt: number;
|
|
69
53
|
metadata?: Record<string, unknown>;
|
|
54
|
+
[key: string]: unknown;
|
|
70
55
|
}
|
|
71
|
-
interface
|
|
72
|
-
|
|
73
|
-
|
|
56
|
+
interface Session {
|
|
57
|
+
sessionId: string;
|
|
58
|
+
userId: string;
|
|
59
|
+
bucketId: string;
|
|
60
|
+
createdAt: number;
|
|
74
61
|
expiresAt: number;
|
|
75
|
-
|
|
62
|
+
refreshToken: string;
|
|
63
|
+
refreshExpiresAt: number;
|
|
76
64
|
}
|
|
77
|
-
interface
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
metadata?: Record<string, unknown>;
|
|
65
|
+
interface AuthResult {
|
|
66
|
+
user: UserRecord;
|
|
67
|
+
session: Session;
|
|
81
68
|
}
|
|
82
|
-
interface
|
|
83
|
-
|
|
84
|
-
|
|
69
|
+
interface UpdateUserOptions {
|
|
70
|
+
sessionId: string;
|
|
71
|
+
userId: string;
|
|
72
|
+
data: Partial<Omit<UserRecord, 'id' | 'email' | 'createdAt'>>;
|
|
73
|
+
}
|
|
74
|
+
interface ChangePasswordOptions {
|
|
75
|
+
sessionId: string;
|
|
76
|
+
userId: string;
|
|
77
|
+
currentPassword: string;
|
|
78
|
+
newPassword: string;
|
|
85
79
|
}
|
|
86
|
-
interface
|
|
87
|
-
|
|
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;
|
|
80
|
+
interface ListUsersOptions {
|
|
81
|
+
sessionId: string;
|
|
97
82
|
limit?: number;
|
|
98
|
-
|
|
83
|
+
offset?: number;
|
|
99
84
|
}
|
|
100
|
-
interface
|
|
101
|
-
|
|
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;
|
|
85
|
+
interface ListUsersResult {
|
|
86
|
+
users: UserRecord[];
|
|
132
87
|
total: number;
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
88
|
+
limit: number;
|
|
89
|
+
offset: number;
|
|
90
|
+
}
|
|
91
|
+
type RecordData = Record<string, unknown>;
|
|
92
|
+
interface RecordResult {
|
|
93
|
+
id: string;
|
|
94
|
+
createdAt?: number;
|
|
95
|
+
updatedAt?: number;
|
|
96
|
+
[key: string]: unknown;
|
|
97
|
+
}
|
|
98
|
+
interface QueryFilter {
|
|
99
|
+
field: string;
|
|
100
|
+
op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'CONTAINS';
|
|
101
|
+
value: string | number | boolean;
|
|
102
|
+
}
|
|
103
|
+
interface QueryOptions {
|
|
104
|
+
filters?: QueryFilter[];
|
|
105
|
+
fields?: string;
|
|
106
|
+
limit?: number;
|
|
107
|
+
offset?: number;
|
|
108
|
+
orderBy?: string;
|
|
109
|
+
order?: 'asc' | 'desc';
|
|
110
|
+
startAfter?: string;
|
|
111
|
+
startAt?: string;
|
|
112
|
+
endAt?: string;
|
|
113
|
+
dateRange?: DateRange;
|
|
114
|
+
}
|
|
115
|
+
interface QueryResult<T = RecordData> {
|
|
116
|
+
records: (T & RecordResult)[];
|
|
117
|
+
total?: number;
|
|
118
|
+
hasMore?: boolean;
|
|
119
|
+
nextCursor?: string;
|
|
120
|
+
}
|
|
121
|
+
interface PatchRecordOptions {
|
|
122
|
+
merge?: boolean;
|
|
123
|
+
}
|
|
124
|
+
interface RecordHistoryEntry {
|
|
125
|
+
id: string;
|
|
126
|
+
version: number;
|
|
127
|
+
createdAt: number;
|
|
128
|
+
data: RecordData;
|
|
129
|
+
}
|
|
130
|
+
interface UploadOptions {
|
|
131
|
+
/** Set to true to make the file publicly accessible without authentication. */
|
|
132
|
+
isPublic?: boolean;
|
|
133
|
+
/** Set to true to overwrite an existing file at the same path. */
|
|
134
|
+
overwrite?: boolean;
|
|
135
|
+
/** MIME type of the file. Auto-detected if omitted. */
|
|
136
136
|
mimeType?: string;
|
|
137
|
-
|
|
137
|
+
/** TTL in seconds for the signed upload URL (default 900 = 15 min). */
|
|
138
|
+
expiresInSeconds?: number;
|
|
138
139
|
}
|
|
139
140
|
interface UploadResult {
|
|
140
141
|
path: string;
|
|
141
|
-
compressed: boolean;
|
|
142
|
-
originalSize: number;
|
|
143
|
-
storedSize: number;
|
|
144
|
-
spaceSaved: number;
|
|
145
142
|
mimeType: string;
|
|
143
|
+
size: number;
|
|
144
|
+
isPublic: boolean;
|
|
145
|
+
publicUrl: string | null;
|
|
146
|
+
downloadUrl: string | null;
|
|
146
147
|
}
|
|
147
|
-
interface
|
|
148
|
+
interface UploadUrlResult {
|
|
149
|
+
uploadUrl: string;
|
|
148
150
|
path: string;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
151
|
+
mimeType: string;
|
|
152
|
+
expiresAt: string;
|
|
153
|
+
expiresIn: number;
|
|
154
|
+
}
|
|
155
|
+
interface ListOptions {
|
|
156
|
+
prefix?: string;
|
|
157
|
+
limit?: number;
|
|
158
|
+
cursor?: string;
|
|
159
|
+
recursive?: boolean;
|
|
160
|
+
}
|
|
161
|
+
interface FileEntry {
|
|
159
162
|
name: string;
|
|
160
163
|
path: string;
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
updatedAt?: string
|
|
167
|
-
createdAt?: string | null;
|
|
168
|
-
md5Hash?: string | null;
|
|
164
|
+
size?: number;
|
|
165
|
+
mimeType?: string;
|
|
166
|
+
isPublic?: boolean;
|
|
167
|
+
publicUrl?: string | null;
|
|
168
|
+
downloadUrl?: string | null;
|
|
169
|
+
updatedAt?: string;
|
|
169
170
|
}
|
|
170
171
|
interface ListResult {
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
172
|
+
files: FileEntry[];
|
|
173
|
+
folders: string[];
|
|
174
|
+
hasMore: boolean;
|
|
175
|
+
nextCursor?: string;
|
|
176
|
+
}
|
|
177
|
+
interface FileMetadata {
|
|
178
|
+
path: string;
|
|
179
|
+
size: number;
|
|
180
|
+
mimeType: string;
|
|
181
|
+
isPublic: boolean;
|
|
182
|
+
publicUrl: string | null;
|
|
183
|
+
downloadUrl: string | null;
|
|
184
|
+
createdAt?: string;
|
|
185
|
+
updatedAt?: string;
|
|
178
186
|
}
|
|
179
187
|
interface SignedUrlResult {
|
|
180
188
|
signedUrl: string;
|
|
@@ -182,373 +190,1351 @@ interface SignedUrlResult {
|
|
|
182
190
|
expiresIn: number;
|
|
183
191
|
path: string;
|
|
184
192
|
}
|
|
185
|
-
interface
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
}>;
|
|
193
|
+
interface StorageStats {
|
|
194
|
+
totalFiles: number;
|
|
195
|
+
totalBytes: number;
|
|
196
|
+
uploadCount: number;
|
|
197
|
+
downloadCount: number;
|
|
198
|
+
deleteCount: number;
|
|
192
199
|
}
|
|
193
|
-
interface
|
|
200
|
+
interface BatchUploadItem {
|
|
194
201
|
path: string;
|
|
195
|
-
content: ArrayBuffer;
|
|
196
202
|
mimeType: string;
|
|
197
203
|
size: number;
|
|
204
|
+
isPublic?: boolean;
|
|
205
|
+
overwrite?: boolean;
|
|
198
206
|
}
|
|
199
|
-
interface
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
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;
|
|
207
|
+
interface BatchUploadUrlResult {
|
|
208
|
+
files: Array<UploadUrlResult & {
|
|
209
|
+
index: number;
|
|
210
|
+
}>;
|
|
212
211
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
onProgress?: (progress: UploadProgress) => void;
|
|
212
|
+
type QueryType = 'count' | 'distribution' | 'sum' | 'timeSeries' | 'fieldTimeSeries' | 'topN' | 'stats' | 'records' | 'multiMetric' | 'storageStats' | 'crossBucket';
|
|
213
|
+
type Aggregation = 'sum' | 'avg' | 'min' | 'max' | 'count';
|
|
214
|
+
type Granularity = 'hour' | 'day' | 'week' | 'month' | 'year';
|
|
215
|
+
type SortOrder = 'asc' | 'desc';
|
|
216
|
+
interface DateRange {
|
|
217
|
+
/** Start timestamp in milliseconds (Unix epoch). */
|
|
218
|
+
start?: number;
|
|
219
|
+
/** End timestamp in milliseconds (Unix epoch). */
|
|
220
|
+
end?: number;
|
|
223
221
|
}
|
|
224
|
-
interface
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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;
|
|
222
|
+
interface AnalyticsFilter {
|
|
223
|
+
field: string;
|
|
224
|
+
op: '==' | '!=' | '>' | '<' | '>=' | '<=' | 'CONTAINS';
|
|
225
|
+
value: string | number | boolean;
|
|
234
226
|
}
|
|
235
|
-
interface
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
autoSave?: boolean;
|
|
227
|
+
interface MetricDefinition {
|
|
228
|
+
/** Field name in your records to aggregate. */
|
|
229
|
+
field: string;
|
|
230
|
+
/** Alias used in the result object. Must be a safe identifier. */
|
|
231
|
+
name: string;
|
|
232
|
+
/** Aggregation function. Defaults to 'count'. */
|
|
233
|
+
aggregation?: Aggregation;
|
|
243
234
|
}
|
|
244
|
-
interface
|
|
245
|
-
|
|
246
|
-
|
|
235
|
+
interface AnalyticsQuery {
|
|
236
|
+
queryType: QueryType;
|
|
237
|
+
dateRange?: DateRange;
|
|
238
|
+
field?: string;
|
|
239
|
+
groupBy?: string;
|
|
240
|
+
labelField?: string;
|
|
241
|
+
aggregation?: Aggregation;
|
|
242
|
+
granularity?: Granularity;
|
|
243
|
+
filters?: AnalyticsFilter[];
|
|
244
|
+
selectFields?: string[];
|
|
247
245
|
limit?: number;
|
|
248
|
-
|
|
246
|
+
offset?: number;
|
|
247
|
+
orderBy?: string;
|
|
248
|
+
order?: SortOrder;
|
|
249
|
+
n?: number;
|
|
250
|
+
metrics?: MetricDefinition[];
|
|
251
|
+
/** For crossBucket queries: list of bucket names to compare. */
|
|
252
|
+
bucketKeys?: string[];
|
|
249
253
|
}
|
|
250
|
-
interface
|
|
251
|
-
|
|
252
|
-
|
|
254
|
+
interface AnalyticsResult<T = unknown> {
|
|
255
|
+
queryType: QueryType;
|
|
256
|
+
data: T;
|
|
253
257
|
}
|
|
254
|
-
|
|
255
|
-
|
|
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;
|
|
258
|
+
interface CountResult {
|
|
259
|
+
count: number;
|
|
273
260
|
}
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
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>>;
|
|
261
|
+
interface DistributionRow {
|
|
262
|
+
value: string | number;
|
|
263
|
+
count: number;
|
|
289
264
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
265
|
+
interface SumRow {
|
|
266
|
+
group?: string | number;
|
|
267
|
+
sum: number;
|
|
268
|
+
}
|
|
269
|
+
interface TimeSeriesRow {
|
|
270
|
+
date: string;
|
|
271
|
+
count: number;
|
|
272
|
+
}
|
|
273
|
+
interface FieldTimeSeriesRow {
|
|
274
|
+
date: string;
|
|
275
|
+
value: number;
|
|
276
|
+
}
|
|
277
|
+
interface TopNRow {
|
|
278
|
+
value: string | number;
|
|
279
|
+
label?: string;
|
|
280
|
+
count: number;
|
|
281
|
+
}
|
|
282
|
+
interface FieldStats {
|
|
283
|
+
min: number;
|
|
284
|
+
max: number;
|
|
285
|
+
avg: number;
|
|
286
|
+
sum: number;
|
|
287
|
+
count: number;
|
|
288
|
+
stddev?: number;
|
|
289
|
+
}
|
|
290
|
+
interface MultiMetricResult {
|
|
291
|
+
[metricName: string]: number;
|
|
292
|
+
}
|
|
293
|
+
interface StorageStatsResult {
|
|
294
|
+
totalRecords: number;
|
|
295
|
+
totalBytes: number;
|
|
296
|
+
avgBytes: number;
|
|
297
|
+
minBytes: number;
|
|
298
|
+
maxBytes: number;
|
|
299
|
+
}
|
|
300
|
+
interface CrossBucketRow {
|
|
301
|
+
bucket: string;
|
|
302
|
+
value: number;
|
|
301
303
|
}
|
|
302
304
|
|
|
303
305
|
/**
|
|
304
|
-
*
|
|
305
|
-
*
|
|
306
|
+
* AuthClient — full user authentication for a single bucket.
|
|
307
|
+
*
|
|
308
|
+
* Every method maps directly to a route in the server's `auth.js` router.
|
|
309
|
+
* The bucket key is the name of your user bucket (e.g. "users" or "customers").
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```ts
|
|
313
|
+
* const db = createClient({ securityKey: 'sk_...' });
|
|
314
|
+
* const auth = db.auth('my-app-users');
|
|
306
315
|
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
316
|
+
* const { user, session } = await auth.signup({
|
|
317
|
+
* email: 'alice@example.com',
|
|
318
|
+
* password: 'hunter2',
|
|
319
|
+
* fullName: 'Alice',
|
|
320
|
+
* });
|
|
321
|
+
* ```
|
|
309
322
|
*/
|
|
310
|
-
declare class
|
|
311
|
-
private readonly
|
|
312
|
-
private readonly
|
|
313
|
-
readonly
|
|
314
|
-
constructor(
|
|
323
|
+
declare class AuthClient {
|
|
324
|
+
private readonly http;
|
|
325
|
+
private readonly bucketKey;
|
|
326
|
+
private readonly basePath;
|
|
327
|
+
constructor(http: HttpClient, bucketKey: string);
|
|
328
|
+
/**
|
|
329
|
+
* Register a new user in this bucket.
|
|
330
|
+
*
|
|
331
|
+
* @example
|
|
332
|
+
* ```ts
|
|
333
|
+
* const { user, session } = await auth.signup({
|
|
334
|
+
* email: 'alice@example.com',
|
|
335
|
+
* password: 'hunter2',
|
|
336
|
+
* fullName: 'Alice Wonderland',
|
|
337
|
+
* // Any extra fields are stored on the user record:
|
|
338
|
+
* plan: 'pro',
|
|
339
|
+
* });
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
342
|
+
signup(options: SignupOptions): Promise<AuthResult>;
|
|
343
|
+
/**
|
|
344
|
+
* Authenticate an existing user and create a session.
|
|
345
|
+
* Sessions are valid for 24 hours; use `refreshSession` to extend.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* const { user, session } = await auth.login({
|
|
350
|
+
* email: 'alice@example.com',
|
|
351
|
+
* password: 'hunter2',
|
|
352
|
+
* });
|
|
353
|
+
* // Store session.sessionId safely — you'll need it for other calls.
|
|
354
|
+
* ```
|
|
355
|
+
*/
|
|
356
|
+
login(options: LoginOptions): Promise<AuthResult>;
|
|
315
357
|
/**
|
|
316
|
-
*
|
|
358
|
+
* Invalidate a session (sign out).
|
|
317
359
|
*
|
|
318
|
-
*
|
|
319
|
-
*
|
|
360
|
+
* @example
|
|
361
|
+
* ```ts
|
|
362
|
+
* await auth.logout({ sessionId: session.sessionId });
|
|
363
|
+
* ```
|
|
364
|
+
*/
|
|
365
|
+
logout({ sessionId }: {
|
|
366
|
+
sessionId: string;
|
|
367
|
+
}): Promise<void>;
|
|
368
|
+
/**
|
|
369
|
+
* Extend a session using its refresh token.
|
|
370
|
+
* Returns a new session object.
|
|
320
371
|
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```ts
|
|
374
|
+
* const newSession = await auth.refreshSession({ refreshToken: session.refreshToken });
|
|
375
|
+
* ```
|
|
376
|
+
*/
|
|
377
|
+
refreshSession({ refreshToken }: {
|
|
378
|
+
refreshToken: string;
|
|
379
|
+
}): Promise<Session>;
|
|
380
|
+
/**
|
|
381
|
+
* Fetch a user by their ID.
|
|
323
382
|
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
383
|
+
* @example
|
|
384
|
+
* ```ts
|
|
385
|
+
* const user = await auth.getUser({ userId: 'usr_abc123' });
|
|
386
|
+
* ```
|
|
387
|
+
*/
|
|
388
|
+
getUser({ userId }: {
|
|
389
|
+
userId: string;
|
|
390
|
+
}): Promise<UserRecord>;
|
|
391
|
+
/**
|
|
392
|
+
* Update fields on a user record. Requires a valid session.
|
|
327
393
|
*
|
|
328
394
|
* @example
|
|
329
|
-
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
333
|
-
*
|
|
334
|
-
* setSpeed(`${p.bytesPerSecond} B/s`);
|
|
335
|
-
* setEta(`${p.eta}s remaining`);
|
|
336
|
-
* },
|
|
395
|
+
* ```ts
|
|
396
|
+
* const updated = await auth.updateUser({
|
|
397
|
+
* sessionId: session.sessionId,
|
|
398
|
+
* userId: user.id,
|
|
399
|
+
* data: { fullName: 'Alice Smith', plan: 'enterprise' },
|
|
337
400
|
* });
|
|
401
|
+
* ```
|
|
338
402
|
*/
|
|
339
|
-
|
|
403
|
+
updateUser(options: UpdateUserOptions): Promise<UserRecord>;
|
|
340
404
|
/**
|
|
341
|
-
*
|
|
405
|
+
* Soft-delete a user. The record is marked deleted but not removed from storage.
|
|
406
|
+
* Requires a valid session (the user can delete themselves, or an admin can delete any user).
|
|
342
407
|
*
|
|
343
408
|
* @example
|
|
344
|
-
*
|
|
345
|
-
* await
|
|
346
|
-
*
|
|
347
|
-
* JSON.stringify({ theme: 'dark' }),
|
|
348
|
-
* { mimeType: 'application/json' }
|
|
349
|
-
* );
|
|
409
|
+
* ```ts
|
|
410
|
+
* await auth.deleteUser({ sessionId: session.sessionId, userId: user.id });
|
|
411
|
+
* ```
|
|
350
412
|
*/
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
413
|
+
deleteUser({ sessionId, userId }: {
|
|
414
|
+
sessionId: string;
|
|
415
|
+
userId: string;
|
|
416
|
+
}): Promise<void>;
|
|
417
|
+
/**
|
|
418
|
+
* List all users in the bucket. **Admin session required.**
|
|
419
|
+
*
|
|
420
|
+
* @example
|
|
421
|
+
* ```ts
|
|
422
|
+
* const { users, total } = await auth.listUsers({
|
|
423
|
+
* sessionId: adminSession.sessionId,
|
|
424
|
+
* limit: 50,
|
|
425
|
+
* offset: 0,
|
|
426
|
+
* });
|
|
427
|
+
* ```
|
|
428
|
+
*/
|
|
429
|
+
listUsers(options: ListUsersOptions): Promise<ListUsersResult>;
|
|
356
430
|
/**
|
|
357
|
-
*
|
|
431
|
+
* Permanently hard-delete a user and all their data. **Admin session required.**
|
|
432
|
+
* This action is irreversible.
|
|
358
433
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
434
|
+
* @example
|
|
435
|
+
* ```ts
|
|
436
|
+
* await auth.hardDeleteUser({ sessionId: adminSession.sessionId, userId: user.id });
|
|
437
|
+
* ```
|
|
438
|
+
*/
|
|
439
|
+
hardDeleteUser({ sessionId, userId }: {
|
|
440
|
+
sessionId: string;
|
|
441
|
+
userId: string;
|
|
442
|
+
}): Promise<void>;
|
|
443
|
+
/**
|
|
444
|
+
* Bulk delete multiple users. **Admin session required.**
|
|
362
445
|
*
|
|
363
446
|
* @example
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
447
|
+
* ```ts
|
|
448
|
+
* const result = await auth.bulkDeleteUsers({
|
|
449
|
+
* sessionId: adminSession.sessionId,
|
|
450
|
+
* userIds: ['usr_a', 'usr_b'],
|
|
367
451
|
* });
|
|
452
|
+
* ```
|
|
368
453
|
*/
|
|
369
|
-
|
|
454
|
+
bulkDeleteUsers({ sessionId, userIds, }: {
|
|
455
|
+
sessionId: string;
|
|
456
|
+
userIds: string[];
|
|
457
|
+
}): Promise<{
|
|
458
|
+
deleted: number;
|
|
459
|
+
failed: string[];
|
|
460
|
+
}>;
|
|
370
461
|
/**
|
|
371
|
-
*
|
|
462
|
+
* Lock a user account, preventing login. **Admin session required.**
|
|
463
|
+
*
|
|
464
|
+
* @param options.duration Lock duration in milliseconds. Defaults to 15 minutes.
|
|
372
465
|
*
|
|
373
466
|
* @example
|
|
374
|
-
*
|
|
375
|
-
*
|
|
376
|
-
*
|
|
467
|
+
* ```ts
|
|
468
|
+
* await auth.lockAccount({
|
|
469
|
+
* sessionId: adminSession.sessionId,
|
|
470
|
+
* userId: user.id,
|
|
471
|
+
* duration: 60 * 60 * 1000, // 1 hour
|
|
472
|
+
* });
|
|
473
|
+
* ```
|
|
377
474
|
*/
|
|
378
|
-
|
|
475
|
+
lockAccount({ sessionId, userId, duration, }: {
|
|
476
|
+
sessionId: string;
|
|
477
|
+
userId: string;
|
|
478
|
+
duration?: number;
|
|
479
|
+
}): Promise<{
|
|
480
|
+
lockedUntil: number;
|
|
481
|
+
unlockTime: string;
|
|
482
|
+
}>;
|
|
379
483
|
/**
|
|
380
|
-
*
|
|
484
|
+
* Unlock a previously locked user account. **Admin session required.**
|
|
381
485
|
*
|
|
382
|
-
*
|
|
486
|
+
* @example
|
|
487
|
+
* ```ts
|
|
488
|
+
* await auth.unlockAccount({ sessionId: adminSession.sessionId, userId: user.id });
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
unlockAccount({ sessionId, userId, }: {
|
|
492
|
+
sessionId: string;
|
|
493
|
+
userId: string;
|
|
494
|
+
}): Promise<void>;
|
|
495
|
+
/**
|
|
496
|
+
* Change a user's password. The user must supply their current password.
|
|
383
497
|
*
|
|
384
498
|
* @example
|
|
385
|
-
*
|
|
386
|
-
*
|
|
387
|
-
*
|
|
388
|
-
*
|
|
499
|
+
* ```ts
|
|
500
|
+
* await auth.changePassword({
|
|
501
|
+
* sessionId: session.sessionId,
|
|
502
|
+
* userId: user.id,
|
|
503
|
+
* currentPassword: 'hunter2',
|
|
504
|
+
* newPassword: 'correcthorsebatterystaple',
|
|
505
|
+
* });
|
|
506
|
+
* ```
|
|
389
507
|
*/
|
|
390
|
-
|
|
508
|
+
changePassword(options: ChangePasswordOptions): Promise<void>;
|
|
391
509
|
/**
|
|
392
|
-
*
|
|
510
|
+
* Request a password reset email for a user.
|
|
511
|
+
* Always returns success to prevent user enumeration.
|
|
393
512
|
*
|
|
394
513
|
* @example
|
|
395
|
-
*
|
|
396
|
-
*
|
|
397
|
-
*
|
|
398
|
-
* }
|
|
514
|
+
* ```ts
|
|
515
|
+
* await auth.requestPasswordReset({ email: 'alice@example.com' });
|
|
516
|
+
* ```
|
|
399
517
|
*/
|
|
400
|
-
|
|
518
|
+
requestPasswordReset({ email }: {
|
|
519
|
+
email: string;
|
|
520
|
+
}): Promise<void>;
|
|
401
521
|
/**
|
|
402
|
-
*
|
|
522
|
+
* Complete a password reset using the token from the reset email.
|
|
403
523
|
*
|
|
404
524
|
* @example
|
|
405
|
-
*
|
|
406
|
-
*
|
|
525
|
+
* ```ts
|
|
526
|
+
* await auth.confirmPasswordReset({
|
|
527
|
+
* resetToken: 'tok_from_email',
|
|
528
|
+
* newPassword: 'correcthorsebatterystaple',
|
|
529
|
+
* });
|
|
530
|
+
* ```
|
|
407
531
|
*/
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
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>>;
|
|
532
|
+
confirmPasswordReset({ resetToken, newPassword, }: {
|
|
533
|
+
resetToken: string;
|
|
534
|
+
newPassword: string;
|
|
535
|
+
}): Promise<void>;
|
|
419
536
|
/**
|
|
420
|
-
*
|
|
537
|
+
* Send (or resend) an email verification message to a user.
|
|
421
538
|
*
|
|
422
539
|
* @example
|
|
423
|
-
*
|
|
424
|
-
*
|
|
540
|
+
* ```ts
|
|
541
|
+
* await auth.requestEmailVerification({ userId: user.id });
|
|
542
|
+
* ```
|
|
425
543
|
*/
|
|
426
|
-
|
|
544
|
+
requestEmailVerification({ userId }: {
|
|
545
|
+
userId: string;
|
|
546
|
+
}): Promise<void>;
|
|
427
547
|
/**
|
|
428
|
-
*
|
|
548
|
+
* Confirm an email address using the token from the verification email.
|
|
429
549
|
*
|
|
430
550
|
* @example
|
|
431
|
-
*
|
|
432
|
-
*
|
|
551
|
+
* ```ts
|
|
552
|
+
* await auth.confirmEmailVerification({ verifyToken: 'tok_from_email' });
|
|
553
|
+
* ```
|
|
433
554
|
*/
|
|
434
|
-
|
|
555
|
+
confirmEmailVerification({ verifyToken }: {
|
|
556
|
+
verifyToken: string;
|
|
557
|
+
}): Promise<void>;
|
|
435
558
|
}
|
|
436
559
|
|
|
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[];
|
|
444
|
-
}
|
|
445
560
|
/**
|
|
446
|
-
*
|
|
561
|
+
* RecordsClient — create, read, update, delete, and query records in a bucket.
|
|
447
562
|
*
|
|
448
|
-
*
|
|
563
|
+
* A bucket is simply a named collection of JSON records. You create buckets
|
|
564
|
+
* from your HydrousDB dashboard. Each record is a JSON object with any fields
|
|
565
|
+
* you want; HydrousDB automatically adds `id`, `createdAt`, and `updatedAt`.
|
|
449
566
|
*
|
|
567
|
+
* @example
|
|
450
568
|
* ```ts
|
|
451
|
-
*
|
|
569
|
+
* const db = createClient({ securityKey: 'sk_...' });
|
|
570
|
+
* const posts = db.records('blog-posts');
|
|
452
571
|
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
*
|
|
457
|
-
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* documents: 'ssk_docs_…',
|
|
461
|
-
* },
|
|
572
|
+
* // Create a record
|
|
573
|
+
* const post = await posts.create({ title: 'Hello World', status: 'draft' });
|
|
574
|
+
*
|
|
575
|
+
* // Query records
|
|
576
|
+
* const { records } = await posts.query({
|
|
577
|
+
* filters: [{ field: 'status', op: '==', value: 'published' }],
|
|
578
|
+
* limit: 20,
|
|
462
579
|
* });
|
|
463
580
|
* ```
|
|
464
581
|
*/
|
|
465
|
-
declare class
|
|
466
|
-
|
|
467
|
-
readonly
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
/** Analytics — track events and query history */
|
|
471
|
-
readonly analytics: AnalyticsClient;
|
|
582
|
+
declare class RecordsClient<T extends RecordData = RecordData> {
|
|
583
|
+
private readonly http;
|
|
584
|
+
private readonly bucketKey;
|
|
585
|
+
private readonly basePath;
|
|
586
|
+
constructor(http: HttpClient, bucketKey: string);
|
|
472
587
|
/**
|
|
473
|
-
*
|
|
588
|
+
* Create a new record.
|
|
474
589
|
*
|
|
590
|
+
* @example
|
|
475
591
|
* ```ts
|
|
476
|
-
*
|
|
477
|
-
*
|
|
478
|
-
*
|
|
592
|
+
* const user = await records.create({
|
|
593
|
+
* name: 'Alice',
|
|
594
|
+
* email: 'alice@example.com',
|
|
595
|
+
* score: 100,
|
|
596
|
+
* });
|
|
597
|
+
* console.log(user.id); // "rec_xxxxxxxx"
|
|
479
598
|
* ```
|
|
480
599
|
*/
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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);
|
|
600
|
+
create(data: T): Promise<T & RecordResult>;
|
|
601
|
+
/**
|
|
602
|
+
* Fetch a single record by ID.
|
|
603
|
+
*
|
|
604
|
+
* @example
|
|
605
|
+
* ```ts
|
|
606
|
+
* const post = await records.get('rec_abc123');
|
|
607
|
+
* ```
|
|
608
|
+
*/
|
|
609
|
+
get(id: string): Promise<T & RecordResult>;
|
|
503
610
|
/**
|
|
504
|
-
*
|
|
611
|
+
* Overwrite a record entirely (full replace).
|
|
505
612
|
*
|
|
506
|
-
* @
|
|
613
|
+
* @example
|
|
614
|
+
* ```ts
|
|
615
|
+
* const updated = await records.set('rec_abc123', {
|
|
616
|
+
* name: 'Alice Updated',
|
|
617
|
+
* email: 'alice2@example.com',
|
|
618
|
+
* });
|
|
619
|
+
* ```
|
|
620
|
+
*/
|
|
621
|
+
set(id: string, data: T): Promise<T & RecordResult>;
|
|
622
|
+
/**
|
|
623
|
+
* Partially update a record (merge by default).
|
|
507
624
|
*
|
|
508
625
|
* @example
|
|
509
|
-
*
|
|
510
|
-
*
|
|
626
|
+
* ```ts
|
|
627
|
+
* // Merge: only the provided fields are updated
|
|
628
|
+
* const updated = await records.patch('rec_abc123', { score: 200 });
|
|
511
629
|
*
|
|
512
|
-
*
|
|
513
|
-
* const
|
|
630
|
+
* // Replace: equivalent to set()
|
|
631
|
+
* const replaced = await records.patch('rec_abc123', { score: 200 }, { merge: false });
|
|
632
|
+
* ```
|
|
514
633
|
*/
|
|
515
|
-
|
|
516
|
-
/**
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
634
|
+
patch(id: string, data: Partial<T>, options?: PatchRecordOptions): Promise<T & RecordResult>;
|
|
635
|
+
/**
|
|
636
|
+
* Delete a record permanently.
|
|
637
|
+
*
|
|
638
|
+
* @example
|
|
639
|
+
* ```ts
|
|
640
|
+
* await records.delete('rec_abc123');
|
|
641
|
+
* ```
|
|
642
|
+
*/
|
|
643
|
+
delete(id: string): Promise<void>;
|
|
644
|
+
/**
|
|
645
|
+
* Create multiple records in one request.
|
|
646
|
+
*
|
|
647
|
+
* @example
|
|
648
|
+
* ```ts
|
|
649
|
+
* const created = await records.batchCreate([
|
|
650
|
+
* { name: 'Alice', score: 100 },
|
|
651
|
+
* { name: 'Bob', score: 200 },
|
|
652
|
+
* ]);
|
|
653
|
+
* ```
|
|
654
|
+
*/
|
|
655
|
+
batchCreate(items: T[]): Promise<(T & RecordResult)[]>;
|
|
656
|
+
/**
|
|
657
|
+
* Delete multiple records by ID in one request.
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```ts
|
|
661
|
+
* await records.batchDelete(['rec_a', 'rec_b', 'rec_c']);
|
|
662
|
+
* ```
|
|
663
|
+
*/
|
|
664
|
+
batchDelete(ids: string[]): Promise<{
|
|
665
|
+
deleted: number;
|
|
666
|
+
failed: string[];
|
|
667
|
+
}>;
|
|
668
|
+
/**
|
|
669
|
+
* Query records with optional filters, sorting, and pagination.
|
|
670
|
+
*
|
|
671
|
+
* @example
|
|
672
|
+
* ```ts
|
|
673
|
+
* // Simple query
|
|
674
|
+
* const { records } = await posts.query({ limit: 10 });
|
|
675
|
+
*
|
|
676
|
+
* // Filtered query with cursor pagination
|
|
677
|
+
* const page1 = await posts.query({
|
|
678
|
+
* filters: [
|
|
679
|
+
* { field: 'status', op: '==', value: 'published' },
|
|
680
|
+
* { field: 'views', op: '>', value: 1000 },
|
|
681
|
+
* ],
|
|
682
|
+
* orderBy: 'createdAt',
|
|
683
|
+
* order: 'desc',
|
|
684
|
+
* limit: 20,
|
|
685
|
+
* });
|
|
686
|
+
*
|
|
687
|
+
* const page2 = await posts.query({
|
|
688
|
+
* filters: [{ field: 'status', op: '==', value: 'published' }],
|
|
689
|
+
* limit: 20,
|
|
690
|
+
* startAfter: page1.nextCursor,
|
|
691
|
+
* });
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
query(options?: QueryOptions): Promise<QueryResult<T>>;
|
|
695
|
+
/**
|
|
696
|
+
* Convenience alias: get all records up to `limit` (default 100).
|
|
697
|
+
*
|
|
698
|
+
* @example
|
|
699
|
+
* ```ts
|
|
700
|
+
* const allPosts = await posts.getAll({ limit: 500 });
|
|
701
|
+
* ```
|
|
702
|
+
*/
|
|
703
|
+
getAll(options?: Omit<QueryOptions, 'filters'>): Promise<(T & RecordResult)[]>;
|
|
704
|
+
/**
|
|
705
|
+
* Count records matching optional filters.
|
|
706
|
+
*
|
|
707
|
+
* @example
|
|
708
|
+
* ```ts
|
|
709
|
+
* const total = await posts.count([{ field: 'status', op: '==', value: 'published' }]);
|
|
710
|
+
* ```
|
|
711
|
+
*/
|
|
712
|
+
count(filters?: QueryOptions['filters']): Promise<number>;
|
|
713
|
+
/**
|
|
714
|
+
* Retrieve the full version history of a record.
|
|
715
|
+
* Each write creates a new version stored in GCS.
|
|
716
|
+
*
|
|
717
|
+
* @example
|
|
718
|
+
* ```ts
|
|
719
|
+
* const history = await records.getHistory('rec_abc123');
|
|
720
|
+
* console.log(history[0].data); // latest version
|
|
721
|
+
* ```
|
|
722
|
+
*/
|
|
723
|
+
getHistory(id: string): Promise<RecordHistoryEntry[]>;
|
|
724
|
+
/**
|
|
725
|
+
* Restore a record to a specific historical version.
|
|
726
|
+
*
|
|
727
|
+
* @example
|
|
728
|
+
* ```ts
|
|
729
|
+
* const history = await records.getHistory('rec_abc123');
|
|
730
|
+
* const restored = await records.restoreVersion('rec_abc123', history[2].version);
|
|
731
|
+
* ```
|
|
732
|
+
*/
|
|
733
|
+
restoreVersion(id: string, version: number): Promise<T & RecordResult>;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
/**
|
|
737
|
+
* AnalyticsClient — run powerful aggregation and time-series queries against
|
|
738
|
+
* your bucket data using BigQuery under the hood.
|
|
739
|
+
*
|
|
740
|
+
* All query types accept an optional `dateRange` to filter by time.
|
|
741
|
+
* The security key is sent automatically — you never pass it manually.
|
|
742
|
+
*
|
|
743
|
+
* @example
|
|
744
|
+
* ```ts
|
|
745
|
+
* const db = createClient({ securityKey: 'sk_...' });
|
|
746
|
+
* const analytics = db.analytics('orders');
|
|
747
|
+
*
|
|
748
|
+
* // How many orders in the last 30 days?
|
|
749
|
+
* const { count } = await analytics.count({
|
|
750
|
+
* dateRange: { start: Date.now() - 30 * 24 * 60 * 60 * 1000, end: Date.now() },
|
|
751
|
+
* });
|
|
752
|
+
* ```
|
|
753
|
+
*/
|
|
754
|
+
declare class AnalyticsClient {
|
|
755
|
+
private readonly http;
|
|
756
|
+
private readonly bucketKey;
|
|
757
|
+
private readonly basePath;
|
|
758
|
+
constructor(http: HttpClient, bucketKey: string);
|
|
759
|
+
/** Internal dispatcher — all queries POST to the same endpoint. */
|
|
760
|
+
private run;
|
|
761
|
+
/**
|
|
762
|
+
* Count the total number of records in the bucket, with optional date filter.
|
|
763
|
+
*
|
|
764
|
+
* @example
|
|
765
|
+
* ```ts
|
|
766
|
+
* const { count } = await analytics.count();
|
|
767
|
+
* // → { count: 4821 }
|
|
768
|
+
*
|
|
769
|
+
* // Count only this month's records
|
|
770
|
+
* const { count } = await analytics.count({
|
|
771
|
+
* dateRange: { start: new Date('2025-01-01').getTime(), end: Date.now() },
|
|
772
|
+
* });
|
|
773
|
+
* ```
|
|
774
|
+
*/
|
|
775
|
+
count(opts?: {
|
|
776
|
+
dateRange?: DateRange;
|
|
777
|
+
}): Promise<CountResult>;
|
|
778
|
+
/**
|
|
779
|
+
* Count how many records have each unique value for a given field.
|
|
780
|
+
* Great for pie charts and bar charts.
|
|
781
|
+
*
|
|
782
|
+
* @example
|
|
783
|
+
* ```ts
|
|
784
|
+
* const rows = await analytics.distribution({
|
|
785
|
+
* field: 'status',
|
|
786
|
+
* limit: 10,
|
|
787
|
+
* order: 'desc',
|
|
788
|
+
* });
|
|
789
|
+
* // → [{ value: 'completed', count: 312 }, { value: 'pending', count: 88 }, ...]
|
|
790
|
+
* ```
|
|
791
|
+
*/
|
|
792
|
+
distribution(opts: {
|
|
793
|
+
field: string;
|
|
794
|
+
limit?: number;
|
|
795
|
+
order?: SortOrder;
|
|
796
|
+
dateRange?: DateRange;
|
|
797
|
+
}): Promise<DistributionRow[]>;
|
|
798
|
+
/**
|
|
799
|
+
* Sum a numeric field, optionally grouped by another field.
|
|
800
|
+
*
|
|
801
|
+
* @example
|
|
802
|
+
* ```ts
|
|
803
|
+
* // Total revenue
|
|
804
|
+
* const rows = await analytics.sum({ field: 'amount' });
|
|
805
|
+
* // → [{ sum: 198432.50 }]
|
|
806
|
+
*
|
|
807
|
+
* // Revenue by country
|
|
808
|
+
* const rows = await analytics.sum({ field: 'amount', groupBy: 'country', limit: 10 });
|
|
809
|
+
* // → [{ group: 'US', sum: 120000 }, { group: 'UK', sum: 45000 }, ...]
|
|
810
|
+
* ```
|
|
811
|
+
*/
|
|
812
|
+
sum(opts: {
|
|
813
|
+
field: string;
|
|
814
|
+
groupBy?: string;
|
|
815
|
+
limit?: number;
|
|
816
|
+
dateRange?: DateRange;
|
|
817
|
+
}): Promise<SumRow[]>;
|
|
818
|
+
/**
|
|
819
|
+
* Count records created over time, grouped by a time granularity.
|
|
820
|
+
* Perfect for line charts and activity graphs.
|
|
821
|
+
*
|
|
822
|
+
* @example
|
|
823
|
+
* ```ts
|
|
824
|
+
* const rows = await analytics.timeSeries({
|
|
825
|
+
* granularity: 'day',
|
|
826
|
+
* dateRange: { start: Date.now() - 7 * 86400000, end: Date.now() },
|
|
827
|
+
* });
|
|
828
|
+
* // → [{ date: '2025-06-01', count: 42 }, { date: '2025-06-02', count: 67 }, ...]
|
|
829
|
+
* ```
|
|
830
|
+
*/
|
|
831
|
+
timeSeries(opts?: {
|
|
832
|
+
granularity?: Granularity;
|
|
833
|
+
dateRange?: DateRange;
|
|
834
|
+
}): Promise<TimeSeriesRow[]>;
|
|
835
|
+
/**
|
|
836
|
+
* Aggregate a numeric field over time (e.g. daily revenue, hourly signups).
|
|
837
|
+
*
|
|
838
|
+
* @example
|
|
839
|
+
* ```ts
|
|
840
|
+
* const rows = await analytics.fieldTimeSeries({
|
|
841
|
+
* field: 'amount',
|
|
842
|
+
* aggregation: 'sum',
|
|
843
|
+
* granularity: 'week',
|
|
844
|
+
* });
|
|
845
|
+
* // → [{ date: '2025-W22', value: 14230.50 }, ...]
|
|
846
|
+
* ```
|
|
847
|
+
*/
|
|
848
|
+
fieldTimeSeries(opts: {
|
|
849
|
+
field: string;
|
|
850
|
+
aggregation?: Aggregation;
|
|
851
|
+
granularity?: Granularity;
|
|
852
|
+
dateRange?: DateRange;
|
|
853
|
+
}): Promise<FieldTimeSeriesRow[]>;
|
|
854
|
+
/**
|
|
855
|
+
* Get the top N values by frequency for a field.
|
|
856
|
+
* Optionally pair with a `labelField` for human-readable labels.
|
|
857
|
+
*
|
|
858
|
+
* @example
|
|
859
|
+
* ```ts
|
|
860
|
+
* // Top 5 most purchased products
|
|
861
|
+
* const rows = await analytics.topN({
|
|
862
|
+
* field: 'productId',
|
|
863
|
+
* labelField: 'productName',
|
|
864
|
+
* n: 5,
|
|
865
|
+
* });
|
|
866
|
+
* // → [{ value: 'prod_123', label: 'Widget Pro', count: 892 }, ...]
|
|
867
|
+
* ```
|
|
868
|
+
*/
|
|
869
|
+
topN(opts: {
|
|
870
|
+
field: string;
|
|
871
|
+
n?: number;
|
|
872
|
+
labelField?: string;
|
|
873
|
+
order?: SortOrder;
|
|
874
|
+
dateRange?: DateRange;
|
|
875
|
+
}): Promise<TopNRow[]>;
|
|
876
|
+
/**
|
|
877
|
+
* Get statistical summary (min, max, avg, sum, count, stddev) for a numeric field.
|
|
878
|
+
*
|
|
879
|
+
* @example
|
|
880
|
+
* ```ts
|
|
881
|
+
* const stats = await analytics.stats({ field: 'orderValue' });
|
|
882
|
+
* // → { min: 4.99, max: 9999.99, avg: 87.23, sum: 420948.27, count: 4823, stddev: 143.2 }
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
stats(opts: {
|
|
886
|
+
field: string;
|
|
887
|
+
dateRange?: DateRange;
|
|
888
|
+
}): Promise<FieldStats>;
|
|
889
|
+
/**
|
|
890
|
+
* Query raw records with filters, field selection, and pagination.
|
|
891
|
+
* This is the analytics version of `records.query()` but powered by BigQuery.
|
|
892
|
+
*
|
|
893
|
+
* @example
|
|
894
|
+
* ```ts
|
|
895
|
+
* const { records } = await analytics.records({
|
|
896
|
+
* filters: [{ field: 'status', op: '==', value: 'refunded' }],
|
|
897
|
+
* selectFields: ['orderId', 'amount', 'createdAt'],
|
|
898
|
+
* limit: 50,
|
|
899
|
+
* orderBy: 'amount',
|
|
900
|
+
* order: 'desc',
|
|
901
|
+
* });
|
|
902
|
+
* ```
|
|
903
|
+
*/
|
|
904
|
+
records<T extends RecordData = RecordData>(opts?: {
|
|
905
|
+
filters?: AnalyticsFilter[];
|
|
906
|
+
selectFields?: string[];
|
|
907
|
+
limit?: number;
|
|
908
|
+
offset?: number;
|
|
909
|
+
orderBy?: string;
|
|
910
|
+
order?: SortOrder;
|
|
911
|
+
dateRange?: DateRange;
|
|
912
|
+
}): Promise<(T & RecordResult)[]>;
|
|
913
|
+
/**
|
|
914
|
+
* Calculate multiple aggregations in a single query.
|
|
915
|
+
* Ideal for dashboards that need several numbers at once.
|
|
916
|
+
*
|
|
917
|
+
* @example
|
|
918
|
+
* ```ts
|
|
919
|
+
* const result = await analytics.multiMetric({
|
|
920
|
+
* metrics: [
|
|
921
|
+
* { field: 'amount', name: 'totalRevenue', aggregation: 'sum' },
|
|
922
|
+
* { field: 'amount', name: 'avgOrderValue', aggregation: 'avg' },
|
|
923
|
+
* { field: 'userId', name: 'uniqueCustomers', aggregation: 'count' },
|
|
924
|
+
* ],
|
|
925
|
+
* });
|
|
926
|
+
* // → { totalRevenue: 198432.50, avgOrderValue: 87.23, uniqueCustomers: 2275 }
|
|
927
|
+
* ```
|
|
928
|
+
*/
|
|
929
|
+
multiMetric(opts: {
|
|
930
|
+
metrics: MetricDefinition[];
|
|
931
|
+
dateRange?: DateRange;
|
|
932
|
+
}): Promise<MultiMetricResult>;
|
|
933
|
+
/**
|
|
934
|
+
* Get storage statistics for this bucket: record counts, byte sizes.
|
|
935
|
+
*
|
|
936
|
+
* @example
|
|
937
|
+
* ```ts
|
|
938
|
+
* const stats = await analytics.storageStats();
|
|
939
|
+
* // → { totalRecords: 4821, totalBytes: 48293820, avgBytes: 10015, ... }
|
|
940
|
+
* ```
|
|
941
|
+
*/
|
|
942
|
+
storageStats(opts?: {
|
|
943
|
+
dateRange?: DateRange;
|
|
944
|
+
}): Promise<StorageStatsResult>;
|
|
945
|
+
/**
|
|
946
|
+
* Compare the same field aggregation across multiple buckets in one query.
|
|
947
|
+
* Your security key must have read access to ALL listed buckets.
|
|
948
|
+
*
|
|
949
|
+
* @example
|
|
950
|
+
* ```ts
|
|
951
|
+
* const rows = await analytics.crossBucket({
|
|
952
|
+
* bucketKeys: ['orders-us', 'orders-eu', 'orders-apac'],
|
|
953
|
+
* field: 'amount',
|
|
954
|
+
* aggregation: 'sum',
|
|
955
|
+
* });
|
|
956
|
+
* // → [
|
|
957
|
+
* // { bucket: 'orders-us', value: 120000 },
|
|
958
|
+
* // { bucket: 'orders-eu', value: 45000 },
|
|
959
|
+
* // { bucket: 'orders-apac', value: 33000 },
|
|
960
|
+
* // ]
|
|
961
|
+
* ```
|
|
962
|
+
*/
|
|
963
|
+
crossBucket(opts: {
|
|
964
|
+
bucketKeys: string[];
|
|
965
|
+
field: string;
|
|
966
|
+
aggregation?: Aggregation;
|
|
967
|
+
dateRange?: DateRange;
|
|
968
|
+
}): Promise<CrossBucketRow[]>;
|
|
969
|
+
/**
|
|
970
|
+
* Send a raw analytics query object. Use this when you need full control
|
|
971
|
+
* over the query shape or want to use a queryType not covered by the helpers.
|
|
972
|
+
*
|
|
973
|
+
* @example
|
|
974
|
+
* ```ts
|
|
975
|
+
* const result = await analytics.query({
|
|
976
|
+
* queryType: 'topN',
|
|
977
|
+
* field: 'category',
|
|
978
|
+
* n: 3,
|
|
979
|
+
* order: 'asc',
|
|
980
|
+
* });
|
|
981
|
+
* ```
|
|
982
|
+
*/
|
|
983
|
+
query<T = unknown>(query: AnalyticsQuery): Promise<AnalyticsResult<T>>;
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
/**
|
|
987
|
+
* StorageManager — upload, download, list, move, copy, and delete files.
|
|
988
|
+
*
|
|
989
|
+
* Authentication is handled automatically via the X-Storage-Key header.
|
|
990
|
+
* Files are scoped to your owner ID — you can never access another owner's files.
|
|
991
|
+
*
|
|
992
|
+
* **Recommended upload flow** (for progress tracking and large files):
|
|
993
|
+
* 1. Call `getUploadUrl()` to get a signed PUT URL.
|
|
994
|
+
* 2. Upload directly to GCS using XHR (supports progress events).
|
|
995
|
+
* 3. Call `confirmUpload()` to finalize metadata.
|
|
996
|
+
*
|
|
997
|
+
* **Simple upload** (small files, no progress needed):
|
|
998
|
+
* - Call `upload()` — it handles everything in one call.
|
|
999
|
+
*
|
|
1000
|
+
* @example
|
|
1001
|
+
* ```ts
|
|
1002
|
+
* const db = createClient({ securityKey: 'sk_...' });
|
|
1003
|
+
*
|
|
1004
|
+
* // Simple upload
|
|
1005
|
+
* const file = await db.storage.upload(fileBlob, 'avatars/alice.jpg', { isPublic: true });
|
|
1006
|
+
* console.log(file.publicUrl); // CDN URL, usable anywhere
|
|
1007
|
+
* ```
|
|
1008
|
+
*/
|
|
1009
|
+
declare class StorageManager {
|
|
1010
|
+
private readonly http;
|
|
1011
|
+
private readonly storageKey;
|
|
1012
|
+
private readonly basePath;
|
|
1013
|
+
constructor(http: HttpClient, storageKey: string);
|
|
1014
|
+
/** Headers for all storage requests — uses X-Storage-Key, not X-Api-Key. */
|
|
1015
|
+
private get authHeaders();
|
|
1016
|
+
/**
|
|
1017
|
+
* Upload a file to storage in one step (server-buffered, up to 500 MB).
|
|
1018
|
+
* For files >10 MB or when you need upload progress, use `getUploadUrl()` instead.
|
|
1019
|
+
*
|
|
1020
|
+
* @param data File data as a Blob, Buffer, Uint8Array, or ArrayBuffer.
|
|
1021
|
+
* @param path Destination path in your storage (e.g. `"avatars/alice.jpg"`).
|
|
1022
|
+
* @param options Upload options: isPublic, overwrite, mimeType.
|
|
1023
|
+
*
|
|
1024
|
+
* @example
|
|
1025
|
+
* ```ts
|
|
1026
|
+
* // Upload a public avatar
|
|
1027
|
+
* const result = await storage.upload(file, 'avatars/alice.jpg', { isPublic: true });
|
|
1028
|
+
* console.log(result.publicUrl); // → https://...
|
|
1029
|
+
*
|
|
1030
|
+
* // Upload a private document
|
|
1031
|
+
* const result = await storage.upload(pdfBuffer, 'docs/contract.pdf');
|
|
1032
|
+
* console.log(result.downloadUrl); // → /storage/download/docs/contract.pdf
|
|
1033
|
+
* ```
|
|
1034
|
+
*/
|
|
1035
|
+
upload(data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer, path: string, options?: UploadOptions): Promise<UploadResult>;
|
|
1036
|
+
/**
|
|
1037
|
+
* Upload raw JSON or plain text data as a file.
|
|
1038
|
+
*
|
|
1039
|
+
* @example
|
|
1040
|
+
* ```ts
|
|
1041
|
+
* const result = await storage.uploadRaw(
|
|
1042
|
+
* { config: { theme: 'dark' } },
|
|
1043
|
+
* 'settings/user-config.json',
|
|
1044
|
+
* { isPublic: false },
|
|
1045
|
+
* );
|
|
1046
|
+
* ```
|
|
1047
|
+
*/
|
|
1048
|
+
uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
|
|
1049
|
+
/**
|
|
1050
|
+
* Step 1 of the recommended upload flow.
|
|
1051
|
+
* Get a signed URL to upload directly to GCS from the client (supports progress).
|
|
1052
|
+
*
|
|
1053
|
+
* @example
|
|
1054
|
+
* ```ts
|
|
1055
|
+
* const { uploadUrl, path: confirmedPath } = await storage.getUploadUrl({
|
|
1056
|
+
* path: 'videos/intro.mp4',
|
|
1057
|
+
* mimeType: 'video/mp4',
|
|
1058
|
+
* size: file.size,
|
|
1059
|
+
* isPublic: true,
|
|
1060
|
+
* });
|
|
1061
|
+
*
|
|
1062
|
+
* // Upload using XHR for progress tracking
|
|
1063
|
+
* await storage.uploadToSignedUrl(uploadUrl, file, 'video/mp4', (pct) => {
|
|
1064
|
+
* console.log(`${pct}% uploaded`);
|
|
1065
|
+
* });
|
|
1066
|
+
*
|
|
1067
|
+
* // Step 3: confirm
|
|
1068
|
+
* const result = await storage.confirmUpload({ path: confirmedPath, mimeType: 'video/mp4', isPublic: true });
|
|
1069
|
+
* ```
|
|
1070
|
+
*/
|
|
1071
|
+
getUploadUrl(opts: {
|
|
1072
|
+
path: string;
|
|
1073
|
+
mimeType: string;
|
|
1074
|
+
size: number;
|
|
1075
|
+
isPublic?: boolean;
|
|
1076
|
+
overwrite?: boolean;
|
|
1077
|
+
expiresInSeconds?: number;
|
|
1078
|
+
}): Promise<UploadUrlResult>;
|
|
1079
|
+
/**
|
|
1080
|
+
* Upload data directly to a signed GCS URL (no auth headers needed).
|
|
1081
|
+
* Optionally tracks progress via a callback.
|
|
1082
|
+
*
|
|
1083
|
+
* @param signedUrl The URL returned by `getUploadUrl()`.
|
|
1084
|
+
* @param data File data.
|
|
1085
|
+
* @param mimeType Must match what was used in `getUploadUrl()`.
|
|
1086
|
+
* @param onProgress Optional callback called with 0–100 progress percentage.
|
|
1087
|
+
*/
|
|
1088
|
+
uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
|
|
1089
|
+
/**
|
|
1090
|
+
* Step 3 of the recommended upload flow.
|
|
1091
|
+
* Confirm a direct upload and register metadata on the server.
|
|
1092
|
+
*
|
|
1093
|
+
* @example
|
|
1094
|
+
* ```ts
|
|
1095
|
+
* const result = await storage.confirmUpload({
|
|
1096
|
+
* path: 'videos/intro.mp4',
|
|
1097
|
+
* mimeType: 'video/mp4',
|
|
1098
|
+
* isPublic: true,
|
|
1099
|
+
* });
|
|
1100
|
+
* console.log(result.publicUrl);
|
|
1101
|
+
* ```
|
|
1102
|
+
*/
|
|
1103
|
+
confirmUpload(opts: {
|
|
1104
|
+
path: string;
|
|
1105
|
+
mimeType: string;
|
|
1106
|
+
isPublic?: boolean;
|
|
1107
|
+
}): Promise<UploadResult>;
|
|
1108
|
+
/**
|
|
1109
|
+
* Get signed upload URLs for multiple files at once.
|
|
1110
|
+
*
|
|
1111
|
+
* @example
|
|
1112
|
+
* ```ts
|
|
1113
|
+
* const { files } = await storage.getBatchUploadUrls([
|
|
1114
|
+
* { path: 'images/photo1.jpg', mimeType: 'image/jpeg', size: 204800 },
|
|
1115
|
+
* { path: 'images/photo2.jpg', mimeType: 'image/jpeg', size: 153600 },
|
|
1116
|
+
* ]);
|
|
1117
|
+
*
|
|
1118
|
+
* // Upload each file and confirm
|
|
1119
|
+
* for (const f of files) {
|
|
1120
|
+
* await storage.uploadToSignedUrl(f.uploadUrl, blobs[f.index], f.mimeType);
|
|
1121
|
+
* await storage.confirmUpload({ path: f.path, mimeType: f.mimeType });
|
|
1122
|
+
* }
|
|
1123
|
+
* ```
|
|
1124
|
+
*/
|
|
1125
|
+
getBatchUploadUrls(files: BatchUploadItem[]): Promise<BatchUploadUrlResult>;
|
|
1126
|
+
/**
|
|
1127
|
+
* Confirm multiple direct uploads at once.
|
|
1128
|
+
*/
|
|
1129
|
+
batchConfirmUploads(items: Array<{
|
|
1130
|
+
path: string;
|
|
1131
|
+
mimeType: string;
|
|
1132
|
+
isPublic?: boolean;
|
|
1133
|
+
}>): Promise<UploadResult[]>;
|
|
1134
|
+
/**
|
|
1135
|
+
* Download a private file as an ArrayBuffer.
|
|
1136
|
+
* For public files, use the `publicUrl` directly — no SDK needed.
|
|
1137
|
+
*
|
|
1138
|
+
* @example
|
|
1139
|
+
* ```ts
|
|
1140
|
+
* const buffer = await storage.download('docs/contract.pdf');
|
|
1141
|
+
* const blob = new Blob([buffer], { type: 'application/pdf' });
|
|
1142
|
+
* // Open in browser:
|
|
1143
|
+
* window.open(URL.createObjectURL(blob));
|
|
1144
|
+
* ```
|
|
1145
|
+
*/
|
|
1146
|
+
download(path: string): Promise<ArrayBuffer>;
|
|
1147
|
+
/**
|
|
1148
|
+
* Download multiple files at once, returned as a JSON map of `{ path: base64 }`.
|
|
1149
|
+
*
|
|
1150
|
+
* @example
|
|
1151
|
+
* ```ts
|
|
1152
|
+
* const files = await storage.batchDownload(['docs/a.pdf', 'docs/b.pdf']);
|
|
1153
|
+
* ```
|
|
1154
|
+
*/
|
|
1155
|
+
batchDownload(paths: string[]): Promise<Record<string, string>>;
|
|
1156
|
+
/**
|
|
1157
|
+
* List files and folders at a given path prefix.
|
|
1158
|
+
*
|
|
1159
|
+
* @example
|
|
1160
|
+
* ```ts
|
|
1161
|
+
* // List everything in the root
|
|
1162
|
+
* const { files, folders } = await storage.list();
|
|
1163
|
+
*
|
|
1164
|
+
* // List a specific folder
|
|
1165
|
+
* const { files, folders, hasMore, nextCursor } = await storage.list({
|
|
1166
|
+
* prefix: 'avatars/',
|
|
1167
|
+
* limit: 20,
|
|
1168
|
+
* });
|
|
1169
|
+
*
|
|
1170
|
+
* // Next page
|
|
1171
|
+
* const page2 = await storage.list({ prefix: 'avatars/', cursor: nextCursor });
|
|
1172
|
+
* ```
|
|
1173
|
+
*/
|
|
1174
|
+
list(opts?: ListOptions): Promise<ListResult>;
|
|
1175
|
+
/**
|
|
1176
|
+
* Get metadata for a file (size, MIME type, visibility, URLs).
|
|
1177
|
+
*
|
|
1178
|
+
* @example
|
|
1179
|
+
* ```ts
|
|
1180
|
+
* const meta = await storage.getMetadata('avatars/alice.jpg');
|
|
1181
|
+
* console.log(meta.size, meta.isPublic, meta.publicUrl);
|
|
1182
|
+
* ```
|
|
1183
|
+
*/
|
|
1184
|
+
getMetadata(path: string): Promise<FileMetadata>;
|
|
1185
|
+
/**
|
|
1186
|
+
* Generate a time-limited download URL for a private file.
|
|
1187
|
+
* The URL can be shared externally without requiring an `X-Storage-Key`.
|
|
1188
|
+
*
|
|
1189
|
+
* > **Note:** Downloads via signed URLs bypass the server, so download stats
|
|
1190
|
+
* > are NOT tracked. Use `downloadUrl` for tracked downloads.
|
|
1191
|
+
*
|
|
1192
|
+
* @param path Path to the file.
|
|
1193
|
+
* @param expiresIn URL lifetime in seconds (default 3600 = 1 hour).
|
|
1194
|
+
*
|
|
1195
|
+
* @example
|
|
1196
|
+
* ```ts
|
|
1197
|
+
* const { signedUrl, expiresAt } = await storage.getSignedUrl('docs/invoice.pdf', 1800);
|
|
1198
|
+
* // Share signedUrl with the recipient — it expires in 30 minutes.
|
|
1199
|
+
* ```
|
|
1200
|
+
*/
|
|
1201
|
+
getSignedUrl(path: string, expiresIn?: number): Promise<SignedUrlResult>;
|
|
1202
|
+
/**
|
|
1203
|
+
* Change a file's visibility between public and private after upload.
|
|
1204
|
+
*
|
|
1205
|
+
* @example
|
|
1206
|
+
* ```ts
|
|
1207
|
+
* // Make a file public
|
|
1208
|
+
* const result = await storage.setVisibility('avatars/alice.jpg', true);
|
|
1209
|
+
* console.log(result.publicUrl); // CDN URL
|
|
1210
|
+
*
|
|
1211
|
+
* // Make a file private
|
|
1212
|
+
* const result = await storage.setVisibility('avatars/alice.jpg', false);
|
|
1213
|
+
* console.log(result.downloadUrl); // Auth-required URL
|
|
1214
|
+
* ```
|
|
1215
|
+
*/
|
|
1216
|
+
setVisibility(path: string, isPublic: boolean): Promise<{
|
|
1217
|
+
path: string;
|
|
1218
|
+
isPublic: boolean;
|
|
1219
|
+
publicUrl: string | null;
|
|
1220
|
+
downloadUrl: string | null;
|
|
1221
|
+
}>;
|
|
1222
|
+
/**
|
|
1223
|
+
* Create a folder (a GCS prefix placeholder).
|
|
1224
|
+
*
|
|
1225
|
+
* @example
|
|
1226
|
+
* ```ts
|
|
1227
|
+
* await storage.createFolder('uploads/2025/');
|
|
1228
|
+
* ```
|
|
1229
|
+
*/
|
|
1230
|
+
createFolder(path: string): Promise<{
|
|
1231
|
+
path: string;
|
|
1232
|
+
}>;
|
|
1233
|
+
/**
|
|
1234
|
+
* Delete a single file.
|
|
1235
|
+
*
|
|
1236
|
+
* @example
|
|
1237
|
+
* ```ts
|
|
1238
|
+
* await storage.deleteFile('avatars/old-avatar.jpg');
|
|
1239
|
+
* ```
|
|
1240
|
+
*/
|
|
1241
|
+
deleteFile(path: string): Promise<void>;
|
|
1242
|
+
/**
|
|
1243
|
+
* Delete a folder and all its contents recursively.
|
|
1244
|
+
*
|
|
1245
|
+
* @example
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* await storage.deleteFolder('temp/');
|
|
1248
|
+
* ```
|
|
1249
|
+
*/
|
|
1250
|
+
deleteFolder(path: string): Promise<void>;
|
|
1251
|
+
/**
|
|
1252
|
+
* Move or rename a file.
|
|
1253
|
+
*
|
|
1254
|
+
* @example
|
|
1255
|
+
* ```ts
|
|
1256
|
+
* // Rename
|
|
1257
|
+
* await storage.move('docs/draft.pdf', 'docs/final.pdf');
|
|
1258
|
+
* // Move to a different folder
|
|
1259
|
+
* await storage.move('inbox/report.xlsx', 'archive/2025/report.xlsx');
|
|
1260
|
+
* ```
|
|
1261
|
+
*/
|
|
1262
|
+
move(from: string, to: string): Promise<{
|
|
1263
|
+
from: string;
|
|
1264
|
+
to: string;
|
|
1265
|
+
}>;
|
|
1266
|
+
/**
|
|
1267
|
+
* Copy a file to a new path.
|
|
1268
|
+
*
|
|
1269
|
+
* @example
|
|
1270
|
+
* ```ts
|
|
1271
|
+
* await storage.copy('templates/base.html', 'sites/my-site/index.html');
|
|
1272
|
+
* ```
|
|
1273
|
+
*/
|
|
1274
|
+
copy(from: string, to: string): Promise<{
|
|
1275
|
+
from: string;
|
|
1276
|
+
to: string;
|
|
1277
|
+
}>;
|
|
1278
|
+
/**
|
|
1279
|
+
* Get storage statistics for your key: total files, bytes, operation counts.
|
|
1280
|
+
*
|
|
1281
|
+
* @example
|
|
1282
|
+
* ```ts
|
|
1283
|
+
* const stats = await storage.getStats();
|
|
1284
|
+
* console.log(`${stats.totalFiles} files, ${(stats.totalBytes / 1e6).toFixed(1)} MB`);
|
|
1285
|
+
* ```
|
|
1286
|
+
*/
|
|
1287
|
+
getStats(): Promise<StorageStats>;
|
|
1288
|
+
/**
|
|
1289
|
+
* Ping the storage service. No authentication required.
|
|
1290
|
+
*
|
|
1291
|
+
* @example
|
|
1292
|
+
* ```ts
|
|
1293
|
+
* const info = await storage.info();
|
|
1294
|
+
* // → { ok: true, storageRoot: 'hydrous-storage' }
|
|
1295
|
+
* ```
|
|
1296
|
+
*/
|
|
1297
|
+
info(): Promise<{
|
|
1298
|
+
ok: boolean;
|
|
1299
|
+
storageRoot: string;
|
|
1300
|
+
}>;
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
/** Accepted data types for file uploads. */
|
|
1304
|
+
type UploadData = Blob | Uint8Array<ArrayBuffer> | ArrayBuffer | Buffer;
|
|
1305
|
+
/**
|
|
1306
|
+
* ScopedStorage — a StorageManager pre-scoped to a specific folder prefix.
|
|
1307
|
+
*
|
|
1308
|
+
* All paths are automatically prepended with the scope, so you never
|
|
1309
|
+
* have to repeat the folder name.
|
|
1310
|
+
*
|
|
1311
|
+
* @example
|
|
1312
|
+
* ```ts
|
|
1313
|
+
* const db = createClient({ securityKey: 'sk_...' });
|
|
1314
|
+
*
|
|
1315
|
+
* // All operations in the "avatars/" folder
|
|
1316
|
+
* const avatars = db.storage.scope('avatars');
|
|
1317
|
+
*
|
|
1318
|
+
* await avatars.upload(file, 'alice.jpg'); // → "avatars/alice.jpg"
|
|
1319
|
+
* const list = await avatars.list(); // → files under "avatars/"
|
|
1320
|
+
* await avatars.deleteFile('alice.jpg'); // → deletes "avatars/alice.jpg"
|
|
1321
|
+
* ```
|
|
1322
|
+
*/
|
|
1323
|
+
declare class ScopedStorage {
|
|
1324
|
+
private readonly manager;
|
|
1325
|
+
private readonly prefix;
|
|
1326
|
+
constructor(manager: StorageManager, prefix: string);
|
|
1327
|
+
private scopedPath;
|
|
1328
|
+
/** Upload a file within the scoped folder. */
|
|
1329
|
+
upload(data: UploadData, path: string, options?: UploadOptions): Promise<UploadResult>;
|
|
1330
|
+
/** Upload raw JSON or text within the scoped folder. */
|
|
1331
|
+
uploadRaw(data: unknown, path: string, options?: UploadOptions): Promise<UploadResult>;
|
|
1332
|
+
/** Get a signed upload URL for a file within the scoped folder. */
|
|
1333
|
+
getUploadUrl(opts: {
|
|
1334
|
+
path: string;
|
|
1335
|
+
mimeType: string;
|
|
1336
|
+
size: number;
|
|
1337
|
+
isPublic?: boolean;
|
|
1338
|
+
overwrite?: boolean;
|
|
1339
|
+
}): Promise<UploadUrlResult>;
|
|
1340
|
+
/** Upload data directly to a signed GCS URL with optional progress tracking. */
|
|
1341
|
+
uploadToSignedUrl(signedUrl: string, data: Blob | Uint8Array<ArrayBuffer> | ArrayBuffer, mimeType: string, onProgress?: (percent: number) => void): Promise<void>;
|
|
1342
|
+
/** Confirm a direct upload within the scoped folder. */
|
|
1343
|
+
confirmUpload(opts: {
|
|
1344
|
+
path: string;
|
|
1345
|
+
mimeType: string;
|
|
1346
|
+
isPublic?: boolean;
|
|
1347
|
+
}): Promise<UploadResult>;
|
|
1348
|
+
/** Download a file within the scoped folder. */
|
|
1349
|
+
download(path: string): Promise<ArrayBuffer>;
|
|
1350
|
+
/** List files within the scoped folder. */
|
|
1351
|
+
list(opts?: ListOptions): Promise<ListResult>;
|
|
1352
|
+
/** Get metadata for a file within the scoped folder. */
|
|
1353
|
+
getMetadata(path: string): Promise<FileMetadata>;
|
|
1354
|
+
/** Get a time-limited signed URL for a file within the scoped folder. */
|
|
1355
|
+
getSignedUrl(path: string, expiresIn?: number): Promise<SignedUrlResult>;
|
|
1356
|
+
/** Change visibility of a file within the scoped folder. */
|
|
1357
|
+
setVisibility(path: string, isPublic: boolean): Promise<{
|
|
1358
|
+
path: string;
|
|
1359
|
+
isPublic: boolean;
|
|
1360
|
+
publicUrl: string | null;
|
|
1361
|
+
downloadUrl: string | null;
|
|
1362
|
+
}>;
|
|
1363
|
+
/** Delete a file within the scoped folder. */
|
|
1364
|
+
deleteFile(path: string): Promise<void>;
|
|
1365
|
+
/** Delete a sub-folder within the scoped folder. */
|
|
1366
|
+
deleteFolder(path: string): Promise<void>;
|
|
1367
|
+
/** Move a file within the scoped folder. */
|
|
1368
|
+
move(from: string, to: string): Promise<{
|
|
1369
|
+
from: string;
|
|
1370
|
+
to: string;
|
|
1371
|
+
}>;
|
|
1372
|
+
/** Copy a file within the scoped folder. */
|
|
1373
|
+
copy(from: string, to: string): Promise<{
|
|
1374
|
+
from: string;
|
|
1375
|
+
to: string;
|
|
1376
|
+
}>;
|
|
1377
|
+
/** Create a sub-folder within the scoped folder. */
|
|
1378
|
+
createFolder(path: string): Promise<{
|
|
1379
|
+
path: string;
|
|
1380
|
+
}>;
|
|
1381
|
+
/**
|
|
1382
|
+
* Create a further-scoped instance nested within this scope.
|
|
1383
|
+
*
|
|
1384
|
+
* @example
|
|
1385
|
+
* ```ts
|
|
1386
|
+
* const uploads = db.storage.scope('user-uploads');
|
|
1387
|
+
* const images = uploads.scope('images'); // → "user-uploads/images/"
|
|
1388
|
+
* ```
|
|
1389
|
+
*/
|
|
1390
|
+
scope(subPrefix: string): ScopedStorage;
|
|
1391
|
+
}
|
|
1392
|
+
|
|
1393
|
+
/**
|
|
1394
|
+
* HydrousClient — the main entry point for the HydrousDB SDK.
|
|
1395
|
+
*
|
|
1396
|
+
* Create one instance per application (it is safe to share globally).
|
|
1397
|
+
* Every sub-client (`auth`, `records`, `analytics`, `storage`) is lazily
|
|
1398
|
+
* instantiated and cached on first access.
|
|
1399
|
+
*
|
|
1400
|
+
* @example
|
|
1401
|
+
* ```ts
|
|
1402
|
+
* import { createClient } from 'hydrousdb';
|
|
1403
|
+
*
|
|
1404
|
+
* const db = createClient({ securityKey: 'sk_live_...' });
|
|
1405
|
+
*
|
|
1406
|
+
* // Records
|
|
1407
|
+
* const posts = db.records('blog-posts');
|
|
1408
|
+
* const users = db.records('app-users');
|
|
1409
|
+
*
|
|
1410
|
+
* // Auth
|
|
1411
|
+
* const auth = db.auth('app-users');
|
|
1412
|
+
*
|
|
1413
|
+
* // Analytics
|
|
1414
|
+
* const stats = db.analytics('orders');
|
|
1415
|
+
*
|
|
1416
|
+
* // Storage (flat)
|
|
1417
|
+
* const storage = db.storage; // StorageManager
|
|
1418
|
+
*
|
|
1419
|
+
* // Storage (scoped to a folder)
|
|
1420
|
+
* const avatars = db.storage.scope('avatars');
|
|
1421
|
+
* ```
|
|
1422
|
+
*/
|
|
1423
|
+
declare class HydrousClient {
|
|
1424
|
+
private readonly http;
|
|
1425
|
+
private readonly _storageKey;
|
|
1426
|
+
private _storage;
|
|
1427
|
+
private readonly _recordsCache;
|
|
1428
|
+
private readonly _authCache;
|
|
1429
|
+
private readonly _analyticsCache;
|
|
1430
|
+
constructor(config: HydrousConfig);
|
|
1431
|
+
/**
|
|
1432
|
+
* Get a typed records client for the given bucket.
|
|
1433
|
+
*
|
|
1434
|
+
* The generic type parameter `T` describes the shape of records in this
|
|
1435
|
+
* bucket. Leave it unset for a generic `Record<string, unknown>` shape.
|
|
1436
|
+
*
|
|
1437
|
+
* @param bucketKey The name of your bucket (must match what you created in the dashboard).
|
|
1438
|
+
*
|
|
1439
|
+
* @example
|
|
1440
|
+
* ```ts
|
|
1441
|
+
* interface Post { title: string; body: string; published: boolean }
|
|
1442
|
+
* const posts = db.records<Post>('blog-posts');
|
|
1443
|
+
*
|
|
1444
|
+
* const post = await posts.create({ title: 'Hello', body: '...', published: false });
|
|
1445
|
+
* // post.id, post.createdAt, post.updatedAt are added automatically
|
|
1446
|
+
* ```
|
|
1447
|
+
*/
|
|
1448
|
+
records<T extends RecordData = RecordData>(bucketKey: string): RecordsClient<T>;
|
|
1449
|
+
/**
|
|
1450
|
+
* Get an auth client for the given user bucket.
|
|
1451
|
+
*
|
|
1452
|
+
* @param bucketKey The name of your user bucket (e.g. `"app-users"`).
|
|
1453
|
+
*
|
|
1454
|
+
* @example
|
|
1455
|
+
* ```ts
|
|
1456
|
+
* const auth = db.auth('app-users');
|
|
1457
|
+
* const { user, session } = await auth.login({ email: '...', password: '...' });
|
|
1458
|
+
* ```
|
|
1459
|
+
*/
|
|
1460
|
+
auth(bucketKey: string): AuthClient;
|
|
1461
|
+
/**
|
|
1462
|
+
* Get an analytics client for the given bucket.
|
|
1463
|
+
*
|
|
1464
|
+
* @param bucketKey The name of the bucket to analyse.
|
|
1465
|
+
*
|
|
1466
|
+
* @example
|
|
1467
|
+
* ```ts
|
|
1468
|
+
* const analytics = db.analytics('orders');
|
|
1469
|
+
* const { count } = await analytics.count();
|
|
1470
|
+
* ```
|
|
1471
|
+
*/
|
|
1472
|
+
analytics(bucketKey: string): AnalyticsClient;
|
|
1473
|
+
/**
|
|
1474
|
+
* The storage manager for uploading, downloading, listing, and managing files.
|
|
1475
|
+
*
|
|
1476
|
+
* Scoped to your project — you can never access another project's files.
|
|
1477
|
+
*
|
|
1478
|
+
* @example
|
|
1479
|
+
* ```ts
|
|
1480
|
+
* // Upload a file
|
|
1481
|
+
* const result = await db.storage.upload(file, 'images/photo.jpg', { isPublic: true });
|
|
1482
|
+
*
|
|
1483
|
+
* // Scope to a folder
|
|
1484
|
+
* const avatars = db.storage.scope('user-avatars');
|
|
1485
|
+
* await avatars.upload(blob, `${userId}.jpg`, { isPublic: true });
|
|
1486
|
+
* ```
|
|
1487
|
+
*/
|
|
1488
|
+
get storage(): StorageManager & {
|
|
1489
|
+
scope: (prefix: string) => ScopedStorage;
|
|
1490
|
+
};
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Create a new HydrousDB client.
|
|
1494
|
+
*
|
|
1495
|
+
* This is the **only** export you need to get started.
|
|
1496
|
+
* The API URL is pre-configured — you only need your Security Key.
|
|
1497
|
+
*
|
|
1498
|
+
* @param config.securityKey Your project's Security Key from https://hydrousdb.com/dashboard
|
|
1499
|
+
* @param config.baseUrl (Optional) Override the API base URL.
|
|
1500
|
+
*
|
|
1501
|
+
* @example
|
|
1502
|
+
* ```ts
|
|
1503
|
+
* import { createClient } from 'hydrousdb';
|
|
1504
|
+
*
|
|
1505
|
+
* const db = createClient({
|
|
1506
|
+
* securityKey: process.env.HYDROUS_SECURITY_KEY!,
|
|
1507
|
+
* });
|
|
1508
|
+
* ```
|
|
1509
|
+
*/
|
|
1510
|
+
declare function createClient(config: HydrousConfig): HydrousClient;
|
|
1511
|
+
|
|
1512
|
+
declare class HydrousError extends Error {
|
|
1513
|
+
readonly code: string;
|
|
1514
|
+
readonly status?: number;
|
|
1515
|
+
readonly requestId?: string;
|
|
1516
|
+
readonly details?: string[];
|
|
1517
|
+
constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
|
|
1518
|
+
toString(): string;
|
|
1519
|
+
}
|
|
1520
|
+
declare class AuthError extends HydrousError {
|
|
1521
|
+
constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
|
|
1522
|
+
}
|
|
1523
|
+
declare class RecordError extends HydrousError {
|
|
1524
|
+
constructor(message: string, code: string, status?: number, requestId?: string, details?: string[]);
|
|
1525
|
+
}
|
|
1526
|
+
declare class StorageError extends HydrousError {
|
|
1527
|
+
constructor(message: string, code: string, status?: number, requestId?: string);
|
|
1528
|
+
}
|
|
1529
|
+
declare class AnalyticsError extends HydrousError {
|
|
1530
|
+
constructor(message: string, code: string, status?: number, requestId?: string);
|
|
1531
|
+
}
|
|
1532
|
+
declare class ValidationError extends HydrousError {
|
|
1533
|
+
constructor(message: string, details?: string[]);
|
|
1534
|
+
}
|
|
1535
|
+
declare class NetworkError extends HydrousError {
|
|
1536
|
+
readonly cause?: unknown;
|
|
1537
|
+
constructor(message: string, cause?: unknown);
|
|
1538
|
+
}
|
|
553
1539
|
|
|
554
|
-
export { AnalyticsClient, type
|
|
1540
|
+
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 };
|