@techfinityedge/koolbase-react-native 5.2.0 → 5.4.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
@@ -73,6 +73,8 @@ const unsubscribe = Koolbase.auth.onAuthStateChange((user) => {
73
73
  });
74
74
  ```
75
75
 
76
+ ---
77
+
76
78
  ### OAuth — Apple
77
79
 
78
80
  Apple Sign-In uses the native authentication flow via `@invertase/react-native-apple-authentication` as a peer dependency:
@@ -100,6 +102,8 @@ const session = await Koolbase.auth.signInWithApple({
100
102
 
101
103
  Configure Apple Sign-In for your environment with your iOS app's Bundle ID. Full setup guide at [docs.koolbase.com/auth/oauth](https://docs.koolbase.com/auth/oauth).
102
104
 
105
+ ---
106
+
103
107
  ### OAuth — Google
104
108
 
105
109
  Google Sign-In uses the native authentication flow via `@react-native-google-signin/google-signin` as a peer dependency:
@@ -121,6 +125,8 @@ const session = await Koolbase.auth.signInWithGoogle({
121
125
 
122
126
  Configure Google Sign-In for your environment with the OAuth client IDs from Google Cloud Console (typically one each for iOS, Android, and web). Full setup guide at [docs.koolbase.com/auth/oauth](https://docs.koolbase.com/auth/oauth).
123
127
 
128
+ ---
129
+
124
130
  ### Phone + OTP
125
131
 
126
132
  ```typescript
@@ -173,6 +179,8 @@ await Koolbase.db.update('record-id', { title: 'Updated' });
173
179
  await Koolbase.db.delete('record-id');
174
180
  ```
175
181
 
182
+ ---
183
+
176
184
  ### Handling unique-constraint conflicts
177
185
 
178
186
  A write that would violate a unique constraint throws `KoolbaseConflictError`:
@@ -187,6 +195,96 @@ try {
187
195
  }
188
196
  ```
189
197
 
