@techfinityedge/koolbase-react-native 5.4.0 → 5.5.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/README.md CHANGED
@@ -485,6 +485,64 @@ so nothing leaks.
485
485
 
486
486
  ---
487
487
 
488
+ ### Object versioning
489
+
490
+ For buckets with versioning enabled, every overwrite preserves the prior
491
+ content as a history version, and deletes are soft (recoverable until
492
+ force-purged). Enable versioning on a bucket from the dashboard.
493
+
494
+ ```typescript
495
+ // List all versions of a path, newest first
496
+ const versions = await Koolbase.storage.listVersions('documents', 'contract.pdf');
497
+
498
+ for (const v of versions) {
499
+ console.log(`${v.versionId}: size=${v.size} isCurrent=${v.isCurrent}`);
500
+ }
501
+
502
+ // Download a specific historical version
503
+ const url = await Koolbase.storage.getDownloadUrl(
504
+ 'documents',
505
+ 'contract.pdf',
506
+ '019e98ed-eed6-7e71-...',
507
+ );
508
+
509
+ // Bring a history version back as current
510
+ // (the existing current is snapshotted to history first)
511
+ const restored = await Koolbase.storage.restoreVersion(
512
+ 'documents',
513
+ 'contract.pdf',
514
+ '019e98ed-eed6-7e71-...',
515
+ );
516
+
517
+ // Hard-remove a single history version (row + R2 bytes)
518
+ await Koolbase.storage.purgeVersion(
519
+ 'documents',
520
+ 'contract.pdf',
521
+ 'old-version-id',
522
+ );
523
+
524
+ // Wipe the entire timeline for a path - every version, every R2 key
525
+ await Koolbase.storage.delete('documents', 'contract.pdf', true);
526
+ ```
527
+
528
+ A few behaviors worth knowing:
529
+
530
+ - **Overwrite snapshots automatically.** Upload to a path that already
531
+ exists in a versioned bucket and the prior bytes are preserved as
532
+ history; the upload becomes the new current.
533
+ - **Delete is soft by default.** On a versioned bucket, `delete`
534
+ snapshots the current content and records a delete marker. The
535
+ content is still recoverable via `restoreVersion` until force-purged.
536
+ - **Restore is itself a versioned event.** The previously-current row
537
+ gets snapshotted before the target's bytes overwrite canonical. The
538
+ restored row gets a fresh `versionId`; the target stays in history at
539
+ its original id - so you can always undo a restore.
540
+ - **Delete markers can be listed but not downloaded.** A marker has
541
+ `size === 0`, `isDeleteMarker === true`, and no bytes. Calling
542
+ `getDownloadUrl` with a marker's `versionId` throws.
543
+
544
+ ---
545
+
488
546
  ## Realtime
489
547
 
490
548
  Subscribe to live changes on a collection. Uses the signed-in user's session, so
