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/LICENSE ADDED
@@ -0,0 +1,182 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship made available under
36
+ the License, as indicated by a copyright notice that is included in
37
+ or attached to the work (an example is provided in the Appendix below).
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other modifications
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean, as submitted to the Licensor for inclusion
48
+ in the Work by the copyright owner or by an individual or Legal Entity
49
+ authorized to submit on behalf of the copyright owner. For the purposes
50
+ of this definition, "submitted" means any form of electronic, verbal,
51
+ or written communication sent to the Licensor or its representatives,
52
+ including but not limited to communication on electronic mailing lists,
53
+ source code control systems, and issue tracking systems that are managed
54
+ by, or on behalf of, the Licensor for the purpose of discussing and
55
+ improving the Work, but excluding communication that is conspicuously
56
+ marked or designated in writing by the copyright owner as "Not a
57
+ Contribution."
58
+
59
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
60
+ whom a Contribution has been received by the Licensor and included
61
+ within the Work.
62
+
63
+ 2. Grant of Copyright License. Subject to the terms and conditions of
64
+ this License, each Contributor hereby grants to You a perpetual,
65
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
66
+ copyright license to reproduce, prepare Derivative Works of,
67
+ publicly display, publicly perform, sublicense, and distribute the
68
+ Work and such Derivative Works in Source or Object form.
69
+
70
+ 3. Grant of Patent License. Subject to the terms and conditions of
71
+ this License, each Contributor hereby grants to You a perpetual,
72
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
73
+ (except as stated in this section) patent license to make, have made,
74
+ use, offer to sell, sell, import, and otherwise transfer the Work,
75
+ where such license applies only to those patent contributions
76
+ Licensors made to the Work. If You institute patent litigation against
77
+ any entity (including a cross-claim or counterclaim in a lawsuit)
78
+ alleging that the Work or a Contribution incorporated within the Work
79
+ constitutes direct or contributory patent infringement, then any patent
80
+ licenses granted to You under this License for that Work shall
81
+ terminate as of the date such litigation is filed.
82
+
83
+ 4. Redistribution. You may reproduce and distribute copies of the
84
+ Work or Derivative Works thereof in any medium, with or without
85
+ modifications, and in Source or Object form, provided that You
86
+ meet the following conditions:
87
+
88
+ (a) You must give any other recipients of the Work or Derivative
89
+ Works a copy of this License; and
90
+
91
+ (b) You must cause any modified files to carry prominent notices
92
+ stating that You changed the files; and
93
+
94
+ (c) You must retain, in the Source form of any Derivative Works
95
+ that You distribute, all copyright, patent, trademark, and
96
+ attribution notices from the Source form of the Work,
97
+ excluding those notices that do not pertain to any part of
98
+ the Derivative Works; and
99
+
100
+ (d) If the Work includes a "NOTICE" text file as part of its
101
+ distribution, You must include a readable copy of the
102
+ attribution notices contained within such NOTICE file, in
103
+ at least one of the following places: within a NOTICE text
104
+ file distributed as part of the Derivative Works; within
105
+ the Source form or documentation, if provided along with the
106
+ Derivative Works; or, within a display generated by the
107
+ Derivative Works, if and wherever such third-party notices
108
+ normally appear. The contents of the NOTICE file are for
109
+ informational purposes only and do not modify the License.
110
+ You may add Your own attribution notices within Derivative
111
+ Works that You distribute, alongside or in addition to the
112
+ NOTICE text from the Work, provided that such additional
113
+ attribution notices cannot be construed as modifying the
114
+ License.
115
+
116
+ You may add Your own license statement for Your modifications and
117
+ may provide additional grant of rights to use, copy, modify, merge,
118
+ publish, distribute, sublicense, and/or sell copies of the
119
+ Derivative Works, and to permit persons to whom the Derivative Works
120
+ is furnished to do so, subject to Your own terms and conditions.
121
+
122
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
123
+ any Contribution intentionally submitted for inclusion in the Work
124
+ by You to the Licensor shall be under the terms and conditions of
125
+ this License, without any additional terms or conditions.
126
+ Notwithstanding the above, nothing herein shall supersede or modify
127
+ the terms of any separate license agreement you may have executed
128
+ with Licensor regarding such Contributions.
129
+
130
+ 6. Trademarks. This License does not grant permission to use the trade
131
+ names, trademarks, service marks, or product names of the Licensor,
132
+ except as required for reasonable and customary use in describing the
133
+ origin of the Work and reproducing the content of the NOTICE file.
134
+
135
+ 7. Disclaimer of Warranty. Unless required by applicable law or
136
+ agreed to in writing, Licensor provides the Work (and each
137
+ Contributor provides its Contributions) on an "AS IS" BASIS,
138
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
139
+ implied, including, without limitation, any warranties or conditions
140
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
141
+ PARTICULAR PURPOSE. You are solely responsible for determining the
142
+ appropriateness of using or reproducing the Work and assume any
143
+ risks associated with Your exercise of permissions under this License.
144
+
145
+ 8. Limitation of Liability. In no event and under no legal theory,
146
+ whether in tort (including negligence), contract, or otherwise,
147
+ unless required by applicable law (such as deliberate and grossly
148
+ negligent acts) or agreed to in writing, shall any Contributor be
149
+ liable to You for damages, including any direct, indirect, special,
150
+ incidental, or exemplary damages of any character arising as a
151
+ result of this License or out of the use or inability to use the
152
+ Work (including but not limited to damages for loss of goodwill,
153
+ work stoppage, computer failure or malfunction, or all other
154
+ commercial damages or losses), even if such Contributor has been
155
+ advised of the possibility of such damages.
156
+
157
+ 9. Accepting Warranty or Additional Liability. While redistributing
158
+ the Work or Derivative Works thereof, You may choose to offer,
159
+ and charge a fee for, acceptance of support, warranty, indemnity,
160
+ or other liability obligations and/or rights consistent with this
161
+ License. However, in accepting such obligations, You may offer such
162
+ conditions only on Your own behalf and on Your sole responsibility,
163
+ not on behalf of any other Contributor, and only if You agree to
164
+ indemnify, defend, and hold each Contributor harmless for any
165
+ liability incurred by, or claims asserted against, such Contributor
166
+ by reason of your accepting any such warranty or additional liability.
167
+
168
+ END OF TERMS AND CONDITIONS
169
+
170
+ Copyright 2026 nycanshu <hkg43700@gmail.com>
171
+
172
+ Licensed under the Apache License, Version 2.0 (the "License");
173
+ you may not use this file except in compliance with the License.
174
+ You may obtain a copy of the License at
175
+
176
+ http://www.apache.org/licenses/LICENSE-2.0
177
+
178
+ Unless required by applicable law or agreed to in writing, software
179
+ distributed under the License is distributed on an "AS IS" BASIS,
180
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
181
+ See the License for the specific language governing permissions and
182
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,411 @@
1
+ # radosgw-admin
2
+
3
+ > Modern, zero-dependency TypeScript client for the Ceph RADOS Gateway Admin Ops API.
4
+
5
+ [![CI](https://github.com/nycanshu/radosgw-admin/actions/workflows/ci.yml/badge.svg)](https://github.com/nycanshu/radosgw-admin/actions/workflows/ci.yml)
6
+ [![npm version](https://img.shields.io/npm/v/radosgw-admin)](https://www.npmjs.com/package/radosgw-admin)
7
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](LICENSE)
8
+ [![TypeScript](https://img.shields.io/badge/TypeScript-strict-blue)](https://www.typescriptlang.org/)
9
+
10
+ ---
11
+
12
+ ## Why?
13
+
14
+ The only existing npm package for RGW Admin Ops (`rgw-admin-client`) was last published **7 years ago** — no TypeScript, no ESM, no maintenance. Meanwhile, Ceph adoption in Kubernetes (Rook-Ceph, OpenShift Data Foundation) keeps growing. This package fills that gap.
15
+
16
+ **What you get:**
17
+
18
+ - [RGW Admin Ops API](https://docs.ceph.com/en/latest/radosgw/adminops/) coverage — users, keys, subusers, buckets, quotas, rate limits
19
+ - Strict TypeScript with zero `any` — every request and response is fully typed
20
+ - Zero runtime dependencies — AWS SigV4 signing uses only `node:crypto`
21
+ - Dual ESM + CJS build — works in every Node.js environment
22
+ - Automatic snake_case/camelCase conversion — idiomatic JS API over RGW's REST interface
23
+ - Structured error hierarchy — catch specific failures, not generic HTTP errors
24
+
25
+ ## Install
26
+
27
+ ```bash
28
+ npm install radosgw-admin
29
+ ```
30
+
31
+ Requires **Node.js >= 18** and a Ceph RGW instance with the Admin Ops API enabled.
32
+
33
+ ## Quick Start
34
+
35
+ ```typescript
36
+ import { RadosGWAdminClient } from 'radosgw-admin';
37
+
38
+ const rgw = new RadosGWAdminClient({
39
+ host: 'http://ceph-rgw.example.com',
40
+ port: 8080,
41
+ accessKey: 'ADMIN_ACCESS_KEY',
42
+ secretKey: 'ADMIN_SECRET_KEY',
43
+ });
44
+
45
+ // Create a user
46
+ const user = await rgw.users.create({
47
+ uid: 'alice',
48
+ displayName: 'Alice',
49
+ email: 'alice@example.com',
50
+ maxBuckets: 100,
51
+ });
52
+
53
+ // List all users
54
+ const uids = await rgw.users.list();
55
+
56
+ // Get user info with keys, caps, quotas
57
+ const info = await rgw.users.get('alice');
58
+
59
+ // Suspend / re-enable
60
+ await rgw.users.suspend('alice');
61
+ await rgw.users.enable('alice');
62
+
63
+ // Delete (with optional purge of all objects)
64
+ await rgw.users.delete({ uid: 'alice', purgeData: true });
65
+ ```
66
+
67
+ ## Configuration
68
+
69
+ ```typescript
70
+ const rgw = new RadosGWAdminClient({
71
+ host: 'https://ceph-rgw.example.com', // Required — RGW endpoint
72
+ port: 443, // Optional — omit to use host URL default
73
+ accessKey: 'ADMIN_KEY', // Required — admin user access key
74
+ secretKey: 'ADMIN_SECRET', // Required — admin user secret key
75
+ adminPath: '/admin', // Optional — API prefix (default: "/admin")
76
+ timeout: 15000, // Optional — request timeout in ms (default: 10000)
77
+ region: 'us-east-1', // Optional — SigV4 region (default: "us-east-1")
78
+ insecure: false, // Optional — skip TLS verification (default: false)
79
+ debug: false, // Optional — enable request/response logging (default: false)
80
+ maxRetries: 3, // Optional — retry transient errors (default: 0)
81
+ retryDelay: 200, // Optional — base delay for exponential backoff in ms (default: 200)
82
+ });
83
+ ```
84
+
85
+ ## API Reference
86
+
87
+ ### Users
88
+
89
+ ```typescript
90
+ rgw.users.create(input); // Create a new RGW user
91
+ rgw.users.get(uid, tenant?); // Get full user info (keys, caps, quotas)
92
+ rgw.users.getByAccessKey(accessKey); // Look up a user by their S3 access key
93
+ rgw.users.modify(input); // Update display name, email, max buckets, etc.
94
+ rgw.users.delete(input); // Delete user (optionally purge all data)
95
+ rgw.users.list(); // List all user IDs in the cluster
96
+ rgw.users.suspend(uid); // Suspend a user account
97
+ rgw.users.enable(uid); // Re-enable a suspended user
98
+ rgw.users.getStats(uid, sync?); // Get storage usage statistics
99
+ ```
100
+
101
+ ### Keys
102
+
103
+ ```typescript
104
+ rgw.keys.generate(input); // Generate or assign S3/Swift keys
105
+ rgw.keys.revoke(input); // Revoke (delete) a key pair
106
+ ```
107
+
108
+ ### Subusers.
109
+
110
+ ```typescript
111
+ rgw.subusers.create(input); // Create a subuser for an existing user
112
+ rgw.subusers.modify(input); // Modify subuser permissions
113
+ rgw.subusers.remove(input); // Remove a subuser
114
+ ```
115
+
116
+ <details>
117
+ <summary>Subuser examples</summary>
118
+
119
+ ```typescript
120
+ // Create a Swift subuser with full access
121
+ await rgw.subusers.create({
122
+ uid: 'alice',
123
+ subuser: 'alice:swift',
124
+ access: 'full',
125
+ keyType: 'swift',
126
+ generateSecret: true,
127
+ });
128
+
129
+ // Restrict to read-only
130
+ await rgw.subusers.modify({
131
+ uid: 'alice',
132
+ subuser: 'alice:swift',
133
+ access: 'read',
134
+ });
135
+
136
+ // Remove the subuser
137
+ await rgw.subusers.remove({ uid: 'alice', subuser: 'alice:swift' });
138
+ ```
139
+
140
+ </details>
141
+
142
+ ### Buckets
143
+
144
+ ```typescript
145
+ rgw.buckets.list(); // List all buckets in the cluster
146
+ rgw.buckets.listByUser(uid); // List buckets owned by a specific user
147
+ rgw.buckets.getInfo(bucket); // Get bucket metadata and stats
148
+ rgw.buckets.delete(input); // Delete a bucket (optionally purge objects)
149
+ rgw.buckets.transferOwnership(input); // Transfer bucket to a different user
150
+ rgw.buckets.removeOwnership(input); // Remove bucket ownership
151
+ rgw.buckets.verifyIndex(input); // Check and optionally repair bucket index
152
+ ```
153
+
154
+ <details>
155
+ <summary>Bucket examples</summary>
156
+
157
+ ```typescript
158
+ // List all buckets in the cluster
159
+ const allBuckets = await rgw.buckets.list();
160
+
161
+ // List buckets owned by a specific user
162
+ const userBuckets = await rgw.buckets.listByUser('alice');
163
+
164
+ // Get detailed bucket info
165
+ const info = await rgw.buckets.getInfo('my-bucket');
166
+ console.log(info.usage.rgwMain.numObjects);
167
+
168
+ // Transfer a bucket to a different user
169
+ await rgw.buckets.transferOwnership({
170
+ bucket: 'my-bucket',
171
+ bucketId: info.id,
172
+ uid: 'bob',
173
+ });
174
+
175
+ // Check and repair bucket index
176
+ const result = await rgw.buckets.verifyIndex({
177
+ bucket: 'my-bucket',
178
+ checkObjects: true,
179
+ fix: true,
180
+ });
181
+
182
+ // Delete bucket and all its objects
183
+ await rgw.buckets.delete({ bucket: 'my-bucket', purgeObjects: true });
184
+ ```
185
+
186
+ </details>
187
+
188
+ ### Quotas
189
+
190
+ ```typescript
191
+ rgw.quota.getUserQuota(uid); // Get user-level quota
192
+ rgw.quota.setUserQuota(input); // Set user-level quota (accepts "10G" size strings)
193
+ rgw.quota.enableUserQuota(uid); // Enable user quota without changing values
194
+ rgw.quota.disableUserQuota(uid); // Disable user quota without changing values
195
+ rgw.quota.getBucketQuota(uid); // Get bucket-level quota for a user
196
+ rgw.quota.setBucketQuota(input); // Set bucket-level quota
197
+ rgw.quota.enableBucketQuota(uid); // Enable bucket quota
198
+ rgw.quota.disableBucketQuota(uid); // Disable bucket quota
199
+ ```
200
+
201
+ <details>
202
+ <summary>Quota examples</summary>
203
+
204
+ `maxSize` accepts a number (bytes) or a human-readable string with binary (1024-based) units:
205
+
206
+ | Input | Bytes |
207
+ |-------|-------|
208
+ | `'1K'` / `'1KB'` | 1,024 |
209
+ | `'500M'` / `'500MB'` | 524,288,000 |
210
+ | `'10G'` / `'10GB'` | 10,737,418,240 |
211
+ | `'1T'` / `'1TB'` | 1,099,511,627,776 |
212
+ | `'1.5G'` | 1,610,612,736 |
213
+ | `1073741824` | 1,073,741,824 (raw bytes) |
214
+ | `-1` | Unlimited |
215
+
216
+ ```typescript
217
+ // Set a 10GB user quota with 50k object limit
218
+ await rgw.quota.setUserQuota({
219
+ uid: 'alice',
220
+ maxSize: '10G', // → 10737418240 bytes
221
+ maxObjects: 50000,
222
+ enabled: true, // default: true when setting
223
+ });
224
+
225
+ // Size limit only, unlimited objects
226
+ await rgw.quota.setUserQuota({
227
+ uid: 'alice',
228
+ maxSize: '10G',
229
+ maxObjects: -1, // -1 = unlimited
230
+ });
231
+
232
+ // Check current quota — maxSize is always returned as bytes
233
+ const quota = await rgw.quota.getUserQuota('alice');
234
+ console.log('Enabled:', quota.enabled);
235
+ console.log('Max size:', quota.maxSize, 'bytes');
236
+
237
+ // Disable quota temporarily (preserves values)
238
+ await rgw.quota.disableUserQuota('alice');
239
+
240
+ // Set a 1GB per-bucket quota (applies to all buckets owned by the user)
241
+ await rgw.quota.setBucketQuota({
242
+ uid: 'alice', // uid, not bucket name — RGW bucket quotas are per-user
243
+ maxSize: '1G',
244
+ maxObjects: 10000,
245
+ });
246
+ ```
247
+
248
+ </details>
249
+
250
+ ### Rate Limits
251
+
252
+ > Requires Ceph **Pacific (v16)** or later. Values are per RGW instance — divide by the number of RGW daemons for cluster-wide limits.
253
+
254
+ ```typescript
255
+ rgw.rateLimit.getUserLimit(uid); // Get user rate limit
256
+ rgw.rateLimit.setUserLimit(input); // Set user rate limit
257
+ rgw.rateLimit.disableUserLimit(uid); // Disable user rate limit
258
+ rgw.rateLimit.getBucketLimit(bucket); // Get bucket rate limit
259
+ rgw.rateLimit.setBucketLimit(input); // Set bucket rate limit
260
+ rgw.rateLimit.disableBucketLimit(bucket); // Disable bucket rate limit
261
+ rgw.rateLimit.getGlobal(); // Get global rate limits (user/bucket/anonymous)
262
+ rgw.rateLimit.setGlobal(input); // Set global rate limit for a scope
263
+ ```
264
+
265
+ <details>
266
+ <summary>Rate limit examples</summary>
267
+
268
+ ```typescript
269
+ // Throttle alice to 100 read ops/min and 50MB/min write per RGW instance
270
+ await rgw.rateLimit.setUserLimit({
271
+ uid: 'alice',
272
+ maxReadOps: 100,
273
+ maxWriteOps: 50,
274
+ maxWriteBytes: 52428800, // 50MB
275
+ });
276
+
277
+ // Disable rate limit temporarily (preserves config)
278
+ await rgw.rateLimit.disableUserLimit('alice');
279
+
280
+ // Set a bucket-level rate limit
281
+ await rgw.rateLimit.setBucketLimit({
282
+ bucket: 'public-assets',
283
+ maxReadOps: 200,
284
+ maxWriteOps: 10,
285
+ });
286
+
287
+ // Protect public-read buckets from anonymous abuse
288
+ await rgw.rateLimit.setGlobal({
289
+ scope: 'anonymous',
290
+ maxReadOps: 50,
291
+ maxWriteOps: 0,
292
+ enabled: true,
293
+ });
294
+
295
+ // View all global rate limits
296
+ const global = await rgw.rateLimit.getGlobal();
297
+ console.log('Anonymous:', global.anonymous);
298
+ console.log('User:', global.user);
299
+ console.log('Bucket:', global.bucket);
300
+ ```
301
+
302
+ </details>
303
+
304
+ ### Usage & Analytics
305
+
306
+ > **Prerequisite:** Usage logging must be enabled in `ceph.conf`: `rgw enable usage log = true`. Restart RGW daemons after changing the config.
307
+
308
+ ```typescript
309
+ rgw.usage.get(input?); // Query usage report (per-user or cluster-wide)
310
+ rgw.usage.trim(input?); // Delete old usage logs — requires removeAll: true when no uid
311
+ ```
312
+
313
+ <details>
314
+ <summary>Usage examples</summary>
315
+
316
+ ```typescript
317
+ // Usage for alice in January 2025
318
+ const report = await rgw.usage.get({
319
+ uid: 'alice',
320
+ start: '2025-01-01', // accepts 'YYYY-MM-DD' or Date object
321
+ end: '2025-01-31',
322
+ });
323
+
324
+ for (const summary of report.summary) {
325
+ for (const cat of summary.categories) {
326
+ console.log(`[${cat.category}] ops: ${cat.ops} | sent: ${(cat.bytesSent / 1e6).toFixed(2)} MB`);
327
+ }
328
+ console.log('Total sent:', (summary.total.bytesSent / 1e9).toFixed(3), 'GB');
329
+ }
330
+
331
+ // Cluster-wide usage, all time (omit all filters)
332
+ const all = await rgw.usage.get();
333
+
334
+ // Trim a single user's logs up to end of 2024
335
+ await rgw.usage.trim({ uid: 'alice', end: '2024-12-31' });
336
+
337
+ // ⚠️ Trim all cluster usage logs — removeAll: true required when no uid
338
+ await rgw.usage.trim({ end: '2023-12-31', removeAll: true });
339
+ ```
340
+
341
+ </details>
342
+
343
+ ### Cluster Info
344
+
345
+ ```typescript
346
+ rgw.info.get(); // Get cluster FSID and basic endpoint info
347
+ ```
348
+
349
+ ```typescript
350
+ const info = await rgw.info.get();
351
+ console.log('Cluster FSID:', info.info.storageBackends[0].clusterId);
352
+ ```
353
+
354
+ ## Error Handling
355
+
356
+ Every error thrown is an instance of `RGWError` with structured properties:
357
+
358
+ ```typescript
359
+ import { RGWNotFoundError, RGWConflictError, RGWAuthError } from 'radosgw-admin';
360
+
361
+ try {
362
+ await rgw.users.get('nonexistent');
363
+ } catch (error) {
364
+ if (error instanceof RGWNotFoundError) {
365
+ // 404 — user does not exist
366
+ } else if (error instanceof RGWAuthError) {
367
+ // 403 — check admin credentials / caps
368
+ }
369
+ }
370
+ ```
371
+
372
+ | Error Class | HTTP Status | Condition |
373
+ | -------------------- | --------------- | ---------------------------------------- |
374
+ | `RGWValidationError` | _(pre-request)_ | Invalid input (missing uid, bad params) |
375
+ | `RGWNotFoundError` | 404 | Resource does not exist |
376
+ | `RGWConflictError` | 409 | Resource already exists |
377
+ | `RGWAuthError` | 403 | Insufficient credentials or capabilities |
378
+ | `RGWError` | 5xx | Server-side failure |
379
+
380
+ > **Note:** Destructive operations (`purgeData`, `purgeObjects`) emit a `console.warn` before executing. To suppress in CI/automation, redirect stderr or patch `console.warn`.
381
+
382
+ ## Compatibility
383
+
384
+ Tested against Ceph **Quincy (v17)** and **Reef (v18)**. The Admin Ops API is available in all Ceph releases with RGW.
385
+
386
+ **Prerequisites:**
387
+
388
+ - The RGW admin user must have `users=*`, `buckets=*` capabilities
389
+ - Admin Ops API must be accessible (default path: `/admin`)
390
+ - For `insecure: true` — only use with self-signed certificates in dev/test environments
391
+
392
+ ## Development
393
+
394
+ ```bash
395
+ git clone https://github.com/nycanshu/radosgw-admin.git
396
+ cd radosgw-admin
397
+ npm install
398
+
399
+ npm run build # ESM + CJS via tsup
400
+ npm run typecheck # tsc --noEmit (strict)
401
+ npm test # vitest
402
+ npm run lint # eslint
403
+ npm run format # prettier
404
+ npm run check # all of the above
405
+ ```
406
+
407
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
408
+
409
+ ## License
410
+
411
+ [Apache 2.0](LICENSE) &copy; nycanshu