hazo_files 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/.cursor/rules/db_schema.mdc +0 -0
- package/.cursor/rules/design.mdc +0 -0
- package/.cursor/rules/general.mdc +0 -0
- package/CHANGE_LOG.md +341 -0
- package/CLAUDE.md +926 -0
- package/README.md +929 -0
- package/SETUP_CHECKLIST.md +931 -0
- package/TECHDOC.md +325 -0
- package/dist/index.d.mts +1031 -0
- package/dist/index.d.ts +1031 -0
- package/dist/index.js +2457 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +2333 -0
- package/dist/index.mjs.map +1 -0
- package/dist/ui/index.js +4054 -0
- package/dist/ui/index.js.map +1 -0
- package/dist/ui/index.mjs +3982 -0
- package/dist/ui/index.mjs.map +1 -0
- package/docs/ADDING_MODULES.md +964 -0
- package/hazo_files_config.ini +31 -0
- package/package.json +83 -0
package/CLAUDE.md
ADDED
|
@@ -0,0 +1,926 @@
|
|
|
1
|
+
# hazo_files - AI Reference Guide
|
|
2
|
+
|
|
3
|
+
AI-optimized technical reference for the hazo_files package. This document is designed for Claude and other AI assistants to quickly understand the project structure and implement features correctly.
|
|
4
|
+
|
|
5
|
+
## Project Overview
|
|
6
|
+
|
|
7
|
+
**Purpose**: Universal file management package supporting multiple storage backends (local, Google Drive) with a unified API and React UI components.
|
|
8
|
+
|
|
9
|
+
**Core Philosophy**:
|
|
10
|
+
- Provider-agnostic: Single API works across all storage types
|
|
11
|
+
- Type-safe: Full TypeScript with comprehensive types
|
|
12
|
+
- Modular: Easy to add new storage providers
|
|
13
|
+
- Server + Client: Works in Node.js servers and React browsers
|
|
14
|
+
|
|
15
|
+
**Key Use Cases**:
|
|
16
|
+
1. Building file management UIs in Next.js/React apps
|
|
17
|
+
2. Server-side file operations with multiple storage backends
|
|
18
|
+
3. Google Drive integration with OAuth
|
|
19
|
+
4. Custom storage provider implementations
|
|
20
|
+
|
|
21
|
+
## Architecture Overview
|
|
22
|
+
|
|
23
|
+
```
|
|
24
|
+
hazo_files/
|
|
25
|
+
├── Core Layer (TypeScript)
|
|
26
|
+
│ ├── FileManager (main service)
|
|
27
|
+
│ ├── StorageModule interface (contract)
|
|
28
|
+
│ ├── Configuration system (INI + env vars)
|
|
29
|
+
│ └── Naming system (file/folder name generation)
|
|
30
|
+
├── Module Layer (storage providers)
|
|
31
|
+
│ ├── LocalStorageModule (filesystem)
|
|
32
|
+
│ └── GoogleDriveModule (Drive API + OAuth)
|
|
33
|
+
├── UI Layer (React components)
|
|
34
|
+
│ ├── FileBrowser (complete solution)
|
|
35
|
+
│ ├── NamingRuleConfigurator (naming pattern builder)
|
|
36
|
+
│ └── Individual components + hooks
|
|
37
|
+
└── Common Layer (utilities)
|
|
38
|
+
├── Error types (12 specific errors)
|
|
39
|
+
├── Path utilities (normalization, joining)
|
|
40
|
+
├── MIME type detection
|
|
41
|
+
├── Naming utilities (pattern generation)
|
|
42
|
+
└── Helper functions
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Critical Patterns
|
|
46
|
+
|
|
47
|
+
### 1. Module System
|
|
48
|
+
|
|
49
|
+
All storage providers implement `StorageModule` interface:
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
interface StorageModule {
|
|
53
|
+
readonly provider: StorageProvider;
|
|
54
|
+
initialize(config: HazoFilesConfig): Promise<void>;
|
|
55
|
+
|
|
56
|
+
// Directory operations
|
|
57
|
+
createDirectory(path: string): Promise<OperationResult<FolderItem>>;
|
|
58
|
+
removeDirectory(path: string, recursive?: boolean): Promise<OperationResult>;
|
|
59
|
+
|
|
60
|
+
// File operations
|
|
61
|
+
uploadFile(source, remotePath, options?): Promise<OperationResult<FileItem>>;
|
|
62
|
+
downloadFile(remotePath, localPath?, options?): Promise<OperationResult<Buffer | string>>;
|
|
63
|
+
moveItem(sourcePath, destinationPath, options?): Promise<OperationResult<FileSystemItem>>;
|
|
64
|
+
deleteFile(path: string): Promise<OperationResult>;
|
|
65
|
+
renameFile(path, newName, options?): Promise<OperationResult<FileItem>>;
|
|
66
|
+
renameFolder(path, newName, options?): Promise<OperationResult<FolderItem>>;
|
|
67
|
+
|
|
68
|
+
// Query operations
|
|
69
|
+
listDirectory(path, options?): Promise<OperationResult<FileSystemItem[]>>;
|
|
70
|
+
getItem(path: string): Promise<OperationResult<FileSystemItem>>;
|
|
71
|
+
exists(path: string): Promise<boolean>;
|
|
72
|
+
getFolderTree(path?, depth?): Promise<OperationResult<TreeNode[]>>;
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Implementation Pattern**: Extend `BaseStorageModule` which provides:
|
|
77
|
+
- Common initialization logic
|
|
78
|
+
- Path utility methods (normalizePath, joinPath, etc.)
|
|
79
|
+
- Result helpers (successResult, errorResult)
|
|
80
|
+
- Default `getFolderTree` implementation
|
|
81
|
+
|
|
82
|
+
### 2. Path System
|
|
83
|
+
|
|
84
|
+
**Virtual Paths**: All modules work with virtual paths (Unix-style: `/folder/file.txt`)
|
|
85
|
+
|
|
86
|
+
**Local Module Mapping**:
|
|
87
|
+
- Virtual path `/documents/file.pdf` → Physical path `{basePath}/documents/file.pdf`
|
|
88
|
+
- Conversion: `resolveFullPath()` and `toVirtualPath()`
|
|
89
|
+
|
|
90
|
+
**Google Drive Mapping**:
|
|
91
|
+
- Virtual path `/documents/file.pdf` → Drive folder hierarchy lookup
|
|
92
|
+
- Root can be custom folder ID or Drive root
|
|
93
|
+
- Path segments resolved recursively via Drive API queries
|
|
94
|
+
|
|
95
|
+
**Path Rules**:
|
|
96
|
+
- Always start with `/`
|
|
97
|
+
- Use forward slashes only
|
|
98
|
+
- No trailing slashes for files
|
|
99
|
+
- Empty path defaults to `/`
|
|
100
|
+
|
|
101
|
+
### 3. Result Pattern
|
|
102
|
+
|
|
103
|
+
All operations return `OperationResult<T>`:
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
interface OperationResult<T = void> {
|
|
107
|
+
success: boolean;
|
|
108
|
+
data?: T;
|
|
109
|
+
error?: string;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Usage
|
|
113
|
+
const result = await fileManager.createDirectory('/test');
|
|
114
|
+
if (result.success) {
|
|
115
|
+
console.log(result.data); // FolderItem
|
|
116
|
+
} else {
|
|
117
|
+
console.error(result.error);
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Why**: Avoids throwing exceptions for expected failures (file not found, etc.)
|
|
122
|
+
|
|
123
|
+
### 4. Configuration System
|
|
124
|
+
|
|
125
|
+
**Priority Order** (highest to lowest):
|
|
126
|
+
1. Programmatic config passed to constructor
|
|
127
|
+
2. Environment variables
|
|
128
|
+
3. INI file (`hazo_files_config.ini`)
|
|
129
|
+
4. Defaults
|
|
130
|
+
|
|
131
|
+
**Environment Variable Mapping**:
|
|
132
|
+
- `GOOGLE_DRIVE_CLIENT_ID` → `google_drive.clientId`
|
|
133
|
+
- `GOOGLE_DRIVE_CLIENT_SECRET` → `google_drive.clientSecret`
|
|
134
|
+
- `GOOGLE_DRIVE_REDIRECT_URI` → `google_drive.redirectUri`
|
|
135
|
+
- `GOOGLE_DRIVE_REFRESH_TOKEN` → `google_drive.refreshToken`
|
|
136
|
+
- `GOOGLE_DRIVE_ACCESS_TOKEN` → `google_drive.accessToken`
|
|
137
|
+
- `GOOGLE_DRIVE_ROOT_FOLDER_ID` → `google_drive.rootFolderId`
|
|
138
|
+
|
|
139
|
+
**Config Loading**:
|
|
140
|
+
```typescript
|
|
141
|
+
// Sync (blocks)
|
|
142
|
+
const config = loadConfig('./custom-config.ini');
|
|
143
|
+
|
|
144
|
+
// Async (preferred)
|
|
145
|
+
const config = await loadConfigAsync('./custom-config.ini');
|
|
146
|
+
|
|
147
|
+
// Via FileManager
|
|
148
|
+
const fm = await createInitializedFileManager({
|
|
149
|
+
config: { provider: 'local', local: { basePath: './files' } }
|
|
150
|
+
});
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## Storage Modules
|
|
154
|
+
|
|
155
|
+
### LocalStorageModule
|
|
156
|
+
|
|
157
|
+
**Location**: `src/modules/local/index.ts`
|
|
158
|
+
|
|
159
|
+
**Key Features**:
|
|
160
|
+
- Direct Node.js `fs` operations
|
|
161
|
+
- Extension filtering via `allowedExtensions`
|
|
162
|
+
- Size limits via `maxFileSize`
|
|
163
|
+
- Recursive directory operations
|
|
164
|
+
- Progress tracking for streams
|
|
165
|
+
|
|
166
|
+
**Critical Methods**:
|
|
167
|
+
- `resolveFullPath(virtualPath)`: Virtual → absolute filesystem path
|
|
168
|
+
- `toVirtualPath(absolutePath)`: Absolute → virtual path
|
|
169
|
+
- `validateExtension(filename)`: Throws `InvalidExtensionError` if not allowed
|
|
170
|
+
- `validateFileSize(size, filename)`: Throws `FileTooLargeError` if exceeds limit
|
|
171
|
+
|
|
172
|
+
**Gotchas**:
|
|
173
|
+
- Always creates parent directories automatically
|
|
174
|
+
- `basePath` is resolved to absolute path on init
|
|
175
|
+
- Relative paths in config are resolved from `process.cwd()`
|
|
176
|
+
|
|
177
|
+
### GoogleDriveModule
|
|
178
|
+
|
|
179
|
+
**Location**: `src/modules/google-drive/index.ts`
|
|
180
|
+
|
|
181
|
+
**Key Features**:
|
|
182
|
+
- Google Drive API v3 integration
|
|
183
|
+
- OAuth 2.0 authentication with refresh
|
|
184
|
+
- Path-to-ID resolution caching concept (not implemented, but needed)
|
|
185
|
+
- Folder hierarchy traversal
|
|
186
|
+
- Metadata storage in item objects
|
|
187
|
+
|
|
188
|
+
**Authentication Flow**:
|
|
189
|
+
1. Create module with OAuth credentials
|
|
190
|
+
2. Generate auth URL: `module.getAuth().getAuthUrl()`
|
|
191
|
+
3. User authorizes, get auth code
|
|
192
|
+
4. Exchange code: `auth.exchangeCodeForTokens(code)`
|
|
193
|
+
5. Module is now authenticated
|
|
194
|
+
|
|
195
|
+
**Auth Callbacks**:
|
|
196
|
+
```typescript
|
|
197
|
+
setAuthCallbacks({
|
|
198
|
+
onTokensUpdated: async (tokens) => {
|
|
199
|
+
// Save tokens to database/file
|
|
200
|
+
},
|
|
201
|
+
getStoredTokens: async () => {
|
|
202
|
+
// Retrieve saved tokens
|
|
203
|
+
return { accessToken, refreshToken, expiryDate };
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**Critical Methods**:
|
|
209
|
+
- `getIdFromPath(path, createIfMissing)`: Resolve virtual path to Drive file ID
|
|
210
|
+
- `getPathFromId(fileId)`: Build virtual path from Drive file ID
|
|
211
|
+
- `ensureAuthenticated()`: Check auth, throw if not authenticated
|
|
212
|
+
- `driveFileToItem(file, virtualPath)`: Convert Drive file to FileSystemItem
|
|
213
|
+
|
|
214
|
+
**Performance Considerations**:
|
|
215
|
+
- Each path resolution requires API calls for each segment
|
|
216
|
+
- No caching implemented (opportunity for optimization)
|
|
217
|
+
- Batch operations not used (opportunity for optimization)
|
|
218
|
+
|
|
219
|
+
**Gotchas**:
|
|
220
|
+
- Must call `ensureAuthenticated()` before every operation
|
|
221
|
+
- Token auto-refresh via `oauth2Client.on('tokens')` event
|
|
222
|
+
- Folder MIME type: `application/vnd.google-apps.folder`
|
|
223
|
+
- Trash vs permanent delete (currently uses trash)
|
|
224
|
+
|
|
225
|
+
## UI Components
|
|
226
|
+
|
|
227
|
+
### FileBrowser Component
|
|
228
|
+
|
|
229
|
+
**Location**: `src/ui/components/FileBrowser.tsx`
|
|
230
|
+
|
|
231
|
+
**Architecture**:
|
|
232
|
+
```
|
|
233
|
+
┌─────────────────────────────────────┐
|
|
234
|
+
│ PathBreadcrumb | FileActions │ Header
|
|
235
|
+
├──────────┬──────────────────────────┤
|
|
236
|
+
│ Folder │ FileList │ Main
|
|
237
|
+
│ Tree │ (grid or list view) │
|
|
238
|
+
├──────────┴──────────────────────────┤
|
|
239
|
+
│ FilePreview │ Footer
|
|
240
|
+
└─────────────────────────────────────┘
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Props Pattern**:
|
|
244
|
+
- `api: FileBrowserAPI` - Required adapter to backend
|
|
245
|
+
- Layout controls: `showPreview`, `showTree`, `viewMode`
|
|
246
|
+
- Sizing: `treeWidth`, `previewHeight`
|
|
247
|
+
- Callbacks: `onError`, `onNavigate`, `onSelect`
|
|
248
|
+
|
|
249
|
+
**State Management**:
|
|
250
|
+
- Internal state for current path, files, tree, selection
|
|
251
|
+
- Callbacks trigger on navigation/selection
|
|
252
|
+
- Dialogs managed via boolean flags
|
|
253
|
+
|
|
254
|
+
**API Adapter Pattern**:
|
|
255
|
+
```typescript
|
|
256
|
+
const api: FileBrowserAPI = {
|
|
257
|
+
listDirectory: (path) => fetch(`/api/files?action=list&path=${path}`).then(r => r.json()),
|
|
258
|
+
uploadFile: (file, path) => { /* FormData upload */ },
|
|
259
|
+
// ... other methods must return OperationResult
|
|
260
|
+
};
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
**Critical**: All API methods must return `OperationResult<T>` format
|
|
264
|
+
|
|
265
|
+
### Component Hierarchy
|
|
266
|
+
|
|
267
|
+
**Standalone Components**:
|
|
268
|
+
- `PathBreadcrumb` - Clickable path navigation
|
|
269
|
+
- `FolderTree` - Hierarchical folder view with expand/collapse
|
|
270
|
+
- `FileList` - Grid or list file display with selection
|
|
271
|
+
- `FilePreview` - Preview pane for images, text, PDFs
|
|
272
|
+
- `FileActions` - Action buttons toolbar
|
|
273
|
+
|
|
274
|
+
**Dialogs**:
|
|
275
|
+
- `CreateFolderDialog` - Folder name input
|
|
276
|
+
- `RenameDialog` - Rename file/folder
|
|
277
|
+
- `DeleteConfirmDialog` - Deletion confirmation
|
|
278
|
+
- `UploadDialog` - File upload interface
|
|
279
|
+
|
|
280
|
+
**Hooks**:
|
|
281
|
+
- `useFileBrowser` - Main state management hook
|
|
282
|
+
- `useFileOperations` - Operation execution hook
|
|
283
|
+
- `useMultiFileOperations` - Batch operations
|
|
284
|
+
- `useNamingRule` - Naming pattern state with undo/redo
|
|
285
|
+
|
|
286
|
+
### NamingRuleConfigurator Component
|
|
287
|
+
|
|
288
|
+
**Location**: `src/ui/components/naming/NamingRuleConfigurator.tsx`
|
|
289
|
+
|
|
290
|
+
**Architecture**:
|
|
291
|
+
```
|
|
292
|
+
┌─────────────────────────────────────┐
|
|
293
|
+
│ VariableList (Category Tabs) │ Variables Panel
|
|
294
|
+
│ [User] [Date] [File] [Counter] │
|
|
295
|
+
├─────────────────────────────────────┤
|
|
296
|
+
│ PatternBuilder │ Pattern Builder
|
|
297
|
+
│ - File Pattern: drag/drop zones │
|
|
298
|
+
│ - Folder Pattern: drag/drop zones │
|
|
299
|
+
├─────────────────────────────────────┤
|
|
300
|
+
│ PatternPreview │ Live Preview
|
|
301
|
+
│ - Generated file name │
|
|
302
|
+
│ - Generated folder path │
|
|
303
|
+
├─────────────────────────────────────┤
|
|
304
|
+
│ Actions: Undo | Redo | Import | Export│ Action Bar
|
|
305
|
+
└─────────────────────────────────────┘
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
**Purpose**: Interactive UI for building file/folder naming rules using drag-and-drop variables and literal text.
|
|
309
|
+
|
|
310
|
+
**Props Pattern**:
|
|
311
|
+
- `variables: NamingVariable[]` - User-defined variables (e.g., project_name, client_id)
|
|
312
|
+
- `initialSchema?: NamingRuleSchema` - Load existing rule for editing
|
|
313
|
+
- `onChange?: (schema) => void` - Callback on every pattern change
|
|
314
|
+
- `onExport?: (schema) => void` - Export JSON schema
|
|
315
|
+
- `onImport?: (schema) => void` - Import JSON schema
|
|
316
|
+
- `customDateFormats?: string[]` - Override default date formats
|
|
317
|
+
- `readOnly?: boolean` - Disable editing
|
|
318
|
+
- `sampleFileName?: string` - Example file for preview (default: "document.pdf")
|
|
319
|
+
|
|
320
|
+
**Usage Example**:
|
|
321
|
+
```typescript
|
|
322
|
+
import { NamingRuleConfigurator } from 'hazo_files/ui';
|
|
323
|
+
|
|
324
|
+
const userVariables = [
|
|
325
|
+
{ variable_name: 'project_name', description: 'Project name', example_value: 'WebApp', category: 'user' },
|
|
326
|
+
{ variable_name: 'client_id', description: 'Client ID', example_value: 'ACME', category: 'user' },
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
<NamingRuleConfigurator
|
|
330
|
+
variables={userVariables}
|
|
331
|
+
onChange={(schema) => saveSchema(schema)}
|
|
332
|
+
sampleFileName="proposal.pdf"
|
|
333
|
+
/>
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**Subcomponents**:
|
|
337
|
+
- `VariableList` - Category tabs with draggable variables
|
|
338
|
+
- `PatternBuilder` - Drop zones for file and folder patterns
|
|
339
|
+
- `PatternPreview` - Live preview with example values
|
|
340
|
+
- `DraggableVariable` - Individual variable chips
|
|
341
|
+
- `PatternSegmentItem` - Segments in the pattern (variable or literal)
|
|
342
|
+
- `SeparatorPicker` - Quick-add common separators (-, _, space, etc.)
|
|
343
|
+
|
|
344
|
+
**Keyboard Shortcuts**:
|
|
345
|
+
- `Ctrl+Z` / `Cmd+Z` - Undo
|
|
346
|
+
- `Ctrl+Y` / `Cmd+Y` - Redo
|
|
347
|
+
- `Ctrl+Shift+Z` / `Cmd+Shift+Z` - Redo (alternative)
|
|
348
|
+
|
|
349
|
+
**Drag-and-Drop Architecture**:
|
|
350
|
+
|
|
351
|
+
**Critical Design Pattern**: Single Parent DndContext
|
|
352
|
+
|
|
353
|
+
The component uses a single DndContext at the top level (NamingRuleConfigurator) that handles ALL drag-and-drop operations. Child components (PatternBuilder, PatternSegmentItem) use only droppable/sortable contexts, never nested DndContext.
|
|
354
|
+
|
|
355
|
+
```
|
|
356
|
+
NamingRuleConfigurator (DndContext - TOP LEVEL ONLY)
|
|
357
|
+
├── PointerSensor (8px activation distance)
|
|
358
|
+
├── DragOverlay (visual feedback during drag)
|
|
359
|
+
├── handleDragStart (track active variable)
|
|
360
|
+
└── handleDragEnd (handle both cases):
|
|
361
|
+
├── Case 1: New variable drop
|
|
362
|
+
│ └── Detect target (file-pattern-drop / folder-pattern-drop)
|
|
363
|
+
└── Case 2: Segment reordering
|
|
364
|
+
└── Call reorderFilePattern / reorderFolderPattern
|
|
365
|
+
|
|
366
|
+
PatternBuilder (NO DndContext)
|
|
367
|
+
├── SortableContext (for reordering segments)
|
|
368
|
+
├── useDroppable (for drop zone)
|
|
369
|
+
└── PatternSegmentItem (useSortable)
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
**Why This Matters**: Nested DndContext blocks drag events from parent. Initially, PatternBuilder had its own DndContext which prevented variables from being dragged into patterns. The fix removed the nested context and moved all drag handling to the parent.
|
|
373
|
+
|
|
374
|
+
**Drag Event Flow**:
|
|
375
|
+
1. User drags variable from VariableList
|
|
376
|
+
2. handleDragStart captures variable data, shows DragOverlay
|
|
377
|
+
3. User drops on pattern drop zone or segment
|
|
378
|
+
4. handleDragEnd determines action:
|
|
379
|
+
- If dropped on drop zone: Add to end of pattern
|
|
380
|
+
- If dropped on segment: Insert after that segment
|
|
381
|
+
- If reordering segment: Call reorder function with indices
|
|
382
|
+
5. DragOverlay hidden, activeVariable cleared
|
|
383
|
+
|
|
384
|
+
**Sensors Configuration**:
|
|
385
|
+
- `PointerSensor` with 8px activation distance prevents accidental drags
|
|
386
|
+
- `closestCenter` collision detection for accurate drop targeting
|
|
387
|
+
|
|
388
|
+
## Common Utilities
|
|
389
|
+
|
|
390
|
+
### Error Types
|
|
391
|
+
|
|
392
|
+
**Location**: `src/common/errors.ts`
|
|
393
|
+
|
|
394
|
+
12 specific error types, all extend `HazoFilesError`:
|
|
395
|
+
|
|
396
|
+
1. `FileNotFoundError` - File doesn't exist
|
|
397
|
+
2. `DirectoryNotFoundError` - Directory doesn't exist
|
|
398
|
+
3. `FileExistsError` - File already exists
|
|
399
|
+
4. `DirectoryExistsError` - Directory already exists
|
|
400
|
+
5. `DirectoryNotEmptyError` - Cannot delete non-empty directory
|
|
401
|
+
6. `PermissionDeniedError` - Access denied
|
|
402
|
+
7. `InvalidPathError` - Malformed path
|
|
403
|
+
8. `FileTooLargeError` - Exceeds size limit
|
|
404
|
+
9. `InvalidExtensionError` - Extension not allowed
|
|
405
|
+
10. `AuthenticationError` - Auth failure
|
|
406
|
+
11. `ConfigurationError` - Config issue
|
|
407
|
+
12. `OperationError` - Generic operation failure
|
|
408
|
+
|
|
409
|
+
**Usage Pattern**:
|
|
410
|
+
```typescript
|
|
411
|
+
try {
|
|
412
|
+
await module.uploadFile(buffer, '/test.exe');
|
|
413
|
+
} catch (error) {
|
|
414
|
+
if (error instanceof InvalidExtensionError) {
|
|
415
|
+
return errorResult(error.message);
|
|
416
|
+
}
|
|
417
|
+
throw error; // Unexpected errors re-throw
|
|
418
|
+
}
|
|
419
|
+
```
|
|
420
|
+
|
|
421
|
+
### Path Utilities
|
|
422
|
+
|
|
423
|
+
**Location**: `src/common/path-utils.ts`
|
|
424
|
+
|
|
425
|
+
Key functions:
|
|
426
|
+
- `normalizePath(path)` - Normalize to Unix style, remove trailing slash
|
|
427
|
+
- `joinPath(...segments)` - Join and normalize
|
|
428
|
+
- `getParentPath(path)` - Get parent directory
|
|
429
|
+
- `getBaseName(path)` - Get filename/folder name
|
|
430
|
+
- `getDirName(path)` - Get directory portion
|
|
431
|
+
- `validatePath(path)` - Check valid path format
|
|
432
|
+
- `sanitizeFilename(name)` - Remove unsafe characters
|
|
433
|
+
- `getExtension(path)` - Get file extension with dot
|
|
434
|
+
- `getBreadcrumbs(path)` - Array of path segments for breadcrumb
|
|
435
|
+
|
|
436
|
+
**Pattern**: All path operations use these utilities, never raw string manipulation
|
|
437
|
+
|
|
438
|
+
### MIME Types
|
|
439
|
+
|
|
440
|
+
**Location**: `src/common/mime-types.ts`
|
|
441
|
+
|
|
442
|
+
- `getMimeType(filename)` - Detect MIME from extension
|
|
443
|
+
- `getExtensionFromMime(mimeType)` - Reverse lookup
|
|
444
|
+
- `isImage(mimeType)` - Image check
|
|
445
|
+
- `isVideo(mimeType)` - Video check
|
|
446
|
+
- `isAudio(mimeType)` - Audio check
|
|
447
|
+
- `isText(mimeType)` - Text check
|
|
448
|
+
- `isDocument(mimeType)` - Document check
|
|
449
|
+
- `isPreviewable(mimeType)` - Can preview in browser
|
|
450
|
+
- `getFileCategory(mimeType)` - Category string
|
|
451
|
+
|
|
452
|
+
### Naming Utilities
|
|
453
|
+
|
|
454
|
+
**Location**: `src/common/naming-utils.ts`
|
|
455
|
+
|
|
456
|
+
**Purpose**: Generate file/folder names from naming rule schemas with variable substitution.
|
|
457
|
+
|
|
458
|
+
**Core Functions**:
|
|
459
|
+
- `hazo_files_generate_file_name(schema, variables, originalFileName?, options?)` - Generate file name from pattern
|
|
460
|
+
- `hazo_files_generate_folder_name(schema, variables, options?)` - Generate folder path from pattern
|
|
461
|
+
- `validateNamingRuleSchema(schema)` - Validate schema structure
|
|
462
|
+
- `createEmptyNamingRuleSchema()` - Create blank schema
|
|
463
|
+
|
|
464
|
+
**Pattern Manipulation**:
|
|
465
|
+
- `parsePatternString(patternStr)` - Parse "{var}text" to segments
|
|
466
|
+
- `patternToString(pattern)` - Convert segments to "{var}text"
|
|
467
|
+
- `createVariableSegment(name)` - Create variable segment
|
|
468
|
+
- `createLiteralSegment(text)` - Create literal segment
|
|
469
|
+
- `clonePattern(pattern)` - Deep clone with new IDs
|
|
470
|
+
- `generateSegmentId()` - Unique ID for segments
|
|
471
|
+
|
|
472
|
+
**Variable Detection**:
|
|
473
|
+
- `isDateVariable(varName, dateFormats?)` - Check if date variable
|
|
474
|
+
- `isFileMetadataVariable(varName)` - Check if file variable
|
|
475
|
+
- `isCounterVariable(varName)` - Check if counter variable
|
|
476
|
+
|
|
477
|
+
**Formatting**:
|
|
478
|
+
- `formatDateToken(date, format)` - Format date to token value
|
|
479
|
+
- `formatCounter(value, digits)` - Pad counter with zeros
|
|
480
|
+
- `getFileMetadataValues(filename)` - Extract original_name, extension, ext
|
|
481
|
+
- `getSystemVariablePreviewValues(date?, options?)` - Get all system variable values
|
|
482
|
+
- `generatePreviewName(pattern, userVariables, options?)` - Preview name with examples
|
|
483
|
+
|
|
484
|
+
**System Variables**:
|
|
485
|
+
- `SYSTEM_DATE_VARIABLES` - Array of date variables (YYYY, MM, DD, etc.)
|
|
486
|
+
- `SYSTEM_FILE_VARIABLES` - Array of file variables (original_name, extension, ext)
|
|
487
|
+
- `SYSTEM_COUNTER_VARIABLES` - Array of counter variables (counter)
|
|
488
|
+
- `ALL_SYSTEM_VARIABLES` - Combined array of all system variables
|
|
489
|
+
- `DEFAULT_DATE_FORMATS` - Default supported date format tokens
|
|
490
|
+
|
|
491
|
+
**Usage Example**:
|
|
492
|
+
```typescript
|
|
493
|
+
import {
|
|
494
|
+
hazo_files_generate_file_name,
|
|
495
|
+
hazo_files_generate_folder_name,
|
|
496
|
+
createVariableSegment,
|
|
497
|
+
createLiteralSegment
|
|
498
|
+
} from 'hazo_files';
|
|
499
|
+
|
|
500
|
+
const schema = {
|
|
501
|
+
version: 1,
|
|
502
|
+
filePattern: [
|
|
503
|
+
createVariableSegment('project_name'),
|
|
504
|
+
createLiteralSegment('_'),
|
|
505
|
+
createVariableSegment('YYYY-MM-DD'),
|
|
506
|
+
createLiteralSegment('_'),
|
|
507
|
+
createVariableSegment('counter'),
|
|
508
|
+
],
|
|
509
|
+
folderPattern: [
|
|
510
|
+
createVariableSegment('client_id'),
|
|
511
|
+
createLiteralSegment('/'),
|
|
512
|
+
createVariableSegment('YYYY'),
|
|
513
|
+
],
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const userVars = {
|
|
517
|
+
project_name: 'WebApp',
|
|
518
|
+
client_id: 'ACME',
|
|
519
|
+
};
|
|
520
|
+
|
|
521
|
+
// Generate file name
|
|
522
|
+
const fileResult = hazo_files_generate_file_name(
|
|
523
|
+
schema,
|
|
524
|
+
userVars,
|
|
525
|
+
'original.pdf',
|
|
526
|
+
{ counterValue: 42, preserveExtension: true }
|
|
527
|
+
);
|
|
528
|
+
// Result: { success: true, name: 'WebApp_2024-12-09_042.pdf' }
|
|
529
|
+
|
|
530
|
+
// Generate folder path
|
|
531
|
+
const folderResult = hazo_files_generate_folder_name(schema, userVars);
|
|
532
|
+
// Result: { success: true, name: 'ACME/2024' }
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Name Generation Options**:
|
|
536
|
+
```typescript
|
|
537
|
+
interface NameGenerationOptions {
|
|
538
|
+
dateFormats?: string[]; // Override default date formats
|
|
539
|
+
date?: Date; // Date for date variables (default: now)
|
|
540
|
+
preserveExtension?: boolean; // Preserve original extension (default: true)
|
|
541
|
+
counterValue?: number; // Counter value (default: 1)
|
|
542
|
+
counterDigits?: number; // Counter padding digits (default: 3)
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**Pattern Segment Structure**:
|
|
547
|
+
```typescript
|
|
548
|
+
interface PatternSegment {
|
|
549
|
+
id: string; // Unique ID for React/drag-drop
|
|
550
|
+
type: 'variable' | 'literal'; // Segment type
|
|
551
|
+
value: string; // Variable name or literal text
|
|
552
|
+
}
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
**Gotchas**:
|
|
556
|
+
- Date variables use current date unless overridden via options.date
|
|
557
|
+
- Counter is formatted with 3 digits by default (001, 042, 123)
|
|
558
|
+
- File extension preservation is automatic unless preserveExtension=false
|
|
559
|
+
- Folder patterns can include "/" for nested paths
|
|
560
|
+
- All generated names are sanitized via sanitizeFilename()
|
|
561
|
+
- Missing variable values return error result, not exception
|
|
562
|
+
|
|
563
|
+
## Integration Patterns
|
|
564
|
+
|
|
565
|
+
### Next.js App Router
|
|
566
|
+
|
|
567
|
+
**API Route Pattern** (`app/api/files/route.ts`):
|
|
568
|
+
```typescript
|
|
569
|
+
export async function GET(request: NextRequest) {
|
|
570
|
+
const { searchParams } = new URL(request.url);
|
|
571
|
+
const action = searchParams.get('action');
|
|
572
|
+
const path = searchParams.get('path') || '/';
|
|
573
|
+
|
|
574
|
+
const fm = await getFileManager();
|
|
575
|
+
|
|
576
|
+
switch (action) {
|
|
577
|
+
case 'list': return NextResponse.json(await fm.listDirectory(path));
|
|
578
|
+
case 'tree': return NextResponse.json(await fm.getFolderTree(path));
|
|
579
|
+
// ...
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export async function POST(request: NextRequest) {
|
|
584
|
+
const body = await request.json();
|
|
585
|
+
const { action, ...params } = body;
|
|
586
|
+
const fm = await getFileManager();
|
|
587
|
+
|
|
588
|
+
switch (action) {
|
|
589
|
+
case 'createDirectory': return NextResponse.json(await fm.createDirectory(params.path));
|
|
590
|
+
// ...
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
```
|
|
594
|
+
|
|
595
|
+
**Upload Route** (`app/api/files/upload/route.ts`):
|
|
596
|
+
```typescript
|
|
597
|
+
export async function POST(request: NextRequest) {
|
|
598
|
+
const formData = await request.formData();
|
|
599
|
+
const file = formData.get('file') as File;
|
|
600
|
+
const path = formData.get('path') as string;
|
|
601
|
+
|
|
602
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
603
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
604
|
+
|
|
605
|
+
const fm = await getFileManager();
|
|
606
|
+
return NextResponse.json(await fm.uploadFile(buffer, path));
|
|
607
|
+
}
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
### Client-Side API Adapter
|
|
611
|
+
|
|
612
|
+
**Pattern** (from `test-app/lib/hazo-files.ts`):
|
|
613
|
+
```typescript
|
|
614
|
+
export function createFileBrowserAPI(provider: 'local' | 'google_drive'): FileBrowserAPI {
|
|
615
|
+
return {
|
|
616
|
+
async listDirectory(path) {
|
|
617
|
+
const res = await fetch(`/api/files?action=list&path=${encodeURIComponent(path)}&provider=${provider}`);
|
|
618
|
+
return res.json();
|
|
619
|
+
},
|
|
620
|
+
async uploadFile(file, remotePath) {
|
|
621
|
+
const formData = new FormData();
|
|
622
|
+
formData.append('file', file);
|
|
623
|
+
formData.append('path', remotePath);
|
|
624
|
+
formData.append('provider', provider);
|
|
625
|
+
const res = await fetch('/api/files/upload', { method: 'POST', body: formData });
|
|
626
|
+
return res.json();
|
|
627
|
+
},
|
|
628
|
+
// ... other methods
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
```
|
|
632
|
+
|
|
633
|
+
## Performance Considerations
|
|
634
|
+
|
|
635
|
+
### Local Module
|
|
636
|
+
|
|
637
|
+
- **Fast**: Direct filesystem access
|
|
638
|
+
- **Blocking**: Sync config loading blocks startup
|
|
639
|
+
- **Streams**: Use streams for large files to avoid memory issues
|
|
640
|
+
- **Progress**: Accurate progress via stream chunks
|
|
641
|
+
|
|
642
|
+
### Google Drive Module
|
|
643
|
+
|
|
644
|
+
- **API Quota**: 1,000 queries per 100 seconds per user
|
|
645
|
+
- **Latency**: Network round trips for every operation
|
|
646
|
+
- **Path Resolution**: O(n) API calls where n = path depth
|
|
647
|
+
- **Optimization Opportunities**:
|
|
648
|
+
- Cache path-to-ID mappings
|
|
649
|
+
- Batch API requests
|
|
650
|
+
- Use partial response fields
|
|
651
|
+
- Implement exponential backoff
|
|
652
|
+
|
|
653
|
+
### UI Components
|
|
654
|
+
|
|
655
|
+
- **Virtual Scrolling**: Not implemented (needed for large directories)
|
|
656
|
+
- **Tree Lazy Loading**: Implemented via `onExpand`
|
|
657
|
+
- **File List**: Re-renders on every file change
|
|
658
|
+
- **Preview**: Loads full file content (memory issue for large files)
|
|
659
|
+
|
|
660
|
+
## Testing Strategy
|
|
661
|
+
|
|
662
|
+
### Unit Tests
|
|
663
|
+
|
|
664
|
+
- Mock filesystem (`memfs`) for local module tests
|
|
665
|
+
- Mock Google APIs for Drive module tests
|
|
666
|
+
- Test each operation independently
|
|
667
|
+
- Test error conditions
|
|
668
|
+
|
|
669
|
+
### Integration Tests
|
|
670
|
+
|
|
671
|
+
- Test-app serves as integration test
|
|
672
|
+
- Manual testing of UI components
|
|
673
|
+
- OAuth flow testing
|
|
674
|
+
|
|
675
|
+
### Not Covered
|
|
676
|
+
|
|
677
|
+
- E2E tests for file operations
|
|
678
|
+
- Performance benchmarks
|
|
679
|
+
- Load testing
|
|
680
|
+
- Browser compatibility matrix
|
|
681
|
+
|
|
682
|
+
## Common Gotchas
|
|
683
|
+
|
|
684
|
+
1. **Path separators**: Always use `/`, never backslash
|
|
685
|
+
2. **Root path**: Must be `/`, not empty string
|
|
686
|
+
3. **File vs Buffer**: `uploadFile` accepts string path, Buffer, or ReadableStream
|
|
687
|
+
4. **Download destination**: If `localPath` omitted, returns Buffer
|
|
688
|
+
5. **Google Drive auth**: Module can be initialized but not authenticated
|
|
689
|
+
6. **Token refresh**: Automatic via googleapis library
|
|
690
|
+
7. **Progress callbacks**: Called synchronously, don't use async functions
|
|
691
|
+
8. **Extension validation**: Applies only to uploads, not to existing files
|
|
692
|
+
9. **Recursive delete**: Must explicitly set `recursive: true`
|
|
693
|
+
10. **Virtual paths**: Never expose physical filesystem paths to clients
|
|
694
|
+
11. **Nested DndContext**: NEVER nest DndContext components (from @dnd-kit/core). Child contexts block drag events from parent handlers. Always use single top-level DndContext with droppable/sortable children only. See NamingRuleConfigurator for correct pattern.
|
|
695
|
+
|
|
696
|
+
## Extension Points
|
|
697
|
+
|
|
698
|
+
### Adding New Storage Provider
|
|
699
|
+
|
|
700
|
+
1. Create class extending `BaseStorageModule`
|
|
701
|
+
2. Implement all `StorageModule` interface methods
|
|
702
|
+
3. Add provider type to `StorageProvider` union
|
|
703
|
+
4. Add config interface to `HazoFilesConfig`
|
|
704
|
+
5. Register via `registerModule(providerName, factory)`
|
|
705
|
+
6. Add to module index exports
|
|
706
|
+
|
|
707
|
+
See `docs/ADDING_MODULES.md` for detailed guide.
|
|
708
|
+
|
|
709
|
+
### Custom UI Components
|
|
710
|
+
|
|
711
|
+
- Use `useFileBrowser` hook for state management
|
|
712
|
+
- Implement `FileBrowserAPI` for backend calls
|
|
713
|
+
- Reuse individual components (FolderTree, FileList, etc.)
|
|
714
|
+
- Style with Tailwind classes or custom CSS
|
|
715
|
+
|
|
716
|
+
### Custom Error Handling
|
|
717
|
+
|
|
718
|
+
- Extend `HazoFilesError` for domain-specific errors
|
|
719
|
+
- Throw from module operations
|
|
720
|
+
- Catch in FileManager layer
|
|
721
|
+
- Return via `OperationResult.error`
|
|
722
|
+
|
|
723
|
+
## Dependencies
|
|
724
|
+
|
|
725
|
+
**Runtime**:
|
|
726
|
+
- `googleapis` - Google Drive API client
|
|
727
|
+
- `ini` - INI file parsing
|
|
728
|
+
|
|
729
|
+
**Development**:
|
|
730
|
+
- `typescript` - Type checking
|
|
731
|
+
- `tsup` - Build tool (uses esbuild)
|
|
732
|
+
- `vitest` - Testing framework
|
|
733
|
+
|
|
734
|
+
**Peer** (UI components):
|
|
735
|
+
- `react` ^18.0.0
|
|
736
|
+
- `react-dom` ^18.0.0
|
|
737
|
+
- `@dnd-kit/core` - Drag and drop for naming configurator
|
|
738
|
+
- `@dnd-kit/sortable` - Sortable lists for naming configurator
|
|
739
|
+
- `@dnd-kit/utilities` - Utility functions for drag and drop
|
|
740
|
+
|
|
741
|
+
## Build System
|
|
742
|
+
|
|
743
|
+
**Tool**: tsup (esbuild wrapper)
|
|
744
|
+
|
|
745
|
+
**Outputs**:
|
|
746
|
+
- `dist/index.js` - CommonJS build
|
|
747
|
+
- `dist/index.mjs` - ESM build
|
|
748
|
+
- `dist/index.d.ts` - Type definitions
|
|
749
|
+
- `dist/ui/` - Separate UI component build
|
|
750
|
+
|
|
751
|
+
**Entry Points**:
|
|
752
|
+
- Main: `src/index.ts`
|
|
753
|
+
- UI: `src/ui/index.ts`
|
|
754
|
+
|
|
755
|
+
**Exports** (package.json):
|
|
756
|
+
```json
|
|
757
|
+
{
|
|
758
|
+
".": {
|
|
759
|
+
"import": "./dist/index.mjs",
|
|
760
|
+
"require": "./dist/index.js",
|
|
761
|
+
"types": "./dist/index.d.ts"
|
|
762
|
+
},
|
|
763
|
+
"./ui": {
|
|
764
|
+
"import": "./dist/ui/index.mjs",
|
|
765
|
+
"require": "./dist/ui/index.js",
|
|
766
|
+
"types": "./dist/ui/index.d.ts"
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
## Quick Reference
|
|
772
|
+
|
|
773
|
+
### File Operations Cheat Sheet
|
|
774
|
+
|
|
775
|
+
```typescript
|
|
776
|
+
// Create
|
|
777
|
+
await fm.createDirectory('/folder');
|
|
778
|
+
await fm.uploadFile(buffer, '/folder/file.pdf');
|
|
779
|
+
|
|
780
|
+
// Read
|
|
781
|
+
await fm.listDirectory('/folder');
|
|
782
|
+
await fm.getItem('/folder/file.pdf');
|
|
783
|
+
await fm.downloadFile('/folder/file.pdf', './local.pdf');
|
|
784
|
+
await fm.exists('/folder/file.pdf');
|
|
785
|
+
|
|
786
|
+
// Update
|
|
787
|
+
await fm.renameFile('/folder/file.pdf', 'renamed.pdf');
|
|
788
|
+
await fm.renameFolder('/folder', 'renamed-folder');
|
|
789
|
+
await fm.moveItem('/folder/file.pdf', '/other/file.pdf');
|
|
790
|
+
|
|
791
|
+
// Delete
|
|
792
|
+
await fm.deleteFile('/folder/file.pdf');
|
|
793
|
+
await fm.removeDirectory('/folder', true); // recursive
|
|
794
|
+
|
|
795
|
+
// Utility
|
|
796
|
+
await fm.writeFile('/text.txt', 'content');
|
|
797
|
+
const { data } = await fm.readFile('/text.txt');
|
|
798
|
+
await fm.copyFile('/source.pdf', '/dest.pdf');
|
|
799
|
+
await fm.ensureDirectory('/folder'); // create if not exists
|
|
800
|
+
```
|
|
801
|
+
|
|
802
|
+
### Naming Rules Cheat Sheet
|
|
803
|
+
|
|
804
|
+
```typescript
|
|
805
|
+
import {
|
|
806
|
+
hazo_files_generate_file_name,
|
|
807
|
+
hazo_files_generate_folder_name,
|
|
808
|
+
createVariableSegment,
|
|
809
|
+
createLiteralSegment,
|
|
810
|
+
SYSTEM_DATE_VARIABLES,
|
|
811
|
+
SYSTEM_FILE_VARIABLES
|
|
812
|
+
} from 'hazo_files';
|
|
813
|
+
|
|
814
|
+
// Create schema
|
|
815
|
+
const schema = {
|
|
816
|
+
version: 1,
|
|
817
|
+
filePattern: [
|
|
818
|
+
createVariableSegment('client_id'),
|
|
819
|
+
createLiteralSegment('_'),
|
|
820
|
+
createVariableSegment('YYYY-MM-DD'),
|
|
821
|
+
createLiteralSegment('_'),
|
|
822
|
+
createVariableSegment('counter'),
|
|
823
|
+
],
|
|
824
|
+
folderPattern: [
|
|
825
|
+
createVariableSegment('YYYY'),
|
|
826
|
+
createLiteralSegment('/'),
|
|
827
|
+
createVariableSegment('client_id'),
|
|
828
|
+
],
|
|
829
|
+
};
|
|
830
|
+
|
|
831
|
+
// Generate names
|
|
832
|
+
const variables = { client_id: 'ACME' };
|
|
833
|
+
|
|
834
|
+
const fileResult = hazo_files_generate_file_name(
|
|
835
|
+
schema,
|
|
836
|
+
variables,
|
|
837
|
+
'document.pdf',
|
|
838
|
+
{ counterValue: 5, preserveExtension: true }
|
|
839
|
+
);
|
|
840
|
+
// Result: { success: true, name: 'ACME_2024-12-09_005.pdf' }
|
|
841
|
+
|
|
842
|
+
const folderResult = hazo_files_generate_folder_name(schema, variables);
|
|
843
|
+
// Result: { success: true, name: '2024/ACME' }
|
|
844
|
+
|
|
845
|
+
// Use with FileBrowser
|
|
846
|
+
import { NamingRuleConfigurator } from 'hazo_files/ui';
|
|
847
|
+
|
|
848
|
+
const userVars = [
|
|
849
|
+
{ variable_name: 'client_id', description: 'Client', example_value: 'ACME', category: 'user' }
|
|
850
|
+
];
|
|
851
|
+
|
|
852
|
+
<NamingRuleConfigurator
|
|
853
|
+
variables={userVars}
|
|
854
|
+
onChange={(schema) => saveSchema(schema)}
|
|
855
|
+
/>
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### Configuration Cheat Sheet
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
// File
|
|
862
|
+
const config = loadConfig('./hazo_files_config.ini');
|
|
863
|
+
|
|
864
|
+
// Code
|
|
865
|
+
const config = {
|
|
866
|
+
provider: 'local',
|
|
867
|
+
local: { basePath: './files', maxFileSize: 10485760 }
|
|
868
|
+
};
|
|
869
|
+
|
|
870
|
+
// FileManager
|
|
871
|
+
const fm = await createInitializedFileManager({ config });
|
|
872
|
+
await fm.initialize(); // if not using create helper
|
|
873
|
+
```
|
|
874
|
+
|
|
875
|
+
### Error Handling Cheat Sheet
|
|
876
|
+
|
|
877
|
+
```typescript
|
|
878
|
+
const result = await fm.uploadFile(buffer, '/file.pdf');
|
|
879
|
+
if (!result.success) {
|
|
880
|
+
console.error(result.error);
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
const fileItem = result.data;
|
|
884
|
+
```
|
|
885
|
+
|
|
886
|
+
## Critical File Locations
|
|
887
|
+
|
|
888
|
+
**Core**:
|
|
889
|
+
- Types: `src/types/index.ts`
|
|
890
|
+
- Naming Types: `src/types/naming.ts`
|
|
891
|
+
- FileManager: `src/services/file-manager.ts`
|
|
892
|
+
- Base Module: `src/common/base-module.ts`
|
|
893
|
+
- Config: `src/config/index.ts`
|
|
894
|
+
|
|
895
|
+
**Storage Modules**:
|
|
896
|
+
- Local Module: `src/modules/local/index.ts`
|
|
897
|
+
- Google Drive Module: `src/modules/google-drive/index.ts`
|
|
898
|
+
- Google Drive Auth: `src/modules/google-drive/auth.ts`
|
|
899
|
+
|
|
900
|
+
**Common Utilities**:
|
|
901
|
+
- Errors: `src/common/errors.ts`
|
|
902
|
+
- Path Utils: `src/common/path-utils.ts`
|
|
903
|
+
- MIME Types: `src/common/mime-types.ts`
|
|
904
|
+
- Naming Utils: `src/common/naming-utils.ts`
|
|
905
|
+
|
|
906
|
+
**UI Components**:
|
|
907
|
+
- FileBrowser: `src/ui/components/FileBrowser.tsx`
|
|
908
|
+
- NamingRuleConfigurator: `src/ui/components/naming/NamingRuleConfigurator.tsx`
|
|
909
|
+
- VariableList: `src/ui/components/naming/VariableList.tsx`
|
|
910
|
+
- PatternBuilder: `src/ui/components/naming/PatternBuilder.tsx`
|
|
911
|
+
- PatternPreview: `src/ui/components/naming/PatternPreview.tsx`
|
|
912
|
+
- PatternSegmentItem: `src/ui/components/naming/PatternSegmentItem.tsx`
|
|
913
|
+
- DraggableVariable: `src/ui/components/naming/DraggableVariable.tsx`
|
|
914
|
+
- SeparatorPicker: `src/ui/components/naming/SeparatorPicker.tsx`
|
|
915
|
+
|
|
916
|
+
**UI Hooks**:
|
|
917
|
+
- useFileBrowser: `src/ui/hooks/useFileBrowser.ts`
|
|
918
|
+
- useNamingRule: `src/ui/hooks/useNamingRule.ts`
|
|
919
|
+
|
|
920
|
+
## Version
|
|
921
|
+
|
|
922
|
+
Current: 1.0.0
|
|
923
|
+
|
|
924
|
+
Node.js: 16+
|
|
925
|
+
React: 18+ (for UI components)
|
|
926
|
+
TypeScript: 5.3+
|