@arela/uploader 0.2.4 → 0.2.6

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.
@@ -0,0 +1,148 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * File Operations Utility
6
+ * Provides optimized file system operations with batching
7
+ */
8
+ export class FileOperations {
9
+ /**
10
+ * Batch read file stats for multiple files
11
+ * @param {string[]} filePaths - Array of file paths
12
+ * @returns {Array} Array of results with path, stats, and error
13
+ */
14
+ static batchReadFileStats(filePaths) {
15
+ const results = [];
16
+
17
+ for (const filePath of filePaths) {
18
+ try {
19
+ const stats = fs.statSync(filePath);
20
+ results.push({ path: filePath, stats, error: null });
21
+ } catch (error) {
22
+ results.push({ path: filePath, stats: null, error: error.message });
23
+ }
24
+ }
25
+
26
+ return results;
27
+ }
28
+
29
+ /**
30
+ * Check if file exists synchronously
31
+ * @param {string} filePath - Path to file
32
+ * @returns {boolean} True if file exists
33
+ */
34
+ static fileExists(filePath) {
35
+ try {
36
+ return fs.existsSync(filePath);
37
+ } catch (error) {
38
+ return false;
39
+ }
40
+ }
41
+
42
+ /**
43
+ * Get file size safely
44
+ * @param {string} filePath - Path to file
45
+ * @returns {number|null} File size in bytes or null if error
46
+ */
47
+ static getFileSize(filePath) {
48
+ try {
49
+ const stats = fs.statSync(filePath);
50
+ return stats.size;
51
+ } catch (error) {
52
+ return null;
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Get file modification time
58
+ * @param {string} filePath - Path to file
59
+ * @returns {Date|null} Modification time or null if error
60
+ */
61
+ static getFileModTime(filePath) {
62
+ try {
63
+ const stats = fs.statSync(filePath);
64
+ return stats.mtime;
65
+ } catch (error) {
66
+ return null;
67
+ }
68
+ }
69
+
70
+ /**
71
+ * Get file stats
72
+ * @param {string} filePath - Path to file
73
+ * @returns {Object|null} File stats or null if error
74
+ */
75
+ static getFileStats(filePath) {
76
+ try {
77
+ return fs.statSync(filePath);
78
+ } catch (error) {
79
+ return null;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Create directory recursively if it doesn't exist
85
+ * @param {string} dirPath - Directory path
86
+ * @returns {boolean} True if successful
87
+ */
88
+ static ensureDirectory(dirPath) {
89
+ try {
90
+ if (!fs.existsSync(dirPath)) {
91
+ fs.mkdirSync(dirPath, { recursive: true });
92
+ }
93
+ return true;
94
+ } catch (error) {
95
+ return false;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Read file with error handling
101
+ * @param {string} filePath - Path to file
102
+ * @param {string} encoding - File encoding (default: 'utf-8')
103
+ * @returns {string|null} File content or null if error
104
+ */
105
+ static readFileContent(filePath, encoding = 'utf-8') {
106
+ try {
107
+ return fs.readFileSync(filePath, encoding);
108
+ } catch (error) {
109
+ return null;
110
+ }
111
+ }
112
+
113
+ /**
114
+ * Write file with error handling and directory creation
115
+ * @param {string} filePath - Path to file
116
+ * @param {string} content - Content to write
117
+ * @param {string} encoding - File encoding (default: 'utf-8')
118
+ * @returns {boolean} True if successful
119
+ */
120
+ static writeFileContent(filePath, content, encoding = 'utf-8') {
121
+ try {
122
+ const dir = path.dirname(filePath);
123
+ this.ensureDirectory(dir);
124
+ fs.writeFileSync(filePath, content, encoding);
125
+ return true;
126
+ } catch (error) {
127
+ return false;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Append to file with error handling
133
+ * @param {string} filePath - Path to file
134
+ * @param {string} content - Content to append
135
+ * @param {string} encoding - File encoding (default: 'utf-8')
136
+ * @returns {boolean} True if successful
137
+ */
138
+ static appendToFile(filePath, content, encoding = 'utf-8') {
139
+ try {
140
+ fs.appendFileSync(filePath, content, encoding);
141
+ return true;
142
+ } catch (error) {
143
+ return false;
144
+ }
145
+ }
146
+ }
147
+
148
+ export default FileOperations;
@@ -0,0 +1,99 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * File Sanitization Utility
5
+ * Handles filename sanitization with caching for performance
6
+ */
7
+ export class FileSanitizer {
8
+ constructor() {
9
+ this.cache = new Map();
10
+ this.patterns = [
11
+ [/[áàâäãåāăą]/gi, 'a'],
12
+ [/[éèêëēĕėę]/gi, 'e'],
13
+ [/[íìîïīĭį]/gi, 'i'],
14
+ [/[óòôöõōŏő]/gi, 'o'],
15
+ [/[úùûüūŭů]/gi, 'u'],
16
+ [/[ñň]/gi, 'n'],
17
+ [/[ç]/gi, 'c'],
18
+ [/[ý]/gi, 'y'],
19
+ [/[멕]/g, 'meok'],
20
+ [/[시]/g, 'si'],
21
+ [/[코]/g, 'ko'],
22
+ [/[용]/g, 'yong'],
23
+ [/[가-힣]/g, 'kr'],
24
+ [/[\u0300-\u036f]/g, ''],
25
+ [/[\\?%*:|"<>[\]~`^]/g, '-'],
26
+ [/[{}]/g, '-'],
27
+ [/[&]/g, 'and'],
28
+ [/[()]/g, ''],
29
+ [/\s+/g, '-'],
30
+ [/-+/g, '-'],
31
+ [/^-+|-+$/g, ''],
32
+ [/^\.+/, ''],
33
+ [/[^\w.-]/g, ''],
34
+ ];
35
+ }
36
+
37
+ /**
38
+ * Sanitize a filename for safe storage
39
+ * @param {string} fileName - Original filename
40
+ * @returns {string} Sanitized filename
41
+ */
42
+ sanitizeFileName(fileName) {
43
+ if (this.cache.has(fileName)) {
44
+ return this.cache.get(fileName);
45
+ }
46
+
47
+ const ext = path.extname(fileName);
48
+ const nameWithoutExt = path.basename(fileName, ext);
49
+
50
+ // If already clean, return as-is
51
+ if (/^[a-zA-Z0-9._-]+$/.test(nameWithoutExt)) {
52
+ const result = fileName;
53
+ this.cache.set(fileName, result);
54
+ return result;
55
+ }
56
+
57
+ let sanitized = nameWithoutExt.normalize('NFD');
58
+
59
+ // Apply all sanitization patterns
60
+ for (const [pattern, replacement] of this.patterns) {
61
+ sanitized = sanitized.replace(pattern, replacement);
62
+ }
63
+
64
+ // Additional cleanup
65
+ sanitized = sanitized
66
+ .replace(/~/g, '-')
67
+ .replace(/\s+/g, '-')
68
+ .replace(/\.+/g, '-')
69
+ .replace(/-+/g, '-')
70
+ .replace(/^-+|-+$/g, '');
71
+
72
+ if (!sanitized) {
73
+ sanitized = 'unnamed_file';
74
+ }
75
+
76
+ const result = sanitized + ext;
77
+ this.cache.set(fileName, result);
78
+ return result;
79
+ }
80
+
81
+ /**
82
+ * Clear the sanitization cache
83
+ */
84
+ clearCache() {
85
+ this.cache.clear();
86
+ }
87
+
88
+ /**
89
+ * Get cache size for monitoring
90
+ * @returns {number} Number of cached entries
91
+ */
92
+ getCacheSize() {
93
+ return this.cache.size;
94
+ }
95
+ }
96
+
97
+ // Export singleton instance
98
+ export const fileSanitizer = new FileSanitizer();
99
+ export default fileSanitizer;
@@ -0,0 +1,198 @@
1
+ import path from 'path';
2
+
3
+ /**
4
+ * Path Detection Utility
5
+ * Extracts year and pedimento information from file paths with caching
6
+ */
7
+ export class PathDetector {
8
+ constructor() {
9
+ this.cache = new Map();
10
+ }
11
+
12
+ /**
13
+ * Extract year and pedimento number from file path with caching
14
+ * @param {string} filePath - Full file path
15
+ * @param {string} basePath - Base path to make relative
16
+ * @returns {Object} Detection result with year, pedimento, and detected flag
17
+ */
18
+ extractYearAndPedimentoFromPath(filePath, basePath) {
19
+ const cacheKey = `${filePath}|${basePath}`;
20
+
21
+ if (this.cache.has(cacheKey)) {
22
+ return this.cache.get(cacheKey);
23
+ }
24
+
25
+ const detection = this.#performDetection(filePath, basePath);
26
+ this.cache.set(cacheKey, detection);
27
+
28
+ return detection;
29
+ }
30
+
31
+ /**
32
+ * Perform the actual path detection
33
+ * @private
34
+ * @param {string} filePath - Full file path
35
+ * @param {string} basePath - Base path to make relative
36
+ * @returns {Object} Detection result
37
+ */
38
+ #performDetection(filePath, basePath) {
39
+ try {
40
+ const relativePath = path.relative(basePath, filePath);
41
+ const pathParts = relativePath.split(path.sep);
42
+
43
+ let year = null;
44
+ let pedimento = null;
45
+
46
+ // Pattern 1: Direct year/pedimento structure (2024/4023260)
47
+ year = this.#detectDirectPattern(pathParts);
48
+ pedimento = year ? this.#findPedimentoForYear(pathParts, year) : null;
49
+
50
+ // Pattern 2: Named patterns (año2024, ped4023260)
51
+ if (!year || !pedimento) {
52
+ const namedDetection = this.#detectNamedPatterns(pathParts);
53
+ year = year || namedDetection.year;
54
+ pedimento = pedimento || namedDetection.pedimento;
55
+ }
56
+
57
+ // Pattern 3: Loose year detection
58
+ if (!year) {
59
+ year = this.#detectLooseYear(pathParts);
60
+ }
61
+
62
+ // Pattern 4: Loose pedimento detection
63
+ if (!pedimento) {
64
+ pedimento = this.#detectLoosePedimento(pathParts);
65
+ }
66
+
67
+ return {
68
+ year,
69
+ pedimento,
70
+ detected: !!(year && pedimento),
71
+ };
72
+ } catch (error) {
73
+ return {
74
+ year: null,
75
+ pedimento: null,
76
+ detected: false,
77
+ error: error.message,
78
+ };
79
+ }
80
+ }
81
+
82
+ /**
83
+ * Detect direct year/pedimento pattern
84
+ * @private
85
+ * @param {string[]} pathParts - Path components
86
+ * @returns {string|null} Detected year
87
+ */
88
+ #detectDirectPattern(pathParts) {
89
+ for (let i = 0; i < pathParts.length - 1; i++) {
90
+ const yearMatch = pathParts[i].match(/^(202[0-9])$/);
91
+ if (yearMatch) {
92
+ return yearMatch[1];
93
+ }
94
+ }
95
+ return null;
96
+ }
97
+
98
+ /**
99
+ * Find pedimento for a detected year
100
+ * @private
101
+ * @param {string[]} pathParts - Path components
102
+ * @param {string} year - Detected year
103
+ * @returns {string|null} Detected pedimento
104
+ */
105
+ #findPedimentoForYear(pathParts, year) {
106
+ const yearIndex = pathParts.findIndex((part) => part === year);
107
+ if (yearIndex >= 0 && yearIndex < pathParts.length - 1) {
108
+ const nextPart = pathParts[yearIndex + 1];
109
+ const pedimentoMatch = nextPart.match(/^(\d{4,8})$/);
110
+ if (pedimentoMatch) {
111
+ return pedimentoMatch[1];
112
+ }
113
+ }
114
+ return null;
115
+ }
116
+
117
+ /**
118
+ * Detect named patterns (año2024, ped4023260)
119
+ * @private
120
+ * @param {string[]} pathParts - Path components
121
+ * @returns {Object} Detection result with year and pedimento
122
+ */
123
+ #detectNamedPatterns(pathParts) {
124
+ let year = null;
125
+ let pedimento = null;
126
+
127
+ for (const part of pathParts) {
128
+ if (!year) {
129
+ const namedYearMatch = part.match(/(?:año|year|anio)(\d{4})/i);
130
+ if (namedYearMatch) {
131
+ year = namedYearMatch[1];
132
+ }
133
+ }
134
+
135
+ if (!pedimento) {
136
+ const namedPedimentoMatch = part.match(
137
+ /(?:ped|pedimento|pedi)(\d{4,8})/i,
138
+ );
139
+ if (namedPedimentoMatch) {
140
+ pedimento = namedPedimentoMatch[1];
141
+ }
142
+ }
143
+ }
144
+
145
+ return { year, pedimento };
146
+ }
147
+
148
+ /**
149
+ * Detect loose year pattern
150
+ * @private
151
+ * @param {string[]} pathParts - Path components
152
+ * @returns {string|null} Detected year
153
+ */
154
+ #detectLooseYear(pathParts) {
155
+ for (const part of pathParts) {
156
+ const yearMatch = part.match(/(202[0-9])/);
157
+ if (yearMatch) {
158
+ return yearMatch[1];
159
+ }
160
+ }
161
+ return null;
162
+ }
163
+
164
+ /**
165
+ * Detect loose pedimento pattern
166
+ * @private
167
+ * @param {string[]} pathParts - Path components
168
+ * @returns {string|null} Detected pedimento
169
+ */
170
+ #detectLoosePedimento(pathParts) {
171
+ for (const part of pathParts) {
172
+ const pedimentoMatch = part.match(/(\d{4,8})/);
173
+ if (pedimentoMatch && pedimentoMatch[1].length >= 4) {
174
+ return pedimentoMatch[1];
175
+ }
176
+ }
177
+ return null;
178
+ }
179
+
180
+ /**
181
+ * Clear the detection cache
182
+ */
183
+ clearCache() {
184
+ this.cache.clear();
185
+ }
186
+
187
+ /**
188
+ * Get cache size for monitoring
189
+ * @returns {number} Number of cached entries
190
+ */
191
+ getCacheSize() {
192
+ return this.cache.size;
193
+ }
194
+ }
195
+
196
+ // Export singleton instance
197
+ export const pathDetector = new PathDetector();
198
+ export default pathDetector;