@@ -771,7 +829,7 @@ try {
771
829
 
772
830
  - Authentication: email + password, Apple Sign-In, Google Sign-In, phone + OTP
773
831
  - Database with offline-first cache, realtime subscriptions, and populate
774
- - Storage with presigned uploads and downloads, safe-by-default conflict handling
832
+ - Storage with presigned uploads and downloads, safe-by-default conflict handling, image transforms, object versioning (history + restore + soft-delete)
775
833
  - Realtime subscriptions over WebSocket
776
834
  - Authenticated functions (`ctx.auth` exposes the caller automatically)
777
835
  - Feature flags and remote config
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { KoolbaseConfig, UploadOptions, UploadResult, KoolbaseObject, KoolbaseImageTransform } from './types';
1
+ import { KoolbaseConfig, UploadOptions, UploadResult, KoolbaseObject, KoolbaseObjectVersion, KoolbaseImageTransform } from './types';
2
2
  /**
3
3
  * Koolbase storage client — uploads, downloads, and deletes via presigned
4
4
  * Cloudflare R2 URLs.
@@ -72,7 +72,7 @@ export declare class KoolbaseStorage {
72
72
  /**
73
73
  * Get a signed download URL for a file.
74
74
  */
75
- getDownloadUrl(bucket: string, path: string): Promise<string>;
75
+ getDownloadUrl(bucket: string, path: string, versionId?: string): Promise<string>;
76
76
  /**
77
77
  * Build the stable public CDN URL for a file in a public bucket.
78
78
  *
@@ -145,5 +145,40 @@ export declare class KoolbaseStorage {
145
145
  /**
146
146
  * Delete a file from a bucket.
147
147
  */
148
- delete(bucket: string, path: string): Promise<void>;
148
+ delete(bucket: string, path: string, forcePurge?: boolean): Promise<void>;
149
+ /**
150
+ * List all versions of a file path, newest-first. Returns a flat list
151
+ * mixing the current row (with `isCurrent: true`) and all history
152
+ * rows. Delete markers are included so callers can render the full
153
+ * timeline; filter client-side to hide them if the UI only wants
154
+ * restorable versions.
155
+ *
156
+ * Returns an empty array (not an error) when the path has no history
157
+ * and no current row.
158
+ */
159
+ listVersions(bucket: string, path: string): Promise<KoolbaseObjectVersion[]>;
160
+ /**
161
+ * Fetch metadata for a single version by id. Works against both the
162
+ * current row and any history row — the response's `isCurrent` tells
163
+ * you which.
164
+ */
165
+ getVersion(bucket: string, path: string, versionId: string): Promise<KoolbaseObjectVersion>;
166
+ /**
167
+ * Bring a history version back as the current version. The
168
+ * previously-current row (if any) is snapshotted into history first,
169
+ * so this operation is itself a versioned event you can undo. The
170
+ * restored row gets a freshly-minted version_id; the target stays in
171
+ * history at its original version_id.
172
+ *
173
+ * Throws if the bucket has versioning off, if the target is the
174
+ * already-current version, or if the target is a delete marker.
175
+ */
176
+ restoreVersion(bucket: string, path: string, versionId: string): Promise<KoolbaseObject>;
177
+ /**
178
+ * Hard-remove a single history version — both the metadata row and
179
+ * the .versions/ R2 bytes (or just the row, for delete markers).
180
+ * Refuses to operate on the current version; use {@link delete} with
181
+ * `forcePurge: true` to wipe everything for a path.
182
+ */
183
+ purgeVersion(bucket: string, path: string, versionId: string): Promise<void>;
149
184
  }
