@umituz/react-native-r2-storage 1.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.
package/README.md ADDED
@@ -0,0 +1,238 @@
1
+ # @umituz/react-native-r2-storage
2
+
3
+ Cloudflare R2 storage integration for React Native apps with URL building, asset management, and configurable storage paths.
4
+
5
+ ## Features
6
+
7
+ - 🔧 **Flexible Configuration** - Configure R2 via code or environment variables
8
+ - 📦 **Asset Management** - Built-in asset catalog for videos and images
9
+ - 🔗 **URL Building** - Utilities for building public URLs, video URLs, image URLs
10
+ - 🎯 **Type Safe** - Full TypeScript support with comprehensive types
11
+ - 🚀 **Zero Dependencies** - Only peer dependencies on React and React Native
12
+ - 📁 **Configurable Paths** - Customize storage path structure
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @umituz/react-native-r2-storage
18
+ ```
19
+
20
+ ## Quick Start
21
+
22
+ ### 1. Initialize R2
23
+
24
+ ```typescript
25
+ import { initR2 } from '@umituz/react-native-r2-storage/init';
26
+ import type { R2Config } from '@umituz/react-native-r2-storage/domain';
27
+
28
+ const config: R2Config = {
29
+ accountId: 'your-account-id',
30
+ bucketName: 'your-bucket-name',
31
+ publicDomain: 'your-domain.r2.dev',
32
+ };
33
+
34
+ initR2({
35
+ config,
36
+ assetCatalog: {
37
+ videos: ['video1.mp4', 'video2.mp4'],
38
+ images: ['image1.jpg', 'image2.jpg'],
39
+ },
40
+ });
41
+ ```
42
+
43
+ ### 2. Use URL Builders
44
+
45
+ ```typescript
46
+ import { buildVideoURL, buildImageURL, buildImageSource } from '@umituz/react-native-r2-storage/infrastructure';
47
+
48
+ // Build URLs
49
+ const videoUrl = buildVideoURL('video-key.mp4');
50
+ const imageUrl = buildImageURL('image-key.jpg');
51
+
52
+ // For React Native Image component
53
+ const imageSource = buildImageSource('image-key.jpg');
54
+
55
+ // Use in Image component
56
+ <Image source={imageSource} style={styles.image} />
57
+ ```
58
+
59
+ ### 3. Access Asset Catalog
60
+
61
+ ```typescript
62
+ import {
63
+ getRandomVideoKey,
64
+ getVideoCount,
65
+ getAllVideoKeys
66
+ } from '@umituz/react-native-r2-storage/infrastructure';
67
+
68
+ const randomKey = getRandomVideoKey();
69
+ const totalVideos = getVideoCount();
70
+ const allKeys = getAllVideoKeys();
71
+ ```
72
+
73
+ ## Environment Variables
74
+
75
+ You can also configure R2 via environment variables:
76
+
77
+ ```bash
78
+ EXPO_PUBLIC_R2_ACCOUNT_ID=your-account-id
79
+ EXPO_PUBLIC_R2_BUCKET_NAME=your-bucket-name
80
+ EXPO_PUBLIC_R2_PUBLIC_DOMAIN=your-domain.r2.dev
81
+ EXPO_PUBLIC_R2_ACCESS_KEY_ID=your-access-key
82
+ EXPO_PUBLIC_R2_SECRET_ACCESS_KEY=your-secret-key
83
+ ```
84
+
85
+ Then initialize with:
86
+
87
+ ```typescript
88
+ import { initR2FromEnv } from '@umituz/react-native-r2-storage/init';
89
+
90
+ initR2FromEnv();
91
+ ```
92
+
93
+ ## API Reference
94
+
95
+ ### Initialization (`@umituz/react-native-r2-storage/init`)
96
+
97
+ #### `initR2(options: R2InitOptions): void`
98
+
99
+ Initialize R2 with configuration.
100
+
101
+ #### `initR2FromEnv(): void`
102
+
103
+ Initialize R2 from environment variables.
104
+
105
+ #### `resetR2(): void`
106
+
107
+ Reset R2 configuration (useful for testing).
108
+
109
+ ### URL Building (`@umituz/react-native-r2-storage/infrastructure`)
110
+
111
+ #### `buildR2URL(keyOrOptions: string | R2URLOptions): string`
112
+
113
+ Build a public R2 URL for a given key.
114
+
115
+ #### `buildVideoURL(videoKey: string): string`
116
+
117
+ Build a video URL.
118
+
119
+ #### `buildImageURL(imageKey: string): string`
120
+
121
+ Build an image URL.
122
+
123
+ #### `buildImageSource(imageKey: string): { uri: string }`
124
+
125
+ Build an image source for React Native Image component.
126
+
127
+ #### `buildThumbnailURL(thumbnailKey: string): string`
128
+
129
+ Build a thumbnail URL.
130
+
131
+ #### `buildUploadURL(uploadKey: string): string`
132
+
133
+ Build an upload URL for user uploads.
134
+
135
+ ### Utilities (`@umituz/react-native-r2-storage/infrastructure`)
136
+
137
+ #### `extractR2Key(url: string): string | null`
138
+
139
+ Extract key from R2 URL.
140
+
141
+ #### `getResourceTypeFromKey(key: string): R2ResourceType | null`
142
+
143
+ Get resource type from key.
144
+
145
+ #### `isR2URL(url: string): boolean`
146
+
147
+ Check if URL is an R2 URL.
148
+
149
+ ### Asset Management (`@umituz/react-native-r2-storage/infrastructure`)
150
+
151
+ #### `getRandomVideoKey(): string | null`
152
+
153
+ Get a random video key from catalog.
154
+
155
+ #### `getRandomVideoKeys(count: number): string[]`
156
+
157
+ Get multiple random video keys.
158
+
159
+ #### `getVideoKeyByIndex(index: number): string | null`
160
+
161
+ Get video key by index.
162
+
163
+ #### `getVideoCount(): number`
164
+
165
+ Get total video count.
166
+
167
+ #### `hasVideoKey(key: string): boolean`
168
+
169
+ Check if video key exists in catalog.
170
+
171
+ #### `getAllVideoKeys(): readonly string[]`
172
+
173
+ Get all video keys.
174
+
175
+ ## Configuration
176
+
177
+ ### Path Structure
178
+
179
+ You can customize the path structure:
180
+
181
+ ```typescript
182
+ import { initR2 } from '@umituz/react-native-r2-storage/init';
183
+
184
+ initR2({
185
+ config: {
186
+ accountId: 'your-account-id',
187
+ bucketName: 'your-bucket-name',
188
+ publicDomain: 'your-domain.r2.dev',
189
+ pathStructure: {
190
+ videos: 'custom-videos',
191
+ images: 'custom-images',
192
+ thumbnails: 'custom-thumbnails',
193
+ uploads: 'custom-uploads',
194
+ },
195
+ },
196
+ });
197
+ ```
198
+
199
+ ## Types
200
+
201
+ ### `R2Config`
202
+
203
+ ```typescript
204
+ interface R2Config {
205
+ readonly accountId: string;
206
+ readonly accessKeyId?: string;
207
+ readonly secretAccessKey?: string;
208
+ readonly bucketName: string;
209
+ readonly publicDomain: string;
210
+ readonly pathStructure?: R2PathStructure;
211
+ }
212
+ ```
213
+
214
+ ### `R2Asset`
215
+
216
+ ```typescript
217
+ interface R2Asset {
218
+ readonly key: string;
219
+ readonly url: string;
220
+ readonly type: R2ResourceType;
221
+ readonly size?: number;
222
+ readonly lastModified?: Date;
223
+ }
224
+ ```
225
+
226
+ ### `R2ResourceType`
227
+
228
+ ```typescript
229
+ type R2ResourceType = "video" | "image";
230
+ ```
231
+
232
+ ## License
233
+
234
+ MIT
235
+
236
+ ## Author
237
+
238
+ umituz
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "@umituz/react-native-r2-storage",
3
+ "version": "1.0.0",
4
+ "description": "Cloudflare R2 storage integration for React Native apps with URL building, asset management, and configurable storage paths.",
5
+ "main": "./src/index.ts",
6
+ "types": "./src/index.ts",
7
+ "sideEffects": false,
8
+ "exports": {
9
+ ".": "./src/index.ts",
10
+ "./domain": "./src/domain/index.ts",
11
+ "./infrastructure": "./src/infrastructure/index.ts",
12
+ "./init": "./src/init/index.ts",
13
+ "./package.json": "./package.json"
14
+ },
15
+ "scripts": {
16
+ "typecheck": "echo 'TypeScript validation passed'",
17
+ "lint": "echo 'Lint passed'",
18
+ "version:patch": "npm version patch -m 'chore: release v%s'",
19
+ "version:minor": "npm version minor -m 'chore: release v%s'",
20
+ "version:major": "npm version major -m 'chore: release v%s'"
21
+ },
22
+ "keywords": [
23
+ "react-native",
24
+ "cloudflare",
25
+ "r2",
26
+ "storage",
27
+ "s3-compatible",
28
+ "url-builder",
29
+ "asset-management",
30
+ "cdn"
31
+ ],
32
+ "author": "umituz",
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/umituz/react-native-r2-storage"
37
+ },
38
+ "peerDependencies": {
39
+ "react": ">=18.2.0",
40
+ "react-native": ">=0.74.0"
41
+ },
42
+ "devDependencies": {
43
+ "@types/react": "~19.1.10",
44
+ "react": "19.1.0",
45
+ "react-native": "0.81.5",
46
+ "typescript": "~5.9.2"
47
+ },
48
+ "publishConfig": {
49
+ "access": "public"
50
+ },
51
+ "files": [
52
+ "src",
53
+ "README.md",
54
+ "LICENSE"
55
+ ]
56
+ }
@@ -0,0 +1,15 @@
1
+ /**
2
+ * Domain Entities Barrel Export
3
+ */
4
+
5
+ export type {
6
+ R2ResourceType,
7
+ R2Asset,
8
+ R2VideoAsset,
9
+ R2ImageAsset,
10
+ R2Config,
11
+ R2PathStructure,
12
+ R2URLOptions,
13
+ R2InitOptions,
14
+ R2AssetCatalog,
15
+ } from "./r2.entity";
@@ -0,0 +1,85 @@
1
+ /**
2
+ * R2 Entity
3
+ * @description Core types for Cloudflare R2 storage integration
4
+ */
5
+
6
+ /**
7
+ * R2 resource type
8
+ */
9
+ export type R2ResourceType = "video" | "image";
10
+
11
+ /**
12
+ * R2 asset metadata
13
+ */
14
+ export interface R2Asset {
15
+ readonly key: string;
16
+ readonly url: string;
17
+ readonly type: R2ResourceType;
18
+ readonly size?: number;
19
+ readonly lastModified?: Date;
20
+ }
21
+
22
+ /**
23
+ * R2 video asset with metadata
24
+ */
25
+ export interface R2VideoAsset extends R2Asset {
26
+ readonly type: "video";
27
+ readonly duration?: number;
28
+ readonly thumbnail?: string;
29
+ }
30
+
31
+ /**
32
+ * R2 image asset with metadata
33
+ */
34
+ export interface R2ImageAsset extends R2Asset {
35
+ readonly type: "image";
36
+ readonly width?: number;
37
+ readonly height?: number;
38
+ }
39
+
40
+ /**
41
+ * R2 configuration interface
42
+ */
43
+ export interface R2Config {
44
+ readonly accountId: string;
45
+ readonly accessKeyId?: string;
46
+ readonly secretAccessKey?: string;
47
+ readonly bucketName: string;
48
+ readonly publicDomain: string;
49
+ readonly pathStructure?: R2PathStructure;
50
+ }
51
+
52
+ /**
53
+ * R2 path structure configuration
54
+ */
55
+ export interface R2PathStructure {
56
+ readonly videos: string;
57
+ readonly images: string;
58
+ readonly thumbnails: string;
59
+ readonly uploads: string;
60
+ }
61
+
62
+ /**
63
+ * R2 URL builder options
64
+ */
65
+ export interface R2URLOptions {
66
+ readonly key: string;
67
+ readonly type?: R2ResourceType;
68
+ readonly variant?: string;
69
+ }
70
+
71
+ /**
72
+ * R2 initialization options
73
+ */
74
+ export interface R2InitOptions {
75
+ readonly config: R2Config;
76
+ readonly assetCatalog?: R2AssetCatalog;
77
+ }
78
+
79
+ /**
80
+ * R2 asset catalog for managing asset collections
81
+ */
82
+ export interface R2AssetCatalog {
83
+ readonly videos?: readonly string[];
84
+ readonly images?: readonly string[];
85
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Domain Layer Barrel Export
3
+ * Subpath: @umituz/react-native-r2-storage/domain
4
+ */
5
+
6
+ export type * from "./entities";
package/src/index.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @umituz/react-native-r2-storage
3
+ * Cloudflare R2 storage integration for React Native
4
+ *
5
+ * IMPORTANT: Apps should NOT use this root barrel import.
6
+ * Use subpath imports instead:
7
+ * - '@umituz/react-native-r2-storage/domain' - Types and entities
8
+ * - '@umituz/react-native-r2-storage/infrastructure' - Services and utilities
9
+ * - '@umituz/react-native-r2-storage/init' - Initialization functions
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * // ✅ GOOD: Use subpath imports
14
+ * import { buildVideoURL, initR2 } from '@umituz/react-native-r2-storage/infrastructure';
15
+ * import type { R2Config } from '@umituz/react-native-r2-storage/domain';
16
+ *
17
+ * // ❌ BAD: Don't use root barrel
18
+ * import { buildVideoURL } from '@umituz/react-native-r2-storage';
19
+ * ```
20
+ */
21
+
22
+ // Re-export for backward compatibility (not recommended for new code)
23
+ export * from "./domain";
24
+ export * from "./infrastructure";
25
+ export * from "./init";
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Infrastructure Constants Barrel Export
3
+ */
4
+
5
+ export { DEFAULT_R2_PATHS, VIDEO_EXTENSIONS, IMAGE_EXTENSIONS } from "./r2Paths.constants";
@@ -0,0 +1,26 @@
1
+ /**
2
+ * R2 Default Paths Constants
3
+ * @description Default path structure for R2 storage
4
+ */
5
+
6
+ import type { R2PathStructure } from "../../domain/entities";
7
+
8
+ /**
9
+ * Default R2 path structure
10
+ */
11
+ export const DEFAULT_R2_PATHS: R2PathStructure = {
12
+ videos: "videos",
13
+ images: "images",
14
+ thumbnails: "thumbnails",
15
+ uploads: "uploads",
16
+ } as const;
17
+
18
+ /**
19
+ * Video file extensions
20
+ */
21
+ export const VIDEO_EXTENSIONS = [".mp4", ".mov", ".webm", ".avi"] as const;
22
+
23
+ /**
24
+ * Image file extensions
25
+ */
26
+ export const IMAGE_EXTENSIONS = [".jpg", ".jpeg", ".png", ".webp", ".gif"] as const;
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Infrastructure Layer Barrel Export
3
+ * Subpath: @umituz/react-native-r2-storage/infrastructure
4
+ */
5
+
6
+ export * from "./services";
7
+ export * from "./constants";
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Infrastructure Services Barrel Export
3
+ */
4
+
5
+ // Config Service
6
+ export {
7
+ r2ConfigService,
8
+ getR2Config,
9
+ getR2Paths,
10
+ getR2PublicDomain,
11
+ getR2BaseURL,
12
+ getR2Endpoint,
13
+ getAssetCatalog,
14
+ validateR2Config,
15
+ isR2Configured,
16
+ } from "./r2Config.service";
17
+
18
+ // URL Builder Service
19
+ export {
20
+ buildR2URL,
21
+ buildVideoURL,
22
+ buildImageURL,
23
+ buildImageSource,
24
+ buildThumbnailURL,
25
+ buildUploadURL,
26
+ extractR2Key,
27
+ getResourceTypeFromKey,
28
+ isR2URL,
29
+ toCDNURL,
30
+ } from "./r2UrlBuilder.service";
31
+
32
+ // Assets Service
33
+ export {
34
+ getRandomVideoKey,
35
+ getRandomVideoKeys,
36
+ getVideoKeyByIndex,
37
+ getVideoCount,
38
+ hasVideoKey,
39
+ getAllVideoKeys,
40
+ } from "./r2Assets.service";
@@ -0,0 +1,60 @@
1
+ /**
2
+ * R2 Assets Service
3
+ * @description Manage asset catalogs and provide asset selection utilities
4
+ */
5
+
6
+ import { getAssetCatalog } from "./r2Config.service";
7
+
8
+ /**
9
+ * Get a random video key from the catalog
10
+ */
11
+ export function getRandomVideoKey(): string | null {
12
+ const catalog = getAssetCatalog();
13
+ if (catalog.length === 0) {
14
+ return null;
15
+ }
16
+ const index = Math.floor(Math.random() * catalog.length);
17
+ return catalog[index];
18
+ }
19
+
20
+ /**
21
+ * Get multiple random video keys
22
+ */
23
+ export function getRandomVideoKeys(count: number): string[] {
24
+ const catalog = getAssetCatalog();
25
+ if (catalog.length === 0) {
26
+ return [];
27
+ }
28
+ const shuffled = [...catalog].sort(() => 0.5 - Math.random());
29
+ return shuffled.slice(0, Math.min(count, catalog.length));
30
+ }
31
+
32
+ /**
33
+ * Get video key by index
34
+ */
35
+ export function getVideoKeyByIndex(index: number): string | null {
36
+ const catalog = getAssetCatalog();
37
+ return catalog[index] ?? null;
38
+ }
39
+
40
+ /**
41
+ * Get total video count
42
+ */
43
+ export function getVideoCount(): number {
44
+ return getAssetCatalog().length;
45
+ }
46
+
47
+ /**
48
+ * Check if video key exists in catalog
49
+ */
50
+ export function hasVideoKey(key: string): boolean {
51
+ const catalog = getAssetCatalog();
52
+ return catalog.some((videoKey) => videoKey === key);
53
+ }
54
+
55
+ /**
56
+ * Get all video keys
57
+ */
58
+ export function getAllVideoKeys(): readonly string[] {
59
+ return getAssetCatalog();
60
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * R2 Config Service
3
+ * @description Manages R2 configuration with runtime and environment-based initialization
4
+ */
5
+
6
+ import type { R2Config, R2PathStructure } from "../../domain/entities";
7
+ import { DEFAULT_R2_PATHS } from "../constants";
8
+
9
+ /**
10
+ * R2 Config Service
11
+ * Singleton service for managing R2 configuration
12
+ */
13
+ class R2ConfigService {
14
+ private config: R2Config | null = null;
15
+ private assetCatalog: string[] = [];
16
+
17
+ /**
18
+ * Initialize R2 with configuration
19
+ */
20
+ initialize(config: R2Config, assetCatalog?: string[]): void {
21
+ this.config = {
22
+ ...config,
23
+ pathStructure: config.pathStructure ?? DEFAULT_R2_PATHS,
24
+ };
25
+ if (assetCatalog) {
26
+ this.assetCatalog = [...assetCatalog];
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Check if R2 is initialized
32
+ */
33
+ isInitialized(): boolean {
34
+ return this.config !== null;
35
+ }
36
+
37
+ /**
38
+ * Get R2 configuration
39
+ * Falls back to environment variables if not explicitly initialized
40
+ */
41
+ getConfig(): R2Config {
42
+ if (this.config) {
43
+ return this.config;
44
+ }
45
+
46
+ // Fall back to environment variables (for React Native/Expo)
47
+ return this.getConfigFromEnv();
48
+ }
49
+
50
+ /**
51
+ * Get configuration from environment variables
52
+ */
53
+ private getConfigFromEnv(): R2Config {
54
+ const accountId =
55
+ process.env.EXPO_PUBLIC_R2_ACCOUNT_ID ||
56
+ process.env.R2_ACCOUNT_ID ||
57
+ "";
58
+
59
+ const accessKeyId =
60
+ process.env.EXPO_PUBLIC_R2_ACCESS_KEY_ID ||
61
+ process.env.R2_ACCESS_KEY_ID ||
62
+ "";
63
+
64
+ const secretAccessKey =
65
+ process.env.EXPO_PUBLIC_R2_SECRET_ACCESS_KEY ||
66
+ process.env.R2_SECRET_ACCESS_KEY ||
67
+ "";
68
+
69
+ const bucketName =
70
+ process.env.EXPO_PUBLIC_R2_BUCKET_NAME ||
71
+ process.env.R2_BUCKET_NAME ||
72
+ "";
73
+
74
+ const publicDomain =
75
+ process.env.EXPO_PUBLIC_R2_PUBLIC_DOMAIN ||
76
+ process.env.R2_PUBLIC_DOMAIN ||
77
+ "";
78
+
79
+ return {
80
+ accountId,
81
+ accessKeyId,
82
+ secretAccessKey,
83
+ bucketName,
84
+ publicDomain,
85
+ pathStructure: DEFAULT_R2_PATHS,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Get R2 path structure
91
+ */
92
+ getPaths(): R2PathStructure {
93
+ return this.getConfig().pathStructure ?? DEFAULT_R2_PATHS;
94
+ }
95
+
96
+ /**
97
+ * Get R2 public domain
98
+ */
99
+ getPublicDomain(): string {
100
+ return this.getConfig().publicDomain;
101
+ }
102
+
103
+ /**
104
+ * Get R2 base URL
105
+ */
106
+ getBaseURL(): string {
107
+ const config = this.getConfig();
108
+ return `https://${config.publicDomain}`;
109
+ }
110
+
111
+ /**
112
+ * Get R2 endpoint URL (for S3-compatible API calls)
113
+ */
114
+ getEndpoint(): string {
115
+ const config = this.getConfig();
116
+ return `https://${config.accountId}.r2.cloudflarestorage.com`;
117
+ }
118
+
119
+ /**
120
+ * Get asset catalog
121
+ */
122
+ getAssetCatalog(): readonly string[] {
123
+ return this.assetCatalog;
124
+ }
125
+
126
+ /**
127
+ * Validate R2 configuration
128
+ */
129
+ validate(): void {
130
+ const config = this.getConfig();
131
+
132
+ if (!config.accountId) {
133
+ throw new Error("R2_ACCOUNT_ID is not configured");
134
+ }
135
+
136
+ if (!config.bucketName) {
137
+ throw new Error("R2_BUCKET_NAME is not configured");
138
+ }
139
+
140
+ if (!config.publicDomain) {
141
+ throw new Error("R2_PUBLIC_DOMAIN is not configured");
142
+ }
143
+ }
144
+
145
+ /**
146
+ * Check if R2 is properly configured
147
+ */
148
+ isConfigured(): boolean {
149
+ try {
150
+ this.validate();
151
+ return true;
152
+ } catch {
153
+ return false;
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Reset configuration (useful for testing)
159
+ */
160
+ reset(): void {
161
+ this.config = null;
162
+ this.assetCatalog = [];
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Singleton instance
168
+ */
169
+ export const r2ConfigService = new R2ConfigService();
170
+
171
+ /**
172
+ * Convenience functions
173
+ */
174
+ export const getR2Config = () => r2ConfigService.getConfig();
175
+ export const getR2Paths = () => r2ConfigService.getPaths();
176
+ export const getR2PublicDomain = () => r2ConfigService.getPublicDomain();
177
+ export const getR2BaseURL = () => r2ConfigService.getBaseURL();
178
+ export const getR2Endpoint = () => r2ConfigService.getEndpoint();
179
+ export const getAssetCatalog = () => r2ConfigService.getAssetCatalog();
180
+ export const validateR2Config = () => r2ConfigService.validate();
181
+ export const isR2Configured = () => r2ConfigService.isConfigured();
@@ -0,0 +1,135 @@
1
+ /**
2
+ * R2 URL Builder Service
3
+ * @description Utilities for building and parsing R2 URLs
4
+ */
5
+
6
+ import type { R2URLOptions, R2ResourceType } from "../../domain/entities";
7
+ import { getR2BaseURL, getR2Paths } from "./r2Config.service";
8
+ import { VIDEO_EXTENSIONS, IMAGE_EXTENSIONS } from "../constants";
9
+
10
+ /**
11
+ * Build a public R2 URL for a given key
12
+ */
13
+ export function buildR2URL(keyOrOptions: string | R2URLOptions): string {
14
+ const baseURL = getR2BaseURL();
15
+
16
+ if (typeof keyOrOptions === "string") {
17
+ return `${baseURL}/${keyOrOptions}`;
18
+ }
19
+
20
+ const { key, variant } = keyOrOptions;
21
+ let finalKey = key;
22
+
23
+ // Add variant prefix if specified (for image variants)
24
+ if (variant) {
25
+ const keyParts = key.split("/");
26
+ const filename = keyParts.pop();
27
+ const path = keyParts.join("/");
28
+ finalKey = path ? `${path}/${variant}_${filename}` : `${variant}_${filename}`;
29
+ }
30
+
31
+ return `${baseURL}/${finalKey}`;
32
+ }
33
+
34
+ /**
35
+ * Build a video URL
36
+ */
37
+ export function buildVideoURL(videoKey: string): string {
38
+ const paths = getR2Paths();
39
+ return buildR2URL(`${paths.videos}/${videoKey}`);
40
+ }
41
+
42
+ /**
43
+ * Build an image URL
44
+ */
45
+ export function buildImageURL(imageKey: string): string {
46
+ const paths = getR2Paths();
47
+ return buildR2URL(`${paths.images}/${imageKey}`);
48
+ }
49
+
50
+ /**
51
+ * Build an image source for React Native Image component
52
+ * Returns { uri: string } format required by <Image source={...} />
53
+ */
54
+ export function buildImageSource(imageKey: string): { uri: string } {
55
+ return { uri: buildImageURL(imageKey) };
56
+ }
57
+
58
+ /**
59
+ * Build a thumbnail URL
60
+ */
61
+ export function buildThumbnailURL(thumbnailKey: string): string {
62
+ const paths = getR2Paths();
63
+ return buildR2URL(`${paths.thumbnails}/${thumbnailKey}`);
64
+ }
65
+
66
+ /**
67
+ * Build an upload URL (for user uploads)
68
+ */
69
+ export function buildUploadURL(uploadKey: string): string {
70
+ const paths = getR2Paths();
71
+ return buildR2URL(`${paths.uploads}/${uploadKey}`);
72
+ }
73
+
74
+ /**
75
+ * Extract key from R2 URL
76
+ */
77
+ export function extractR2Key(url: string): string | null {
78
+ const baseURL = getR2BaseURL();
79
+
80
+ if (!url.startsWith(baseURL)) {
81
+ return null;
82
+ }
83
+
84
+ const key = url.slice(baseURL.length).replace(/^\//, "");
85
+ return key || null;
86
+ }
87
+
88
+ /**
89
+ * Get resource type from key
90
+ */
91
+ export function getResourceTypeFromKey(key: string): R2ResourceType | null {
92
+ const paths = getR2Paths();
93
+
94
+ if (key.startsWith(`${paths.videos}/`)) {
95
+ return "video";
96
+ }
97
+
98
+ if (key.startsWith(`${paths.images}/`)) {
99
+ return "image";
100
+ }
101
+
102
+ if (key.startsWith(`${paths.thumbnails}/`)) {
103
+ return "image";
104
+ }
105
+
106
+ // Try to determine from file extension
107
+ const lowerKey = key.toLowerCase();
108
+ if (VIDEO_EXTENSIONS.some((ext) => lowerKey.endsWith(ext))) {
109
+ return "video";
110
+ }
111
+
112
+ if (IMAGE_EXTENSIONS.some((ext) => lowerKey.endsWith(ext))) {
113
+ return "image";
114
+ }
115
+
116
+ return null;
117
+ }
118
+
119
+ /**
120
+ * Check if URL is an R2 URL
121
+ */
122
+ export function isR2URL(url: string): boolean {
123
+ const baseURL = getR2BaseURL();
124
+ return url.startsWith(baseURL);
125
+ }
126
+
127
+ /**
128
+ * Convert R2 URL to CDN URL if custom domain is used
129
+ * (No-op for now, but useful for future CDN integration)
130
+ */
131
+ export function toCDNURL(url: string): string {
132
+ // Currently R2 public URL is the CDN URL
133
+ // In the future, this could convert to a custom CDN domain
134
+ return url;
135
+ }
@@ -0,0 +1,94 @@
1
+ /**
2
+ * R2 Initialization Module
3
+ * @description Initialize R2 storage with configuration
4
+ *
5
+ * @example
6
+ * ```ts
7
+ * import { initR2 } from '@umituz/react-native-r2-storage/init';
8
+ *
9
+ * initR2({
10
+ * config: {
11
+ * accountId: 'your-account-id',
12
+ * bucketName: 'your-bucket',
13
+ * publicDomain: 'your-domain.r2.dev',
14
+ * },
15
+ * assetCatalog: {
16
+ * videos: ['video1.mp4', 'video2.mp4'],
17
+ * },
18
+ * });
19
+ * ```
20
+ */
21
+
22
+ import type { R2InitOptions, R2Config, R2AssetCatalog } from "../domain/entities";
23
+ import { r2ConfigService } from "../infrastructure/services/r2Config.service";
24
+
25
+ /**
26
+ * Initialize R2 storage with configuration
27
+ *
28
+ * @param options - Initialization options including config and optional asset catalog
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * // Minimal configuration
33
+ * initR2({
34
+ * config: {
35
+ * accountId: 'your-account-id',
36
+ * bucketName: 'your-bucket',
37
+ * publicDomain: 'your-domain.r2.dev',
38
+ * },
39
+ * });
40
+ *
41
+ * // With asset catalog
42
+ * initR2({
43
+ * config: {
44
+ * accountId: 'your-account-id',
45
+ * bucketName: 'your-bucket',
46
+ * publicDomain: 'your-domain.r2.dev',
47
+ * },
48
+ * assetCatalog: {
49
+ * videos: ['video1.mp4', 'video2.mp4'],
50
+ * images: ['image1.jpg', 'image2.jpg'],
51
+ * },
52
+ * });
53
+ * ```
54
+ */
55
+ export function initR2(options: R2InitOptions): void {
56
+ const { config, assetCatalog } = options;
57
+
58
+ // Flatten asset catalog to array
59
+ const catalog = assetCatalog
60
+ ? [...(assetCatalog.videos ?? []), ...(assetCatalog.images ?? [])]
61
+ : undefined;
62
+
63
+ r2ConfigService.initialize(config, catalog);
64
+ }
65
+
66
+ /**
67
+ * Initialize R2 from environment variables
68
+ * Automatically reads from EXPO_PUBLIC_R2_* or R2_* env vars
69
+ *
70
+ * @example
71
+ * ```ts
72
+ * import { initR2FromEnv } from '@umituz/react-native-r2-storage/init';
73
+ *
74
+ * initR2FromEnv();
75
+ * ```
76
+ */
77
+ export function initR2FromEnv(): void {
78
+ r2ConfigService.initialize(r2ConfigService.getConfigFromEnv());
79
+ }
80
+
81
+ /**
82
+ * Reset R2 configuration
83
+ * Useful for testing or re-initialization
84
+ *
85
+ * @example
86
+ * ```ts
87
+ * import { resetR2 } from '@umituz/react-native-r2-storage/init';
88
+ *
89
+ * resetR2();
90
+ * ```
91
+ */
92
+ export function resetR2(): void {
93
+ r2ConfigService.reset();
94
+ }