Skip to content

Commit e44b816

Browse files
committed
feat(storage): typed errors for bucket-limit failures (v5.1.0)
Three new error classes covering the bucket-limit failure modes introduced by Storage #2 server-side. All extend KoolbaseStorageError so existing catch-all handlers (instanceof KoolbaseStorageError) keep working; check the specifics to branch on the kind of limit hit. - KoolbaseStorageQuotaError — 409 + QUOTA_EXCEEDED - KoolbaseStorageFileTooLargeError — 413 + FILE_TOO_LARGE - KoolbaseStorageMimeTypeError — 415 + MIME_NOT_ALLOWED Mapper (koolbaseStorageError / koolbaseStorageErrorFromResponse) recognizes the new codes via code-first lookup and the new HTTP statuses (413, 415) via status fallback. Status-fallback for 409 remains KoolbaseStorageConflictError (path collisions are the more common case); modern servers always emit code so the ambiguity only affects very old API responses. No breaking changes — pure additive surface. v5.0.0 → v5.1.0. Pairs with koolbase_flutter v6.1.0 (published earlier today). Same three error types, same code-first mapper extension.
1 parent a5858db commit e44b816

4 files changed

Lines changed: 159 additions & 19 deletions

File tree

CHANGELOG.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,35 @@ adheres to [Semantic Versioning][semver].
77
[kac]: https://keepachangelog.com/en/1.1.0/
88
[semver]: https://semver.org/
99

10+
## 5.1.0
11+
12+
### Added — storage
13+
14+
- Three new typed errors covering the bucket-limit failure modes
15+
introduced server-side in Storage #2. All extend
16+
`KoolbaseStorageError`, so existing `instanceof KoolbaseStorageError`
17+
catch-all blocks continue to work; check the specific type to branch
18+
on the kind of limit hit.
19+
- `KoolbaseStorageQuotaError` — 409 + `QUOTA_EXCEEDED`, thrown when
20+
an upload would push the bucket past its `max_size_bytes` cap.
21+
- `KoolbaseStorageFileTooLargeError` — 413 + `FILE_TOO_LARGE`, thrown
22+
when a single file exceeds the bucket's `max_file_size_bytes` cap.
23+
- `KoolbaseStorageMimeTypeError` — 415 + `MIME_NOT_ALLOWED`, thrown
24+
when an upload's content-type isn't in the bucket's
25+
`allowed_mime_types` allowlist (supports `type/*` wildcards).
26+
- Mapper (`koolbaseStorageError` / `koolbaseStorageErrorFromResponse`)
27+
recognizes the new codes via code-first lookup and the new HTTP
28+
statuses (413, 415) via status fallback.
29+
30+
### Notes
31+
32+
- Backwards-compatible: pure additive surface. v5.0.0 → v5.1.0.
33+
- Status-fallback for 409 remains `KoolbaseStorageConflictError` (path
34+
collisions are the more common case); modern servers always emit
35+
`code`, so the ambiguity only affects very old API responses.
36+
- Pairs with `koolbase_flutter` v6.1.0 (published earlier today). Same
37+
three error types, same code-first mapper extension.
38+
1039
## 5.0.0
1140

1241
### Breaking — storage

README.md