package/dist/storage.js CHANGED
@@ -195,9 +195,12 @@ class KoolbaseStorage {
195
195
  /**
196
196
  * Get a signed download URL for a file.
197
197
  */
198
- async getDownloadUrl(bucket, path) {
199
- const url = `${this.config.baseUrl}/v1/sdk/storage/download-url` +
198
+ async getDownloadUrl(bucket, path, versionId) {
199
+ let url = `${this.config.baseUrl}/v1/sdk/storage/download-url` +
200
200
  `?bucket=${encodeURIComponent(bucket)}&path=${encodeURIComponent(path)}`;
201
+ if (versionId) {
202
+ url += `&version_id=${encodeURIComponent(versionId)}`;
203
+ }
201
204
  const res = await fetch(url, { headers: await this.buildHeaders() });
202
205
  if (!res.ok) {
203
206
  throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to get download URL');
@@ -289,8 +292,11 @@ class KoolbaseStorage {
289
292
  /**
290
293
  * Delete a file from a bucket.
291
294
  */
292
- async delete(bucket, path) {
293
- const res = await fetch(`${this.config.baseUrl}/v1/sdk/storage/object`, {
295
+ async delete(bucket, path, forcePurge) {
296
+ const url = forcePurge
297
+ ? `${this.config.baseUrl}/v1/sdk/storage/object?force_purge=true`
298
+ : `${this.config.baseUrl}/v1/sdk/storage/object`;
299
+ const res = await fetch(url, {
294
300
  method: 'DELETE',
295
301
  headers: {
296
302
  ...(await this.buildHeaders()),
@@ -304,6 +310,82 @@ class KoolbaseStorage {
304
310
  throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to delete file');
305
311
  }
306
312
  }
313
+ /**
314
+ * List all versions of a file path, newest-first. Returns a flat list
315
+ * mixing the current row (with `isCurrent: true`) and all history
316
+ * rows. Delete markers are included so callers can render the full
317
+ * timeline; filter client-side to hide them if the UI only wants
318
+ * restorable versions.
319
+ *
320
+ * Returns an empty array (not an error) when the path has no history
321
+ * and no current row.
322
+ */
323
+ async listVersions(bucket, path) {
324
+ const url = `${this.config.baseUrl}/v1/sdk/storage/object-versions` +
325
+ `?bucket=${encodeURIComponent(bucket)}&path=${encodeURIComponent(path)}`;
326
+ const res = await fetch(url, { headers: await this.buildHeaders() });
327
+ if (!res.ok) {
328
+ throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to list versions');
329
+ }
330
+ const data = (await res.json());
331
+ const list = Array.isArray(data.versions) ? data.versions : [];
332
+ return list.map((v) => fromVersionJson(v));
333
+ }
334
+ /**
335
+ * Fetch metadata for a single version by id. Works against both the
336
+ * current row and any history row — the response's `isCurrent` tells
337
+ * you which.
338
+ */
339
+ async getVersion(bucket, path, versionId) {
340
+ const url = `${this.config.baseUrl}/v1/sdk/storage/object-versions/${encodeURIComponent(versionId)}` +
341
+ `?bucket=${encodeURIComponent(bucket)}&path=${encodeURIComponent(path)}`;
342
+ const res = await fetch(url, { headers: await this.buildHeaders() });
343
+ if (!res.ok) {
344
+ throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to fetch version');
345
+ }
346
+ return fromVersionJson((await res.json()));
347
+ }
348
+ /**
349
+ * Bring a history version back as the current version. The
350
+ * previously-current row (if any) is snapshotted into history first,
351
+ * so this operation is itself a versioned event you can undo. The
352
+ * restored row gets a freshly-minted version_id; the target stays in
353
+ * history at its original version_id.
354
+ *
355
+ * Throws if the bucket has versioning off, if the target is the
356
+ * already-current version, or if the target is a delete marker.
357
+ */
358
+ async restoreVersion(bucket, path, versionId) {
359
+ const url = `${this.config.baseUrl}/v1/sdk/storage/object-versions/${encodeURIComponent(versionId)}/restore` +
360
+ `?bucket=${encodeURIComponent(bucket)}&path=${encodeURIComponent(path)}`;
361
+ const res = await fetch(url, {
362
+ method: 'POST',
363
+ headers: await this.buildHeaders(),
364
+ });
365
+ if (!res.ok) {
366
+ throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to restore version');
367
+ }
368
+ return mapObjectFromServer(await res.json());
369
+ }
370
+ /**
371
+ * Hard-remove a single history version — both the metadata row and
372
+ * the .versions/ R2 bytes (or just the row, for delete markers).
373
+ * Refuses to operate on the current version; use {@link delete} with
374
+ * `forcePurge: true` to wipe everything for a path.
375
+ */
376
+ async purgeVersion(bucket, path, versionId) {
377
+ const url = `${this.config.baseUrl}/v1/sdk/storage/object-versions/${encodeURIComponent(versionId)}` +
378
+ `?bucket=${encodeURIComponent(bucket)}&path=${encodeURIComponent(path)}`;
379
+ const res = await fetch(url, {
380
+ method: 'DELETE',
381
+ headers: await this.buildHeaders(),
382
+ });
383
+ if (res.status === 204)
384
+ return;
385
+ if (!res.ok) {
386
+ throw await (0, storage_errors_1.koolbaseStorageErrorFromResponse)(res, 'Failed to purge version');
387
+ }
388
+ }
307
389
  }
308
390
  exports.KoolbaseStorage = KoolbaseStorage;
309
391
  /**
@@ -327,3 +409,30 @@ function mapObjectFromServer(raw) {
327
409
  updatedAt: raw.updated_at,
328
410
  };
329
411
  }
412
+ /**
413
+ * Maps the snake_case server JSON of a version row to the camelCase
414
+ * {@link KoolbaseObjectVersion}. Mirrors `fromObjectJson` shape.
415
+ */
416
+ function fromVersionJson(j) {
417
+ const rawMeta = j.metadata;
418
+ const metadata = {};
419
+ if (rawMeta && typeof rawMeta === 'object') {
420
+ for (const [k, v] of Object.entries(rawMeta)) {
421
+ if (typeof v === 'string')
422
+ metadata[k] = v;
423
+ }
424
+ }
425
+ return {
426
+ versionId: j.version_id ?? null,
427
+ path: j.path,
428
+ size: Number(j.size ?? 0),
429
+ contentType: j.content_type ?? null,
430
+ etag: j.etag ?? null,
431
+ metadata,
432
+ r2Bucket: j.r2_bucket ?? '',
433
+ userId: j.user_id ?? null,
434
+ isDeleteMarker: Boolean(j.is_delete_marker),
435
+ isCurrent: Boolean(j.is_current),
436
+ createdAt: j.created_at,
437
+ };
438
+ }
package/dist/types.d.ts CHANGED
@@ -212,6 +212,48 @@ export interface KoolbaseObject {
212
212
  /** ISO 8601 timestamp from the server. */
213
213
  updatedAt: string;
214
214
  }
215
+ /**
216
+ * One entry in an object's version timeline. Covers both the current
217
+ * row (when {@link isCurrent} is true) and every history row, including
218
+ * soft-delete markers (when {@link isDeleteMarker} is true — size 0, no
219
+ * fetchable bytes). Returned from {@link KoolbaseStorage.listVersions}
220
+ * and {@link KoolbaseStorage.getVersion}; the underlying bytes are
221
+ * downloadable via {@link KoolbaseStorage.getDownloadUrl} with the
222
+ * `versionId` argument.
223
+ *
224
+ * `versionId` may be null only on legacy rows uploaded before versioning
225
+ * was enabled on the bucket — for those, {@link isCurrent} is true and
226
+ * the row carries no history identity yet (gets backfilled on the next
227
+ * overwrite).
228
+ */
229
+ export interface KoolbaseObjectVersion {
230
+ versionId: string | null;
231
+ path: string;
232
+ size: number;
233
+ contentType: string | null;
234
+ etag: string | null;
235
+ metadata: Record<string, string>;
236
+ r2Bucket: string;
237
+ userId: string | null;
238
+ /**
239
+ * True for a tombstone row recording a soft-delete event. Size is 0
240
+ * and there are no R2 bytes — treat as "the path was deleted at this
241
+ * time" rather than fetchable content.
242
+ */
243
+ isDeleteMarker: boolean;
244
+ /**
245
+ * True for the row that currently lives in `storage_objects` (i.e.
246
+ * what a no-versionId download returns). False for everything in
247
+ * `storage_object_versions`.
248
+ */
249
+ isCurrent: boolean;
250
+ /**
251
+ * For the current row this is the time the current version became
252
+ * current (overwrite or upload time). For history rows it's the time
253
+ * the version was originally uploaded.
254
+ */
255
+ createdAt: string;
256
+ }
215
257
  /**
216
258
  * Result of a successful `KoolbaseStorage.upload()` call.
217
259
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techfinityedge/koolbase-react-native",
3
- "version": "5.4.0",
3
+ "version": "5.5.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",