@techfinityedge/koolbase-react-native 5.1.1 → 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.
@@ -103,6 +103,46 @@ 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
@@ -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
  /**
@@ -148,6 +148,46 @@ class KoolbaseStorageMimeTypeError extends KoolbaseStorageError {
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
@@ -173,6 +213,8 @@ function koolbaseStorageError(status, body, fallbackMessage = 'Storage request f
173
213
  return new KoolbaseStorageFileTooLargeError(message);
174
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.1",
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",