radosgw-admin 0.1.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/dist/index.cjs ADDED
@@ -0,0 +1,1903 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BaseClient: () => BaseClient,
24
+ RGWAuthError: () => RGWAuthError,
25
+ RGWConflictError: () => RGWConflictError,
26
+ RGWError: () => RGWError,
27
+ RGWNotFoundError: () => RGWNotFoundError,
28
+ RGWValidationError: () => RGWValidationError,
29
+ RadosGWAdminClient: () => RadosGWAdminClient,
30
+ signRequest: () => signRequest
31
+ });
32
+ module.exports = __toCommonJS(index_exports);
33
+
34
+ // src/signer.ts
35
+ var import_node_crypto = require("crypto");
36
+ var ALGORITHM = "AWS4-HMAC-SHA256";
37
+ var SERVICE = "s3";
38
+ var UNSIGNED_PAYLOAD = "UNSIGNED-PAYLOAD";
39
+ function sha256(data) {
40
+ return (0, import_node_crypto.createHash)("sha256").update(data).digest("hex");
41
+ }
42
+ function hmacSha256(key, data) {
43
+ return (0, import_node_crypto.createHmac)("sha256", key).update(data).digest();
44
+ }
45
+ function hmacSha256Hex(key, data) {
46
+ return (0, import_node_crypto.createHmac)("sha256", key).update(data).digest("hex");
47
+ }
48
+ function getDateStamp(date) {
49
+ return date.toISOString().replace(/[-:]/g, "").slice(0, 8);
50
+ }
51
+ function getAmzDate(date) {
52
+ return date.toISOString().replace(/[-:]/g, "").replace(/\.\d{3}/, "");
53
+ }
54
+ function getSigningKey(secretKey, dateStamp, region) {
55
+ const kDate = hmacSha256(`AWS4${secretKey}`, dateStamp);
56
+ const kRegion = hmacSha256(kDate, region);
57
+ const kService = hmacSha256(kRegion, SERVICE);
58
+ return hmacSha256(kService, "aws4_request");
59
+ }
60
+ function getCanonicalQueryString(url) {
61
+ const params = Array.from(url.searchParams.entries());
62
+ params.sort((a, b) => {
63
+ if (a[0] === b[0]) return a[1].localeCompare(b[1]);
64
+ return a[0].localeCompare(b[0]);
65
+ });
66
+ return params.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`).join("&");
67
+ }
68
+ function signRequest(request, date) {
69
+ const now = date ?? /* @__PURE__ */ new Date();
70
+ const dateStamp = getDateStamp(now);
71
+ const amzDate = getAmzDate(now);
72
+ const { method, url, accessKey, secretKey, region } = request;
73
+ const headers = {};
74
+ for (const [k, v] of Object.entries(request.headers)) {
75
+ headers[k.toLowerCase()] = v;
76
+ }
77
+ headers["host"] = url.host;
78
+ headers["x-amz-date"] = amzDate;
79
+ headers["x-amz-content-sha256"] = UNSIGNED_PAYLOAD;
80
+ const sortedHeaderKeys = Object.keys(headers).sort((a, b) => a.localeCompare(b));
81
+ const canonicalHeaders = sortedHeaderKeys.map((k) => `${k}:${headers[k].trim()}`).join("\n");
82
+ const signedHeaders = sortedHeaderKeys.join(";");
83
+ const canonicalRequest = [
84
+ method,
85
+ url.pathname,
86
+ getCanonicalQueryString(url),
87
+ canonicalHeaders + "\n",
88
+ signedHeaders,
89
+ UNSIGNED_PAYLOAD
90
+ ].join("\n");
91
+ const credentialScope = `${dateStamp}/${region}/${SERVICE}/aws4_request`;
92
+ const stringToSign = [ALGORITHM, amzDate, credentialScope, sha256(canonicalRequest)].join("\n");
93
+ const signingKey = getSigningKey(secretKey, dateStamp, region);
94
+ const signature = hmacSha256Hex(signingKey, stringToSign);
95
+ const authorization = `${ALGORITHM} Credential=${accessKey}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
96
+ return {
97
+ "x-amz-date": amzDate,
98
+ "x-amz-content-sha256": UNSIGNED_PAYLOAD,
99
+ Authorization: authorization
100
+ };
101
+ }
102
+
103
+ // src/errors.ts
104
+ var RGWError = class extends Error {
105
+ constructor(message, statusCode, code, uid) {
106
+ super(message);
107
+ this.statusCode = statusCode;
108
+ this.code = code;
109
+ this.uid = uid;
110
+ this.name = "RGWError";
111
+ }
112
+ };
113
+ var RGWNotFoundError = class extends RGWError {
114
+ constructor(resource, id) {
115
+ super(`${resource} not found: "${id}"`, 404, "NoSuchResource");
116
+ this.name = "RGWNotFoundError";
117
+ }
118
+ };
119
+ var RGWValidationError = class extends RGWError {
120
+ constructor(message) {
121
+ super(message, void 0, "ValidationError");
122
+ this.name = "RGWValidationError";
123
+ }
124
+ };
125
+ var RGWAuthError = class extends RGWError {
126
+ constructor(message) {
127
+ super(message, 403, "AccessDenied");
128
+ this.name = "RGWAuthError";
129
+ }
130
+ };
131
+ var RGWConflictError = class extends RGWError {
132
+ constructor(resource, id) {
133
+ super(`${resource} already exists: "${id}"`, 409, "AlreadyExists");
134
+ this.name = "RGWConflictError";
135
+ }
136
+ };
137
+
138
+ // src/client.ts
139
+ var DEFAULT_ADMIN_PATH = "/admin";
140
+ var DEFAULT_TIMEOUT = 1e4;
141
+ var DEFAULT_REGION = "us-east-1";
142
+ function validateConfig(config) {
143
+ if (!config.host || typeof config.host !== "string") {
144
+ throw new RGWValidationError("host is required and must be a non-empty string");
145
+ }
146
+ if (!config.accessKey || typeof config.accessKey !== "string") {
147
+ throw new RGWValidationError("accessKey is required and must be a non-empty string");
148
+ }
149
+ if (!config.secretKey || typeof config.secretKey !== "string") {
150
+ throw new RGWValidationError("secretKey is required and must be a non-empty string");
151
+ }
152
+ if (config.port !== void 0 && (config.port < 1 || config.port > 65535 || !Number.isInteger(config.port))) {
153
+ throw new RGWValidationError("port must be an integer between 1 and 65535");
154
+ }
155
+ if (config.timeout !== void 0 && (config.timeout < 0 || !Number.isFinite(config.timeout))) {
156
+ throw new RGWValidationError("timeout must be a non-negative finite number");
157
+ }
158
+ if (config.maxRetries !== void 0 && (config.maxRetries < 0 || !Number.isInteger(config.maxRetries))) {
159
+ throw new RGWValidationError("maxRetries must be a non-negative integer");
160
+ }
161
+ if (config.retryDelay !== void 0 && (config.retryDelay < 0 || !Number.isFinite(config.retryDelay))) {
162
+ throw new RGWValidationError("retryDelay must be a non-negative finite number");
163
+ }
164
+ }
165
+ function toCamelCase(obj) {
166
+ if (Array.isArray(obj)) return obj.map(toCamelCase);
167
+ if (obj !== null && typeof obj === "object") {
168
+ return Object.fromEntries(
169
+ Object.entries(obj).map(([k, v]) => [
170
+ k.replaceAll(/[-_.]([a-z])/g, (_, c) => c.toUpperCase()),
171
+ toCamelCase(v)
172
+ ])
173
+ );
174
+ }
175
+ return obj;
176
+ }
177
+ function toKebabCase(key) {
178
+ return key.replaceAll(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
179
+ }
180
+ function mapHttpError(status, body, code) {
181
+ switch (status) {
182
+ case 404:
183
+ return new RGWNotFoundError("Resource", code ?? "unknown");
184
+ case 403:
185
+ return new RGWAuthError(
186
+ body || "Access denied. Check your admin credentials and user capabilities."
187
+ );
188
+ case 409:
189
+ return new RGWConflictError("Resource", code ?? "unknown");
190
+ case 400:
191
+ return new RGWValidationError(body || "Invalid request parameters");
192
+ default:
193
+ return new RGWError(body || `HTTP ${status}`, status, code);
194
+ }
195
+ }
196
+ var BaseClient = class {
197
+ host;
198
+ port;
199
+ accessKey;
200
+ secretKey;
201
+ adminPath;
202
+ timeout;
203
+ region;
204
+ debug;
205
+ maxRetries;
206
+ retryDelay;
207
+ insecure;
208
+ constructor(config) {
209
+ validateConfig(config);
210
+ let host = config.host;
211
+ while (host.endsWith("/")) {
212
+ host = host.slice(0, -1);
213
+ }
214
+ this.host = host;
215
+ this.port = config.port;
216
+ this.accessKey = config.accessKey;
217
+ this.secretKey = config.secretKey;
218
+ this.adminPath = config.adminPath ?? DEFAULT_ADMIN_PATH;
219
+ this.timeout = config.timeout ?? DEFAULT_TIMEOUT;
220
+ this.region = config.region ?? DEFAULT_REGION;
221
+ this.debug = config.debug ?? false;
222
+ this.maxRetries = config.maxRetries ?? 0;
223
+ this.retryDelay = config.retryDelay ?? 200;
224
+ this.insecure = config.insecure ?? false;
225
+ if (this.insecure) {
226
+ this.log("WARNING: TLS certificate verification is disabled");
227
+ }
228
+ }
229
+ /**
230
+ * Logs a debug message with optional structured data.
231
+ */
232
+ log(message, data) {
233
+ if (!this.debug) return;
234
+ const prefix = "[radosgw-admin]";
235
+ if (data) {
236
+ console.debug(prefix, message, JSON.stringify(data, null, 2));
237
+ } else {
238
+ console.debug(prefix, message);
239
+ }
240
+ }
241
+ /**
242
+ * Determines whether an error is retryable (5xx, timeouts, network errors).
243
+ */
244
+ isRetryable(error) {
245
+ if (error instanceof RGWError) {
246
+ if (error.statusCode !== void 0) {
247
+ return error.statusCode >= 500;
248
+ }
249
+ if (error.code === "NetworkError" || error.code === "Timeout") {
250
+ return true;
251
+ }
252
+ }
253
+ if (error instanceof Error) {
254
+ return error.name === "AbortError" || this.hasNetworkErrorPattern(error);
255
+ }
256
+ return false;
257
+ }
258
+ /**
259
+ * Checks an error and its cause chain for network error patterns.
260
+ */
261
+ hasNetworkErrorPattern(error) {
262
+ const patterns = [
263
+ "ECONNRESET",
264
+ "ECONNREFUSED",
265
+ "ETIMEDOUT",
266
+ "ENOTFOUND",
267
+ "EHOSTUNREACH",
268
+ "ENETUNREACH",
269
+ "ECONNABORTED",
270
+ "fetch failed"
271
+ ];
272
+ let current = error;
273
+ while (current) {
274
+ if (patterns.some((p) => current.message.includes(p))) {
275
+ return true;
276
+ }
277
+ current = current.cause instanceof Error ? current.cause : void 0;
278
+ }
279
+ return false;
280
+ }
281
+ /**
282
+ * Returns a promise that resolves after the given number of milliseconds.
283
+ */
284
+ async delay(ms) {
285
+ return new Promise((resolve) => setTimeout(resolve, ms));
286
+ }
287
+ /**
288
+ * Makes a signed HTTP request to the RGW Admin API with retry support.
289
+ *
290
+ * @param options - Request method, path, query params, and optional body
291
+ * @returns Parsed and camelCase-transformed JSON response, or void for empty responses
292
+ */
293
+ async request(options) {
294
+ let lastError;
295
+ for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
296
+ if (attempt > 0) {
297
+ const backoff = this.retryDelay * Math.pow(2, attempt - 1);
298
+ this.log(`retry ${attempt}/${this.maxRetries} after ${backoff}ms`);
299
+ await this.delay(backoff);
300
+ }
301
+ try {
302
+ return await this.executeRequest(options);
303
+ } catch (error) {
304
+ lastError = error instanceof Error ? error : new Error(String(error));
305
+ this.log("error", { error: lastError.message });
306
+ if (attempt < this.maxRetries && this.isRetryable(error)) {
307
+ this.log("retryable error", { error: lastError.message, attempt });
308
+ continue;
309
+ }
310
+ throw error;
311
+ }
312
+ }
313
+ throw lastError ?? new RGWError("Request failed after retries", void 0, "RetryExhausted");
314
+ }
315
+ /**
316
+ * Builds the full request URL with query parameters.
317
+ */
318
+ buildUrl(path, query) {
319
+ const baseUrl = this.port ? `${this.host}:${this.port}` : this.host;
320
+ const fullPath = `${this.adminPath}${path}`;
321
+ const url = new URL(fullPath, baseUrl);
322
+ if (query) {
323
+ for (const [key, value] of Object.entries(query)) {
324
+ if (value !== void 0) {
325
+ url.searchParams.set(toKebabCase(key), String(value));
326
+ }
327
+ }
328
+ }
329
+ url.searchParams.set("format", "json");
330
+ return url;
331
+ }
332
+ /**
333
+ * Temporarily disables TLS certificate verification for insecure mode.
334
+ * Returns the previous value so it can be restored.
335
+ */
336
+ disableTlsVerification() {
337
+ const prev = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
338
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
339
+ return prev;
340
+ }
341
+ /**
342
+ * Restores the TLS verification setting to its previous value.
343
+ */
344
+ restoreTlsVerification(prev) {
345
+ if (prev === void 0) {
346
+ delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
347
+ } else {
348
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = prev;
349
+ }
350
+ }
351
+ /**
352
+ * Parses an error response body to extract the RGW error code.
353
+ */
354
+ parseErrorCode(text) {
355
+ try {
356
+ const errorBody = JSON.parse(text);
357
+ return errorBody.Code ?? void 0;
358
+ } catch {
359
+ return void 0;
360
+ }
361
+ }
362
+ /**
363
+ * Wraps a non-RGW error into the appropriate RGW error type.
364
+ */
365
+ wrapFetchError(error) {
366
+ if (error instanceof RGWError) return error;
367
+ if (error instanceof Error && error.name === "AbortError") {
368
+ return new RGWError(`Request timed out after ${this.timeout}ms`, void 0, "Timeout");
369
+ }
370
+ return new RGWError(
371
+ error instanceof Error ? error.message : "Unknown error occurred",
372
+ void 0,
373
+ "NetworkError"
374
+ );
375
+ }
376
+ /**
377
+ * Executes a single signed HTTP request to the RGW Admin API.
378
+ */
379
+ async executeRequest(options) {
380
+ const { method, path, query, body } = options;
381
+ const url = this.buildUrl(path, query);
382
+ const headers = {
383
+ "Content-Type": "application/json"
384
+ };
385
+ const signedHeaders = signRequest({
386
+ method,
387
+ url,
388
+ headers,
389
+ accessKey: this.accessKey,
390
+ secretKey: this.secretKey,
391
+ region: this.region
392
+ });
393
+ const controller = new AbortController();
394
+ const timeoutId = setTimeout(() => controller.abort(), this.timeout);
395
+ const prevTls = this.insecure ? this.disableTlsVerification() : void 0;
396
+ try {
397
+ const fetchOptions = {
398
+ method,
399
+ headers: { ...headers, ...signedHeaders },
400
+ signal: controller.signal
401
+ };
402
+ if (body) {
403
+ fetchOptions.body = JSON.stringify(body);
404
+ }
405
+ const safeUrl = url.toString().replaceAll(/([?&]secret-key=)[^&]*/gi, "$1[REDACTED]");
406
+ this.log("request", { method, url: safeUrl });
407
+ const response = await fetch(url.toString(), fetchOptions);
408
+ const text = await response.text();
409
+ if (!response.ok) {
410
+ throw mapHttpError(response.status, text, this.parseErrorCode(text));
411
+ }
412
+ this.log("response", { status: response.status, body: text.slice(0, 500) });
413
+ if (!text) {
414
+ return void 0;
415
+ }
416
+ const parsed = JSON.parse(text);
417
+ return toCamelCase(parsed);
418
+ } catch (error) {
419
+ throw this.wrapFetchError(error);
420
+ } finally {
421
+ clearTimeout(timeoutId);
422
+ if (this.insecure) {
423
+ this.restoreTlsVerification(prevTls);
424
+ }
425
+ }
426
+ }
427
+ };
428
+
429
+ // src/validators.ts
430
+ function validateUid(uid) {
431
+ if (!uid || typeof uid !== "string" || uid.trim() !== uid || uid.trim().length === 0) {
432
+ throw new RGWValidationError(
433
+ "uid is required and must be a non-empty string without leading/trailing whitespace"
434
+ );
435
+ }
436
+ }
437
+ function validateUidNoColon(uid) {
438
+ validateUid(uid);
439
+ if (uid.includes(":")) {
440
+ throw new RGWValidationError(
441
+ 'uid must not contain colons \u2014 colons are reserved for subuser notation (e.g. "uid:subuser")'
442
+ );
443
+ }
444
+ }
445
+
446
+ // src/modules/users.ts
447
+ function validateEmail(email) {
448
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
449
+ throw new RGWValidationError(
450
+ `Invalid email address: "${email}". Expected format: "user@domain.tld"`
451
+ );
452
+ }
453
+ }
454
+ function validateUserCaps(caps) {
455
+ if (!caps.trim()) {
456
+ throw new RGWValidationError("userCaps must not be empty if provided");
457
+ }
458
+ const capSegment = /^[a-z][a-z-]*=(?:\*|read,\s*write|read|write)$/i;
459
+ const valid = caps.split(";").map((s) => s.trim()).every((seg) => capSegment.test(seg));
460
+ if (!valid) {
461
+ throw new RGWValidationError(
462
+ `Invalid userCaps format: "${caps}". Expected "type=perm" or "type1=perm;type2=perm". Valid perms: *, read, write, "read, write". Example: "users=*;buckets=read"`
463
+ );
464
+ }
465
+ }
466
+ var UsersModule = class {
467
+ constructor(client) {
468
+ this.client = client;
469
+ }
470
+ /**
471
+ * Create a new RGW user.
472
+ *
473
+ * @param input - User creation parameters. `uid` and `displayName` are required.
474
+ * @returns The newly created user object with keys, caps, and quotas.
475
+ * @throws {RGWValidationError} If `uid` contains colons, `displayName` is blank,
476
+ * `email` is malformed, or `userCaps` is an invalid capability string.
477
+ * @throws {RGWConflictError} If a user with the given `uid` already exists.
478
+ *
479
+ * @example
480
+ * ```typescript
481
+ * const user = await client.users.create({
482
+ * uid: 'alice',
483
+ * displayName: 'Alice Example',
484
+ * email: 'alice@example.com',
485
+ * maxBuckets: 100,
486
+ * userCaps: 'users=read;buckets=*',
487
+ * });
488
+ * console.log(user.keys[0].accessKey);
489
+ * ```
490
+ */
491
+ async create(input) {
492
+ validateUidNoColon(input.uid);
493
+ if (!input.displayName || typeof input.displayName !== "string" || input.displayName.trim().length === 0) {
494
+ throw new RGWValidationError("displayName is required and must be a non-empty string");
495
+ }
496
+ if (input.displayName !== input.displayName.trim()) {
497
+ throw new RGWValidationError("displayName must not have leading or trailing whitespace");
498
+ }
499
+ if (input.email !== void 0) {
500
+ validateEmail(input.email);
501
+ }
502
+ if (input.userCaps !== void 0) {
503
+ validateUserCaps(input.userCaps);
504
+ }
505
+ return this.client.request({
506
+ method: "PUT",
507
+ path: "/user",
508
+ query: {
509
+ uid: input.uid,
510
+ displayName: input.displayName,
511
+ email: input.email,
512
+ keyType: input.keyType,
513
+ accessKey: input.accessKey,
514
+ secretKey: input.secretKey,
515
+ userCaps: input.userCaps,
516
+ generateKey: input.generateKey,
517
+ maxBuckets: input.maxBuckets,
518
+ suspended: input.suspended,
519
+ tenant: input.tenant,
520
+ opMask: input.opMask
521
+ }
522
+ });
523
+ }
524
+ /**
525
+ * Get full user information including keys, caps, and quotas.
526
+ *
527
+ * For multi-tenant setups pass the `tenant` parameter instead of embedding
528
+ * it in the uid string. Both `get('alice', 'acme')` and `get('acme$alice')`
529
+ * resolve to the same user; the former is preferred for clarity.
530
+ *
531
+ * @param uid - The user ID (without tenant prefix).
532
+ * @param tenant - Optional tenant name. When provided, resolves to `tenant$uid`.
533
+ * @returns Full user object.
534
+ * @throws {RGWValidationError} If `uid` is empty.
535
+ * @throws {RGWNotFoundError} If the user does not exist.
536
+ *
537
+ * @example
538
+ * ```typescript
539
+ * // Standard lookup
540
+ * const user = await client.users.get('alice');
541
+ *
542
+ * // Multi-tenant lookup
543
+ * const tenantUser = await client.users.get('alice', 'acme');
544
+ * ```
545
+ */
546
+ async get(uid, tenant) {
547
+ validateUid(uid);
548
+ const effectiveUid = tenant ? `${tenant}$${uid}` : uid;
549
+ return this.client.request({
550
+ method: "GET",
551
+ path: "/user",
552
+ query: { uid: effectiveUid }
553
+ });
554
+ }
555
+ /**
556
+ * Look up a user by their S3 access key.
557
+ *
558
+ * Useful for mapping an incoming S3 request's access key to the owning user
559
+ * without knowing the uid in advance.
560
+ *
561
+ * @param accessKey - The S3 access key to look up.
562
+ * @returns The user that owns this access key.
563
+ * @throws {RGWValidationError} If `accessKey` is empty.
564
+ * @throws {RGWNotFoundError} If no user owns this access key.
565
+ *
566
+ * @example
567
+ * ```typescript
568
+ * const user = await client.users.getByAccessKey('AKIAIOSFODNN7EXAMPLE');
569
+ * console.log('Key belongs to:', user.userId);
570
+ * ```
571
+ */
572
+ async getByAccessKey(accessKey) {
573
+ if (!accessKey || typeof accessKey !== "string" || accessKey.trim().length === 0) {
574
+ throw new RGWValidationError("accessKey is required and must be a non-empty string");
575
+ }
576
+ return this.client.request({
577
+ method: "GET",
578
+ path: "/user",
579
+ query: { accessKey }
580
+ });
581
+ }
582
+ /**
583
+ * Modify an existing user's properties.
584
+ *
585
+ * Only the provided fields are updated — omitted fields retain their current values.
586
+ *
587
+ * @param input - Properties to update. `uid` is required; all other fields are optional.
588
+ * @returns The updated user object.
589
+ * @throws {RGWValidationError} If `uid` is empty, `email` is malformed,
590
+ * or `userCaps` is an invalid capability string.
591
+ * @throws {RGWNotFoundError} If the user does not exist.
592
+ *
593
+ * @example
594
+ * ```typescript
595
+ * const updated = await client.users.modify({
596
+ * uid: 'alice',
597
+ * displayName: 'Alice Updated',
598
+ * maxBuckets: 200,
599
+ * opMask: 'read, write',
600
+ * });
601
+ * ```
602
+ */
603
+ async modify(input) {
604
+ validateUid(input.uid);
605
+ if (input.email !== void 0) {
606
+ validateEmail(input.email);
607
+ }
608
+ if (input.userCaps !== void 0) {
609
+ validateUserCaps(input.userCaps);
610
+ }
611
+ return this.client.request({
612
+ method: "POST",
613
+ path: "/user",
614
+ query: {
615
+ uid: input.uid,
616
+ displayName: input.displayName,
617
+ email: input.email,
618
+ maxBuckets: input.maxBuckets,
619
+ suspended: input.suspended,
620
+ userCaps: input.userCaps,
621
+ opMask: input.opMask
622
+ }
623
+ });
624
+ }
625
+ /**
626
+ * Delete a user. Optionally purge all user data (buckets and objects).
627
+ *
628
+ * @param input - `uid` is required. Set `purgeData: true` to delete all objects.
629
+ * @throws {RGWValidationError} If `uid` is empty.
630
+ * @throws {RGWNotFoundError} If the user does not exist.
631
+ *
632
+ * @example
633
+ * ```typescript
634
+ * // Safe delete — fails if user owns buckets
635
+ * await client.users.delete({ uid: 'alice' });
636
+ *
637
+ * // ⚠️ Force delete with full data purge
638
+ * await client.users.delete({ uid: 'alice', purgeData: true });
639
+ * ```
640
+ */
641
+ async delete(input) {
642
+ validateUid(input.uid);
643
+ if (input.purgeData) {
644
+ console.warn(
645
+ `[radosgw-admin] WARNING: purgeData=true will permanently delete all objects for user "${input.uid}"`
646
+ );
647
+ }
648
+ return this.client.request({
649
+ method: "DELETE",
650
+ path: "/user",
651
+ query: {
652
+ uid: input.uid,
653
+ purgeData: input.purgeData
654
+ }
655
+ });
656
+ }
657
+ /**
658
+ * List all user IDs in the cluster (or tenant).
659
+ *
660
+ * Returns the full list in a single call. The RGW `/metadata/user` endpoint
661
+ * supports `marker` and `max-entries` for server-side pagination, but has a
662
+ * default limit of 1000 entries. This method requests up to 100,000 entries
663
+ * to avoid silent truncation on large clusters.
664
+ *
665
+ * For clusters with more than 100k users, use the planned `paginate()` method
666
+ * (see v1.6 roadmap).
667
+ *
668
+ * @returns Array of user ID strings.
669
+ *
670
+ * @example
671
+ * ```typescript
672
+ * const uids = await client.users.list();
673
+ * console.log('Total users:', uids.length);
674
+ * ```
675
+ */
676
+ async list() {
677
+ const result = await this.client.request({
678
+ method: "GET",
679
+ path: "/metadata/user",
680
+ query: { maxEntries: 1e5 }
681
+ });
682
+ if (Array.isArray(result)) {
683
+ return result;
684
+ }
685
+ return result.keys;
686
+ }
687
+ /**
688
+ * Suspend a user account. The user's data is preserved but all API access is denied.
689
+ *
690
+ * @param uid - The user ID to suspend.
691
+ * @returns The updated user object with `suspended: 1`.
692
+ * @throws {RGWValidationError} If `uid` is empty.
693
+ * @throws {RGWNotFoundError} If the user does not exist.
694
+ *
695
+ * @example
696
+ * ```typescript
697
+ * const suspended = await client.users.suspend('alice');
698
+ * console.log(suspended.suspended); // 1
699
+ * ```
700
+ */
701
+ async suspend(uid) {
702
+ validateUid(uid);
703
+ return this.client.request({
704
+ method: "POST",
705
+ path: "/user",
706
+ query: { uid, suspended: true }
707
+ });
708
+ }
709
+ /**
710
+ * Re-enable a suspended user account.
711
+ *
712
+ * @param uid - The user ID to enable.
713
+ * @returns The updated user object with `suspended: 0`.
714
+ * @throws {RGWValidationError} If `uid` is empty.
715
+ * @throws {RGWNotFoundError} If the user does not exist.
716
+ *
717
+ * @example
718
+ * ```typescript
719
+ * const enabled = await client.users.enable('alice');
720
+ * console.log(enabled.suspended); // 0
721
+ * ```
722
+ */
723
+ async enable(uid) {
724
+ validateUid(uid);
725
+ return this.client.request({
726
+ method: "POST",
727
+ path: "/user",
728
+ query: { uid, suspended: false }
729
+ });
730
+ }
731
+ /**
732
+ * Get storage usage statistics for a user.
733
+ *
734
+ * Returns the full user object with an additional `stats` field containing:
735
+ * - `size` / `sizeKb` — logical bytes/KB used
736
+ * - `sizeActual` / `sizeKbActual` — bytes/KB on disk (accounting for alignment)
737
+ * - `sizeUtilized` / `sizeKbUtilized` — bytes/KB utilized (used + overhead)
738
+ * - `numObjects` — total number of objects stored
739
+ *
740
+ * Pass `sync: true` to force RGW to recalculate stats from the backing store
741
+ * before returning (slower but accurate).
742
+ *
743
+ * @param uid - The user ID.
744
+ * @param sync - If true, forces a stats sync before returning. Default: false.
745
+ * @returns User object with embedded {@link RGWUserStatData} `stats` field.
746
+ * @throws {RGWValidationError} If `uid` is empty.
747
+ * @throws {RGWNotFoundError} If the user does not exist.
748
+ *
749
+ * @example
750
+ * ```typescript
751
+ * // Fast read (may be slightly stale)
752
+ * const result = await client.users.getStats('alice');
753
+ *
754
+ * // Force sync — accurate but slower
755
+ * const synced = await client.users.getStats('alice', true);
756
+ *
757
+ * console.log('Objects:', result.stats.numObjects);
758
+ * console.log('Size (KB):', result.stats.sizeKb);
759
+ * console.log('Actual disk (KB):', result.stats.sizeKbActual);
760
+ * ```
761
+ */
762
+ async getStats(uid, sync) {
763
+ validateUid(uid);
764
+ return this.client.request({
765
+ method: "GET",
766
+ path: "/user",
767
+ query: {
768
+ uid,
769
+ stats: true,
770
+ sync
771
+ }
772
+ });
773
+ }
774
+ };
775
+
776
+ // src/modules/keys.ts
777
+ var KeysModule = class {
778
+ constructor(client) {
779
+ this.client = client;
780
+ }
781
+ /**
782
+ * Generate a new S3 or Swift key for a user.
783
+ *
784
+ * Returns the user's **entire** key list after the operation, not just the
785
+ * newly created key. To identify the new key, compare with the key list
786
+ * before generation or look for the last entry.
787
+ *
788
+ * @param input - Key generation parameters. `uid` is required.
789
+ * @returns Array of **all** keys belonging to the user after generation.
790
+ * @throws {RGWValidationError} If `uid` is missing or invalid.
791
+ * @throws {RGWNotFoundError} If the user does not exist.
792
+ *
793
+ * @example
794
+ * ```typescript
795
+ * // Auto-generate a new S3 key
796
+ * const allKeys = await client.keys.generate({ uid: 'alice' });
797
+ * const newKey = allKeys[allKeys.length - 1]; // newest key is last
798
+ * console.log('New key:', newKey.accessKey);
799
+ *
800
+ * // Supply specific credentials (disable auto-generation)
801
+ * const allKeys = await client.keys.generate({
802
+ * uid: 'alice',
803
+ * accessKey: 'MY_ACCESS_KEY',
804
+ * secretKey: 'MY_SECRET_KEY',
805
+ * generateKey: false,
806
+ * });
807
+ * ```
808
+ */
809
+ async generate(input) {
810
+ validateUid(input.uid);
811
+ return this.client.request({
812
+ method: "PUT",
813
+ path: "/user",
814
+ query: {
815
+ key: "",
816
+ uid: input.uid,
817
+ keyType: input.keyType,
818
+ accessKey: input.accessKey,
819
+ secretKey: input.secretKey,
820
+ generateKey: input.generateKey
821
+ }
822
+ });
823
+ }
824
+ /**
825
+ * Revoke an S3 or Swift key.
826
+ *
827
+ * @param input - `accessKey` is required. `uid` is required for Swift keys.
828
+ * @returns Resolves when the key has been revoked.
829
+ * @throws {RGWValidationError} If `accessKey` is missing.
830
+ * @throws {RGWNotFoundError} If the key does not exist.
831
+ *
832
+ * @example
833
+ * ```typescript
834
+ * // Revoke an S3 key by access key ID
835
+ * await client.keys.revoke({ accessKey: 'OLDKEY123' });
836
+ *
837
+ * // Revoke a Swift key (uid required)
838
+ * await client.keys.revoke({
839
+ * accessKey: 'SWIFTKEY',
840
+ * uid: 'alice',
841
+ * keyType: 'swift',
842
+ * });
843
+ * ```
844
+ */
845
+ async revoke(input) {
846
+ if (!input.accessKey || typeof input.accessKey !== "string" || input.accessKey.trim().length === 0) {
847
+ throw new RGWValidationError("accessKey is required and must be a non-empty string");
848
+ }
849
+ if (input.keyType === "swift" && !input.uid) {
850
+ throw new RGWValidationError('uid is required when revoking a Swift key (keyType: "swift")');
851
+ }
852
+ return this.client.request({
853
+ method: "DELETE",
854
+ path: "/user",
855
+ query: {
856
+ key: "",
857
+ accessKey: input.accessKey,
858
+ uid: input.uid,
859
+ keyType: input.keyType
860
+ }
861
+ });
862
+ }
863
+ };
864
+
865
+ // src/modules/subusers.ts
866
+ function validateSubuser(subuser) {
867
+ if (!subuser || typeof subuser !== "string" || subuser.trim().length === 0) {
868
+ throw new RGWValidationError("subuser is required and must be a non-empty string");
869
+ }
870
+ if (!subuser.includes(":")) {
871
+ throw new RGWValidationError('subuser must be in "uid:name" format (e.g. "alice:swift")');
872
+ }
873
+ }
874
+ var SubusersModule = class {
875
+ constructor(client) {
876
+ this.client = client;
877
+ }
878
+ /**
879
+ * Create a new subuser for a user.
880
+ *
881
+ * Returns the user's **entire** subuser list after the operation, not just the
882
+ * newly created subuser. To identify the new entry, compare with the list
883
+ * before creation or look for the matching `id`.
884
+ *
885
+ * @param input - Subuser creation parameters. `uid` and `subuser` are required.
886
+ * `subuser` must be in `uid:name` format (e.g. `"alice:swift"`).
887
+ * @returns Array of **all** subusers belonging to the user after creation.
888
+ * @throws {RGWValidationError} If `uid` or `subuser` is missing, invalid, or not in `uid:name` format.
889
+ * @throws {RGWNotFoundError} If the parent user does not exist.
890
+ *
891
+ * @example
892
+ * ```typescript
893
+ * const subusers = await client.subusers.create({
894
+ * uid: 'alice',
895
+ * subuser: 'alice:swift',
896
+ * access: 'readwrite',
897
+ * keyType: 'swift',
898
+ * generateSecret: true,
899
+ * });
900
+ * console.log(subusers[0].id, subusers[0].permissions);
901
+ * ```
902
+ */
903
+ async create(input) {
904
+ validateUid(input.uid);
905
+ validateSubuser(input.subuser);
906
+ return this.client.request({
907
+ method: "PUT",
908
+ path: "/user",
909
+ query: {
910
+ subuser: input.subuser,
911
+ uid: input.uid,
912
+ secretKey: input.secretKey,
913
+ keyType: input.keyType,
914
+ access: input.access,
915
+ generateSecret: input.generateSecret
916
+ }
917
+ });
918
+ }
919
+ /**
920
+ * Modify an existing subuser's properties.
921
+ *
922
+ * @param input - Properties to update. `uid` and `subuser` are required.
923
+ * @returns Array of subusers belonging to the user after modification.
924
+ * @throws {RGWValidationError} If `uid` or `subuser` is missing or invalid.
925
+ * @throws {RGWNotFoundError} If the parent user or subuser does not exist.
926
+ *
927
+ * @example
928
+ * ```typescript
929
+ * const subusers = await client.subusers.modify({
930
+ * uid: 'alice',
931
+ * subuser: 'alice:swift',
932
+ * access: 'full',
933
+ * });
934
+ * ```
935
+ */
936
+ async modify(input) {
937
+ validateUid(input.uid);
938
+ validateSubuser(input.subuser);
939
+ return this.client.request({
940
+ method: "POST",
941
+ path: "/user",
942
+ query: {
943
+ subuser: input.subuser,
944
+ uid: input.uid,
945
+ secretKey: input.secretKey,
946
+ keyType: input.keyType,
947
+ access: input.access,
948
+ generateSecret: input.generateSecret
949
+ }
950
+ });
951
+ }
952
+ /**
953
+ * Remove a subuser from a user. Optionally purge the subuser's keys.
954
+ *
955
+ * @param input - `uid` and `subuser` are required. `purgeKeys` defaults to true.
956
+ * @returns Resolves when the subuser has been removed.
957
+ * @throws {RGWValidationError} If `uid` or `subuser` is missing or invalid.
958
+ * @throws {RGWNotFoundError} If the parent user or subuser does not exist.
959
+ *
960
+ * @example
961
+ * ```typescript
962
+ * // Remove subuser and purge keys
963
+ * await client.subusers.remove({
964
+ * uid: 'alice',
965
+ * subuser: 'alice:swift',
966
+ * });
967
+ *
968
+ * // Remove subuser but keep keys
969
+ * await client.subusers.remove({
970
+ * uid: 'alice',
971
+ * subuser: 'alice:swift',
972
+ * purgeKeys: false,
973
+ * });
974
+ * ```
975
+ */
976
+ async remove(input) {
977
+ validateUid(input.uid);
978
+ validateSubuser(input.subuser);
979
+ if (input.purgeKeys === true) {
980
+ console.warn(
981
+ `[radosgw-admin] WARNING: purgeKeys=true will permanently delete all keys for subuser "${input.subuser}"`
982
+ );
983
+ }
984
+ return this.client.request({
985
+ method: "DELETE",
986
+ path: "/user",
987
+ query: {
988
+ subuser: input.subuser,
989
+ uid: input.uid,
990
+ purgeKeys: input.purgeKeys
991
+ }
992
+ });
993
+ }
994
+ };
995
+
996
+ // src/modules/buckets.ts
997
+ function validateBucket(bucket) {
998
+ if (!bucket || typeof bucket !== "string" || bucket.trim().length === 0) {
999
+ throw new RGWValidationError("bucket is required and must be a non-empty string");
1000
+ }
1001
+ }
1002
+ var BucketsModule = class {
1003
+ constructor(client) {
1004
+ this.client = client;
1005
+ }
1006
+ /**
1007
+ * List all buckets in the cluster.
1008
+ *
1009
+ * The RGW `/bucket` endpoint has a default limit of 1000 entries.
1010
+ * This method requests up to 100,000 entries to avoid silent truncation
1011
+ * on large clusters.
1012
+ *
1013
+ * For clusters with more than 100k buckets, use the planned `paginate()`
1014
+ * method (see v1.6 roadmap).
1015
+ *
1016
+ * @returns Array of bucket name strings.
1017
+ *
1018
+ * @example
1019
+ * ```typescript
1020
+ * const all = await client.buckets.list();
1021
+ * console.log(`Cluster has ${all.length} buckets`);
1022
+ * ```
1023
+ */
1024
+ async list() {
1025
+ const result = await this.client.request({
1026
+ method: "GET",
1027
+ path: "/bucket",
1028
+ query: { maxEntries: 1e5 }
1029
+ });
1030
+ if (Array.isArray(result)) {
1031
+ return result;
1032
+ }
1033
+ return result.buckets;
1034
+ }
1035
+ /**
1036
+ * List buckets owned by a specific user.
1037
+ *
1038
+ * @param uid - User ID to filter buckets by. Required.
1039
+ * @returns Array of bucket name strings owned by the user.
1040
+ * @throws {RGWValidationError} If `uid` is missing or invalid.
1041
+ *
1042
+ * @example
1043
+ * ```typescript
1044
+ * const userBuckets = await client.buckets.listByUser('alice');
1045
+ * console.log(`alice has ${userBuckets.length} buckets`);
1046
+ * ```
1047
+ */
1048
+ async listByUser(uid) {
1049
+ validateUid(uid);
1050
+ return this.client.request({
1051
+ method: "GET",
1052
+ path: "/bucket",
1053
+ query: { uid }
1054
+ });
1055
+ }
1056
+ /**
1057
+ * Get detailed metadata about a bucket.
1058
+ *
1059
+ * @param bucket - The bucket name to inspect.
1060
+ * @returns Full bucket object with owner, usage, quota, and placement info.
1061
+ * @throws {RGWValidationError} If `bucket` is empty.
1062
+ * @throws {RGWNotFoundError} If the bucket does not exist.
1063
+ *
1064
+ * @example
1065
+ * ```typescript
1066
+ * const info = await client.buckets.getInfo('my-bucket');
1067
+ * console.log(`Owner: ${info.owner}`);
1068
+ * console.log(`Objects: ${info.usage.rgwMain.numObjects}`);
1069
+ * console.log(`Size: ${(info.usage.rgwMain.sizeKb / 1024).toFixed(2)} MB`);
1070
+ * ```
1071
+ */
1072
+ async getInfo(bucket) {
1073
+ validateBucket(bucket);
1074
+ return this.client.request({
1075
+ method: "GET",
1076
+ path: "/bucket",
1077
+ query: { bucket }
1078
+ });
1079
+ }
1080
+ /**
1081
+ * Delete a bucket. Optionally purge all objects inside it.
1082
+ *
1083
+ * @param input - `bucket` is required. Set `purgeObjects: true` to delete all objects.
1084
+ * @returns Resolves when the bucket has been deleted.
1085
+ * @throws {RGWValidationError} If `bucket` is empty.
1086
+ * @throws {RGWNotFoundError} If the bucket does not exist.
1087
+ *
1088
+ * @example
1089
+ * ```typescript
1090
+ * // Safe delete (fails if bucket has objects)
1091
+ * await client.buckets.delete({ bucket: 'my-bucket' });
1092
+ *
1093
+ * // Force delete with object purge
1094
+ * await client.buckets.delete({ bucket: 'my-bucket', purgeObjects: true });
1095
+ * ```
1096
+ */
1097
+ async delete(input) {
1098
+ validateBucket(input.bucket);
1099
+ if (input.purgeObjects) {
1100
+ console.warn(
1101
+ `[radosgw-admin] WARNING: purgeObjects=true will permanently delete all objects in bucket "${input.bucket}"`
1102
+ );
1103
+ }
1104
+ return this.client.request({
1105
+ method: "DELETE",
1106
+ path: "/bucket",
1107
+ query: {
1108
+ bucket: input.bucket,
1109
+ purgeObjects: input.purgeObjects
1110
+ }
1111
+ });
1112
+ }
1113
+ /**
1114
+ * Transfer ownership of a bucket to a different user.
1115
+ *
1116
+ * @param input - `bucket`, `bucketId`, and `uid` are all required.
1117
+ * @returns Resolves when ownership has been transferred.
1118
+ * @throws {RGWValidationError} If any required field is missing.
1119
+ * @throws {RGWNotFoundError} If the bucket or user does not exist.
1120
+ *
1121
+ * @example
1122
+ * ```typescript
1123
+ * const info = await client.buckets.getInfo('my-bucket');
1124
+ * await client.buckets.transferOwnership({
1125
+ * bucket: 'my-bucket',
1126
+ * bucketId: info.id,
1127
+ * uid: 'bob',
1128
+ * });
1129
+ * ```
1130
+ */
1131
+ async transferOwnership(input) {
1132
+ validateBucket(input.bucket);
1133
+ validateUid(input.uid);
1134
+ if (!input.bucketId || typeof input.bucketId !== "string" || input.bucketId.trim().length === 0) {
1135
+ throw new RGWValidationError("bucketId is required and must be a non-empty string");
1136
+ }
1137
+ return this.client.request({
1138
+ method: "PUT",
1139
+ path: "/bucket",
1140
+ query: {
1141
+ bucket: input.bucket,
1142
+ bucketId: input.bucketId,
1143
+ uid: input.uid
1144
+ }
1145
+ });
1146
+ }
1147
+ /**
1148
+ * Remove ownership of a bucket from a user without deleting the bucket.
1149
+ *
1150
+ * **Warning:** This leaves the bucket in an orphaned state with no owner.
1151
+ * The bucket and its objects remain intact but cannot be managed via the
1152
+ * S3 API until ownership is reassigned with {@link transferOwnership}.
1153
+ *
1154
+ * @param input - `bucket` and `uid` are required.
1155
+ * @returns Resolves when ownership has been removed.
1156
+ * @throws {RGWValidationError} If any required field is missing.
1157
+ * @throws {RGWNotFoundError} If the bucket or user does not exist.
1158
+ *
1159
+ * @example
1160
+ * ```typescript
1161
+ * await client.buckets.removeOwnership({
1162
+ * bucket: 'my-bucket',
1163
+ * uid: 'alice',
1164
+ * });
1165
+ * ```
1166
+ */
1167
+ async removeOwnership(input) {
1168
+ validateBucket(input.bucket);
1169
+ validateUid(input.uid);
1170
+ return this.client.request({
1171
+ method: "POST",
1172
+ path: "/bucket",
1173
+ query: {
1174
+ bucket: input.bucket,
1175
+ uid: input.uid
1176
+ }
1177
+ });
1178
+ }
1179
+ /**
1180
+ * Verify and optionally repair a bucket's index.
1181
+ *
1182
+ * With `fix: false` (default), this is a safe read-only operation that reports
1183
+ * any inconsistencies. Set `fix: true` to actually repair detected issues.
1184
+ *
1185
+ * @param input - `bucket` is required. `checkObjects` and `fix` are optional.
1186
+ * @returns Index check result with invalid entries and header comparison.
1187
+ * @throws {RGWValidationError} If `bucket` is empty.
1188
+ * @throws {RGWNotFoundError} If the bucket does not exist.
1189
+ *
1190
+ * @example
1191
+ * ```typescript
1192
+ * // Dry run — check only
1193
+ * const result = await client.buckets.verifyIndex({
1194
+ * bucket: 'my-bucket',
1195
+ * checkObjects: true,
1196
+ * fix: false,
1197
+ * });
1198
+ * console.log('Invalid entries:', result.invalidMultipartEntries);
1199
+ *
1200
+ * // Fix detected issues
1201
+ * await client.buckets.verifyIndex({
1202
+ * bucket: 'my-bucket',
1203
+ * fix: true,
1204
+ * });
1205
+ * ```
1206
+ */
1207
+ async verifyIndex(input) {
1208
+ validateBucket(input.bucket);
1209
+ if (input.fix === true) {
1210
+ console.warn(
1211
+ `[radosgw-admin] WARNING: fix=true will repair the bucket index for "${input.bucket}" \u2014 this mutates index data`
1212
+ );
1213
+ }
1214
+ return this.client.request({
1215
+ method: "GET",
1216
+ path: "/bucket",
1217
+ query: {
1218
+ index: "",
1219
+ bucket: input.bucket,
1220
+ checkObjects: input.checkObjects,
1221
+ fix: input.fix
1222
+ }
1223
+ });
1224
+ }
1225
+ };
1226
+
1227
+ // src/modules/quota.ts
1228
+ function validateQuotaValue(field, value) {
1229
+ if (value !== void 0 && typeof value === "number" && value < -1) {
1230
+ throw new RGWValidationError(`${field} must be -1 (unlimited) or >= 0, got ${value}`);
1231
+ }
1232
+ }
1233
+ function parseSizeString(size) {
1234
+ if (typeof size === "number") return size;
1235
+ const units = {
1236
+ K: 1024,
1237
+ M: 1024 ** 2,
1238
+ G: 1024 ** 3,
1239
+ T: 1024 ** 4
1240
+ };
1241
+ const match = size.toUpperCase().trim().match(/^(\d+(?:\.\d+)?)\s*([KMGT]?)B?$/);
1242
+ if (!match) {
1243
+ throw new RGWValidationError(
1244
+ `Invalid size string: "${size}". Use format like "10G", "500M", "1T", or a number in bytes`
1245
+ );
1246
+ }
1247
+ const value = parseFloat(match[1]);
1248
+ const unit = match[2];
1249
+ return Math.floor(value * (units[unit] ?? 1));
1250
+ }
1251
+ var QuotaModule = class {
1252
+ constructor(client) {
1253
+ this.client = client;
1254
+ }
1255
+ /**
1256
+ * Get the user-level quota for a user.
1257
+ *
1258
+ * @param uid - The user ID.
1259
+ * @returns The user's quota configuration.
1260
+ * @throws {RGWValidationError} If `uid` is empty.
1261
+ * @throws {RGWNotFoundError} If the user does not exist.
1262
+ *
1263
+ * @example
1264
+ * ```typescript
1265
+ * const quota = await client.quota.getUserQuota('alice');
1266
+ * console.log('Enabled:', quota.enabled);
1267
+ * console.log('Max size:', quota.maxSize, 'bytes');
1268
+ * console.log('Max objects:', quota.maxObjects);
1269
+ * ```
1270
+ */
1271
+ async getUserQuota(uid) {
1272
+ validateUid(uid);
1273
+ return this.client.request({
1274
+ method: "GET",
1275
+ path: "/user",
1276
+ query: { uid, quota: "", quotaType: "user" }
1277
+ });
1278
+ }
1279
+ /**
1280
+ * Set the user-level quota for a user.
1281
+ *
1282
+ * The `maxSize` field accepts either a number (bytes) or a human-readable string
1283
+ * like `"10G"`, `"500M"`, `"1T"`.
1284
+ *
1285
+ * @param input - Quota settings. `uid` is required.
1286
+ * @throws {RGWValidationError} If `uid` is empty, `maxSize` format is invalid, or `maxSize`/`maxObjects` is a negative number other than -1.
1287
+ * @throws {RGWNotFoundError} If the user does not exist.
1288
+ *
1289
+ * @example
1290
+ * ```typescript
1291
+ * await client.quota.setUserQuota({
1292
+ * uid: 'alice',
1293
+ * maxSize: '10G',
1294
+ * maxObjects: 50000,
1295
+ * enabled: true,
1296
+ * });
1297
+ * ```
1298
+ */
1299
+ async setUserQuota(input) {
1300
+ validateUid(input.uid);
1301
+ validateQuotaValue("maxObjects", input.maxObjects);
1302
+ const maxSize = input.maxSize !== void 0 ? parseSizeString(input.maxSize) : void 0;
1303
+ validateQuotaValue("maxSize", maxSize);
1304
+ return this.client.request({
1305
+ method: "PUT",
1306
+ path: "/user",
1307
+ query: {
1308
+ uid: input.uid,
1309
+ quota: "",
1310
+ quotaType: "user",
1311
+ maxSize,
1312
+ maxObjects: input.maxObjects,
1313
+ enabled: input.enabled ?? true
1314
+ }
1315
+ });
1316
+ }
1317
+ /**
1318
+ * Enable the user-level quota for a user without changing quota values.
1319
+ *
1320
+ * @param uid - The user ID.
1321
+ * @throws {RGWValidationError} If `uid` is empty.
1322
+ * @throws {RGWNotFoundError} If the user does not exist.
1323
+ *
1324
+ * @example
1325
+ * ```typescript
1326
+ * await client.quota.enableUserQuota('alice');
1327
+ * ```
1328
+ */
1329
+ async enableUserQuota(uid) {
1330
+ validateUid(uid);
1331
+ return this.client.request({
1332
+ method: "PUT",
1333
+ path: "/user",
1334
+ query: { uid, quota: "", quotaType: "user", enabled: true }
1335
+ });
1336
+ }
1337
+ /**
1338
+ * Disable the user-level quota for a user without changing quota values.
1339
+ *
1340
+ * @param uid - The user ID.
1341
+ * @throws {RGWValidationError} If `uid` is empty.
1342
+ * @throws {RGWNotFoundError} If the user does not exist.
1343
+ *
1344
+ * @example
1345
+ * ```typescript
1346
+ * await client.quota.disableUserQuota('alice');
1347
+ * ```
1348
+ */
1349
+ async disableUserQuota(uid) {
1350
+ validateUid(uid);
1351
+ return this.client.request({
1352
+ method: "PUT",
1353
+ path: "/user",
1354
+ query: { uid, quota: "", quotaType: "user", enabled: false }
1355
+ });
1356
+ }
1357
+ /**
1358
+ * Get the bucket-level quota for a user.
1359
+ *
1360
+ * This returns the per-bucket quota applied to all buckets owned by the user,
1361
+ * not a quota for a specific bucket. RGW bucket quotas are configured per-user.
1362
+ *
1363
+ * @param uid - The user ID (bucket owner), not a bucket name.
1364
+ * @throws {RGWValidationError} If `uid` is empty.
1365
+ * @throws {RGWNotFoundError} If the user does not exist.
1366
+ *
1367
+ * @example
1368
+ * ```typescript
1369
+ * const quota = await client.quota.getBucketQuota('alice');
1370
+ * console.log('Bucket quota enabled:', quota.enabled);
1371
+ * ```
1372
+ */
1373
+ async getBucketQuota(uid) {
1374
+ validateUid(uid);
1375
+ return this.client.request({
1376
+ method: "GET",
1377
+ path: "/user",
1378
+ query: { uid, quota: "", quotaType: "bucket" }
1379
+ });
1380
+ }
1381
+ /**
1382
+ * Set the bucket-level quota for a user's buckets.
1383
+ *
1384
+ * The `maxSize` field accepts either a number (bytes) or a human-readable string
1385
+ * like `"1G"`, `"500M"`.
1386
+ *
1387
+ * @param input - Quota settings. `uid` is required.
1388
+ * @throws {RGWValidationError} If `uid` is empty, `maxSize` format is invalid, or `maxSize`/`maxObjects` is a negative number other than -1.
1389
+ * @throws {RGWNotFoundError} If the user does not exist.
1390
+ *
1391
+ * @example
1392
+ * ```typescript
1393
+ * await client.quota.setBucketQuota({
1394
+ * uid: 'alice',
1395
+ * maxSize: '1G',
1396
+ * maxObjects: 10000,
1397
+ * enabled: true,
1398
+ * });
1399
+ * ```
1400
+ */
1401
+ async setBucketQuota(input) {
1402
+ validateUid(input.uid);
1403
+ validateQuotaValue("maxObjects", input.maxObjects);
1404
+ const maxSize = input.maxSize !== void 0 ? parseSizeString(input.maxSize) : void 0;
1405
+ validateQuotaValue("maxSize", maxSize);
1406
+ return this.client.request({
1407
+ method: "PUT",
1408
+ path: "/user",
1409
+ query: {
1410
+ uid: input.uid,
1411
+ quota: "",
1412
+ quotaType: "bucket",
1413
+ maxSize,
1414
+ maxObjects: input.maxObjects,
1415
+ enabled: input.enabled ?? true
1416
+ }
1417
+ });
1418
+ }
1419
+ /**
1420
+ * Enable the bucket-level quota for a user without changing quota values.
1421
+ *
1422
+ * @param uid - The user ID.
1423
+ * @throws {RGWValidationError} If `uid` is empty.
1424
+ * @throws {RGWNotFoundError} If the user does not exist.
1425
+ *
1426
+ * @example
1427
+ * ```typescript
1428
+ * await client.quota.enableBucketQuota('alice');
1429
+ * ```
1430
+ */
1431
+ async enableBucketQuota(uid) {
1432
+ validateUid(uid);
1433
+ return this.client.request({
1434
+ method: "PUT",
1435
+ path: "/user",
1436
+ query: { uid, quota: "", quotaType: "bucket", enabled: true }
1437
+ });
1438
+ }
1439
+ /**
1440
+ * Disable the bucket-level quota for a user without changing quota values.
1441
+ *
1442
+ * @param uid - The user ID.
1443
+ * @throws {RGWValidationError} If `uid` is empty.
1444
+ * @throws {RGWNotFoundError} If the user does not exist.
1445
+ *
1446
+ * @example
1447
+ * ```typescript
1448
+ * await client.quota.disableBucketQuota('alice');
1449
+ * ```
1450
+ */
1451
+ async disableBucketQuota(uid) {
1452
+ validateUid(uid);
1453
+ return this.client.request({
1454
+ method: "PUT",
1455
+ path: "/user",
1456
+ query: { uid, quota: "", quotaType: "bucket", enabled: false }
1457
+ });
1458
+ }
1459
+ };
1460
+
1461
+ // src/modules/ratelimit.ts
1462
+ function validateBucket2(bucket) {
1463
+ if (!bucket || typeof bucket !== "string" || bucket.trim().length === 0) {
1464
+ throw new RGWValidationError("bucket is required and must be a non-empty string");
1465
+ }
1466
+ }
1467
+ function validateRateLimitValue(field, value) {
1468
+ if (value !== void 0 && typeof value === "number" && value < 0) {
1469
+ throw new RGWValidationError(`${field} must be >= 0 (0 = unlimited), got ${value}`);
1470
+ }
1471
+ }
1472
+ function validateRateLimitFields(input) {
1473
+ validateRateLimitValue("maxReadOps", input.maxReadOps);
1474
+ validateRateLimitValue("maxWriteOps", input.maxWriteOps);
1475
+ validateRateLimitValue("maxReadBytes", input.maxReadBytes);
1476
+ validateRateLimitValue("maxWriteBytes", input.maxWriteBytes);
1477
+ }
1478
+ var RateLimitModule = class {
1479
+ constructor(client) {
1480
+ this.client = client;
1481
+ }
1482
+ /**
1483
+ * Get the rate limit configuration for a user.
1484
+ *
1485
+ * @param uid - The user ID.
1486
+ * @returns The user's rate limit configuration.
1487
+ * @throws {RGWValidationError} If `uid` is empty.
1488
+ * @throws {RGWNotFoundError} If the user does not exist.
1489
+ *
1490
+ * @example
1491
+ * ```typescript
1492
+ * const limit = await client.rateLimit.getUserLimit('alice');
1493
+ * console.log('Read ops/min:', limit.maxReadOps);
1494
+ * console.log('Write ops/min:', limit.maxWriteOps);
1495
+ * ```
1496
+ */
1497
+ async getUserLimit(uid) {
1498
+ validateUid(uid);
1499
+ return this.client.request({
1500
+ method: "GET",
1501
+ path: "/ratelimit",
1502
+ query: { ratelimit: "", uid, scope: "user" }
1503
+ });
1504
+ }
1505
+ /**
1506
+ * Set the rate limit for a user.
1507
+ *
1508
+ * Values are per RGW instance. Divide by the number of RGW daemons for cluster-wide limits.
1509
+ *
1510
+ * @param input - Rate limit settings. `uid` is required. `enabled` defaults to `true`.
1511
+ * @throws {RGWValidationError} If `uid` is empty or any rate limit value is negative.
1512
+ * @throws {RGWNotFoundError} If the user does not exist.
1513
+ *
1514
+ * @example
1515
+ * ```typescript
1516
+ * await client.rateLimit.setUserLimit({
1517
+ * uid: 'alice',
1518
+ * maxReadOps: 100,
1519
+ * maxWriteOps: 50,
1520
+ * maxWriteBytes: 52428800, // 50MB/min
1521
+ * enabled: true,
1522
+ * });
1523
+ * ```
1524
+ */
1525
+ async setUserLimit(input) {
1526
+ validateUid(input.uid);
1527
+ validateRateLimitFields(input);
1528
+ return this.client.request({
1529
+ method: "POST",
1530
+ path: "/ratelimit",
1531
+ query: {
1532
+ ratelimit: "",
1533
+ uid: input.uid,
1534
+ scope: "user",
1535
+ maxReadOps: input.maxReadOps,
1536
+ maxWriteOps: input.maxWriteOps,
1537
+ maxReadBytes: input.maxReadBytes,
1538
+ maxWriteBytes: input.maxWriteBytes,
1539
+ enabled: input.enabled ?? true
1540
+ }
1541
+ });
1542
+ }
1543
+ /**
1544
+ * Disable the rate limit for a user without changing the configured values.
1545
+ *
1546
+ * @param uid - The user ID.
1547
+ * @throws {RGWValidationError} If `uid` is empty.
1548
+ * @throws {RGWNotFoundError} If the user does not exist.
1549
+ *
1550
+ * @example
1551
+ * ```typescript
1552
+ * await client.rateLimit.disableUserLimit('alice');
1553
+ * ```
1554
+ */
1555
+ async disableUserLimit(uid) {
1556
+ validateUid(uid);
1557
+ return this.client.request({
1558
+ method: "POST",
1559
+ path: "/ratelimit",
1560
+ query: { ratelimit: "", uid, scope: "user", enabled: false }
1561
+ });
1562
+ }
1563
+ /**
1564
+ * Get the rate limit configuration for a bucket.
1565
+ *
1566
+ * @param bucket - The bucket name.
1567
+ * @returns The bucket's rate limit configuration.
1568
+ * @throws {RGWValidationError} If `bucket` is empty.
1569
+ * @throws {RGWNotFoundError} If the bucket does not exist.
1570
+ *
1571
+ * @example
1572
+ * ```typescript
1573
+ * const limit = await client.rateLimit.getBucketLimit('my-bucket');
1574
+ * console.log('Read ops/min:', limit.maxReadOps);
1575
+ * ```
1576
+ */
1577
+ async getBucketLimit(bucket) {
1578
+ validateBucket2(bucket);
1579
+ return this.client.request({
1580
+ method: "GET",
1581
+ path: "/ratelimit",
1582
+ query: { ratelimit: "", bucket, scope: "bucket" }
1583
+ });
1584
+ }
1585
+ /**
1586
+ * Set the rate limit for a bucket.
1587
+ *
1588
+ * Values are per RGW instance. Divide by the number of RGW daemons for cluster-wide limits.
1589
+ *
1590
+ * @param input - Rate limit settings. `bucket` is required. `enabled` defaults to `true`.
1591
+ * @throws {RGWValidationError} If `bucket` is empty or any rate limit value is negative.
1592
+ * @throws {RGWNotFoundError} If the bucket does not exist.
1593
+ *
1594
+ * @example
1595
+ * ```typescript
1596
+ * await client.rateLimit.setBucketLimit({
1597
+ * bucket: 'my-bucket',
1598
+ * maxReadOps: 200,
1599
+ * maxWriteOps: 100,
1600
+ * enabled: true,
1601
+ * });
1602
+ * ```
1603
+ */
1604
+ async setBucketLimit(input) {
1605
+ validateBucket2(input.bucket);
1606
+ validateRateLimitFields(input);
1607
+ return this.client.request({
1608
+ method: "POST",
1609
+ path: "/ratelimit",
1610
+ query: {
1611
+ ratelimit: "",
1612
+ bucket: input.bucket,
1613
+ scope: "bucket",
1614
+ maxReadOps: input.maxReadOps,
1615
+ maxWriteOps: input.maxWriteOps,
1616
+ maxReadBytes: input.maxReadBytes,
1617
+ maxWriteBytes: input.maxWriteBytes,
1618
+ enabled: input.enabled ?? true
1619
+ }
1620
+ });
1621
+ }
1622
+ /**
1623
+ * Disable the rate limit for a bucket without changing the configured values.
1624
+ *
1625
+ * @param bucket - The bucket name.
1626
+ * @throws {RGWValidationError} If `bucket` is empty.
1627
+ * @throws {RGWNotFoundError} If the bucket does not exist.
1628
+ *
1629
+ * @example
1630
+ * ```typescript
1631
+ * await client.rateLimit.disableBucketLimit('my-bucket');
1632
+ * ```
1633
+ */
1634
+ async disableBucketLimit(bucket) {
1635
+ validateBucket2(bucket);
1636
+ return this.client.request({
1637
+ method: "POST",
1638
+ path: "/ratelimit",
1639
+ query: { ratelimit: "", bucket, scope: "bucket", enabled: false }
1640
+ });
1641
+ }
1642
+ /**
1643
+ * Get the global rate limit configuration for all scopes (user, bucket, anonymous).
1644
+ *
1645
+ * @returns Global rate limits for user, bucket, and anonymous scopes.
1646
+ *
1647
+ * @example
1648
+ * ```typescript
1649
+ * const global = await client.rateLimit.getGlobal();
1650
+ * console.log('Anonymous read limit:', global.anonymous.maxReadOps);
1651
+ * ```
1652
+ */
1653
+ async getGlobal() {
1654
+ return this.client.request({
1655
+ method: "GET",
1656
+ path: "/ratelimit",
1657
+ query: { ratelimit: "", global: "" }
1658
+ });
1659
+ }
1660
+ /**
1661
+ * Set a global rate limit for a specific scope.
1662
+ *
1663
+ * Use `scope: 'anonymous'` to protect public-read buckets from abuse.
1664
+ *
1665
+ * @param input - Rate limit settings. `scope` is required. `enabled` defaults to `true`.
1666
+ * @throws {RGWValidationError} If `scope` is invalid or any rate limit value is negative.
1667
+ *
1668
+ * @example
1669
+ * ```typescript
1670
+ * // Limit anonymous access globally
1671
+ * await client.rateLimit.setGlobal({
1672
+ * scope: 'anonymous',
1673
+ * maxReadOps: 50,
1674
+ * maxWriteOps: 0,
1675
+ * enabled: true,
1676
+ * });
1677
+ * ```
1678
+ */
1679
+ async setGlobal(input) {
1680
+ const validScopes = ["user", "bucket", "anonymous"];
1681
+ if (!validScopes.includes(input.scope)) {
1682
+ throw new RGWValidationError(
1683
+ `scope must be one of: ${validScopes.join(", ")}. Got: "${input.scope}"`
1684
+ );
1685
+ }
1686
+ validateRateLimitFields(input);
1687
+ return this.client.request({
1688
+ method: "POST",
1689
+ path: "/ratelimit",
1690
+ query: {
1691
+ ratelimit: "",
1692
+ global: "",
1693
+ scope: input.scope,
1694
+ maxReadOps: input.maxReadOps,
1695
+ maxWriteOps: input.maxWriteOps,
1696
+ maxReadBytes: input.maxReadBytes,
1697
+ maxWriteBytes: input.maxWriteBytes,
1698
+ enabled: input.enabled ?? true
1699
+ }
1700
+ });
1701
+ }
1702
+ };
1703
+
1704
+ // src/modules/usage.ts
1705
+ function normalizeDate(value) {
1706
+ if (value instanceof Date) {
1707
+ if (isNaN(value.getTime())) {
1708
+ throw new RGWValidationError("Invalid Date object provided for date range filter");
1709
+ }
1710
+ return value.toISOString().slice(0, 10);
1711
+ }
1712
+ if (!/^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2}:\d{2})?$/.test(value.trim())) {
1713
+ throw new RGWValidationError(
1714
+ `Invalid date string: "${value}". Use "YYYY-MM-DD" or "YYYY-MM-DD HH:MM:SS"`
1715
+ );
1716
+ }
1717
+ return value.trim();
1718
+ }
1719
+ function warnIfInvertedRange(start, end) {
1720
+ if (start !== void 0 && end !== void 0 && start > end) {
1721
+ console.warn(
1722
+ `[radosgw-admin] WARNING: start "${start}" is after end "${end}" \u2014 RGW will return empty results`
1723
+ );
1724
+ }
1725
+ }
1726
+ var UsageModule = class {
1727
+ constructor(client) {
1728
+ this.client = client;
1729
+ }
1730
+ /**
1731
+ * Retrieve a usage report for a user or the entire cluster.
1732
+ *
1733
+ * Omit `uid` to get cluster-wide usage across all users.
1734
+ * Omit `start`/`end` to get all available usage data.
1735
+ *
1736
+ * > **Note:** Usage logging must be enabled in `ceph.conf`:
1737
+ * > `rgw enable usage log = true`
1738
+ *
1739
+ * @param input - Optional filters: `uid`, `start`, `end`, `showEntries`, `showSummary`.
1740
+ * @returns A usage report with `entries` (per-bucket detail) and `summary` (per-user totals).
1741
+ * @throws {RGWValidationError} If `uid` is provided but empty/whitespace.
1742
+ * @throws {RGWValidationError} If a date string is not in a recognised format.
1743
+ *
1744
+ * @example
1745
+ * ```typescript
1746
+ * // Cluster-wide usage, all time
1747
+ * const all = await client.usage.get();
1748
+ *
1749
+ * // Single user, date range
1750
+ * const report = await client.usage.get({
1751
+ * uid: 'alice',
1752
+ * start: '2025-01-01',
1753
+ * end: '2025-01-31',
1754
+ * });
1755
+ *
1756
+ * for (const s of report.summary) {
1757
+ * console.log(s.user, 'sent', s.total.bytesSent, 'bytes');
1758
+ * }
1759
+ * ```
1760
+ */
1761
+ async get(input = {}) {
1762
+ if (input.uid !== void 0) {
1763
+ validateUid(input.uid);
1764
+ }
1765
+ const start = input.start !== void 0 ? normalizeDate(input.start) : void 0;
1766
+ const end = input.end !== void 0 ? normalizeDate(input.end) : void 0;
1767
+ warnIfInvertedRange(start, end);
1768
+ return this.client.request({
1769
+ method: "GET",
1770
+ path: "/usage",
1771
+ query: {
1772
+ uid: input.uid,
1773
+ start,
1774
+ end,
1775
+ showEntries: input.showEntries,
1776
+ showSummary: input.showSummary
1777
+ }
1778
+ });
1779
+ }
1780
+ /**
1781
+ * Delete (trim) usage log entries matching the given filters.
1782
+ *
1783
+ * **Destructive operation** — deleted log entries cannot be recovered.
1784
+ *
1785
+ * When no `uid` is provided the trim applies cluster-wide. In that case
1786
+ * `removeAll: true` is required to prevent accidental full-cluster log wipes.
1787
+ *
1788
+ * @param input - Trim filters. Omit entirely to trim nothing (use `removeAll: true` explicitly).
1789
+ * @throws {RGWValidationError} If `uid` is provided but empty/whitespace.
1790
+ * @throws {RGWValidationError} If `uid` is omitted but `removeAll` is not `true`.
1791
+ * @throws {RGWValidationError} If a date string is not in a recognised format.
1792
+ *
1793
+ * @example
1794
+ * ```typescript
1795
+ * // Trim a specific user's logs up to end of 2024
1796
+ * await client.usage.trim({ uid: 'alice', end: '2024-12-31' });
1797
+ *
1798
+ * // ⚠️ Trim all logs before 2024 across the entire cluster
1799
+ * await client.usage.trim({ end: '2023-12-31', removeAll: true });
1800
+ * ```
1801
+ */
1802
+ async trim(input = {}) {
1803
+ if (input.uid !== void 0) {
1804
+ validateUid(input.uid);
1805
+ }
1806
+ if (!input.uid && input.removeAll !== true) {
1807
+ throw new RGWValidationError(
1808
+ "trim() without a uid removes logs for ALL users. Set removeAll: true to confirm."
1809
+ );
1810
+ }
1811
+ if (input.removeAll) {
1812
+ console.warn(
1813
+ "[radosgw-admin] WARNING: usage.trim() with removeAll=true will permanently delete usage logs" + (input.uid ? ` for user "${input.uid}"` : " for ALL users")
1814
+ );
1815
+ }
1816
+ const start = input.start !== void 0 ? normalizeDate(input.start) : void 0;
1817
+ const end = input.end !== void 0 ? normalizeDate(input.end) : void 0;
1818
+ warnIfInvertedRange(start, end);
1819
+ return this.client.request({
1820
+ method: "DELETE",
1821
+ path: "/usage",
1822
+ query: {
1823
+ uid: input.uid,
1824
+ start,
1825
+ end,
1826
+ removeAll: input.removeAll
1827
+ }
1828
+ });
1829
+ }
1830
+ };
1831
+
1832
+ // src/modules/info.ts
1833
+ var InfoModule = class {
1834
+ constructor(client) {
1835
+ this.client = client;
1836
+ }
1837
+ /**
1838
+ * Get basic cluster information including the Ceph cluster FSID.
1839
+ *
1840
+ * Useful for confirming connectivity and identifying which cluster the
1841
+ * client is connected to when managing multiple environments.
1842
+ *
1843
+ * @returns Cluster info containing the cluster ID (FSID).
1844
+ * @throws {RGWAuthError} If the credentials are invalid or lack permission.
1845
+ *
1846
+ * @example
1847
+ * ```typescript
1848
+ * const info = await client.info.get();
1849
+ * console.log('Cluster FSID:', info.info.storageBackends[0].clusterId);
1850
+ * ```
1851
+ */
1852
+ async get() {
1853
+ return this.client.request({
1854
+ method: "GET",
1855
+ path: "/info",
1856
+ query: {}
1857
+ });
1858
+ }
1859
+ };
1860
+
1861
+ // src/index.ts
1862
+ var RadosGWAdminClient = class {
1863
+ /** @internal */
1864
+ _client;
1865
+ /** User management operations. */
1866
+ users;
1867
+ /** S3/Swift key management operations. */
1868
+ keys;
1869
+ /** Subuser management operations. */
1870
+ subusers;
1871
+ /** Bucket management operations. */
1872
+ buckets;
1873
+ /** Quota management operations (user-level and bucket-level). */
1874
+ quota;
1875
+ /** Rate limit management operations (user, bucket, and global). */
1876
+ rateLimit;
1877
+ /** Usage & analytics operations — query and trim RGW usage logs. */
1878
+ usage;
1879
+ /** Cluster info operations. */
1880
+ info;
1881
+ constructor(config) {
1882
+ this._client = new BaseClient(config);
1883
+ this.users = new UsersModule(this._client);
1884
+ this.keys = new KeysModule(this._client);
1885
+ this.subusers = new SubusersModule(this._client);
1886
+ this.buckets = new BucketsModule(this._client);
1887
+ this.quota = new QuotaModule(this._client);
1888
+ this.rateLimit = new RateLimitModule(this._client);
1889
+ this.usage = new UsageModule(this._client);
1890
+ this.info = new InfoModule(this._client);
1891
+ }
1892
+ };
1893
+ // Annotate the CommonJS export names for ESM import in node:
1894
+ 0 && (module.exports = {
1895
+ BaseClient,
1896
+ RGWAuthError,
1897
+ RGWConflictError,
1898
+ RGWError,
1899
+ RGWNotFoundError,
1900
+ RGWValidationError,
1901
+ RadosGWAdminClient,
1902
+ signRequest
1903
+ });