@koolbase/react-native 9.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. package/README.md +1025 -0
  2. package/dist/analytics.d.ts +26 -0
  3. package/dist/analytics.js +138 -0
  4. package/dist/apple-auth.d.ts +22 -0
  5. package/dist/apple-auth.js +74 -0
  6. package/dist/auth-errors.d.ts +117 -0
  7. package/dist/auth-errors.js +250 -0
  8. package/dist/auth-storage.d.ts +26 -0
  9. package/dist/auth-storage.js +105 -0
  10. package/dist/auth.d.ts +199 -0
  11. package/dist/auth.js +794 -0
  12. package/dist/cache-store.d.ts +11 -0
  13. package/dist/cache-store.js +136 -0
  14. package/dist/code-push.d.ts +59 -0
  15. package/dist/code-push.js +255 -0
  16. package/dist/database-errors.d.ts +95 -0
  17. package/dist/database-errors.js +173 -0
  18. package/dist/database.d.ts +208 -0
  19. package/dist/database.js +508 -0
  20. package/dist/device-metadata.d.ts +36 -0
  21. package/dist/device-metadata.js +102 -0
  22. package/dist/flags.d.ts +15 -0
  23. package/dist/flags.js +76 -0
  24. package/dist/functions.d.ts +8 -0
  25. package/dist/functions.js +70 -0
  26. package/dist/index.d.ts +45 -0
  27. package/dist/index.js +194 -0
  28. package/dist/logic-engine.d.ts +17 -0
  29. package/dist/logic-engine.js +193 -0
  30. package/dist/messaging.d.ts +20 -0
  31. package/dist/messaging.js +58 -0
  32. package/dist/realtime.d.ts +19 -0
  33. package/dist/realtime.js +148 -0
  34. package/dist/record.d.ts +2 -0
  35. package/dist/record.js +20 -0
  36. package/dist/storage-errors.d.ts +163 -0
  37. package/dist/storage-errors.js +249 -0
  38. package/dist/storage.d.ts +184 -0
  39. package/dist/storage.js +438 -0
  40. package/dist/sync-engine.d.ts +16 -0
  41. package/dist/sync-engine.js +86 -0
  42. package/dist/types.d.ts +470 -0
  43. package/dist/types.js +40 -0
  44. package/package.json +52 -0
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KoolbaseMessaging = void 0;
4
+ // ─── KoolbaseMessaging ────────────────────────────────────────────────────────
5
+ class KoolbaseMessaging {
6
+ constructor(config) {
7
+ this.deviceId = '';
8
+ this.config = config;
9
+ }
10
+ setDeviceId(deviceId) {
11
+ this.deviceId = deviceId;
12
+ }
13
+ // ─── Register token ───────────────────────────────────────────────────────
14
+ async registerToken(options) {
15
+ try {
16
+ const response = await fetch(`${this.config.baseUrl}/v1/messaging/register`, {
17
+ method: 'POST',
18
+ headers: {
19
+ 'Content-Type': 'application/json',
20
+ 'x-api-key': this.config.publicKey,
21
+ },
22
+ body: JSON.stringify({
23
+ device_id: this.deviceId,
24
+ token: options.token,
25
+ platform: options.platform,
26
+ ...(options.userId && { user_id: options.userId }),
27
+ }),
28
+ });
29
+ return response.ok;
30
+ }
31
+ catch {
32
+ return false;
33
+ }
34
+ }
35
+ // ─── Send notification ────────────────────────────────────────────────────
36
+ async send(options) {
37
+ try {
38
+ const response = await fetch(`${this.config.baseUrl}/v1/messaging/send`, {
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ 'x-api-key': this.config.publicKey,
43
+ },
44
+ body: JSON.stringify({
45
+ token: options.to,
46
+ title: options.title,
47
+ body: options.body,
48
+ data: options.data ?? {},
49
+ }),
50
+ });
51
+ return response.ok;
52
+ }
53
+ catch {
54
+ return false;
55
+ }
56
+ }
57
+ }
58
+ exports.KoolbaseMessaging = KoolbaseMessaging;
@@ -0,0 +1,19 @@
1
+ import { KoolbaseConfig, RealtimeCallback } from './types';
2
+ type TokenProvider = () => Promise<string | null>;
3
+ export declare class KoolbaseRealtime {
4
+ private config;
5
+ private getToken;
6
+ private ws;
7
+ private projectId;
8
+ private listeners;
9
+ private reconnectTimer;
10
+ private connecting;
11
+ constructor(config: KoolbaseConfig, getToken: TokenProvider);
12
+ subscribe(collection: string, callback: RealtimeCallback): () => void;
13
+ private connect;
14
+ private sendSubscribe;
15
+ private sendUnsubscribe;
16
+ private scheduleReconnect;
17
+ disconnect(): void;
18
+ }
19
+ export {};
@@ -0,0 +1,148 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KoolbaseRealtime = void 0;
4
+ const record_1 = require("./record");
5
+ const EVENT_TYPE_MAP = {
6
+ 'db.record.created': 'created',
7
+ 'db.record.updated': 'updated',
8
+ 'db.record.deleted': 'deleted',
9
+ };
10
+ function projectIdFromToken(token) {
11
+ try {
12
+ const part = token.split('.')[1];
13
+ if (!part)
14
+ return null;
15
+ const b64 = part.replace(/-/g, '+').replace(/_/g, '/');
16
+ const g = globalThis;
17
+ let json;
18
+ if (typeof g.atob === 'function') {
19
+ const bin = g.atob(b64);
20
+ json = decodeURIComponent(bin.split('').map((c) => '%' + c.charCodeAt(0).toString(16).padStart(2, '0')).join(''));
21
+ }
22
+ else if (g.Buffer) {
23
+ json = g.Buffer.from(b64, 'base64').toString('utf8');
24
+ }
25
+ else {
26
+ return null;
27
+ }
28
+ return JSON.parse(json).project_id ?? null;
29
+ }
30
+ catch {
31
+ return null;
32
+ }
33
+ }
34
+ class KoolbaseRealtime {
35
+ constructor(config, getToken) {
36
+ this.ws = null;
37
+ this.projectId = null;
38
+ this.listeners = new Map();
39
+ this.reconnectTimer = null;
40
+ this.connecting = false;
41
+ this.config = config;
42
+ this.getToken = getToken;
43
+ }
44
+ subscribe(collection, callback) {
45
+ if (!this.listeners.has(collection))
46
+ this.listeners.set(collection, []);
47
+ this.listeners.get(collection).push(callback);
48
+ if (this.ws && this.ws.readyState === WebSocket.OPEN) {
49
+ this.sendSubscribe(collection);
50
+ }
51
+ else {
52
+ void this.connect();
53
+ }
54
+ return () => {
55
+ const callbacks = this.listeners.get(collection) ?? [];
56
+ const i = callbacks.indexOf(callback);
57
+ if (i > -1)
58
+ callbacks.splice(i, 1);
59
+ if (callbacks.length === 0) {
60
+ this.listeners.delete(collection);
61
+ this.sendUnsubscribe(collection);
62
+ }
63
+ };
64
+ }
65
+ async connect() {
66
+ if (this.connecting)
67
+ return;
68
+ if (this.ws && (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING))
69
+ return;
70
+ const token = await this.getToken();
71
+ if (!token) {
72
+ this.scheduleReconnect(); // sign-in may be in flight
73
+ return;
74
+ }
75
+ this.projectId = projectIdFromToken(token);
76
+ this.connecting = true;
77
+ const wsUrl = this.config.baseUrl.replace('https://', 'wss://').replace('http://', 'ws://');
78
+ const ws = new WebSocket(`${wsUrl}/v1/realtime/ws?token=${encodeURIComponent(token)}`);
79
+ this.ws = ws;
80
+ ws.onopen = () => {
81
+ this.connecting = false;
82
+ for (const collection of this.listeners.keys())
83
+ this.sendSubscribe(collection); // (re)subscribe all
84
+ };
85
+ ws.onmessage = (event) => {
86
+ let raw;
87
+ try {
88
+ raw = JSON.parse(event.data);
89
+ }
90
+ catch {
91
+ return;
92
+ }
93
+ const mapped = EVENT_TYPE_MAP[raw?.type];
94
+ if (!mapped)
95
+ return; // ignore subscribed / unsubscribed / error / unknown
96
+ const payload = raw.payload;
97
+ if (!payload || !payload.collection)
98
+ return;
99
+ let msg;
100
+ if (mapped === 'deleted') {
101
+ msg = { type: 'deleted', collection: payload.collection, recordId: payload.record_id };
102
+ }
103
+ else if (payload.record) {
104
+ msg = { type: mapped, collection: payload.collection, record: (0, record_1.recordFromWire)(payload.record) };
105
+ }
106
+ else {
107
+ return;
108
+ }
109
+ (this.listeners.get(payload.collection) ?? []).forEach((cb) => cb(msg));
110
+ };
111
+ ws.onclose = () => {
112
+ this.connecting = false;
113
+ if (this.ws === ws)
114
+ this.ws = null;
115
+ this.scheduleReconnect();
116
+ };
117
+ ws.onerror = () => { };
118
+ }
119
+ sendSubscribe(collection) {
120
+ if (!this.projectId || !this.ws || this.ws.readyState !== WebSocket.OPEN)
121
+ return;
122
+ this.ws.send(JSON.stringify({ action: 'subscribe', project_id: this.projectId, collection }));
123
+ }
124
+ sendUnsubscribe(collection) {
125
+ if (!this.projectId || !this.ws || this.ws.readyState !== WebSocket.OPEN)
126
+ return;
127
+ this.ws.send(JSON.stringify({ action: 'unsubscribe', project_id: this.projectId, collection }));
128
+ }
129
+ scheduleReconnect() {
130
+ if (this.listeners.size === 0 || this.reconnectTimer)
131
+ return;
132
+ this.reconnectTimer = setTimeout(() => {
133
+ this.reconnectTimer = null;
134
+ void this.connect();
135
+ }, 3000);
136
+ }
137
+ disconnect() {
138
+ if (this.reconnectTimer) {
139
+ clearTimeout(this.reconnectTimer);
140
+ this.reconnectTimer = null;
141
+ }
142
+ this.ws?.close();
143
+ this.ws = null;
144
+ this.projectId = null;
145
+ this.listeners.clear();
146
+ }
147
+ }
148
+ exports.KoolbaseRealtime = KoolbaseRealtime;
@@ -0,0 +1,2 @@
1
+ import { KoolbaseRecord } from './types';
2
+ export declare function recordFromWire(raw: Record<string, unknown>): KoolbaseRecord;
package/dist/record.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.recordFromWire = recordFromWire;
4
+ // Converts the flat public wire shape into a KoolbaseRecord.
5
+ // Server sends: { $id, $createdAt, $updatedAt, $collection, $createdBy?, ...fields }
6
+ function recordFromWire(raw) {
7
+ const data = {};
8
+ for (const key of Object.keys(raw)) {
9
+ if (!key.startsWith('$'))
10
+ data[key] = raw[key];
11
+ }
12
+ return {
13
+ id: raw['$id'],
14
+ collection: raw['$collection'],
15
+ createdBy: raw['$createdBy'],
16
+ data,
17
+ createdAt: raw['$createdAt'],
18
+ updatedAt: raw['$updatedAt'],
19
+ };
20
+ }
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Base error type for all Koolbase storage errors. Catchable via
3
+ * `instanceof KoolbaseStorageError` to handle any storage-related failure
4
+ * generically; subclasses let you handle specific cases.
5
+ */
6
+ export declare class KoolbaseStorageError extends Error {
7
+ code?: string;
8
+ constructor(message: string, code?: string);
9
+ }
10
+ /**
11
+ * Thrown when an upload is rejected because an object already exists at
12
+ * the requested path — the server responds with 409 Conflict and code
13
+ * `path_conflict`. Catch it to give the user an "overwrite this file?"
14
+ * prompt, then retry the upload with `overwrite: true`.
15
+ *
16
+ * `path` is the colliding path the server rejected, surfaced from the
17
+ * response body for diagnostics and UI.
18
+ *
19
+ * @example
20
+ * try {
21
+ * await Koolbase.storage.upload({
22
+ * bucket: 'avatars',
23
+ * path: 'me.png',
24
+ * file: { uri, name, type: 'image/png' },
25
+ * });
26
+ * } catch (e) {
27
+ * if (e instanceof KoolbaseStorageConflictError) {
28
+ * const ok = await confirm(`${e.path} already exists. Overwrite?`);
29
+ * if (ok) {
30
+ * await Koolbase.storage.upload({
31
+ * bucket: 'avatars',
32
+ * path: 'me.png',
33
+ * file: { uri, name, type: 'image/png' },
34
+ * overwrite: true,
35
+ * });
36
+ * }
37
+ * }
38
+ * }
39
+ */
40
+ export declare class KoolbaseStorageConflictError extends KoolbaseStorageError {
41
+ path?: string;
42
+ constructor(message?: string, path?: string);
43
+ }
44
+ /**
45
+ * Thrown when the requested bucket or object does not exist — the server
46
+ * responds with 404. Also surfaced for cross-tenant access attempts
47
+ * (Koolbase's 404-over-403 convention prevents enumeration in
48
+ * multi-tenant contexts).
49
+ */
50
+ export declare class KoolbaseStorageNotFoundError extends KoolbaseStorageError {
51
+ constructor(message?: string);
52
+ }
53
+ /**
54
+ * Thrown when the request is rejected as invalid — the server responds
55
+ * with 400 (e.g. a malformed path, missing field, invalid bucket name).
56
+ */
57
+ export declare class KoolbaseStorageValidationError extends KoolbaseStorageError {
58
+ constructor(message?: string);
59
+ }
60
+ /**
61
+ * Thrown when the caller is authenticated but not allowed to perform the
62
+ * storage operation — the server responds with 403.
63
+ */
64
+ export declare class KoolbaseStoragePermissionError extends KoolbaseStorageError {
65
+ constructor(message?: string);
66
+ }
67
+ /**
68
+ * Thrown when an upload would push the bucket past its configured
69
+ * `max_size_bytes` quota — the server responds with 409 Conflict and code
70
+ * `quota_exceeded`. The server cleans up the underlying R2 object before
71
+ * returning; nothing leaks. Catch this to surface a "bucket is full"
72
+ * message or prompt the caller to delete older files. The per-bucket
73
+ * quota is set at bucket creation time and is currently immutable.
74
+ *
75
+ * Distinct from {@link KoolbaseStorageConflictError} (which also uses
76
+ * 409 but means "path collides"); branch on the error type via
77
+ * `instanceof`, not on status.
78
+ */
79
+ export declare class KoolbaseStorageQuotaError extends KoolbaseStorageError {
80
+ constructor(message?: string);
81
+ }
82
+ /**
83
+ * Thrown when a single file exceeds the bucket's configured
84
+ * `max_file_size_bytes` — the server responds with 413 Payload Too Large
85
+ * and code `file_too_large`. The server cleans up the underlying R2
86
+ * object before returning. The configured per-file limit lives on the
87
+ * bucket record; check `Bucket.maxFileSizeBytes` to surface a clear
88
+ * "files must be under X MB" message at the call site.
89
+ */
90
+ export declare class KoolbaseStorageFileTooLargeError extends KoolbaseStorageError {
91
+ constructor(message?: string);
92
+ }
93
+ /**
94
+ * Thrown when an upload's content-type isn't in the bucket's configured
95
+ * `allowed_mime_types` allowlist — the server responds with 415
96
+ * Unsupported Media Type and code `mime_not_allowed`. The check runs at
97
+ * presign time, so no bytes are transferred before rejection.
98
+ *
99
+ * Allowlists support `type/*` wildcards (e.g. `image/*` matches every
100
+ * image content-type). A bucket with no allowlist configured accepts
101
+ * every type.
102
+ */
103
+ export declare class KoolbaseStorageMimeTypeError extends KoolbaseStorageError {
104
+ constructor(message?: string);
105
+ }
106
+ /**
107
+ * Thrown when an object metadata payload (either at upload-confirm time
108
+ * or via `updateMetadata`) fails server-side validation — the server
109
+ * responds with 400 and code `metadata_invalid`.
110
+ *
111
+ * The `detail` field carries the specific reason from the server — e.g.
112
+ * `'key "foo bar": must match [a-z0-9_]+'`, `'exceeds 50 keys (got 53)'`,
113
+ * or `'exceeds 8192 bytes total (sum of all key + value lengths)'`. The
114
+ * detail names the failing key and rule so callers can fix the offending
115
+ * entry without guessing what shape rule was violated.
116
+ *
117
+ * Validation rules (enforced server-side):
118
+ * - At most 50 keys per object.
119
+ * - At most 8KB total (sum of byte lengths across all keys + values).
120
+ * - Keys: 1–64 chars, must match `[a-z0-9_]+`.
121
+ * - Keys with a leading underscore are reserved for system use.
122
+ * - Values: at most 1024 chars each.
123
+ *
124
+ * @example
125
+ * try {
126
+ * await Koolbase.storage.updateMetadata('photos', 'sunset.jpg', {
127
+ * tag: 'sunset',
128
+ * 'BAD KEY': 'oops',
129
+ * });
130
+ * } catch (e) {
131
+ * if (e instanceof KoolbaseStorageMetadataInvalidError) {
132
+ * console.warn('Metadata rejected:', e.detail);
133
+ * // -> 'Metadata rejected: key "BAD KEY": must match [a-z0-9_]+'
134
+ * }
135
+ * }
136
+ */
137
+ export declare class KoolbaseStorageMetadataInvalidError extends KoolbaseStorageError {
138
+ /**
139
+ * The specific validation failure reported by the server. Names the
140
+ * failing key (when applicable) and the rule that was violated.
141
+ * Surface this directly to developer logs or user-facing UI.
142
+ */
143
+ detail?: string;
144
+ constructor(message?: string, detail?: string);
145
+ }
146
+ /**
147
+ * Maps a non-2xx storage-layer response to a typed
148
+ * {@link KoolbaseStorageError}, preferring the server's stable `code` and
149
+ * falling back to the HTTP status for older or uncoded responses. Always
150
+ * returns an error to throw.
151
+ *
152
+ * Status-fallback note: HTTP 409 covers both path_conflict and
153
+ * quota_exceeded. Without a `code` field, the mapper defaults 409 to
154
+ * {@link KoolbaseStorageConflictError} since path collisions are the more
155
+ * common case. Modern Koolbase servers always emit `code`, so this only
156
+ * matters for very old API responses or non-Koolbase 409s.
157
+ */
158
+ export declare function koolbaseStorageError(status: number, body: any, fallbackMessage?: string): KoolbaseStorageError;
159
+ /**
160
+ * Convenience wrapper over {@link koolbaseStorageError} that decodes the
161
+ * response body for you. Use at call sites that have the raw `Response`.
162
+ */
163
+ export declare function koolbaseStorageErrorFromResponse(res: Response, fallbackMessage?: string): Promise<KoolbaseStorageError>;
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.KoolbaseStorageMetadataInvalidError = exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageError = void 0;
4
+ exports.koolbaseStorageError = koolbaseStorageError;
5
+ exports.koolbaseStorageErrorFromResponse = koolbaseStorageErrorFromResponse;
6
+ /**
7
+ * Base error type for all Koolbase storage errors. Catchable via
8
+ * `instanceof KoolbaseStorageError` to handle any storage-related failure
9
+ * generically; subclasses let you handle specific cases.
10
+ */
11
+ class KoolbaseStorageError extends Error {
12
+ constructor(message, code) {
13
+ super(message);
14
+ this.code = code;
15
+ this.name = 'KoolbaseStorageError';
16
+ Object.setPrototypeOf(this, KoolbaseStorageError.prototype);
17
+ }
18
+ }
19
+ exports.KoolbaseStorageError = KoolbaseStorageError;
20
+ /**
21
+ * Thrown when an upload is rejected because an object already exists at
22
+ * the requested path — the server responds with 409 Conflict and code
23
+ * `path_conflict`. Catch it to give the user an "overwrite this file?"
24
+ * prompt, then retry the upload with `overwrite: true`.
25
+ *
26
+ * `path` is the colliding path the server rejected, surfaced from the
27
+ * response body for diagnostics and UI.
28
+ *
29
+ * @example
30
+ * try {
31
+ * await Koolbase.storage.upload({
32
+ * bucket: 'avatars',
33
+ * path: 'me.png',
34
+ * file: { uri, name, type: 'image/png' },
35
+ * });
36
+ * } catch (e) {
37
+ * if (e instanceof KoolbaseStorageConflictError) {
38
+ * const ok = await confirm(`${e.path} already exists. Overwrite?`);
39
+ * if (ok) {
40
+ * await Koolbase.storage.upload({
41
+ * bucket: 'avatars',
42
+ * path: 'me.png',
43
+ * file: { uri, name, type: 'image/png' },
44
+ * overwrite: true,
45
+ * });
46
+ * }
47
+ * }
48
+ * }
49
+ */
50
+ class KoolbaseStorageConflictError extends KoolbaseStorageError {
51
+ constructor(message, path) {
52
+ super(message ?? 'An object already exists at this path', 'path_conflict');
53
+ this.path = path;
54
+ this.name = 'KoolbaseStorageConflictError';
55
+ Object.setPrototypeOf(this, KoolbaseStorageConflictError.prototype);
56
+ }
57
+ }
58
+ exports.KoolbaseStorageConflictError = KoolbaseStorageConflictError;
59
+ /**
60
+ * Thrown when the requested bucket or object does not exist — the server
61
+ * responds with 404. Also surfaced for cross-tenant access attempts
62
+ * (Koolbase's 404-over-403 convention prevents enumeration in
63
+ * multi-tenant contexts).
64
+ */
65
+ class KoolbaseStorageNotFoundError extends KoolbaseStorageError {
66
+ constructor(message) {
67
+ super(message ?? 'The requested bucket or object was not found', 'not_found');
68
+ this.name = 'KoolbaseStorageNotFoundError';
69
+ Object.setPrototypeOf(this, KoolbaseStorageNotFoundError.prototype);
70
+ }
71
+ }
72
+ exports.KoolbaseStorageNotFoundError = KoolbaseStorageNotFoundError;
73
+ /**
74
+ * Thrown when the request is rejected as invalid — the server responds
75
+ * with 400 (e.g. a malformed path, missing field, invalid bucket name).
76
+ */
77
+ class KoolbaseStorageValidationError extends KoolbaseStorageError {
78
+ constructor(message) {
79
+ super(message ?? 'The storage request was invalid', 'validation_error');
80
+ this.name = 'KoolbaseStorageValidationError';
81
+ Object.setPrototypeOf(this, KoolbaseStorageValidationError.prototype);
82
+ }
83
+ }
84
+ exports.KoolbaseStorageValidationError = KoolbaseStorageValidationError;
85
+ /**
86
+ * Thrown when the caller is authenticated but not allowed to perform the
87
+ * storage operation — the server responds with 403.
88
+ */
89
+ class KoolbaseStoragePermissionError extends KoolbaseStorageError {
90
+ constructor(message) {
91
+ super(message ?? 'You do not have permission to perform this storage action', 'permission_denied');
92
+ this.name = 'KoolbaseStoragePermissionError';
93
+ Object.setPrototypeOf(this, KoolbaseStoragePermissionError.prototype);
94
+ }
95
+ }
96
+ exports.KoolbaseStoragePermissionError = KoolbaseStoragePermissionError;
97
+ /**
98
+ * Thrown when an upload would push the bucket past its configured
99
+ * `max_size_bytes` quota — the server responds with 409 Conflict and code
100
+ * `quota_exceeded`. The server cleans up the underlying R2 object before
101
+ * returning; nothing leaks. Catch this to surface a "bucket is full"
102
+ * message or prompt the caller to delete older files. The per-bucket
103
+ * quota is set at bucket creation time and is currently immutable.
104
+ *
105
+ * Distinct from {@link KoolbaseStorageConflictError} (which also uses
106
+ * 409 but means "path collides"); branch on the error type via
107
+ * `instanceof`, not on status.
108
+ */
109
+ class KoolbaseStorageQuotaError extends KoolbaseStorageError {
110
+ constructor(message) {
111
+ super(message ?? 'Bucket quota exceeded', 'quota_exceeded');
112
+ this.name = 'KoolbaseStorageQuotaError';
113
+ Object.setPrototypeOf(this, KoolbaseStorageQuotaError.prototype);
114
+ }
115
+ }
116
+ exports.KoolbaseStorageQuotaError = KoolbaseStorageQuotaError;
117
+ /**
118
+ * Thrown when a single file exceeds the bucket's configured
119
+ * `max_file_size_bytes` — the server responds with 413 Payload Too Large
120
+ * and code `file_too_large`. The server cleans up the underlying R2
121
+ * object before returning. The configured per-file limit lives on the
122
+ * bucket record; check `Bucket.maxFileSizeBytes` to surface a clear
123
+ * "files must be under X MB" message at the call site.
124
+ */
125
+ class KoolbaseStorageFileTooLargeError extends KoolbaseStorageError {
126
+ constructor(message) {
127
+ super(message ?? 'File exceeds the bucket maximum file size', 'file_too_large');
128
+ this.name = 'KoolbaseStorageFileTooLargeError';
129
+ Object.setPrototypeOf(this, KoolbaseStorageFileTooLargeError.prototype);
130
+ }
131
+ }
132
+ exports.KoolbaseStorageFileTooLargeError = KoolbaseStorageFileTooLargeError;
133
+ /**
134
+ * Thrown when an upload's content-type isn't in the bucket's configured
135
+ * `allowed_mime_types` allowlist — the server responds with 415
136
+ * Unsupported Media Type and code `mime_not_allowed`. The check runs at
137
+ * presign time, so no bytes are transferred before rejection.
138
+ *
139
+ * Allowlists support `type/*` wildcards (e.g. `image/*` matches every
140
+ * image content-type). A bucket with no allowlist configured accepts
141
+ * every type.
142
+ */
143
+ class KoolbaseStorageMimeTypeError extends KoolbaseStorageError {
144
+ constructor(message) {
145
+ super(message ?? 'Content-type not allowed for this bucket', 'mime_not_allowed');
146
+ this.name = 'KoolbaseStorageMimeTypeError';
147
+ Object.setPrototypeOf(this, KoolbaseStorageMimeTypeError.prototype);
148
+ }
149
+ }
150
+ exports.KoolbaseStorageMimeTypeError = KoolbaseStorageMimeTypeError;
151
+ /**
152
+ * Thrown when an object metadata payload (either at upload-confirm time
153
+ * or via `updateMetadata`) fails server-side validation — the server
154
+ * responds with 400 and code `metadata_invalid`.
155
+ *
156
+ * The `detail` field carries the specific reason from the server — e.g.
157
+ * `'key "foo bar": must match [a-z0-9_]+'`, `'exceeds 50 keys (got 53)'`,
158
+ * or `'exceeds 8192 bytes total (sum of all key + value lengths)'`. The
159
+ * detail names the failing key and rule so callers can fix the offending
160
+ * entry without guessing what shape rule was violated.
161
+ *
162
+ * Validation rules (enforced server-side):
163
+ * - At most 50 keys per object.
164
+ * - At most 8KB total (sum of byte lengths across all keys + values).
165
+ * - Keys: 1–64 chars, must match `[a-z0-9_]+`.
166
+ * - Keys with a leading underscore are reserved for system use.
167
+ * - Values: at most 1024 chars each.
168
+ *
169
+ * @example
170
+ * try {
171
+ * await Koolbase.storage.updateMetadata('photos', 'sunset.jpg', {
172
+ * tag: 'sunset',
173
+ * 'BAD KEY': 'oops',
174
+ * });
175
+ * } catch (e) {
176
+ * if (e instanceof KoolbaseStorageMetadataInvalidError) {
177
+ * console.warn('Metadata rejected:', e.detail);
178
+ * // -> 'Metadata rejected: key "BAD KEY": must match [a-z0-9_]+'
179
+ * }
180
+ * }
181
+ */
182
+ class KoolbaseStorageMetadataInvalidError extends KoolbaseStorageError {
183
+ constructor(message, detail) {
184
+ super(message ?? 'Metadata payload is invalid', 'metadata_invalid');
185
+ this.detail = detail;
186
+ this.name = 'KoolbaseStorageMetadataInvalidError';
187
+ Object.setPrototypeOf(this, KoolbaseStorageMetadataInvalidError.prototype);
188
+ }
189
+ }
190
+ exports.KoolbaseStorageMetadataInvalidError = KoolbaseStorageMetadataInvalidError;
191
+ /**
192
+ * Maps a non-2xx storage-layer response to a typed
193
+ * {@link KoolbaseStorageError}, preferring the server's stable `code` and
194
+ * falling back to the HTTP status for older or uncoded responses. Always
195
+ * returns an error to throw.
196
+ *
197
+ * Status-fallback note: HTTP 409 covers both path_conflict and
198
+ * quota_exceeded. Without a `code` field, the mapper defaults 409 to
199
+ * {@link KoolbaseStorageConflictError} since path collisions are the more
200
+ * common case. Modern Koolbase servers always emit `code`, so this only
201
+ * matters for very old API responses or non-Koolbase 409s.
202
+ */
203
+ function koolbaseStorageError(status, body, fallbackMessage = 'Storage request failed') {
204
+ const code = body?.code;
205
+ const message = body?.error ?? fallbackMessage;
206
+ // ─── code-first ───
207
+ switch (code) {
208
+ case 'path_conflict':
209
+ return new KoolbaseStorageConflictError(message, body?.path);
210
+ case 'quota_exceeded':
211
+ return new KoolbaseStorageQuotaError(message);
212
+ case 'file_too_large':
213
+ return new KoolbaseStorageFileTooLargeError(message);
214
+ case 'mime_not_allowed':
215
+ return new KoolbaseStorageMimeTypeError(message);
216
+ case 'metadata_invalid':
217
+ return new KoolbaseStorageMetadataInvalidError(message, body?.detail);
218
+ }
219
+ // ─── status fallback (pre-code servers or uncoded paths) ───
220
+ switch (status) {
221
+ case 409:
222
+ return new KoolbaseStorageConflictError(message);
223
+ case 413:
224
+ return new KoolbaseStorageFileTooLargeError(message);
225
+ case 415:
226
+ return new KoolbaseStorageMimeTypeError(message);
227
+ case 404:
228
+ return new KoolbaseStorageNotFoundError(message);
229
+ case 403:
230
+ return new KoolbaseStoragePermissionError(message);
231
+ case 400:
232
+ return new KoolbaseStorageValidationError(message);
233
+ }
234
+ return new KoolbaseStorageError(message, code);
235
+ }
236
+ /**
237
+ * Convenience wrapper over {@link koolbaseStorageError} that decodes the
238
+ * response body for you. Use at call sites that have the raw `Response`.
239
+ */
240
+ async function koolbaseStorageErrorFromResponse(res, fallbackMessage = 'Storage request failed') {
241
+ let body = {};
242
+ try {
243
+ body = await res.json();
244
+ }
245
+ catch (_) {
246
+ // body wasn't JSON — fall through with empty object
247
+ }
248
+ return koolbaseStorageError(res.status, body, fallbackMessage);
249
+ }