mcard-js 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.
Files changed (111) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +117 -0
  3. package/dist/__mocks__/better-sqlite3.js +20 -0
  4. package/dist/__mocks__/better-sqlite3.js.map +1 -0
  5. package/dist/config/config_constants.js +188 -0
  6. package/dist/config/config_constants.js.map +1 -0
  7. package/dist/config/env_parameters.js +62 -0
  8. package/dist/config/env_parameters.js.map +1 -0
  9. package/dist/content/model/content_type_detector.js +89 -0
  10. package/dist/content/model/content_type_detector.js.map +1 -0
  11. package/dist/core/card-collection.js +279 -0
  12. package/dist/core/card-collection.js.map +1 -0
  13. package/dist/core/event-producer.js +132 -0
  14. package/dist/core/event-producer.js.map +1 -0
  15. package/dist/core/g_time.js +201 -0
  16. package/dist/core/g_time.js.map +1 -0
  17. package/dist/core/hash/enums.js +19 -0
  18. package/dist/core/hash/enums.js.map +1 -0
  19. package/dist/core/hash/validator.js +260 -0
  20. package/dist/core/hash/validator.js.map +1 -0
  21. package/dist/core/mcard.js +205 -0
  22. package/dist/core/mcard.js.map +1 -0
  23. package/dist/engine/sqlite_engine.js +723 -0
  24. package/dist/engine/sqlite_engine.js.map +1 -0
  25. package/dist/index.js +10 -0
  26. package/dist/index.js.map +1 -0
  27. package/dist/middleware/mcardPersistenceMiddleware.js +45 -0
  28. package/dist/middleware/mcardPersistenceMiddleware.js.map +1 -0
  29. package/dist/models/database_schemas.js +31 -0
  30. package/dist/models/database_schemas.js.map +1 -0
  31. package/dist/services/logger.js +80 -0
  32. package/dist/services/logger.js.map +1 -0
  33. package/dist/services/mcardStorageService.js +36 -0
  34. package/dist/services/mcardStorageService.js.map +1 -0
  35. package/dist/utils/actionHelpers.js +25 -0
  36. package/dist/utils/actionHelpers.js.map +1 -0
  37. package/dist/utils/bufferContentHelper.js +393 -0
  38. package/dist/utils/bufferContentHelper.js.map +1 -0
  39. package/dist/utils/bufferPolyfill.js +198 -0
  40. package/dist/utils/bufferPolyfill.js.map +1 -0
  41. package/dist/utils/content-detection.js +74 -0
  42. package/dist/utils/content-detection.js.map +1 -0
  43. package/dist/utils/content-utils.js +269 -0
  44. package/dist/utils/content-utils.js.map +1 -0
  45. package/dist/utils/content_type_detector copy.js +480 -0
  46. package/dist/utils/content_type_detector copy.js.map +1 -0
  47. package/dist/utils/content_type_detector.js +480 -0
  48. package/dist/utils/content_type_detector.js.map +1 -0
  49. package/dist/utils/cryptoPolyfill.js +166 -0
  50. package/dist/utils/cryptoPolyfill.js.map +1 -0
  51. package/dist/utils/dotenv-browser.js +35 -0
  52. package/dist/utils/dotenv-browser.js.map +1 -0
  53. package/dist/utils/environmentDetector.js +93 -0
  54. package/dist/utils/environmentDetector.js.map +1 -0
  55. package/dist/utils/logWriter.js +27 -0
  56. package/dist/utils/logWriter.js.map +1 -0
  57. package/dist/utils/serviceWorkerManager.js +118 -0
  58. package/dist/utils/serviceWorkerManager.js.map +1 -0
  59. package/dist/utils/test-content-detection.js +79 -0
  60. package/dist/utils/test-content-detection.js.map +1 -0
  61. package/dist/utils/test-detection-fix.js +121 -0
  62. package/dist/utils/test-detection-fix.js.map +1 -0
  63. package/dist/utils/test-format-conversion.js +170 -0
  64. package/dist/utils/test-format-conversion.js.map +1 -0
  65. package/dist/utils/test-mov-viewer.js +57 -0
  66. package/dist/utils/test-mov-viewer.js.map +1 -0
  67. package/dist/utils/testDetection.js +21 -0
  68. package/dist/utils/testDetection.js.map +1 -0
  69. package/dist/utils/textEncoderPolyfill.js +87 -0
  70. package/dist/utils/textEncoderPolyfill.js.map +1 -0
  71. package/package.json +74 -0
  72. package/src/__mocks__/better-sqlite3.js +14 -0
  73. package/src/config/config_constants.js +227 -0
  74. package/src/config/env_parameters.js +69 -0
  75. package/src/content/model/content_type_detector.js +87 -0
  76. package/src/core/card-collection.js +300 -0
  77. package/src/core/event-producer.js +160 -0
  78. package/src/core/g_time.js +215 -0
  79. package/src/core/hash/enums.js +13 -0
  80. package/src/core/hash/validator.js +271 -0
  81. package/src/core/mcard.js +203 -0
  82. package/src/engine/sqlite_engine.js +755 -0
  83. package/src/index.js +10 -0
  84. package/src/middleware/mcardPersistenceMiddleware.js +45 -0
  85. package/src/models/database_schemas.js +26 -0
  86. package/src/services/logger.js +74 -0
  87. package/src/services/mcardStorageService.js +34 -0
  88. package/src/utils/actionHelpers.js +13 -0
  89. package/src/utils/bufferContentHelper.js +436 -0
  90. package/src/utils/bufferPolyfill.js +202 -0
  91. package/src/utils/cn.ts +6 -0
  92. package/src/utils/content-detection.js +66 -0
  93. package/src/utils/content-utils.js +250 -0
  94. package/src/utils/content_type_detector copy.js +501 -0
  95. package/src/utils/content_type_detector.js +501 -0
  96. package/src/utils/cryptoPolyfill.js +180 -0
  97. package/src/utils/dateUtils.ts +18 -0
  98. package/src/utils/dbInitializer.ts +27 -0
  99. package/src/utils/dotenv-browser.js +29 -0
  100. package/src/utils/environmentDetector.js +92 -0
  101. package/src/utils/logWriter.js +20 -0
  102. package/src/utils/serviceWorkerManager.js +122 -0
  103. package/src/utils/stateWatcher.ts +78 -0
  104. package/src/utils/storeAdapter copy.ts +157 -0
  105. package/src/utils/storeAdapter.ts +157 -0
  106. package/src/utils/test-content-detection.js +71 -0
  107. package/src/utils/test-detection-fix.js +136 -0
  108. package/src/utils/test-format-conversion.js +165 -0
  109. package/src/utils/test-mov-viewer.js +59 -0
  110. package/src/utils/testDetection.js +16 -0
  111. package/src/utils/textEncoderPolyfill.js +88 -0