198
+ ---
199
+
200
+ ### Public bucket URLs
201
+
202
+ For files in public buckets, you can construct the stable CDN URL directly — no
203
+ network call, no expiry, embeddable anywhere a browser fetches a URL.
204
+
205
+ ```typescript
206
+ import { KoolbaseStorage } from '@techfinityedge/koolbase-react-native';
207
+
208
+ // From a KoolbaseObject you already have (e.g. from upload() or another read)
209
+ const { object } = await Koolbase.storage.upload({
210
+ bucket: 'avatars',
211
+ path: `user-${userId}.jpg`,
212
+ file: { uri: imageUri, name: 'avatar.jpg', type: 'image/jpeg' },
213
+ });
214
+
215
+ const url = KoolbaseStorage.publicUrlForObject(object, 'avatars');
216
+ // url is null for private-bucket objects; the CDN URL for public-bucket ones.
217
+
218
+ if (url) {
219
+ // Safe to use — file lives in the public R2 bucket
220
+ return <Image source={{ uri: url }} />;
221
+ }
222
+
223
+ // For build-time URL construction (no Object on hand)
224
+ const url = KoolbaseStorage.publicUrl({
225
+ projectId: 'proj_abc',
226
+ bucket: 'avatars',
227
+ path: 'user-123.jpg',
228
+ });
229
+ // Always returns the URL pattern; caller is responsible for knowing
230
+ // the file lives in a public bucket. For files in private buckets,
231
+ // the resulting URL will 404.
232
+ ```
233
+
234
+ URLs follow the pattern `https://cdn.koolbase.com/{project_id}/{bucket}/{path}` — long-lived, edge-cached, no authentication. For files in private buckets, use `getDownloadUrl` instead, which returns a 1-hour presigned URL.
235
+
236
+ ---
237
+
238
+ ### Image transforms
239
+
240
+ Public bucket URLs can be transformed at the edge — resize, reformat,
241
+ optimize — without any preprocessing. Two ways:
242
+
243
+ **Direct transforms** — pass a `transform` option to `publicUrl`:
244
+
245
+ ```ts
246
+ const url = KoolbaseStorage.publicUrl({
247
+ projectId: 'proj_abc',
248
+ bucket: 'avatars',
249
+ path: 'user-123.jpg',
250
+ transform: {
251
+ width: 200,
252
+ height: 200,
253
+ fit: 'cover',
254
+ format: 'auto',
255
+ quality: 85,
256
+ },
257
+ });
258
+ ```
259
+
260
+ **Named presets** — store an option set server-side (via the dashboard or
261
+ REST API), reference it by name:
262
+
263
+ ```ts
264
+ const url = KoolbaseStorage.publicUrlWithPreset({
265
+ projectId: 'proj_abc',
266
+ presetName: 'thumbnail',
267
+ bucket: 'avatars',
268
+ path: 'user-123.jpg',
269
+ });
270
+
271
+ // Or from a KoolbaseObject instance:
272
+ const url = KoolbaseStorage.publicUrlForObjectWithPreset(object, 'avatars', 'thumbnail');
273
+ ```
274
+
275
+ Available options: `width` and `height` (1–2000), `format`
276
+ (`auto`/`webp`/`avif`/`jpeg`/`png`), `quality` (1–100), `fit`
277
+ (`scale-down`/`contain`/`cover`/`crop`/`pad`), `dpr` (1–3), `gravity`
278
+ (`auto`/`center`/`top`/`bottom`/`left`/`right`/`top-left`/`top-right`/
279
+ `bottom-left`/`bottom-right`). Transformed responses are edge-cached for 4
280
+ hours; Cloudflare includes 5,000 unique transformations/month free per
281
+ account.
282
+
283
+ See the [Image Transforms docs](https://docs.koolbase.com/storage/image-transforms)
284
+ for the full reference.
285
+
286
+ ---
287
+
190
288
  ### Upsert
191
289
 
192
290
  Insert a record, or update the existing one matching a filter.
@@ -230,6 +328,8 @@ if (isFromCache) console.log('Served from local cache');
230
328
  await Koolbase.db.syncPendingWrites();
231
329
  ```
232
330
 
331
+ ---
332
+
233
333
  ### Atomic batch writes
234
334
 
235
335
  Run multiple writes in a single server-side transaction. All operations commit together or none are applied — any failure rolls back the entire batch.
@@ -309,6 +409,8 @@ const url = await Koolbase.storage.getDownloadUrl('avatars', `user-${userId}.jpg
309
409
  await Koolbase.storage.delete('avatars', `user-${userId}.jpg`);
310
410
  ```
311
411
 
412
+ ---
413
+
312
414
  ### Handling upload conflicts
313
415
 
314
416
  For user-supplied filenames, prompt the user before overwriting:
@@ -622,6 +724,8 @@ try {
622
724
  > the background, so their conflicts surface via the sync engine, not as a
623
725
  > thrown error.
624
726
 
727
+ ---
728
+
625
729
  ### Storage errors
626
730
 
627
731
  All storage failures extend `KoolbaseStorageError` (which extends `Error`):
package/dist/storage.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { KoolbaseConfig, UploadOptions, UploadResult, KoolbaseObject } from './types';
1
+ import { KoolbaseConfig, UploadOptions, UploadResult, KoolbaseObject, KoolbaseImageTransform } from './types';
2
2
  /**
3
3
  * Koolbase storage client — uploads, downloads, and deletes via presigned
4
4
  * Cloudflare R2 URLs.
@@ -73,6 +73,75 @@ export declare class KoolbaseStorage {
73
73
  * Get a signed download URL for a file.
74
74
  */
75
75
  getDownloadUrl(bucket: string, path: string): Promise<string>;
76
+ /**
77
+ * Build the stable public CDN URL for a file in a public bucket.
78
+ *
79
+ * Returns the URL unconditionally — no check on whether the file
80
+ * exists or whether the bucket is actually public. Use when you
81
+ * know the file is in a public bucket and want the URL without a
82
+ * network round-trip (build-time URL generation, server-side
83
+ * rendering, batch image processing, etc.).
84
+ *
85
+ * For safer construction from an Object you already have, use
86
+ * {@link KoolbaseStorage.publicUrlForObject} — it checks the stored
87
+ * `r2Bucket` value and returns `null` when the object isn't in the
88
+ * public R2 bucket.
89
+ */
90
+ static publicUrl(args: {
91
+ projectId: string;
92
+ bucket: string;
93
+ path: string;
94
+ /**
95
+ * Optional Cloudflare Image Transformations. Adds a `/cdn-cgi/image/`
96
+ * URL prefix; billed against the koolbase.com zone's free monthly
97
+ * allocation (5,000 unique transforms/month). Each unique combination
98
+ * of `path` + options is cached and billed only once per calendar month.
99
+ */
100
+ transform?: KoolbaseImageTransform;
101
+ }): string;
102
+ /**
103
+ * Returns the stable CDN URL for an object when its bytes physically
104
+ * live in the public R2 bucket, `null` otherwise.
105
+ *
106
+ * Returns `null` for:
107
+ * - Files in private buckets (no public URL ever)
108
+ * - Legacy files in public buckets whose bytes still live in the
109
+ * private R2 bucket from before Gap #2 (no permanent URL until
110
+ * they're re-uploaded)
111
+ *
112
+ * The bucket name must be supplied because {@link KoolbaseObject}
113
+ * carries only the bucket ID, not its name. Typically the caller
114
+ * already knows which bucket they queried.
115
+ */
116
+ static publicUrlForObject(obj: KoolbaseObject, bucket: string, options?: {
117
+ transform?: KoolbaseImageTransform;
118
+ }): string | null;
119
+ /**
120
+ * Builds a named-preset CDN URL. The preset is resolved at the Cloudflare
121
+ * edge by the koolbase-cdn-worker, which looks up
122
+ * `preset:{project_id}:{preset_name}` in Workers KV and applies the stored
123
+ * transformation options. Presets are managed in the dashboard under
124
+ * Storage → Presets.
125
+ *
126
+ * Unknown preset names yield a 404 at the edge — the URL itself always
127
+ * constructs successfully without a network round-trip.
128
+ *
129
+ * For safer construction from an Object you already have, use
130
+ * {@link KoolbaseStorage.publicUrlForObjectWithPreset} — it checks the
131
+ * stored `r2Bucket` value and returns `null` when the object isn't in the
132
+ * public R2 bucket.
133
+ */
134
+ static publicUrlWithPreset(args: {
135
+ projectId: string;
136
+ presetName: string;
137
+ bucket: string;
138
+ path: string;
139
+ }): string;
140
+ /**
141
+ * Returns the named-preset CDN URL for the given object, or `null` if the
142
+ * object isn't in the public R2 bucket.
143
+ */
144
+ static publicUrlForObjectWithPreset(obj: KoolbaseObject, bucket: string, presetName: string): string | null;
76
145
  /**
77
146
  * Delete a file from a bucket.
78
147
  */
package/dist/storage.js CHANGED
@@ -2,6 +2,35 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.KoolbaseStorage = void 0;
4
4
  const storage_errors_1 = require("./storage-errors");
5
+ // --- Cloudflare image-transform URL helpers -------------------------------
6
+ // Module-private — callers use KoolbaseStorage.publicUrl / publicUrlForObject.
7
+ function clampInt(v, min, max) {
8
+ return Math.max(min, Math.min(max, Math.floor(v)));
9
+ }
10
+ /**
11
+ * Serializes a transform spec to Cloudflare's comma-separated key=value
12
+ * options segment (e.g. `width=400,format=webp,quality=80`). Returns the
13
+ * empty string when no fields are set — callers can use that to skip the
14
+ * `/cdn-cgi/image/` URL prefix entirely.
15
+ */
16
+ function serializeTransform(t) {
17
+ const parts = [];
18
+ if (t.width != null)
19
+ parts.push(`width=${clampInt(t.width, 1, 2000)}`);
20
+ if (t.height != null)
21
+ parts.push(`height=${clampInt(t.height, 1, 2000)}`);
22
+ if (t.format)
23
+ parts.push(`format=${t.format}`);
24
+ if (t.quality != null)
25
+ parts.push(`quality=${clampInt(t.quality, 1, 100)}`);
26
+ if (t.fit)
27
+ parts.push(`fit=${t.fit}`);
28
+ if (t.dpr != null)
29
+ parts.push(`dpr=${clampInt(t.dpr, 1, 3)}`);
30
+ if (t.gravity)
31
+ parts.push(`gravity=${t.gravity}`);
32
+ return parts.join(',');
33
+ }
5
34
  /**
6
35
  * Koolbase storage client — uploads, downloads, and deletes via presigned
7
36
  * Cloudflare R2 URLs.
@@ -176,6 +205,87 @@ class KoolbaseStorage {
176
205
  const data = (await res.json());
177
206
  return data.url;
178
207
  }
208
+ /**
209
+ * Build the stable public CDN URL for a file in a public bucket.
210
+ *
211
+ * Returns the URL unconditionally — no check on whether the file
212
+ * exists or whether the bucket is actually public. Use when you
213
+ * know the file is in a public bucket and want the URL without a
214
+ * network round-trip (build-time URL generation, server-side
215
+ * rendering, batch image processing, etc.).
216
+ *
217
+ * For safer construction from an Object you already have, use
218
+ * {@link KoolbaseStorage.publicUrlForObject} — it checks the stored
219
+ * `r2Bucket` value and returns `null` when the object isn't in the
220
+ * public R2 bucket.
221
+ */
222
+ static publicUrl(args) {
223
+ // Encode each path segment individually so slashes are preserved
224
+ // while spaces, parens, hashes, and query characters are escaped.
225
+ const encoded = args.path.split('/').map(encodeURIComponent).join('/');
226
+ const opts = args.transform ? serializeTransform(args.transform) : '';
227
+ if (!opts) {
228
+ return `https://cdn.koolbase.com/${args.projectId}/${args.bucket}/${encoded}`;
229
+ }
230
+ return `https://cdn.koolbase.com/cdn-cgi/image/${opts}/${args.projectId}/${args.bucket}/${encoded}`;
231
+ }
232
+ /**
233
+ * Returns the stable CDN URL for an object when its bytes physically
234
+ * live in the public R2 bucket, `null` otherwise.
235
+ *
236
+ * Returns `null` for:
237
+ * - Files in private buckets (no public URL ever)
238
+ * - Legacy files in public buckets whose bytes still live in the
239
+ * private R2 bucket from before Gap #2 (no permanent URL until
240
+ * they're re-uploaded)
241
+ *
242
+ * The bucket name must be supplied because {@link KoolbaseObject}
243
+ * carries only the bucket ID, not its name. Typically the caller
244
+ * already knows which bucket they queried.
245
+ */
246
+ static publicUrlForObject(obj, bucket, options) {
247
+ if (obj.r2Bucket !== 'koolbase-storage-public')
248
+ return null;
249
+ return KoolbaseStorage.publicUrl({
250
+ projectId: obj.projectId,
251
+ bucket,
252
+ path: obj.path,
253
+ transform: options?.transform,
254
+ });
255
+ }
256
+ /**
257
+ * Builds a named-preset CDN URL. The preset is resolved at the Cloudflare
258
+ * edge by the koolbase-cdn-worker, which looks up
259
+ * `preset:{project_id}:{preset_name}` in Workers KV and applies the stored
260
+ * transformation options. Presets are managed in the dashboard under
261
+ * Storage → Presets.
262
+ *
263
+ * Unknown preset names yield a 404 at the edge — the URL itself always
264
+ * constructs successfully without a network round-trip.
265
+ *
266
+ * For safer construction from an Object you already have, use
267
+ * {@link KoolbaseStorage.publicUrlForObjectWithPreset} — it checks the
268
+ * stored `r2Bucket` value and returns `null` when the object isn't in the
269
+ * public R2 bucket.
270
+ */
271
+ static publicUrlWithPreset(args) {
272
+ const encoded = args.path.split('/').map(encodeURIComponent).join('/');
273
+ return `https://cdn.koolbase.com/p/${args.projectId}/${args.presetName}/${args.bucket}/${encoded}`;
274
+ }
275
+ /**
276
+ * Returns the named-preset CDN URL for the given object, or `null` if the
277
+ * object isn't in the public R2 bucket.
278
+ */
279
+ static publicUrlForObjectWithPreset(obj, bucket, presetName) {
280
+ if (obj.r2Bucket !== 'koolbase-storage-public')
281
+ return null;
282
+ return KoolbaseStorage.publicUrlWithPreset({
283
+ projectId: obj.projectId,
284
+ presetName,
285
+ bucket,
286
+ path: obj.path,
287
+ });
288
+ }
179
289
  /**
180
290
  * Delete a file from a bucket.
181
291
  */