Lines changed: 59 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -322,24 +322,64 @@ try {
322322
path: filename,
323323
file: { uri, name: filename, type: mimeType },
324324
});
325+
} catch (e) catch (e) {
326+
if (e instanceof KoolbaseStorageConflictError) {
327+
const ok = await confirm(${e.path} already exists. Overwrite?);
328+
if (ok) {
329+
await Koolbase.storage.upload({
330+
bucket: 'documents',
331+
path: filename,
332+
file: { uri, name: filename, type: mimeType },
333+
overwrite: true,
334+
});
335+
}
336+
} else {
337+
throw e;
338+
}
339+
}
340+
```
341+
342+
See [Error handling](#error-handling) for the full set of storage errors.
343+
344+
---
345+
346+
### Handling bucket limits
347+
348+
Buckets can be configured at creation time with a total size cap
349+
(`max_size_bytes`), a per-file cap (`max_file_size_bytes`), and a
350+
content-type allowlist (`allowed_mime_types`, supports `image/*`-style
351+
wildcards). The server surfaces violations as typed errors:
352+
353+
````typescript
354+
import {
355+
KoolbaseStorageQuotaError,
356+
KoolbaseStorageFileTooLargeError,
357+
KoolbaseStorageMimeTypeError,
358+
} from '@techfinityedge/koolbase-react-native';
359+
360+
try {
361+
await Koolbase.storage.upload({
362+
bucket: 'user-photos',
363+
path: filename,
364+
file: { uri, name: filename, type: mimeType },
365+
});
325366
} catch (e) {
326-
if (e instanceof KoolbaseStorageConflictError) {
327-
const ok = await confirm(`${e.path} already exists. Overwrite?`);
328-
if (ok) {
329-
await Koolbase.storage.upload({
330-
bucket: 'documents',
331-
path: filename,
332-
file: { uri, name: filename, type: mimeType },
333-
overwrite: true,
334-
});
335-
}
367+
if (e instanceof KoolbaseStorageMimeTypeError) {
368+
showError('That file type is not allowed in this bucket.');
369+
} else if (e instanceof KoolbaseStorageFileTooLargeError) {
370+
showError('That file is too big — pick a smaller one.');
371+
} else if (e instanceof KoolbaseStorageQuotaError) {
372+
showError('This bucket is full — delete some files and try again.');
336373
} else {
337374
throw e;
338375
}
339376
}
340-
```
377+
````
341378
342-
See [Error handling](#error-handling) for the full set of storage errors.
379+
MIME enforcement runs at presign time — no bytes are transferred before
380+
rejection. File-size and quota enforcement run at confirm time; the
381+
server cleans up the underlying R2 object before returning the error,
382+
so nothing leaks.
343383
344384
---
345385
@@ -555,12 +595,13 @@ handling doesn't depend on message text.
555595
All data-layer failures extend `KoolbaseDataError` (which extends `Error`):
556596
557597
| Error | When |
558-
|---|---|
559-
| `KoolbaseConflictError` | A write violates a unique constraint (409). Exposes `.field`. |
560-
| `KoolbaseNotFoundError` | The record or collection doesn't exist (404). |
561-
| `KoolbaseValidationError` | The request was rejected as invalid (400). |
562-
| `KoolbasePermissionError` | An access rule denied the operation (403). |
563-
| `KoolbaseRateLimitError` | The caller is being rate-limited (429). |
598+
| `KoolbaseStorageConflictError` | An upload targets a path that's already taken and `overwrite: false` (409, code `PATH_CONFLICT`). Exposes `.path` — the colliding path. |
599+
| `KoolbaseStorageNotFoundError` | The bucket or object doesn't exist (404). |
600+
| `KoolbaseStorageValidationError` | The request was rejected as invalid — bad path, missing field (400). |
601+
| `KoolbaseStoragePermissionError` | The caller is not allowed to perform the operation (403). |
602+
| `KoolbaseStorageQuotaError` | An upload would push the bucket past its `max_size_bytes` cap (409, code `QUOTA_EXCEEDED`). |
603+
| `KoolbaseStorageFileTooLargeError` | A single file exceeds the bucket's `max_file_size_bytes` cap (413, code `FILE_TOO_LARGE`). |
604+
| `KoolbaseStorageMimeTypeError` | The upload's content-type isn't in the bucket's `allowed_mime_types` allowlist (415, code `MIME_NOT_ALLOWED`). |
564605
565606
```ts
566607
import { KoolbaseConflictError, KoolbaseDataError } from '@techfinityedge/koolbase-react-native';

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@techfinityedge/koolbase-react-native",
3-
"version": "5.0.0",
3+
"version": "5.1.0",
44
"description": "React Native SDK for Koolbase — auth, database, storage, realtime, feature flags, and functions in one package.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",

src/storage-errors.ts

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,71 @@ export class KoolbaseStoragePermissionError extends KoolbaseStorageError {
9696
}
9797
}
9898

99+
/**
100+
* Thrown when an upload would push the bucket past its configured
101+
* `max_size_bytes` quota — the server responds with 409 Conflict and code
102+
* `QUOTA_EXCEEDED`. The server cleans up the underlying R2 object before
103+
* returning; nothing leaks. Catch this to surface a "bucket is full"
104+
* message or prompt the caller to delete older files. The per-bucket
105+
* quota is set at bucket creation time and is currently immutable.
106+
*
107+
* Distinct from {@link KoolbaseStorageConflictError} (which also uses
108+
* 409 but means "path collides"); branch on the error type via
109+
* `instanceof`, not on status.
110+
*/
111+
export class KoolbaseStorageQuotaError extends KoolbaseStorageError {
112+
constructor(message?: string) {
113+
super(message ?? 'Bucket quota exceeded', 'QUOTA_EXCEEDED');
114+
this.name = 'KoolbaseStorageQuotaError';
115+
Object.setPrototypeOf(this, KoolbaseStorageQuotaError.prototype);
116+
}
117+
}
118+
119+
/**
120+
* Thrown when a single file exceeds the bucket's configured
121+
* `max_file_size_bytes` — the server responds with 413 Payload Too Large
122+
* and code `FILE_TOO_LARGE`. The server cleans up the underlying R2
123+
* object before returning. The configured per-file limit lives on the
124+
* bucket record; check `Bucket.maxFileSizeBytes` to surface a clear
125+
* "files must be under X MB" message at the call site.
126+
*/
127+
export class KoolbaseStorageFileTooLargeError extends KoolbaseStorageError {
128+
constructor(message?: string) {
129+
super(message ?? 'File exceeds the bucket maximum file size', 'FILE_TOO_LARGE');
130+
this.name = 'KoolbaseStorageFileTooLargeError';
131+
Object.setPrototypeOf(this, KoolbaseStorageFileTooLargeError.prototype);
132+
}
133+
}
134+
135+
/**
136+
* Thrown when an upload's content-type isn't in the bucket's configured
137+
* `allowed_mime_types` allowlist — the server responds with 415
138+
* Unsupported Media Type and code `MIME_NOT_ALLOWED`. The check runs at
139+
* presign time, so no bytes are transferred before rejection.
140+
*
141+
* Allowlists support `type/*` wildcards (e.g. `image/*` matches every
142+
* image content-type). A bucket with no allowlist configured accepts
143+
* every type.
144+
*/
145+
export class KoolbaseStorageMimeTypeError extends KoolbaseStorageError {
146+
constructor(message?: string) {
147+
super(message ?? 'Content-type not allowed for this bucket', 'MIME_NOT_ALLOWED');
148+
this.name = 'KoolbaseStorageMimeTypeError';
149+
Object.setPrototypeOf(this, KoolbaseStorageMimeTypeError.prototype);
150+
}
151+
}
152+
99153
/**
100154
* Maps a non-2xx storage-layer response to a typed
101155
* {@link KoolbaseStorageError}, preferring the server's stable `code` and
102156
* falling back to the HTTP status for older or uncoded responses. Always
103157
* returns an error to throw.
158+
*
159+
* Status-fallback note: HTTP 409 covers both PATH_CONFLICT and
160+
* QUOTA_EXCEEDED. Without a `code` field, the mapper defaults 409 to
161+
* {@link KoolbaseStorageConflictError} since path collisions are the more
162+
* common case. Modern Koolbase servers always emit `code`, so this only
163+
* matters for very old API responses or non-Koolbase 409s.
104164
*/
105165
export function koolbaseStorageError(
106166
status: number,
@@ -114,12 +174,22 @@ export function koolbaseStorageError(
114174
switch (code) {
115175
case 'PATH_CONFLICT':
116176
return new KoolbaseStorageConflictError(message, body?.path);
177+
case 'QUOTA_EXCEEDED':
178+
return new KoolbaseStorageQuotaError(message);
179+
case 'FILE_TOO_LARGE':
180+
return new KoolbaseStorageFileTooLargeError(message);
181+
case 'MIME_NOT_ALLOWED':
182+
return new KoolbaseStorageMimeTypeError(message);
117183
}
118184

119185
// ─── status fallback (pre-code servers or uncoded paths) ───
120186
switch (status) {
121187
case 409:
122188
return new KoolbaseStorageConflictError(message);
189+
case 413:
190+
return new KoolbaseStorageFileTooLargeError(message);
191+
case 415:
192+
return new KoolbaseStorageMimeTypeError(message);
123193
case 404:
124194
return new KoolbaseStorageNotFoundError(message);
125195
case 403:

0 commit comments

Comments
 (0)