package/src/index.js ADDED
@@ -0,0 +1,10 @@
1
+ // mcard - Monadic Card Library
2
+
3
+ // Placeholder for your library's main functionality
4
+ function exampleFunction() {
5
+ console.log('mcard library is ready!');
6
+ }
7
+
8
+ module.exports = {
9
+ exampleFunction
10
+ };
@@ -0,0 +1,45 @@
1
+ // Middleware for Redux persistence of MCard data
2
+ const createMcardPersistenceMiddleware = (storageService) => (store) => (next) => async (action) => {
3
+ // Call the next middleware in the chain first
4
+ const result = next(action);
5
+
6
+ try {
7
+ // Handle persistence based on action type
8
+ if (action && action.type) {
9
+ // Check if this is a persistable action (e.g., todo actions)
10
+ const isPersistable = [
11
+ 'todo/addTask',
12
+ 'ADD_CARD',
13
+ 'test/action' // For testing purposes
14
+ ].some(type => action.type === type);
15
+
16
+ if (isPersistable && storageService) {
17
+ // Get the current state from the store
18
+ const state = typeof store.getState === 'function' ? store.getState() : store.state || {};
19
+
20
+ // Create a copy of the action with the current state snapshot
21
+ const actionWithState = {
22
+ ...action,
23
+ meta: {
24
+ ...(action.meta || {}),
25
+ stateSnapshot: state
26
+ }
27
+ };
28
+
29
+ // Persist the action using the storage service
30
+ await storageService.createAndStoreMCard(actionWithState);
31
+ }
32
+ }
33
+ } catch (error) {
34
+ console.error('Error in mcardPersistenceMiddleware:', error);
35
+ }
36
+
37
+ return result;
38
+ };
39
+
40
+ // Create a named export for backward compatibility
41
+ const mcardPersistenceMiddleware = createMcardPersistenceMiddleware;
42
+
43
+ // Export both the named and default exports for flexibility
44
+ export { mcardPersistenceMiddleware };
45
+ export default createMcardPersistenceMiddleware;
@@ -0,0 +1,26 @@
1
+ // SQLite database schema definitions for mcards
2
+
3
+ export const MCARD_TABLE_SCHEMA = `
4
+ CREATE TABLE IF NOT EXISTS card (
5
+ hash TEXT PRIMARY KEY,
6
+ g_time TEXT NOT NULL,
7
+ content BLOB NOT NULL
8
+ )
9
+ `;
10
+
11
+ export const TRIGGERS = {
12
+ ensureUnique: `
13
+ CREATE TRIGGER IF NOT EXISTS ensure_unique_hash
14
+ BEFORE INSERT ON card
15
+ FOR EACH ROW
16
+ BEGIN
17
+ SELECT RAISE(ABORT, 'Card with this hash already exists')
18
+ WHERE EXISTS (SELECT 1 FROM card WHERE hash = NEW.hash);
19
+ END
20
+ `
21
+ };
22
+
23
+ export default {
24
+ MCARD_TABLE_SCHEMA,
25
+ TRIGGERS
26
+ };
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Simple logging service with configurable log levels
3
+ */
4
+ class Logger {
5
+ /**
6
+ * Create a new logger instance
7
+ * @param {string} [level='info'] - Logging level
8
+ */
9
+ constructor(level = 'info') {
10
+ this.level = level.toLowerCase();
11
+ this.levels = ['error', 'warn', 'info', 'debug'];
12
+ }
13
+
14
+ /**
15
+ * Check if a log level is enabled
16
+ * @param {string} level - Log level to check
17
+ * @returns {boolean} Whether the log level is enabled
18
+ */
19
+ isLevelEnabled(level) {
20
+ const currentLevelIndex = this.levels.indexOf(this.level);
21
+ const checkLevelIndex = this.levels.indexOf(level);
22
+ return checkLevelIndex <= currentLevelIndex;
23
+ }
24
+
25
+ /**
26
+ * Log an error message
27
+ * @param {string} message - Message to log
28
+ * @param {Object} [metadata] - Optional metadata
29
+ */
30
+ error(message, metadata = {}) {
31
+ if (this.isLevelEnabled('error')) {
32
+ console.error(`[ERROR] ${message}`, metadata);
33
+ }
34
+ }
35
+
36
+ /**
37
+ * Log a warning message
38
+ * @param {string} message - Message to log
39
+ * @param {Object} [metadata] - Optional metadata
40
+ */
41
+ warn(message, metadata = {}) {
42
+ if (this.isLevelEnabled('warn')) {
43
+ console.warn(`[WARN] ${message}`, metadata);
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Log an info message
49
+ * @param {string} message - Message to log
50
+ * @param {Object} [metadata] - Optional metadata
51
+ */
52
+ info(message, metadata = {}) {
53
+ if (this.isLevelEnabled('info')) {
54
+ console.log(`[INFO] ${message}`, metadata);
55
+ }
56
+ }
57
+
58
+ /**
59
+ * Log a debug message
60
+ * @param {string} message - Message to log
61
+ * @param {Object} [metadata] - Optional metadata
62
+ */
63
+ debug(message, metadata = {}) {
64
+ if (this.isLevelEnabled('debug')) {
65
+ console.debug(`[DEBUG] ${message}`, metadata);
66
+ }
67
+ }
68
+ }
69
+
70
+ // Create a default logger instance using environment variable
71
+ const logger = new Logger(process.env.LOG_LEVEL || 'info');
72
+
73
+ export default logger;
74
+ export { Logger };
@@ -0,0 +1,34 @@
1
+ // Service for MCard storage operations
2
+ class McardStorageService {
3
+ constructor(engine) {
4
+ this.engine = engine;
5
+ }
6
+
7
+ async saveMcards(mcards) {
8
+ if (!this.engine) {
9
+ throw new Error('Storage engine not initialized');
10
+ }
11
+
12
+ try {
13
+ return await this.engine.saveMcards(mcards);
14
+ } catch (error) {
15
+ console.error('Error saving mcards:', error);
16
+ throw error;
17
+ }
18
+ }
19
+
20
+ async loadMcards() {
21
+ if (!this.engine) {
22
+ throw new Error('Storage engine not initialized');
23
+ }
24
+
25
+ try {
26
+ return await this.engine.loadMcards();
27
+ } catch (error) {
28
+ console.error('Error loading mcards:', error);
29
+ throw error;
30
+ }
31
+ }
32
+ }
33
+
34
+ export default McardStorageService;
@@ -0,0 +1,13 @@
1
+ export const formatTimestamp = (timestamp) => {
2
+ const date = new Date(timestamp);
3
+ return date.toLocaleTimeString();
4
+ };
5
+
6
+ export const getActionText = (action) => {
7
+ switch (action.type) {
8
+ case 'ADD': return `Added item: "${action.content}"`;
9
+ case 'REMOVE': return `Removed item: "${action.content}"`;
10
+ case 'SELECT': return `Selected item: "${action.content}"`;
11
+ default: return `Unknown action on: "${action.content}"`;
12
+ }
13
+ };
@@ -0,0 +1,436 @@
1
+ /**
2
+ * Utility functions for handling Buffer content stored in Node.js Buffer JSON format
3
+ * Specifically handles the {"type":"Buffer","data":[...]} format
4
+ */
5
+
6
+ /**
7
+ * Converts a Buffer JSON object or string to a regular string
8
+ * Works with all these formats:
9
+ * - {"type":"Buffer","data":[...]} (object)
10
+ * - '{"type":"Buffer","data":[...]}' (string)
11
+ * - Regular string
12
+ * - Regular objects
13
+ *
14
+ * @param {any} content - The content to process
15
+ * @returns {string|null} Decoded string or null if not convertible
16
+ */
17
+ export const convertBufferToString = (content) => {
18
+ if (!content) return null;
19
+
20
+ try {
21
+ // Case 1: Direct Buffer JSON object
22
+ if (typeof content === 'object' && content !== null &&
23
+ content.type === 'Buffer' && Array.isArray(content.data)) {
24
+ const array = new Uint8Array(content.data);
25
+ return new TextDecoder().decode(array);
26
+ }
27
+
28
+ // Case 2: JSON string containing serialized Buffer
29
+ if (typeof content === 'string') {
30
+ if (content.includes('"type":"Buffer"') && content.includes('"data":[')) {
31
+ try {
32
+ const bufferObj = JSON.parse(content);
33
+ if (bufferObj && bufferObj.type === 'Buffer' && Array.isArray(bufferObj.data)) {
34
+ const array = new Uint8Array(bufferObj.data);
35
+ return new TextDecoder().decode(array);
36
+ }
37
+ } catch (e) {
38
+ // If parsing fails, treat as regular string
39
+ console.log("Failed to parse as Buffer JSON:", e);
40
+ }
41
+ }
42
+
43
+ // Regular string, just return it
44
+ return content;
45
+ }
46
+
47
+ // Case 3: Other object, convert to JSON string
48
+ if (typeof content === 'object' && content !== null) {
49
+ return JSON.stringify(content, null, 2);
50
+ }
51
+
52
+ // Case 4: Primitives, convert to string
53
+ return String(content);
54
+ } catch (e) {
55
+ console.error("Error converting buffer to string:", e);
56
+ return typeof content === 'string' ? content : null;
57
+ }
58
+ };
59
+
60
+ /**
61
+ * Get raw binary data from a Buffer-like object
62
+ * @param {any} content - The content to extract binary data from
63
+ * @returns {Uint8Array|null} Binary data as Uint8Array or null
64
+ */
65
+ export const extractBinaryContent = (content) => {
66
+ if (!content) return null;
67
+
68
+ try {
69
+ // Case 1: Direct Buffer JSON object
70
+ if (typeof content === 'object' && content !== null &&
71
+ content.type === 'Buffer' && Array.isArray(content.data)) {
72
+ return new Uint8Array(content.data);
73
+ }
74
+
75
+ // Case 2: JSON string containing serialized Buffer
76
+ if (typeof content === 'string') {
77
+ if (content.includes('"type":"Buffer"') && content.includes('"data":[')) {
78
+ try {
79
+ const bufferObj = JSON.parse(content);
80
+ if (bufferObj && bufferObj.type === 'Buffer' && Array.isArray(bufferObj.data)) {
81
+ return new Uint8Array(bufferObj.data);
82
+ }
83
+ } catch (e) {
84
+ // If parsing fails, can't extract binary
85
+ return null;
86
+ }
87
+ }
88
+
89
+ // Regular string as bytes
90
+ return new TextEncoder().encode(content);
91
+ }
92
+
93
+ return null;
94
+ } catch (e) {
95
+ console.error("Error extracting binary content:", e);
96
+ return null;
97
+ }
98
+ };
99
+
100
+ /**
101
+ * Detects the content type based on actual content analysis
102
+ *
103
+ * @param {any} content - The content to analyze
104
+ * @returns {string|null} Detected content type or null if undetectable
105
+ */
106
+ export const detectContentType = (content) => {
107
+ // Early handling for direct Buffer format containing CSV data
108
+ if (typeof content === 'object' && content !== null &&
109
+ content.type === 'Buffer' && Array.isArray(content.data)) {
110
+
111
+ // Get the decoded string for analysis
112
+ const decodedStr = convertBufferToString(content);
113
+
114
+ // Debug the decoded content
115
+ console.log("Decoded Buffer content:", decodedStr ? decodedStr.substring(0, 100) : "null");
116
+
117
+ // Simple CSV detection based on commas and line structure
118
+ if (decodedStr &&
119
+ decodedStr.includes(',') &&
120
+ decodedStr.includes('\n') &&
121
+ decodedStr.split('\n').length > 1 &&
122
+ decodedStr.split('\n')[0].includes(',')) {
123
+
124
+ console.log("Detected CSV pattern in buffer data");
125
+ return 'csv';
126
+ }
127
+
128
+ // Check for TSV
129
+ if (decodedStr &&
130
+ decodedStr.includes('\t') &&
131
+ decodedStr.includes('\n') &&
132
+ decodedStr.split('\n').length > 1 &&
133
+ decodedStr.split('\n')[0].includes('\t')) {
134
+
135
+ console.log("Detected TSV pattern in buffer data");
136
+ return 'tsv';
137
+ }
138
+
139
+ // Check for JSON
140
+ if (decodedStr &&
141
+ (decodedStr.trim().startsWith('{') || decodedStr.trim().startsWith('['))) {
142
+ try {
143
+ JSON.parse(decodedStr);
144
+ console.log("Detected JSON pattern in buffer data");
145
+ return 'json';
146
+ } catch (e) {
147
+ // Not valid JSON
148
+ }
149
+ }
150
+ }
151
+
152
+ // Early detection for Buffer JSON format by examining binary patterns
153
+ const binaryData = extractBinaryContent(content);
154
+ if (binaryData) {
155
+ // Check for file signatures (magic numbers)
156
+ const fileSignature = detectFileSignature(binaryData);
157
+ if (fileSignature) {
158
+ console.log("Detected file type from signature:", fileSignature);
159
+ return fileSignature;
160
+ }
161
+ }
162
+
163
+ const textContent = convertBufferToString(content);
164
+ if (!textContent) return 'bin';
165
+
166
+ // Size check to avoid processing very large content
167
+ if (textContent.length > 1000000) {
168
+ console.log("Content too large for detailed analysis, using basic checks");
169
+ return detectBasicContentType(textContent.slice(0, 1000));
170
+ }
171
+
172
+ // Debug logging
173
+ console.log("Analyzing content for type detection:", textContent.slice(0, 100) + "...");
174
+
175
+ try {
176
+ // Check if it's valid JSON first
177
+ try {
178
+ JSON.parse(textContent);
179
+ if (textContent.trim().startsWith('{') || textContent.trim().startsWith('[')) {
180
+ return 'json';
181
+ }
182
+ } catch (e) {
183
+ // Not JSON, continue with other checks
184
+ }
185
+
186
+ // Check for XML
187
+ if ((textContent.trim().startsWith('<?xml') || textContent.trim().startsWith('<')) &&
188
+ textContent.includes('</') && textContent.includes('>')) {
189
+ return 'xml';
190
+ }
191
+
192
+ // Check for HTML
193
+ if (textContent.includes('<html') || textContent.includes('<body') ||
194
+ textContent.includes('<head') || textContent.includes('<!DOCTYPE html')) {
195
+ return 'html';
196
+ }
197
+
198
+ // Check for CSV format - improved detection
199
+ if (textContent.includes(',') &&
200
+ textContent.includes('\n') &&
201
+ textContent.split('\n').filter(line => line.trim()).length > 1) {
202
+
203
+ // Count lines with commas
204
+ const lines = textContent.split('\n').filter(line => line.trim());
205
+ const linesWithCommas = lines.filter(line => line.includes(','));
206
+
207
+ // If more than 50% of lines have commas, likely a CSV
208
+ if (linesWithCommas.length / lines.length > 0.5) {
209
+ console.log("Detected CSV pattern");
210
+ return 'csv';
211
+ }
212
+ }
213
+
214
+ // Check for TSV format
215
+ if (textContent.includes('\t') &&
216
+ textContent.includes('\n') &&
217
+ textContent.split('\n').filter(line => line.trim()).length > 1) {
218
+
219
+ // Count lines with tabs
220
+ const lines = textContent.split('\n').filter(line => line.trim());
221
+ const linesWithTabs = lines.filter(line => line.includes('\t'));
222
+
223
+ // If more than 50% of lines have tabs, likely a TSV
224
+ if (linesWithTabs.length / lines.length > 0.5) {
225
+ return 'tsv';
226
+ }
227
+ }
228
+
229
+ // Check for SQL
230
+ if ((textContent.includes('SELECT ') || textContent.includes('INSERT INTO ') ||
231
+ textContent.includes('CREATE TABLE ') || textContent.includes('UPDATE ')) &&
232
+ /;/.test(textContent)) {
233
+ return 'sql';
234
+ }
235
+
236
+ // Check for Markdown
237
+ if ((textContent.includes('# ') || textContent.includes('## ') ||
238
+ textContent.includes('```') || textContent.includes('**')) &&
239
+ !textContent.includes('<html') && !textContent.includes('<body')) {
240
+ return 'md';
241
+ }
242
+
243
+ // Check for CSS
244
+ if ((textContent.includes('{') && textContent.includes('}') &&
245
+ textContent.includes(':') && textContent.includes(';')) &&
246
+ /[.#]?[a-zA-Z][a-zA-Z0-9_-]*\s*\{/.test(textContent)) {
247
+ return 'css';
248
+ }
249
+
250
+ // Check for JavaScript
251
+ if ((textContent.includes('function') || textContent.includes('=>') ||
252
+ textContent.includes('var ') || textContent.includes('let ') ||
253
+ textContent.includes('const ') || textContent.includes('import ')) &&
254
+ /[;{}()]/.test(textContent)) {
255
+ return 'js';
256
+ }
257
+
258
+ // Check for YAML
259
+ if ((/^\s*[a-zA-Z_][a-zA-Z0-9_]*\s*:/.test(textContent) ||
260
+ /^\s*-\s+[a-zA-Z_][a-zA-Z0-9_]*\s*:/.test(textContent)) &&
261
+ !textContent.includes('{') && !textContent.includes('}')) {
262
+ return 'yaml';
263
+ }
264
+
265
+ // Default to text if content is mostly text characters
266
+ const textChars = textContent.replace(/[\n\r\t ]/g, '').length;
267
+ const nonTextChars = /[^\x20-\x7E\n\r\t]/.test(textContent);
268
+
269
+ if (textChars > 0 && !nonTextChars) {
270
+ return 'txt';
271
+ }
272
+
273
+ // Last resort: binary
274
+ return 'bin';
275
+ } catch (e) {
276
+ console.error("Error during content type detection:", e);
277
+ return 'bin';
278
+ }
279
+ };
280
+
281
+ /**
282
+ * Detects file signatures (magic numbers) from binary data
283
+ * @param {Uint8Array} data - Binary data to analyze
284
+ * @returns {string|null} File type or null if unrecognized
285
+ */
286
+ const detectFileSignature = (data) => {
287
+ if (!data || data.length < 4) return null;
288
+
289
+ // Common file signatures (magic numbers)
290
+ // PNG: 89 50 4E 47 0D 0A 1A 0A
291
+ if (data[0] === 0x89 && data[1] === 0x50 && data[2] === 0x4E && data[3] === 0x47) {
292
+ return 'png';
293
+ }
294
+
295
+ // JPEG: FF D8 FF
296
+ if (data[0] === 0xFF && data[1] === 0xD8 && data[2] === 0xFF) {
297
+ return 'jpg';
298
+ }
299
+
300
+ // GIF87a: 47 49 46 38 37 61
301
+ // GIF89a: 47 49 46 38 39 61
302
+ if (data[0] === 0x47 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x38 &&
303
+ (data[4] === 0x37 || data[4] === 0x39) && data[5] === 0x61) {
304
+ return 'gif';
305
+ }
306
+
307
+ // PDF: 25 50 44 46
308
+ if (data[0] === 0x25 && data[1] === 0x50 && data[2] === 0x44 && data[3] === 0x46) {
309
+ return 'pdf';
310
+ }
311
+
312
+ // ZIP: 50 4B 03 04
313
+ if (data[0] === 0x50 && data[1] === 0x4B && data[2] === 0x03 && data[3] === 0x04) {
314
+ return 'zip';
315
+ }
316
+
317
+ // check for BMP: 42 4D
318
+ if (data[0] === 0x42 && data[1] === 0x4D) {
319
+ return 'bmp';
320
+ }
321
+
322
+ // Check for WEBP: 52 49 46 46 followed by WEBP
323
+ if (data[0] === 0x52 && data[1] === 0x49 && data[2] === 0x46 && data[3] === 0x46 &&
324
+ data.length > 11 && data[8] === 0x57 && data[9] === 0x45 && data[10] === 0x42 && data[11] === 0x50) {
325
+ return 'webp';
326
+ }
327
+
328
+ // Check if it looks like a text file (mostly ASCII characters)
329
+ let textChars = 0;
330
+ let totalChars = Math.min(data.length, 100); // Check first 100 bytes
331
+
332
+ for (let i = 0; i < totalChars; i++) {
333
+ if ((data[i] >= 32 && data[i] <= 126) || data[i] === 9 || data[i] === 10 || data[i] === 13) {
334
+ textChars++;
335
+ }
336
+ }
337
+
338
+ if (textChars / totalChars > 0.9) {
339
+ // This is likely a text file
340
+ return null; // Let the text content analysis take over
341
+ }
342
+
343
+ return 'bin'; // Default to binary if no specific format recognized
344
+ };
345
+
346
+ /**
347
+ * Performs basic content type detection for very large files
348
+ * Only looks at the first portion of the content
349
+ */
350
+ const detectBasicContentType = (sample) => {
351
+ if (!sample) return 'bin';
352
+
353
+ // Quick check for common formats based on initial characters
354
+ if (sample.trim().startsWith('{') || sample.trim().startsWith('[')) {
355
+ try {
356
+ JSON.parse(sample);
357
+ return 'json';
358
+ } catch (e) {
359
+ // Not valid JSON
360
+ }
361
+ }
362
+
363
+ if (sample.trim().startsWith('<?xml') ||
364
+ (sample.trim().startsWith('<') && sample.includes('</') && sample.includes('>'))) {
365
+ return 'xml';
366
+ }
367
+
368
+ if (sample.includes('<html') || sample.includes('<!DOCTYPE html')) {
369
+ return 'html';
370
+ }
371
+
372
+ // Check if it's mostly text
373
+ const nonTextRatio = (sample.replace(/[\x20-\x7E\r\n\t]/g, '').length / sample.length);
374
+
375
+ if (nonTextRatio < 0.1) {
376
+ // Mostly text, try to detect format
377
+ if (sample.includes('# ') || sample.includes('## ') || sample.includes('```')) {
378
+ return 'md';
379
+ }
380
+
381
+ if (sample.includes(',') && sample.split(/\r?\n/).some(line => line.includes(','))) {
382
+ return 'csv';
383
+ }
384
+
385
+ if (sample.includes('\t') && sample.split(/\r?\n/).some(line => line.includes('\t'))) {
386
+ return 'tsv';
387
+ }
388
+
389
+ return 'txt';
390
+ }
391
+
392
+ return 'bin';
393
+ };
394
+
395
+ /**
396
+ * Gets the MIME type from a simplified content type extension
397
+ *
398
+ * @param {string} simpleType - Simple content type like 'json', 'csv', etc.
399
+ * @returns {string} The corresponding MIME type
400
+ */
401
+ export const getMimeType = (simpleType) => {
402
+ if (!simpleType) return 'application/octet-stream';
403
+
404
+ // Avoid generic "data" type
405
+ if (simpleType === 'data') {
406
+ return 'application/octet-stream';
407
+ }
408
+
409
+ const mimeMap = {
410
+ 'json': 'application/json',
411
+ 'js': 'application/javascript',
412
+ 'txt': 'text/plain',
413
+ 'html': 'text/html',
414
+ 'htm': 'text/html',
415
+ 'css': 'text/css',
416
+ 'svg': 'image/svg+xml',
417
+ 'png': 'image/png',
418
+ 'jpg': 'image/jpeg',
419
+ 'jpeg': 'image/jpeg',
420
+ 'gif': 'image/gif',
421
+ 'pdf': 'application/pdf',
422
+ 'csv': 'text/csv',
423
+ 'tsv': 'text/tab-separated-values',
424
+ 'xml': 'application/xml',
425
+ 'md': 'text/markdown',
426
+ 'yaml': 'application/yaml',
427
+ 'yml': 'application/yaml',
428
+ 'sql': 'application/sql',
429
+ 'bin': 'application/octet-stream',
430
+ 'zip': 'application/zip',
431
+ 'bmp': 'image/bmp',
432
+ 'webp': 'image/webp'
433
+ };
434
+
435
+ return mimeMap[simpleType] || `application/${simpleType}`;
436
+ };