@@ -212,6 +322,7 @@ function mapObjectFromServer(raw) {
212
322
  size: raw.size ?? 0,
213
323
  contentType: raw.content_type ?? null,
214
324
  metadata: raw.metadata ?? {},
325
+ r2Bucket: raw.r2_bucket ?? 'koolbase-storage',
215
326
  createdAt: raw.created_at,
216
327
  updatedAt: raw.updated_at,
217
328
  };
package/dist/types.d.ts CHANGED
@@ -185,6 +185,16 @@ export interface KoolbaseObject {
185
185
  id: string;
186
186
  projectId: string;
187
187
  bucketId: string;
188
+ /**
189
+ * Name of the physical R2 bucket holding this object's bytes
190
+ * (Gap #2). `'koolbase-storage-public'` means the object has a
191
+ * stable CDN URL — construct it with
192
+ * `KoolbaseStorage.publicUrlForObject(obj, bucket)`. Anything else
193
+ * (typically `'koolbase-storage'`) means the object is in private
194
+ * storage and reads go through {@link KoolbaseStorage.getDownloadUrl},
195
+ * which returns a 1-hour presigned URL.
196
+ */
197
+ r2Bucket: string;
188
198
  userId: string | null;
189
199
  path: string;
190
200
  size: number;
@@ -335,3 +345,37 @@ export interface BatchResult {
335
345
  /** True for a successful delete. */
336
346
  deleted?: boolean;
337
347
  }
348
+ export type KoolbaseImageFormat = 'auto' | 'webp' | 'avif' | 'jpeg' | 'png';
349
+ export type KoolbaseImageFit = 'scale-down' | 'contain' | 'cover' | 'crop' | 'pad';
350
+ export type KoolbaseImageGravity = 'auto' | 'center' | 'top' | 'bottom' | 'left' | 'right' | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right';
351
+ /**
352
+ * Image-transformation options for `KoolbaseStorage.publicUrl` and
353
+ * `KoolbaseStorage.publicUrlForObject`. Each field maps to one Cloudflare
354
+ * Image Transformations parameter; unset fields are omitted.
355
+ *
356
+ * All numeric inputs are clamped silently to Cloudflare-supported ranges
357
+ * (width/height 1-2000, quality 1-100, dpr 1-3) so a stray `width: 99999`
358
+ * can't trigger error 9422 at the edge.
359
+ *
360
+ * @example
361
+ * const url = KoolbaseStorage.publicUrl({
362
+ * projectId: pid, bucket: 'avatars', path: 'user.jpg',
363
+ * transform: { width: 400, height: 400, format: 'webp', quality: 80, fit: 'cover' },
364
+ * });
365
+ */
366
+ export interface KoolbaseImageTransform {
367
+ /** Output width in pixels. Clamped to 1-2000. */
368
+ width?: number;
369
+ /** Output height in pixels. Clamped to 1-2000. */
370
+ height?: number;
371
+ /** Output format. `auto` negotiates based on the request's `Accept` header. */
372
+ format?: KoolbaseImageFormat;
373
+ /** Quality 1-100. Clamped. Has no effect on lossless formats (`png`). */
374
+ quality?: number;
375
+ /** Resize mode when both width and height are specified. */
376
+ fit?: KoolbaseImageFit;
377
+ /** Device pixel ratio multiplier. Clamped to 1-3. */
378
+ dpr?: number;
379
+ /** Crop anchor. Use with `fit: 'cover'` or `fit: 'crop'`. */
380
+ gravity?: KoolbaseImageGravity;
381
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techfinityedge/koolbase-react-native",
3
- "version": "5.2.0",
3
+ "version": "5.4.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",
@@ -27,6 +27,8 @@
27
27
  },
28
28
  "devDependencies": {
29
29
  "@types/jszip": "^3.4.0",
30
+ "@types/node": "^25.9.1",
31
+ "react-native": "^0.85.3",
30
32
  "react-native-keychain": "^10.0.0",
31
33
  "typescript": "^5.0.0"
32
34
  },