@techfinityedge/koolbase-react-native 5.1.0 → 5.2.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.
@@ -10,7 +10,7 @@ export declare class KoolbaseStorageError extends Error {
10
10
  /**
11
11
  * Thrown when an upload is rejected because an object already exists at
12
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?"
13
+ * `path_conflict`. Catch it to give the user an "overwrite this file?"
14
14
  * prompt, then retry the upload with `overwrite: true`.
15
15
  *
16
16
  * `path` is the colliding path the server rejected, surfaced from the
@@ -67,7 +67,7 @@ export declare class KoolbaseStoragePermissionError extends KoolbaseStorageError
67
67
  /**
68
68
  * Thrown when an upload would push the bucket past its configured
69
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
70
+ * `quota_exceeded`. The server cleans up the underlying R2 object before
71
71
  * returning; nothing leaks. Catch this to surface a "bucket is full"
72
72
  * message or prompt the caller to delete older files. The per-bucket
73
73
  * quota is set at bucket creation time and is currently immutable.
@@ -82,7 +82,7 @@ export declare class KoolbaseStorageQuotaError extends KoolbaseStorageError {
82
82
  /**
83
83
  * Thrown when a single file exceeds the bucket's configured
84
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
85
+ * and code `file_too_large`. The server cleans up the underlying R2
86
86
  * object before returning. The configured per-file limit lives on the
87
87
  * bucket record; check `Bucket.maxFileSizeBytes` to surface a clear
88
88
  * "files must be under X MB" message at the call site.
@@ -93,7 +93,7 @@ export declare class KoolbaseStorageFileTooLargeError extends KoolbaseStorageErr
93
93
  /**
94
94
  * Thrown when an upload's content-type isn't in the bucket's configured
95
95
  * `allowed_mime_types` allowlist — the server responds with 415
96
- * Unsupported Media Type and code `MIME_NOT_ALLOWED`. The check runs at
96
+ * Unsupported Media Type and code `mime_not_allowed`. The check runs at
97
97
  * presign time, so no bytes are transferred before rejection.
98
98
  *
99
99
  * Allowlists support `type/*` wildcards (e.g. `image/*` matches every
@@ -103,14 +103,54 @@ export declare class KoolbaseStorageFileTooLargeError extends KoolbaseStorageErr
103
103
  export declare class KoolbaseStorageMimeTypeError extends KoolbaseStorageError {
104
104
  constructor(message?: string);
105
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
+ }
106
146
  /**
107
147
  * Maps a non-2xx storage-layer response to a typed
108
148
  * {@link KoolbaseStorageError}, preferring the server's stable `code` and
109
149
  * falling back to the HTTP status for older or uncoded responses. Always
110
150
  * returns an error to throw.
111
151
  *
112
- * Status-fallback note: HTTP 409 covers both PATH_CONFLICT and
113
- * QUOTA_EXCEEDED. Without a `code` field, the mapper defaults 409 to
152
+ * Status-fallback note: HTTP 409 covers both path_conflict and
153
+ * quota_exceeded. Without a `code` field, the mapper defaults 409 to
114
154
  * {@link KoolbaseStorageConflictError} since path collisions are the more
115
155
  * common case. Modern Koolbase servers always emit `code`, so this only
116
156
  * matters for very old API responses or non-Koolbase 409s.
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageError = void 0;
3
+ exports.KoolbaseStorageMetadataInvalidError = exports.KoolbaseStorageMimeTypeError = exports.KoolbaseStorageFileTooLargeError = exports.KoolbaseStorageQuotaError = exports.KoolbaseStoragePermissionError = exports.KoolbaseStorageValidationError = exports.KoolbaseStorageNotFoundError = exports.KoolbaseStorageConflictError = exports.KoolbaseStorageError = void 0;
4
4
  exports.koolbaseStorageError = koolbaseStorageError;
5
5
  exports.koolbaseStorageErrorFromResponse = koolbaseStorageErrorFromResponse;
6
6
  /**
@@ -20,7 +20,7 @@ exports.KoolbaseStorageError = KoolbaseStorageError;
20
20
  /**
21
21
  * Thrown when an upload is rejected because an object already exists at
22
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?"
23
+ * `path_conflict`. Catch it to give the user an "overwrite this file?"
24
24
  * prompt, then retry the upload with `overwrite: true`.
25
25
  *
26
26
  * `path` is the colliding path the server rejected, surfaced from the
@@ -49,7 +49,7 @@ exports.KoolbaseStorageError = KoolbaseStorageError;
49
49
  */
50
50
  class KoolbaseStorageConflictError extends KoolbaseStorageError {
51
51
  constructor(message, path) {
52
- super(message ?? 'An object already exists at this path', 'PATH_CONFLICT');
52
+ super(message ?? 'An object already exists at this path', 'path_conflict');
53
53
  this.path = path;
54
54
  this.name = 'KoolbaseStorageConflictError';
55
55
  Object.setPrototypeOf(this, KoolbaseStorageConflictError.prototype);
@@ -97,7 +97,7 @@ exports.KoolbaseStoragePermissionError = KoolbaseStoragePermissionError;
97
97
  /**
98
98
  * Thrown when an upload would push the bucket past its configured
99
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
100
+ * `quota_exceeded`. The server cleans up the underlying R2 object before
101
101
  * returning; nothing leaks. Catch this to surface a "bucket is full"
102
102
  * message or prompt the caller to delete older files. The per-bucket
103
103
  * quota is set at bucket creation time and is currently immutable.
@@ -108,7 +108,7 @@ exports.KoolbaseStoragePermissionError = KoolbaseStoragePermissionError;
108
108
  */
109
109
  class KoolbaseStorageQuotaError extends KoolbaseStorageError {
110
110
  constructor(message) {
111
- super(message ?? 'Bucket quota exceeded', 'QUOTA_EXCEEDED');
111
+ super(message ?? 'Bucket quota exceeded', 'quota_exceeded');
112
112
  this.name = 'KoolbaseStorageQuotaError';
113
113
  Object.setPrototypeOf(this, KoolbaseStorageQuotaError.prototype);
114
114
  }
@@ -117,14 +117,14 @@ exports.KoolbaseStorageQuotaError = KoolbaseStorageQuotaError;
117
117
  /**
118
118
  * Thrown when a single file exceeds the bucket's configured
119
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
120
+ * and code `file_too_large`. The server cleans up the underlying R2
121
121
  * object before returning. The configured per-file limit lives on the
122
122
  * bucket record; check `Bucket.maxFileSizeBytes` to surface a clear
123
123
  * "files must be under X MB" message at the call site.
124
124
  */
125
125
  class KoolbaseStorageFileTooLargeError extends KoolbaseStorageError {
126
126
  constructor(message) {
127
- super(message ?? 'File exceeds the bucket maximum file size', 'FILE_TOO_LARGE');
127
+ super(message ?? 'File exceeds the bucket maximum file size', 'file_too_large');
128
128
  this.name = 'KoolbaseStorageFileTooLargeError';
129
129
  Object.setPrototypeOf(this, KoolbaseStorageFileTooLargeError.prototype);
130
130
  }
@@ -133,7 +133,7 @@ exports.KoolbaseStorageFileTooLargeError = KoolbaseStorageFileTooLargeError;
133
133
  /**
134
134
  * Thrown when an upload's content-type isn't in the bucket's configured
135
135
  * `allowed_mime_types` allowlist — the server responds with 415
136
- * Unsupported Media Type and code `MIME_NOT_ALLOWED`. The check runs at
136
+ * Unsupported Media Type and code `mime_not_allowed`. The check runs at
137
137
  * presign time, so no bytes are transferred before rejection.
138
138
  *
139
139
  * Allowlists support `type/*` wildcards (e.g. `image/*` matches every
@@ -142,20 +142,60 @@ exports.KoolbaseStorageFileTooLargeError = KoolbaseStorageFileTooLargeError;
142
142
  */
143
143
  class KoolbaseStorageMimeTypeError extends KoolbaseStorageError {
144
144
  constructor(message) {
145
- super(message ?? 'Content-type not allowed for this bucket', 'MIME_NOT_ALLOWED');
145
+ super(message ?? 'Content-type not allowed for this bucket', 'mime_not_allowed');
146
146
  this.name = 'KoolbaseStorageMimeTypeError';
147
147
  Object.setPrototypeOf(this, KoolbaseStorageMimeTypeError.prototype);
148
148
  }
149
149
  }
150
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;
151
191
  /**
152
192
  * Maps a non-2xx storage-layer response to a typed
153
193
  * {@link KoolbaseStorageError}, preferring the server's stable `code` and
154
194
  * falling back to the HTTP status for older or uncoded responses. Always
155
195
  * returns an error to throw.
156
196
  *
157
- * Status-fallback note: HTTP 409 covers both PATH_CONFLICT and
158
- * QUOTA_EXCEEDED. Without a `code` field, the mapper defaults 409 to
197
+ * Status-fallback note: HTTP 409 covers both path_conflict and
198
+ * quota_exceeded. Without a `code` field, the mapper defaults 409 to
159
199
  * {@link KoolbaseStorageConflictError} since path collisions are the more
160
200
  * common case. Modern Koolbase servers always emit `code`, so this only
161
201
  * matters for very old API responses or non-Koolbase 409s.
@@ -165,14 +205,16 @@ function koolbaseStorageError(status, body, fallbackMessage = 'Storage request f
165
205
  const message = body?.error ?? fallbackMessage;
166
206
  // ─── code-first ───
167
207
  switch (code) {
168
- case 'PATH_CONFLICT':
208
+ case 'path_conflict':
169
209
  return new KoolbaseStorageConflictError(message, body?.path);
170
- case 'QUOTA_EXCEEDED':
210
+ case 'quota_exceeded':
171
211
  return new KoolbaseStorageQuotaError(message);
172
- case 'FILE_TOO_LARGE':
212
+ case 'file_too_large':
173
213
  return new KoolbaseStorageFileTooLargeError(message);
174
- case 'MIME_NOT_ALLOWED':
214
+ case 'mime_not_allowed':
175
215
  return new KoolbaseStorageMimeTypeError(message);
216
+ case 'metadata_invalid':
217
+ return new KoolbaseStorageMetadataInvalidError(message, body?.detail);
176
218
  }
177
219
  // ─── status fallback (pre-code servers or uncoded paths) ───
178
220
  switch (status) {
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { KoolbaseConfig, UploadOptions, UploadResult } from './types';
1
+ import { KoolbaseConfig, UploadOptions, UploadResult, KoolbaseObject } from './types';
2
2
  /**
3
3
  * Koolbase storage client — uploads, downloads, and deletes via presigned
4
4
  * Cloudflare R2 URLs.
@@ -23,11 +23,52 @@ export declare class KoolbaseStorage {
23
23
  * Set `overwrite: true` for true upsert semantics — silently replace any
24
24
  * existing object at this path.
25
25
  *
26
+ * Pass `options.metadata` to attach arbitrary user-defined key/value pairs
27
+ * to the object at confirm time. Subject to the limits documented on
28
+ * {@link KoolbaseObject.metadata}; violations throw
29
+ * `KoolbaseStorageMetadataInvalidError`. On the `overwrite: true` path the
30
+ * metadata REPLACES any prior metadata at this path (matches GCS semantics).
31
+ * Use {@link updateMetadata} for post-upload merge changes.
32
+ *
26
33
  * **Breaking change in v5.0.0**: the default flipped from silent overwrite
27
34
  * (legacy behavior) to safe-by-default. If you previously relied on uploads
28
35
  * overwriting silently, pass `overwrite: true` explicitly.
29
36
  */
30
37
  upload(options: UploadOptions): Promise<UploadResult>;
38
+ /**
39
+ * Apply a partial metadata update to an existing object. Returns the
40
+ * post-update {@link KoolbaseObject} with the merged metadata.
41
+ *
42
+ * **Merge semantics** (mirrors the server's JSONB merge):
43
+ *
44
+ * - Keys with a non-null string value are SET — added if missing,
45
+ * replacing any existing value at the key otherwise.
46
+ * - Keys with `null` are DELETED from the stored metadata.
47
+ * - Keys ABSENT from `metadata` are untouched — pre-existing entries
48
+ * for those keys remain unchanged.
49
+ *
50
+ * Validation runs server-side against the same rules as upload-time
51
+ * metadata; violations throw `KoolbaseStorageMetadataInvalidError`,
52
+ * whose `detail` field names the failing key and rule. The check is
53
+ * performed against the projected post-merge state, so adding a key
54
+ * that would push the object past the 50-key or 8KB ceiling is
55
+ * rejected before the row is mutated.
56
+ *
57
+ * @example
58
+ * // Add a tag, update an existing key, and drop another in one call:
59
+ * const updated = await Koolbase.storage.updateMetadata(
60
+ * 'photos',
61
+ * 'sunset.jpg',
62
+ * {
63
+ * category: 'landscape', // SET or UPDATE
64
+ * tag: 'sunset', // SET or UPDATE
65
+ * owner: null, // DELETE
66
+ * }
67
+ * );
68
+ * console.log(updated.metadata);
69
+ * // -> { category: 'landscape', tag: 'sunset' }
70
+ */
71
+ updateMetadata(bucket: string, path: string, metadata: Record<string, string | null>): Promise<KoolbaseObject>;
31
72
  /**
32
73
  * Get a signed download URL for a file.
33
74
  */
package/dist/storage.js CHANGED
@@ -33,6 +33,13 @@ class KoolbaseStorage {
33
33
  * Set `overwrite: true` for true upsert semantics — silently replace any
34
34
  * existing object at this path.
35
35
  *
36
+ * Pass `options.metadata` to attach arbitrary user-defined key/value pairs
37
+ * to the object at confirm time. Subject to the limits documented on
38
+ * {@link KoolbaseObject.metadata}; violations throw
39
+ * `KoolbaseStorageMetadataInvalidError`. On the `overwrite: true` path the
40
+ * metadata REPLACES any prior metadata at this path (matches GCS semantics).
41
+ * Use {@link updateMetadata} for post-upload merge changes.
42
+ *
36
43
  * **Breaking change in v5.0.0**: the default flipped from silent overwrite
37
44
  * (legacy behavior) to safe-by-default. If you previously relied on uploads
38
45
  * overwriting silently, pass `overwrite: true` explicitly.
@@ -76,20 +83,28 @@ class KoolbaseStorage {
76
83
  }
77
84
  const etag = uploadRes.headers.get('etag') ?? '';
78
85
  // ─── Step 3: Confirm upload ───
86
+ // Build the body conditionally so the `metadata` field is only sent
87
+ // when the caller passed it — keeps the wire shape clean for callers
88
+ // that don't care, and lets the server's omitempty path treat absent
89
+ // as "no metadata."
90
+ const confirmBody = {
91
+ bucket: options.bucket,
92
+ path: options.path,
93
+ size: fileSize,
94
+ content_type: contentType,
95
+ etag,
96
+ overwrite,
97
+ };
98
+ if (options.metadata !== undefined) {
99
+ confirmBody.metadata = options.metadata;
100
+ }
79
101
  const confirmRes = await fetch(`${this.config.baseUrl}/v1/sdk/storage/confirm`, {
80
102
  method: 'POST',
81
103
  headers: {
82
104
  ...(await this.buildHeaders()),
83
105
  'Content-Type': 'application/json',
84
106
  },
85
- body: JSON.stringify({
86
- bucket: options.bucket,
87
- path: options.path,
88
- size: fileSize,
89
- content_type: contentType,
90
- etag,
91
- overwrite,
92
- }),
107
+ body: JSON.stringify(confirmBody),
93
108
  });
94
109
  if (!confirmRes.ok) {
95
110
  throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(confirmRes, 'Failed to confirm upload');
@@ -100,6 +115,54 @@ class KoolbaseStorage {
100
115
  const downloadUrl = await this.getDownloadUrl(options.bucket, options.path);
101
116
  return { object, downloadUrl };
102
117
  }
118
+ /**
119
+ * Apply a partial metadata update to an existing object. Returns the
120
+ * post-update {@link KoolbaseObject} with the merged metadata.
121
+ *
122
+ * **Merge semantics** (mirrors the server's JSONB merge):
123
+ *
124
+ * - Keys with a non-null string value are SET — added if missing,
125
+ * replacing any existing value at the key otherwise.
126
+ * - Keys with `null` are DELETED from the stored metadata.
127
+ * - Keys ABSENT from `metadata` are untouched — pre-existing entries
128
+ * for those keys remain unchanged.
129
+ *
130
+ * Validation runs server-side against the same rules as upload-time
131
+ * metadata; violations throw `KoolbaseStorageMetadataInvalidError`,
132
+ * whose `detail` field names the failing key and rule. The check is
133
+ * performed against the projected post-merge state, so adding a key
134
+ * that would push the object past the 50-key or 8KB ceiling is
135
+ * rejected before the row is mutated.
136
+ *
137
+ * @example
138
+ * // Add a tag, update an existing key, and drop another in one call:
139
+ * const updated = await Koolbase.storage.updateMetadata(
140
+ * 'photos',
141
+ * 'sunset.jpg',
142
+ * {
143
+ * category: 'landscape', // SET or UPDATE
144
+ * tag: 'sunset', // SET or UPDATE
145
+ * owner: null, // DELETE
146
+ * }
147
+ * );
148
+ * console.log(updated.metadata);
149
+ * // -> { category: 'landscape', tag: 'sunset' }
150
+ */
151
+ async updateMetadata(bucket, path, metadata) {
152
+ const res = await fetch(`${this.config.baseUrl}/v1/sdk/storage/objects/metadata`, {
153
+ method: 'PATCH',
154
+ headers: {
155
+ ...(await this.buildHeaders()),
156
+ 'Content-Type': 'application/json',
157
+ },
158
+ body: JSON.stringify({ bucket, path, metadata }),
159
+ });
160
+ if (!res.ok) {
161
+ throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to update metadata');
162
+ }
163
+ const raw = await res.json();
164
+ return mapObjectFromServer(raw);
165
+ }
103
166
  /**
104
167
  * Get a signed download URL for a file.
105
168
  */
@@ -135,6 +198,9 @@ class KoolbaseStorage {
135
198
  exports.KoolbaseStorage = KoolbaseStorage;
136
199
  /**
137
200
  * Maps the snake_case server JSON to the camelCase {@link KoolbaseObject}.
201
+ * Defensive: missing or null `metadata` (older / non-Koolbase responses)
202
+ * is coerced to an empty object so callers always see a typed
203
+ * `Record<string, string>` rather than null.
138
204
  */
139
205
  function mapObjectFromServer(raw) {
140
206
  return {
@@ -145,6 +211,7 @@ function mapObjectFromServer(raw) {
145
211
  path: raw.path,
146
212
  size: raw.size ?? 0,
147
213
  contentType: raw.content_type ?? null,
214
+ metadata: raw.metadata ?? {},
148
215
  createdAt: raw.created_at,
149
216
  updatedAt: raw.updated_at,
150
217
  };
package/dist/types.d.ts CHANGED
@@ -161,6 +161,20 @@ export interface UploadOptions {
161
161
  * `true` to silently replace the existing object.
162
162
  */
163
163
  overwrite?: boolean;
164
+ /**
165
+ * User-defined key/value metadata to attach to the object at confirm
166
+ * time. Optional — when omitted, the object stores empty metadata `{}`.
167
+ *
168
+ * Subject to server-side validation (≤50 keys, ≤8KB total, keys 1–64
169
+ * chars matching `[a-z0-9_]+`, values ≤1024 chars, leading underscore
170
+ * reserved); violations throw `KoolbaseStorageMetadataInvalidError`.
171
+ *
172
+ * On the `overwrite: true` path, metadata REPLACES any prior metadata
173
+ * at this path (matches GCS semantics — a new upload at a path
174
+ * produces a new object, not a patch of the old). Use `updateMetadata`
175
+ * for post-upload merge changes.
176
+ */
177
+ metadata?: Record<string, string>;
164
178
  onProgress?: (percent: number) => void;
165
179
  }
166
180
  /**
@@ -175,6 +189,14 @@ export interface KoolbaseObject {
175
189
  path: string;
176
190
  size: number;
177
191
  contentType: string | null;
192
+ /**
193
+ * User-defined key/value metadata attached to this object. Always
194
+ * non-null — empty object when no metadata has been set (the server
195
+ * returns `{}` rather than `null` so callers can treat it as a
196
+ * guaranteed object without null checks). Set on upload via
197
+ * `upload({ metadata })` or mutated post-upload via `updateMetadata`.
198
+ */
199
+ metadata: Record<string, string>;
178
200
  /** ISO 8601 timestamp from the server. */
179
201
  createdAt: string;
180
202
  /** ISO 8601 timestamp from the server. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techfinityedge/koolbase-react-native",
3
- "version": "5.1.0",
3
+ "version": "5.2.0",
4
4
  "description": "React Native SDK for Koolbase — auth, database, storage, realtime, feature flags, and functions in one package.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",