@umituz/react-native-filesystem 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/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@umituz/react-native-filesystem",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Domain-Driven Design filesystem utilities for React Native apps with build-time module loading and runtime file operations",
|
|
5
|
+
"main": "./src/index.ts",
|
|
6
|
+
"types": "./src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"typecheck": "tsc --noEmit",
|
|
9
|
+
"lint": "tsc --noEmit",
|
|
10
|
+
"version:patch": "npm version patch -m 'chore: release v%s'",
|
|
11
|
+
"version:minor": "npm version minor -m 'chore: release v%s'",
|
|
12
|
+
"version:major": "npm version major -m 'chore: release v%s'"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"react-native",
|
|
16
|
+
"filesystem",
|
|
17
|
+
"file-system",
|
|
18
|
+
"expo-file-system",
|
|
19
|
+
"module-loader",
|
|
20
|
+
"require-context",
|
|
21
|
+
"ddd",
|
|
22
|
+
"domain-driven-design",
|
|
23
|
+
"type-safe"
|
|
24
|
+
],
|
|
25
|
+
"author": "Ümit UZ <umit@umituz.com>",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"repository": {
|
|
28
|
+
"type": "git",
|
|
29
|
+
"url": "git@github.com:umituz/react-native-filesystem.git"
|
|
30
|
+
},
|
|
31
|
+
"peerDependencies": {
|
|
32
|
+
"expo-file-system": "~17.0.0",
|
|
33
|
+
"react": ">=18.2.0",
|
|
34
|
+
"react-native": ">=0.74.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@types/react": "^18.2.45",
|
|
38
|
+
"@types/react-native": "^0.73.0",
|
|
39
|
+
"expo-file-system": "~17.0.0",
|
|
40
|
+
"react": "^18.2.0",
|
|
41
|
+
"react-native": "^0.74.0",
|
|
42
|
+
"typescript": "^5.3.3"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"src",
|
|
49
|
+
"README.md",
|
|
50
|
+
"LICENSE"
|
|
51
|
+
]
|
|
52
|
+
}
|
|
53
|
+
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Domain - File Entities
|
|
3
|
+
*
|
|
4
|
+
* This file defines core types and interfaces for runtime file operations.
|
|
5
|
+
* Uses expo-file-system for reading, writing, and managing files on device.
|
|
6
|
+
*
|
|
7
|
+
* @domain filesystem
|
|
8
|
+
* @layer domain/entities
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* File information interface
|
|
13
|
+
*/
|
|
14
|
+
export interface FileInfo {
|
|
15
|
+
uri: string;
|
|
16
|
+
name: string;
|
|
17
|
+
size: number;
|
|
18
|
+
exists: boolean;
|
|
19
|
+
isDirectory: boolean;
|
|
20
|
+
modificationTime: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* File encoding types (compatible with expo-file-system)
|
|
25
|
+
*/
|
|
26
|
+
export type FileEncoding = 'utf8' | 'base64';
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Directory type (compatible with expo-file-system)
|
|
30
|
+
*/
|
|
31
|
+
export type DirectoryType = 'documentDirectory' | 'cacheDirectory';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* File operation result
|
|
35
|
+
*/
|
|
36
|
+
export interface FileOperationResult {
|
|
37
|
+
success: boolean;
|
|
38
|
+
uri?: string;
|
|
39
|
+
error?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Download progress info
|
|
44
|
+
*/
|
|
45
|
+
export interface DownloadProgress {
|
|
46
|
+
totalBytesWritten: number;
|
|
47
|
+
totalBytesExpectedToWrite: number;
|
|
48
|
+
progress: number; // 0 to 1
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* File constants
|
|
53
|
+
*/
|
|
54
|
+
export const FILE_CONSTANTS = {
|
|
55
|
+
MAX_FILE_NAME_LENGTH: 255,
|
|
56
|
+
DEFAULT_ENCODING: 'utf8' as FileEncoding,
|
|
57
|
+
TEMP_FILE_PREFIX: 'tmp_',
|
|
58
|
+
} as const;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* File utilities
|
|
62
|
+
*/
|
|
63
|
+
export class FileUtils {
|
|
64
|
+
/**
|
|
65
|
+
* Generate unique filename with timestamp
|
|
66
|
+
*/
|
|
67
|
+
static generateUniqueFilename(originalName: string): string {
|
|
68
|
+
const timestamp = Date.now();
|
|
69
|
+
const randomStr = Math.random().toString(36).substring(7);
|
|
70
|
+
const extension = FileUtils.getFileExtension(originalName);
|
|
71
|
+
const baseName = originalName.replace(extension, '').substring(0, 50);
|
|
72
|
+
return `${baseName}_${timestamp}_${randomStr}${extension}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Get file extension from filename
|
|
77
|
+
*/
|
|
78
|
+
static getFileExtension(filename: string): string {
|
|
79
|
+
const match = filename.match(/\.[^.]+$/);
|
|
80
|
+
return match ? match[0] : '';
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get filename without extension
|
|
85
|
+
*/
|
|
86
|
+
static getFilenameWithoutExtension(filename: string): string {
|
|
87
|
+
return filename.replace(/\.[^.]+$/, '');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Sanitize filename (remove invalid characters)
|
|
92
|
+
*/
|
|
93
|
+
static sanitizeFilename(filename: string): string {
|
|
94
|
+
return filename
|
|
95
|
+
.replace(/[^a-zA-Z0-9._-]/g, '_')
|
|
96
|
+
.substring(0, FILE_CONSTANTS.MAX_FILE_NAME_LENGTH);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Format file size to human-readable string
|
|
101
|
+
*/
|
|
102
|
+
static formatFileSize(bytes: number): string {
|
|
103
|
+
if (bytes === 0) return '0 B';
|
|
104
|
+
const k = 1024;
|
|
105
|
+
const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
106
|
+
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
107
|
+
return `${(bytes / Math.pow(k, i)).toFixed(2)} ${sizes[i]}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Parse download progress percentage
|
|
112
|
+
*/
|
|
113
|
+
static calculateProgress(written: number, total: number): number {
|
|
114
|
+
if (total === 0) return 0;
|
|
115
|
+
return Math.min(Math.max(written / total, 0), 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Check if path is absolute
|
|
120
|
+
*/
|
|
121
|
+
static isAbsolutePath(path: string): boolean {
|
|
122
|
+
return path.startsWith('/') || path.startsWith('file://');
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Join path segments
|
|
127
|
+
*/
|
|
128
|
+
static joinPaths(...segments: string[]): string {
|
|
129
|
+
return segments
|
|
130
|
+
.filter(Boolean)
|
|
131
|
+
.join('/')
|
|
132
|
+
.replace(/\/+/g, '/');
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Context Entity
|
|
3
|
+
*
|
|
4
|
+
* Type definitions for Metro bundler's require.context feature
|
|
5
|
+
* Used for automatic file discovery and module loading
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Metro bundler require.context return type
|
|
10
|
+
*/
|
|
11
|
+
export interface RequireContext {
|
|
12
|
+
keys(): string[];
|
|
13
|
+
(id: string): any;
|
|
14
|
+
<T>(id: string): T;
|
|
15
|
+
resolve(id: string): string;
|
|
16
|
+
id: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Module collection type
|
|
21
|
+
* Key: module name (without extension)
|
|
22
|
+
* Value: module content
|
|
23
|
+
*/
|
|
24
|
+
export type ModuleCollection = Record<string, any>;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* File extension type for filtering
|
|
28
|
+
*/
|
|
29
|
+
export type FileExtension = '.json' | '.js' | '.ts' | '.tsx';
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Domain - Barrel Export
|
|
3
|
+
*
|
|
4
|
+
* CENTRAL PLACE FOR ALL FILE OPERATIONS
|
|
5
|
+
*
|
|
6
|
+
* Provides two main capabilities:
|
|
7
|
+
* 1. Build-time module loading (require.context)
|
|
8
|
+
* 2. Runtime file operations (expo-file-system)
|
|
9
|
+
*
|
|
10
|
+
* @domain filesystem
|
|
11
|
+
* @enabled true (All apps - Infrastructure domain)
|
|
12
|
+
*
|
|
13
|
+
* ARCHITECTURE:
|
|
14
|
+
* - Domain Layer: Entities (ModuleContext for build-time, File for runtime)
|
|
15
|
+
* - Infrastructure Layer: Utils (module loading) + Services (file operations)
|
|
16
|
+
*
|
|
17
|
+
* ============================================================================
|
|
18
|
+
* BUILD-TIME MODULE LOADING (require.context)
|
|
19
|
+
* ============================================================================
|
|
20
|
+
*
|
|
21
|
+
* FEATURES:
|
|
22
|
+
* - Auto-discover JSON files in directory
|
|
23
|
+
* - Load all modules with single function call
|
|
24
|
+
* - Type-safe module loading
|
|
25
|
+
* - Metro bundler compatible (React Native)
|
|
26
|
+
* - Zero runtime dependencies
|
|
27
|
+
* - Build-time resolution (fast)
|
|
28
|
+
*
|
|
29
|
+
* USAGE - Build-time Module Loading:
|
|
30
|
+
*
|
|
31
|
+
* Basic Example:
|
|
32
|
+
* ```typescript
|
|
33
|
+
* import { loadJsonModules } from '@domains/filesystem';
|
|
34
|
+
*
|
|
35
|
+
* // Auto-load all JSON files in current directory
|
|
36
|
+
* const translationContext = require.context('./', false, /\.json$/);
|
|
37
|
+
* const translations = loadJsonModules(translationContext);
|
|
38
|
+
*
|
|
39
|
+
* export default translations;
|
|
40
|
+
* // Result: { common: {...}, errors: {...}, settings: {...} }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* Custom Transform Example:
|
|
44
|
+
* ```typescript
|
|
45
|
+
* import { loadModulesWithTransform } from '@domains/filesystem';
|
|
46
|
+
*
|
|
47
|
+
* const context = require.context('./', false, /\.json$/);
|
|
48
|
+
* const modules = loadModulesWithTransform(context, name => name.toUpperCase());
|
|
49
|
+
* // Result: { COMMON: {...}, ERRORS: {...} }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* Get Names Example:
|
|
53
|
+
* ```typescript
|
|
54
|
+
* import { getModuleNames } from '@domains/filesystem';
|
|
55
|
+
*
|
|
56
|
+
* const context = require.context('./', false, /\.json$/);
|
|
57
|
+
* const names = getModuleNames(context);
|
|
58
|
+
* // Result: ['common', 'errors', 'settings']
|
|
59
|
+
* ```
|
|
60
|
+
*
|
|
61
|
+
* ============================================================================
|
|
62
|
+
* RUNTIME FILE OPERATIONS (expo-file-system)
|
|
63
|
+
* ============================================================================
|
|
64
|
+
*
|
|
65
|
+
* FEATURES:
|
|
66
|
+
* - Read/write files on device
|
|
67
|
+
* - Download files from URLs
|
|
68
|
+
* - Directory management
|
|
69
|
+
* - Cache management
|
|
70
|
+
* - File copying/moving
|
|
71
|
+
*
|
|
72
|
+
* USAGE - Runtime File Operations:
|
|
73
|
+
*
|
|
74
|
+
* Read/Write Files:
|
|
75
|
+
* ```typescript
|
|
76
|
+
* import { FileSystemService } from '@domains/filesystem';
|
|
77
|
+
*
|
|
78
|
+
* // Write file
|
|
79
|
+
* const result = await FileSystemService.writeFile(
|
|
80
|
+
* FileSystemService.generateFilePath('data.json'),
|
|
81
|
+
* JSON.stringify({ key: 'value' })
|
|
82
|
+
* );
|
|
83
|
+
*
|
|
84
|
+
* // Read file
|
|
85
|
+
* const content = await FileSystemService.readFile(result.uri);
|
|
86
|
+
* ```
|
|
87
|
+
*
|
|
88
|
+
* Download Files:
|
|
89
|
+
* ```typescript
|
|
90
|
+
* import { FileSystemService } from '@domains/filesystem';
|
|
91
|
+
*
|
|
92
|
+
* const result = await FileSystemService.downloadFile(
|
|
93
|
+
* 'https://example.com/file.pdf'
|
|
94
|
+
* );
|
|
95
|
+
* if (result.success) {
|
|
96
|
+
* console.log('Downloaded to:', result.uri);
|
|
97
|
+
* }
|
|
98
|
+
* ```
|
|
99
|
+
*
|
|
100
|
+
* Directory Operations:
|
|
101
|
+
* ```typescript
|
|
102
|
+
* import { FileSystemService, FileUtils } from '@domains/filesystem';
|
|
103
|
+
*
|
|
104
|
+
* // Get document directory
|
|
105
|
+
* const docDir = FileSystemService.getDocumentDirectory();
|
|
106
|
+
*
|
|
107
|
+
* // List files
|
|
108
|
+
* const files = await FileSystemService.listDirectory(docDir);
|
|
109
|
+
*
|
|
110
|
+
* // Clear cache
|
|
111
|
+
* await FileSystemService.clearCache();
|
|
112
|
+
* ```
|
|
113
|
+
*
|
|
114
|
+
* File Utilities:
|
|
115
|
+
* ```typescript
|
|
116
|
+
* import { FileUtils } from '@domains/filesystem';
|
|
117
|
+
*
|
|
118
|
+
* // Format file size
|
|
119
|
+
* const size = FileUtils.formatFileSize(1024000); // "1.00 MB"
|
|
120
|
+
*
|
|
121
|
+
* // Generate unique filename
|
|
122
|
+
* const filename = FileUtils.generateUniqueFilename('photo.jpg');
|
|
123
|
+
*
|
|
124
|
+
* // Sanitize filename
|
|
125
|
+
* const safe = FileUtils.sanitizeFilename('my file!@#.txt'); // "my_file___.txt"
|
|
126
|
+
* ```
|
|
127
|
+
*
|
|
128
|
+
* BENEFITS:
|
|
129
|
+
* - Centralized file operations (no duplication)
|
|
130
|
+
* - Type-safe with TypeScript
|
|
131
|
+
* - Works across all 100+ apps
|
|
132
|
+
* - Used by other domains (media, etc.)
|
|
133
|
+
* - Clean, maintainable code
|
|
134
|
+
*
|
|
135
|
+
* @see https://docs.expo.dev/versions/latest/sdk/filesystem/
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
// ============================================================================
|
|
139
|
+
// BUILD-TIME MODULE LOADING
|
|
140
|
+
// ============================================================================
|
|
141
|
+
|
|
142
|
+
// Domain Layer - Module Context Entities
|
|
143
|
+
export type {
|
|
144
|
+
RequireContext,
|
|
145
|
+
ModuleCollection,
|
|
146
|
+
FileExtension,
|
|
147
|
+
} from './domain/entities/ModuleContext';
|
|
148
|
+
|
|
149
|
+
// Infrastructure Layer - Module Loading Utils
|
|
150
|
+
export {
|
|
151
|
+
loadJsonModules,
|
|
152
|
+
loadModulesWithTransform,
|
|
153
|
+
getModuleNames,
|
|
154
|
+
} from './infrastructure/utils/moduleLoader';
|
|
155
|
+
|
|
156
|
+
// ============================================================================
|
|
157
|
+
// RUNTIME FILE OPERATIONS
|
|
158
|
+
// ============================================================================
|
|
159
|
+
|
|
160
|
+
// Domain Layer - File Entities
|
|
161
|
+
export type {
|
|
162
|
+
FileInfo,
|
|
163
|
+
FileOperationResult,
|
|
164
|
+
DownloadProgress,
|
|
165
|
+
} from './domain/entities/File';
|
|
166
|
+
|
|
167
|
+
export type {
|
|
168
|
+
FileEncoding,
|
|
169
|
+
DirectoryType,
|
|
170
|
+
} from './domain/entities/File';
|
|
171
|
+
|
|
172
|
+
export {
|
|
173
|
+
FILE_CONSTANTS,
|
|
174
|
+
FileUtils,
|
|
175
|
+
} from './domain/entities/File';
|
|
176
|
+
|
|
177
|
+
// Infrastructure Layer - FileSystem Service
|
|
178
|
+
export { FileSystemService } from './infrastructure/services/FileSystemService';
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Filesystem Domain - FileSystem Service
|
|
3
|
+
*
|
|
4
|
+
* Service for runtime file operations using expo-file-system.
|
|
5
|
+
* Provides abstraction layer for file read/write/delete operations on device.
|
|
6
|
+
*
|
|
7
|
+
* @domain filesystem
|
|
8
|
+
* @layer infrastructure/services
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import * as FileSystem from 'expo-file-system';
|
|
12
|
+
import type { FileInfo, FileOperationResult, DirectoryType, FileEncoding } from '../../domain/entities/File';
|
|
13
|
+
import { FileUtils } from '../../domain/entities/File';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* FileSystem service for device file operations
|
|
17
|
+
*
|
|
18
|
+
* CENTRAL PLACE FOR ALL FILE OPERATIONS
|
|
19
|
+
* - Reading/writing files
|
|
20
|
+
* - Directory management
|
|
21
|
+
* - File downloads
|
|
22
|
+
* - Cache management
|
|
23
|
+
*
|
|
24
|
+
* Used by other domains (media, etc.) for file operations
|
|
25
|
+
*/
|
|
26
|
+
export class FileSystemService {
|
|
27
|
+
/**
|
|
28
|
+
* Get file information
|
|
29
|
+
*/
|
|
30
|
+
static async getFileInfo(uri: string): Promise<FileInfo | null> {
|
|
31
|
+
try {
|
|
32
|
+
const info = await FileSystem.getInfoAsync(uri);
|
|
33
|
+
|
|
34
|
+
if (!info.exists) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return {
|
|
39
|
+
uri,
|
|
40
|
+
name: uri.split('/').pop() || '',
|
|
41
|
+
size: info.size || 0,
|
|
42
|
+
exists: info.exists,
|
|
43
|
+
isDirectory: info.isDirectory || false,
|
|
44
|
+
modificationTime: info.modificationTime || 0,
|
|
45
|
+
};
|
|
46
|
+
} catch (error) {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Read file as string
|
|
53
|
+
*/
|
|
54
|
+
static async readFile(uri: string, encoding: FileEncoding = 'utf8'): Promise<string | null> {
|
|
55
|
+
try {
|
|
56
|
+
const content = await FileSystem.readAsStringAsync(uri, { encoding });
|
|
57
|
+
return content;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Write string to file
|
|
65
|
+
*/
|
|
66
|
+
static async writeFile(uri: string, content: string, encoding: FileEncoding = 'utf8'): Promise<FileOperationResult> {
|
|
67
|
+
try {
|
|
68
|
+
await FileSystem.writeAsStringAsync(uri, content, { encoding });
|
|
69
|
+
return { success: true, uri };
|
|
70
|
+
} catch (error) {
|
|
71
|
+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Delete file or directory
|
|
77
|
+
*/
|
|
78
|
+
static async deleteFile(uri: string): Promise<boolean> {
|
|
79
|
+
try {
|
|
80
|
+
await FileSystem.deleteAsync(uri, { idempotent: true });
|
|
81
|
+
return true;
|
|
82
|
+
} catch (error) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Copy file
|
|
89
|
+
*/
|
|
90
|
+
static async copyFile(sourceUri: string, destinationUri: string): Promise<FileOperationResult> {
|
|
91
|
+
try {
|
|
92
|
+
await FileSystem.copyAsync({
|
|
93
|
+
from: sourceUri,
|
|
94
|
+
to: destinationUri,
|
|
95
|
+
});
|
|
96
|
+
return { success: true, uri: destinationUri };
|
|
97
|
+
} catch (error) {
|
|
98
|
+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Move file
|
|
104
|
+
*/
|
|
105
|
+
static async moveFile(sourceUri: string, destinationUri: string): Promise<FileOperationResult> {
|
|
106
|
+
try {
|
|
107
|
+
await FileSystem.moveAsync({
|
|
108
|
+
from: sourceUri,
|
|
109
|
+
to: destinationUri,
|
|
110
|
+
});
|
|
111
|
+
return { success: true, uri: destinationUri };
|
|
112
|
+
} catch (error) {
|
|
113
|
+
return { success: false, error: error instanceof Error ? error.message : 'Unknown error' };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Create directory
|
|
119
|
+
*/
|
|
120
|
+
static async createDirectory(uri: string): Promise<boolean> {
|
|
121
|
+
try {
|
|
122
|
+
await FileSystem.makeDirectoryAsync(uri, { intermediates: true });
|
|
123
|
+
return true;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* List directory contents
|
|
131
|
+
*/
|
|
132
|
+
static async listDirectory(uri: string): Promise<string[]> {
|
|
133
|
+
try {
|
|
134
|
+
const files = await FileSystem.readDirectoryAsync(uri);
|
|
135
|
+
return files;
|
|
136
|
+
} catch (error) {
|
|
137
|
+
return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Check if file exists
|
|
143
|
+
*/
|
|
144
|
+
static async exists(uri: string): Promise<boolean> {
|
|
145
|
+
try {
|
|
146
|
+
const info = await FileSystem.getInfoAsync(uri);
|
|
147
|
+
return info.exists;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get directory path by type
|
|
155
|
+
*/
|
|
156
|
+
static getDirectoryPath(type: DirectoryType): string {
|
|
157
|
+
switch (type) {
|
|
158
|
+
case 'documentDirectory':
|
|
159
|
+
return FileSystem.documentDirectory || '';
|
|
160
|
+
case 'cacheDirectory':
|
|
161
|
+
return FileSystem.cacheDirectory || '';
|
|
162
|
+
default:
|
|
163
|
+
return '';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Get document directory path
|
|
169
|
+
*/
|
|
170
|
+
static getDocumentDirectory(): string {
|
|
171
|
+
return FileSystemService.getDirectoryPath('documentDirectory');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get cache directory path
|
|
176
|
+
*/
|
|
177
|
+
static getCacheDirectory(): string {
|
|
178
|
+
return FileSystemService.getDirectoryPath('cacheDirectory');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Generate unique file path in specified directory
|
|
183
|
+
*/
|
|
184
|
+
static generateFilePath(filename: string, directory: DirectoryType = 'documentDirectory'): string {
|
|
185
|
+
const dirPath = FileSystemService.getDirectoryPath(directory);
|
|
186
|
+
const uniqueFilename = FileUtils.generateUniqueFilename(filename);
|
|
187
|
+
return FileUtils.joinPaths(dirPath, uniqueFilename);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Download file from URL
|
|
192
|
+
*/
|
|
193
|
+
static async downloadFile(url: string, destinationUri?: string): Promise<FileOperationResult> {
|
|
194
|
+
try {
|
|
195
|
+
const destination = destinationUri || FileSystemService.generateFilePath('download');
|
|
196
|
+
const result = await FileSystem.downloadAsync(url, destination);
|
|
197
|
+
return { success: true, uri: result.uri };
|
|
198
|
+
} catch (error) {
|
|
199
|
+
return { success: false, error: error instanceof Error ? error.message : 'Download failed' };
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get file size
|
|
205
|
+
*/
|
|
206
|
+
static async getFileSize(uri: string): Promise<number> {
|
|
207
|
+
const info = await FileSystemService.getFileInfo(uri);
|
|
208
|
+
return info?.size || 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Clear cache directory
|
|
213
|
+
*/
|
|
214
|
+
static async clearCache(): Promise<boolean> {
|
|
215
|
+
try {
|
|
216
|
+
const cacheDir = FileSystemService.getCacheDirectory();
|
|
217
|
+
if (!cacheDir) return false;
|
|
218
|
+
|
|
219
|
+
const files = await FileSystemService.listDirectory(cacheDir);
|
|
220
|
+
await Promise.all(
|
|
221
|
+
files.map(file => FileSystemService.deleteFile(FileUtils.joinPaths(cacheDir, file)))
|
|
222
|
+
);
|
|
223
|
+
return true;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Get total size of directory
|
|
231
|
+
*/
|
|
232
|
+
static async getDirectorySize(uri: string): Promise<number> {
|
|
233
|
+
try {
|
|
234
|
+
const files = await FileSystemService.listDirectory(uri);
|
|
235
|
+
const sizes = await Promise.all(
|
|
236
|
+
files.map(async file => {
|
|
237
|
+
const filePath = FileUtils.joinPaths(uri, file);
|
|
238
|
+
const info = await FileSystemService.getFileInfo(filePath);
|
|
239
|
+
return info?.size || 0;
|
|
240
|
+
})
|
|
241
|
+
);
|
|
242
|
+
return sizes.reduce((total, size) => total + size, 0);
|
|
243
|
+
} catch (error) {
|
|
244
|
+
return 0;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Copy file to cache directory
|
|
250
|
+
*/
|
|
251
|
+
static async copyToCache(sourceUri: string, filename?: string): Promise<FileOperationResult> {
|
|
252
|
+
const name = filename || sourceUri.split('/').pop() || 'file';
|
|
253
|
+
const destinationUri = FileSystemService.generateFilePath(name, 'cacheDirectory');
|
|
254
|
+
return FileSystemService.copyFile(sourceUri, destinationUri);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Copy file to document directory
|
|
259
|
+
*/
|
|
260
|
+
static async copyToDocuments(sourceUri: string, filename?: string): Promise<FileOperationResult> {
|
|
261
|
+
const name = filename || sourceUri.split('/').pop() || 'file';
|
|
262
|
+
const destinationUri = FileSystemService.generateFilePath(name, 'documentDirectory');
|
|
263
|
+
return FileSystemService.copyFile(sourceUri, destinationUri);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module Loader Utilities
|
|
3
|
+
*
|
|
4
|
+
* Provides automatic module loading using Metro bundler's require.context
|
|
5
|
+
* Used for auto-discovering and importing files at build time
|
|
6
|
+
*
|
|
7
|
+
* USAGE:
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Auto-load all JSON files in current directory
|
|
10
|
+
* const context = require.context('./', false, /\.json$/);
|
|
11
|
+
* const modules = loadJsonModules(context);
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { RequireContext, ModuleCollection } from '../../domain/entities/ModuleContext';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Load JSON modules from a require.context
|
|
19
|
+
*
|
|
20
|
+
* @param context - Metro bundler require.context result
|
|
21
|
+
* @returns Object with module names as keys and content as values
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* const translationContext = require.context('./', false, /\.json$/);
|
|
26
|
+
* const translations = loadJsonModules(translationContext);
|
|
27
|
+
* // Result: { common: {...}, errors: {...}, settings: {...} }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function loadJsonModules(context: RequireContext): ModuleCollection {
|
|
31
|
+
const modules: ModuleCollection = {};
|
|
32
|
+
|
|
33
|
+
context.keys().forEach((key: string) => {
|
|
34
|
+
// Extract module name from path
|
|
35
|
+
// './animation.json' -> 'animation'
|
|
36
|
+
// './common.json' -> 'common'
|
|
37
|
+
const moduleName = key
|
|
38
|
+
.replace('./', '')
|
|
39
|
+
.replace(/\.(json|js|ts|tsx)$/, '');
|
|
40
|
+
|
|
41
|
+
// Load module content
|
|
42
|
+
modules[moduleName] = context(key);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
return modules;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Load modules with custom name transformation
|
|
50
|
+
*
|
|
51
|
+
* @param context - Metro bundler require.context result
|
|
52
|
+
* @param transformName - Function to transform module name
|
|
53
|
+
* @returns Object with transformed names as keys
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const context = require.context('./', false, /\.json$/);
|
|
58
|
+
* const modules = loadModulesWithTransform(context, name => name.toUpperCase());
|
|
59
|
+
* // Result: { COMMON: {...}, ERRORS: {...} }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function loadModulesWithTransform(
|
|
63
|
+
context: RequireContext,
|
|
64
|
+
transformName: (name: string) => string
|
|
65
|
+
): ModuleCollection {
|
|
66
|
+
const modules: ModuleCollection = {};
|
|
67
|
+
|
|
68
|
+
context.keys().forEach((key: string) => {
|
|
69
|
+
const baseName = key
|
|
70
|
+
.replace('./', '')
|
|
71
|
+
.replace(/\.(json|js|ts|tsx)$/, '');
|
|
72
|
+
|
|
73
|
+
const transformedName = transformName(baseName);
|
|
74
|
+
modules[transformedName] = context(key);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return modules;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Get list of module names from context
|
|
82
|
+
*
|
|
83
|
+
* @param context - Metro bundler require.context result
|
|
84
|
+
* @returns Array of module names (without extensions)
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```typescript
|
|
88
|
+
* const context = require.context('./', false, /\.json$/);
|
|
89
|
+
* const names = getModuleNames(context);
|
|
90
|
+
* // Result: ['animation', 'common', 'errors', 'settings']
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
export function getModuleNames(context: RequireContext): string[] {
|
|
94
|
+
return context.keys().map((key: string) =>
|
|
95
|
+
key
|
|
96
|
+
.replace('./', '')
|
|
97
|
+
.replace(/\.(json|js|ts|tsx)$/, '')
|
|
98
|
+
);
|
|
99
|
+
}
|