hazo_files 1.4.4 → 1.4.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -1
- package/{hazo_files_config.ini → config/hazo_files_config.ini.sample} +14 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +33 -20
- package/dist/index.mjs +33 -20
- package/dist/server/index.d.mts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +36 -20
- package/dist/server/index.mjs +36 -20
- package/migrations/001_initial_schema.sql +49 -0
- package/migrations/002_reference_tracking.sql +51 -0
- package/migrations/003_content_tags.sql +15 -0
- package/package.json +21 -8
package/README.md
CHANGED
|
@@ -45,13 +45,20 @@ For the NamingRuleConfigurator component (drag-and-drop interface), also install
|
|
|
45
45
|
npm install @dnd-kit/core @dnd-kit/sortable @dnd-kit/utilities
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
+
For cloud storage providers (install only what you need):
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install googleapis # Google Drive support
|
|
52
|
+
npm install dropbox # Dropbox support
|
|
53
|
+
```
|
|
54
|
+
|
|
48
55
|
For database tracking and LLM extraction features (optional):
|
|
49
56
|
|
|
50
57
|
```bash
|
|
51
58
|
npm install hazo_connect # Database tracking
|
|
52
59
|
npm install hazo_llm_api # LLM document extraction
|
|
53
60
|
npm install server-only # Server-side safety (recommended)
|
|
54
|
-
|
|
61
|
+
npm install xxhash-wasm # File change detection (optional)
|
|
55
62
|
```
|
|
56
63
|
|
|
57
64
|
### Tailwind CSS v4 Setup (Required for UI Components)
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
; Hazo Files Configuration
|
|
2
2
|
; This file configures the file management system
|
|
3
|
+
; Copy to hazo_files_config.ini and fill in your values
|
|
3
4
|
|
|
4
5
|
[general]
|
|
5
|
-
; Available providers: local, google_drive
|
|
6
|
+
; Available providers: local, google_drive, dropbox
|
|
6
7
|
provider = local
|
|
7
8
|
|
|
8
9
|
[local]
|
|
@@ -25,6 +26,18 @@ access_token =
|
|
|
25
26
|
; Optional: Root folder ID to use as base (empty = root of Drive)
|
|
26
27
|
root_folder_id =
|
|
27
28
|
|
|
29
|
+
[dropbox]
|
|
30
|
+
; Dropbox OAuth credentials
|
|
31
|
+
; These can also be set via environment variables:
|
|
32
|
+
; HAZO_DROPBOX_CLIENT_ID, HAZO_DROPBOX_CLIENT_SECRET, etc.
|
|
33
|
+
client_id =
|
|
34
|
+
client_secret =
|
|
35
|
+
redirect_uri = http://localhost:3000/api/auth/callback/dropbox
|
|
36
|
+
refresh_token =
|
|
37
|
+
access_token =
|
|
38
|
+
; Optional: Root folder path within Dropbox (empty = root)
|
|
39
|
+
root_path =
|
|
40
|
+
|
|
28
41
|
[naming]
|
|
29
42
|
; Comma-separated list of supported date format tokens for naming rules
|
|
30
43
|
; Available: YYYY, YY, MM, M, DD, D, MMM, MMMM, YYYY-MM-DD, YYYY-MMM-DD, DD-MM-YYYY, MM-DD-YYYY
|
package/dist/index.d.mts
CHANGED
|
@@ -685,9 +685,9 @@ declare class FileManager {
|
|
|
685
685
|
*/
|
|
686
686
|
initialize(config?: HazoFilesConfig): Promise<void>;
|
|
687
687
|
/**
|
|
688
|
-
*
|
|
688
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
689
689
|
*/
|
|
690
|
-
initializeSync(
|
|
690
|
+
initializeSync(_config?: HazoFilesConfig): void;
|
|
691
691
|
/**
|
|
692
692
|
* Check if manager is initialized
|
|
693
693
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -685,9 +685,9 @@ declare class FileManager {
|
|
|
685
685
|
*/
|
|
686
686
|
initialize(config?: HazoFilesConfig): Promise<void>;
|
|
687
687
|
/**
|
|
688
|
-
*
|
|
688
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
689
689
|
*/
|
|
690
|
-
initializeSync(
|
|
690
|
+
initializeSync(_config?: HazoFilesConfig): void;
|
|
691
691
|
/**
|
|
692
692
|
* Check if manager is initialized
|
|
693
693
|
*/
|
package/dist/index.js
CHANGED
|
@@ -1096,6 +1096,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1096
1096
|
if (!stats || stats.isDirectory()) {
|
|
1097
1097
|
throw new FileNotFoundError(virtualPath);
|
|
1098
1098
|
}
|
|
1099
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
1100
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
1101
|
+
}
|
|
1099
1102
|
this.validateExtension(newName);
|
|
1100
1103
|
const parentDir = path2.dirname(fullPath);
|
|
1101
1104
|
const newFullPath = path2.join(parentDir, newName);
|
|
@@ -1110,7 +1113,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1110
1113
|
const item = await this.statToItem(newFullPath, newStats);
|
|
1111
1114
|
return this.successResult(item);
|
|
1112
1115
|
} catch (error) {
|
|
1113
|
-
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError) {
|
|
1116
|
+
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError || error instanceof InvalidPathError) {
|
|
1114
1117
|
return this.errorResult(error.message);
|
|
1115
1118
|
}
|
|
1116
1119
|
return this.errorResult(`Failed to rename file: ${error.message}`);
|
|
@@ -1124,6 +1127,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1124
1127
|
if (!stats || !stats.isDirectory()) {
|
|
1125
1128
|
throw new DirectoryNotFoundError(virtualPath);
|
|
1126
1129
|
}
|
|
1130
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
1131
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
1132
|
+
}
|
|
1127
1133
|
const parentDir = path2.dirname(fullPath);
|
|
1128
1134
|
const newFullPath = path2.join(parentDir, newName);
|
|
1129
1135
|
if (!options.overwrite) {
|
|
@@ -1137,7 +1143,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1137
1143
|
const item = await this.statToItem(newFullPath, newStats);
|
|
1138
1144
|
return this.successResult(item);
|
|
1139
1145
|
} catch (error) {
|
|
1140
|
-
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError) {
|
|
1146
|
+
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError || error instanceof InvalidPathError) {
|
|
1141
1147
|
return this.errorResult(error.message);
|
|
1142
1148
|
}
|
|
1143
1149
|
return this.errorResult(`Failed to rename folder: ${error.message}`);
|
|
@@ -1279,9 +1285,12 @@ var GoogleDriveAuth = class {
|
|
|
1279
1285
|
*/
|
|
1280
1286
|
async exchangeCodeForTokens(code) {
|
|
1281
1287
|
const { tokens } = await this.oauth2Client.getToken(code);
|
|
1288
|
+
if (!tokens.access_token) {
|
|
1289
|
+
throw new Error("No access token received from Google OAuth");
|
|
1290
|
+
}
|
|
1282
1291
|
this.tokens = {
|
|
1283
1292
|
accessToken: tokens.access_token,
|
|
1284
|
-
refreshToken: tokens.refresh_token,
|
|
1293
|
+
refreshToken: tokens.refresh_token ?? this.tokens?.refreshToken ?? "",
|
|
1285
1294
|
expiryDate: tokens.expiry_date || void 0,
|
|
1286
1295
|
scope: tokens.scope || void 0
|
|
1287
1296
|
};
|
|
@@ -1466,7 +1475,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1466
1475
|
const segments = normalized.split("/").filter(Boolean);
|
|
1467
1476
|
let currentParentId = this.rootFolderId;
|
|
1468
1477
|
for (const segment of segments) {
|
|
1469
|
-
const
|
|
1478
|
+
const escapedSegment = segment.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1479
|
+
const query = `name='${escapedSegment}' and '${currentParentId}' in parents and trashed=false`;
|
|
1470
1480
|
const response = await this.drive.files.list({
|
|
1471
1481
|
q: query,
|
|
1472
1482
|
fields: "files(id, name, mimeType)",
|
|
@@ -1534,7 +1544,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1534
1544
|
if (!parentId) {
|
|
1535
1545
|
throw new DirectoryNotFoundError(parentPath);
|
|
1536
1546
|
}
|
|
1537
|
-
const
|
|
1547
|
+
const escapedFolderName = folderName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1548
|
+
const existingQuery = `name='${escapedFolderName}' and '${parentId}' in parents and mimeType='${FOLDER_MIME_TYPE}' and trashed=false`;
|
|
1538
1549
|
const existingResponse = await this.drive.files.list({
|
|
1539
1550
|
q: existingQuery,
|
|
1540
1551
|
fields: "files(id)",
|
|
@@ -1597,7 +1608,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1597
1608
|
throw new DirectoryNotFoundError(parentPath);
|
|
1598
1609
|
}
|
|
1599
1610
|
if (!options.overwrite) {
|
|
1600
|
-
const
|
|
1611
|
+
const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1612
|
+
const existingQuery = `name='${escapedFileName}' and '${parentId}' in parents and trashed=false`;
|
|
1601
1613
|
const existingResponse = await this.drive.files.list({
|
|
1602
1614
|
q: existingQuery,
|
|
1603
1615
|
fields: "files(id)",
|
|
@@ -2552,20 +2564,12 @@ var FileManager = class {
|
|
|
2552
2564
|
this.initialized = true;
|
|
2553
2565
|
}
|
|
2554
2566
|
/**
|
|
2555
|
-
*
|
|
2567
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
2556
2568
|
*/
|
|
2557
|
-
initializeSync(
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
if (config) {
|
|
2562
|
-
this.config = config;
|
|
2563
|
-
} else if (this.options.config) {
|
|
2564
|
-
this.config = this.options.config;
|
|
2565
|
-
} else {
|
|
2566
|
-
this.config = loadConfig(this.options.configPath);
|
|
2567
|
-
}
|
|
2568
|
-
this.module = createModule(this.config.provider);
|
|
2569
|
+
initializeSync(_config) {
|
|
2570
|
+
throw new Error(
|
|
2571
|
+
"initializeSync is not supported. Storage modules require async initialization. Use initialize() instead."
|
|
2572
|
+
);
|
|
2569
2573
|
}
|
|
2570
2574
|
/**
|
|
2571
2575
|
* Check if manager is initialized
|
|
@@ -4007,7 +4011,7 @@ var TrackedFileManager = class extends FileManager {
|
|
|
4007
4011
|
}
|
|
4008
4012
|
let buffer;
|
|
4009
4013
|
if (typeof downloadResult.data === "string") {
|
|
4010
|
-
|
|
4014
|
+
return null;
|
|
4011
4015
|
} else if (downloadResult.data instanceof Buffer) {
|
|
4012
4016
|
buffer = downloadResult.data;
|
|
4013
4017
|
} else {
|
|
@@ -5322,7 +5326,13 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
5322
5326
|
"content_tag"
|
|
5323
5327
|
]
|
|
5324
5328
|
};
|
|
5329
|
+
function validateTableName(tableName) {
|
|
5330
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(tableName)) {
|
|
5331
|
+
throw new Error(`Invalid table name: ${tableName}`);
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5325
5334
|
function getSchemaForTable(tableName, dbType) {
|
|
5335
|
+
validateTableName(tableName);
|
|
5326
5336
|
const schema = HAZO_FILES_TABLE_SCHEMA[dbType];
|
|
5327
5337
|
const defaultName = HAZO_FILES_TABLE_SCHEMA.tableName;
|
|
5328
5338
|
return {
|
|
@@ -5392,6 +5402,7 @@ WHERE file_refs IS NULL OR ref_count IS NULL OR status IS NULL`
|
|
|
5392
5402
|
]
|
|
5393
5403
|
};
|
|
5394
5404
|
function getMigrationForTable(tableName, dbType) {
|
|
5405
|
+
validateTableName(tableName);
|
|
5395
5406
|
const migration = HAZO_FILES_MIGRATION_V2[dbType];
|
|
5396
5407
|
const defaultName = HAZO_FILES_MIGRATION_V2.tableName;
|
|
5397
5408
|
return {
|
|
@@ -5451,6 +5462,7 @@ var HAZO_FILES_NAMING_TABLE_SCHEMA = {
|
|
|
5451
5462
|
]
|
|
5452
5463
|
};
|
|
5453
5464
|
function getNamingSchemaForTable(tableName, dbType) {
|
|
5465
|
+
validateTableName(tableName);
|
|
5454
5466
|
const schema = HAZO_FILES_NAMING_TABLE_SCHEMA[dbType];
|
|
5455
5467
|
const defaultName = HAZO_FILES_NAMING_TABLE_SCHEMA.tableName;
|
|
5456
5468
|
return {
|
|
@@ -5487,6 +5499,7 @@ var HAZO_FILES_MIGRATION_V3 = {
|
|
|
5487
5499
|
]
|
|
5488
5500
|
};
|
|
5489
5501
|
function getMigrationV3ForTable(tableName, dbType) {
|
|
5502
|
+
validateTableName(tableName);
|
|
5490
5503
|
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5491
5504
|
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5492
5505
|
return {
|
package/dist/index.mjs
CHANGED
|
@@ -913,6 +913,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
913
913
|
if (!stats || stats.isDirectory()) {
|
|
914
914
|
throw new FileNotFoundError(virtualPath);
|
|
915
915
|
}
|
|
916
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
917
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
918
|
+
}
|
|
916
919
|
this.validateExtension(newName);
|
|
917
920
|
const parentDir = path2.dirname(fullPath);
|
|
918
921
|
const newFullPath = path2.join(parentDir, newName);
|
|
@@ -927,7 +930,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
927
930
|
const item = await this.statToItem(newFullPath, newStats);
|
|
928
931
|
return this.successResult(item);
|
|
929
932
|
} catch (error) {
|
|
930
|
-
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError) {
|
|
933
|
+
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError || error instanceof InvalidPathError) {
|
|
931
934
|
return this.errorResult(error.message);
|
|
932
935
|
}
|
|
933
936
|
return this.errorResult(`Failed to rename file: ${error.message}`);
|
|
@@ -941,6 +944,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
941
944
|
if (!stats || !stats.isDirectory()) {
|
|
942
945
|
throw new DirectoryNotFoundError(virtualPath);
|
|
943
946
|
}
|
|
947
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
948
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
949
|
+
}
|
|
944
950
|
const parentDir = path2.dirname(fullPath);
|
|
945
951
|
const newFullPath = path2.join(parentDir, newName);
|
|
946
952
|
if (!options.overwrite) {
|
|
@@ -954,7 +960,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
954
960
|
const item = await this.statToItem(newFullPath, newStats);
|
|
955
961
|
return this.successResult(item);
|
|
956
962
|
} catch (error) {
|
|
957
|
-
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError) {
|
|
963
|
+
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError || error instanceof InvalidPathError) {
|
|
958
964
|
return this.errorResult(error.message);
|
|
959
965
|
}
|
|
960
966
|
return this.errorResult(`Failed to rename folder: ${error.message}`);
|
|
@@ -1096,9 +1102,12 @@ var GoogleDriveAuth = class {
|
|
|
1096
1102
|
*/
|
|
1097
1103
|
async exchangeCodeForTokens(code) {
|
|
1098
1104
|
const { tokens } = await this.oauth2Client.getToken(code);
|
|
1105
|
+
if (!tokens.access_token) {
|
|
1106
|
+
throw new Error("No access token received from Google OAuth");
|
|
1107
|
+
}
|
|
1099
1108
|
this.tokens = {
|
|
1100
1109
|
accessToken: tokens.access_token,
|
|
1101
|
-
refreshToken: tokens.refresh_token,
|
|
1110
|
+
refreshToken: tokens.refresh_token ?? this.tokens?.refreshToken ?? "",
|
|
1102
1111
|
expiryDate: tokens.expiry_date || void 0,
|
|
1103
1112
|
scope: tokens.scope || void 0
|
|
1104
1113
|
};
|
|
@@ -1283,7 +1292,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1283
1292
|
const segments = normalized.split("/").filter(Boolean);
|
|
1284
1293
|
let currentParentId = this.rootFolderId;
|
|
1285
1294
|
for (const segment of segments) {
|
|
1286
|
-
const
|
|
1295
|
+
const escapedSegment = segment.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1296
|
+
const query = `name='${escapedSegment}' and '${currentParentId}' in parents and trashed=false`;
|
|
1287
1297
|
const response = await this.drive.files.list({
|
|
1288
1298
|
q: query,
|
|
1289
1299
|
fields: "files(id, name, mimeType)",
|
|
@@ -1351,7 +1361,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1351
1361
|
if (!parentId) {
|
|
1352
1362
|
throw new DirectoryNotFoundError(parentPath);
|
|
1353
1363
|
}
|
|
1354
|
-
const
|
|
1364
|
+
const escapedFolderName = folderName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1365
|
+
const existingQuery = `name='${escapedFolderName}' and '${parentId}' in parents and mimeType='${FOLDER_MIME_TYPE}' and trashed=false`;
|
|
1355
1366
|
const existingResponse = await this.drive.files.list({
|
|
1356
1367
|
q: existingQuery,
|
|
1357
1368
|
fields: "files(id)",
|
|
@@ -1414,7 +1425,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1414
1425
|
throw new DirectoryNotFoundError(parentPath);
|
|
1415
1426
|
}
|
|
1416
1427
|
if (!options.overwrite) {
|
|
1417
|
-
const
|
|
1428
|
+
const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1429
|
+
const existingQuery = `name='${escapedFileName}' and '${parentId}' in parents and trashed=false`;
|
|
1418
1430
|
const existingResponse = await this.drive.files.list({
|
|
1419
1431
|
q: existingQuery,
|
|
1420
1432
|
fields: "files(id)",
|
|
@@ -2369,20 +2381,12 @@ var FileManager = class {
|
|
|
2369
2381
|
this.initialized = true;
|
|
2370
2382
|
}
|
|
2371
2383
|
/**
|
|
2372
|
-
*
|
|
2384
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
2373
2385
|
*/
|
|
2374
|
-
initializeSync(
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
if (config) {
|
|
2379
|
-
this.config = config;
|
|
2380
|
-
} else if (this.options.config) {
|
|
2381
|
-
this.config = this.options.config;
|
|
2382
|
-
} else {
|
|
2383
|
-
this.config = loadConfig(this.options.configPath);
|
|
2384
|
-
}
|
|
2385
|
-
this.module = createModule(this.config.provider);
|
|
2386
|
+
initializeSync(_config) {
|
|
2387
|
+
throw new Error(
|
|
2388
|
+
"initializeSync is not supported. Storage modules require async initialization. Use initialize() instead."
|
|
2389
|
+
);
|
|
2386
2390
|
}
|
|
2387
2391
|
/**
|
|
2388
2392
|
* Check if manager is initialized
|
|
@@ -3824,7 +3828,7 @@ var TrackedFileManager = class extends FileManager {
|
|
|
3824
3828
|
}
|
|
3825
3829
|
let buffer;
|
|
3826
3830
|
if (typeof downloadResult.data === "string") {
|
|
3827
|
-
|
|
3831
|
+
return null;
|
|
3828
3832
|
} else if (downloadResult.data instanceof Buffer) {
|
|
3829
3833
|
buffer = downloadResult.data;
|
|
3830
3834
|
} else {
|
|
@@ -5139,7 +5143,13 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
5139
5143
|
"content_tag"
|
|
5140
5144
|
]
|
|
5141
5145
|
};
|
|
5146
|
+
function validateTableName(tableName) {
|
|
5147
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(tableName)) {
|
|
5148
|
+
throw new Error(`Invalid table name: ${tableName}`);
|
|
5149
|
+
}
|
|
5150
|
+
}
|
|
5142
5151
|
function getSchemaForTable(tableName, dbType) {
|
|
5152
|
+
validateTableName(tableName);
|
|
5143
5153
|
const schema = HAZO_FILES_TABLE_SCHEMA[dbType];
|
|
5144
5154
|
const defaultName = HAZO_FILES_TABLE_SCHEMA.tableName;
|
|
5145
5155
|
return {
|
|
@@ -5209,6 +5219,7 @@ WHERE file_refs IS NULL OR ref_count IS NULL OR status IS NULL`
|
|
|
5209
5219
|
]
|
|
5210
5220
|
};
|
|
5211
5221
|
function getMigrationForTable(tableName, dbType) {
|
|
5222
|
+
validateTableName(tableName);
|
|
5212
5223
|
const migration = HAZO_FILES_MIGRATION_V2[dbType];
|
|
5213
5224
|
const defaultName = HAZO_FILES_MIGRATION_V2.tableName;
|
|
5214
5225
|
return {
|
|
@@ -5268,6 +5279,7 @@ var HAZO_FILES_NAMING_TABLE_SCHEMA = {
|
|
|
5268
5279
|
]
|
|
5269
5280
|
};
|
|
5270
5281
|
function getNamingSchemaForTable(tableName, dbType) {
|
|
5282
|
+
validateTableName(tableName);
|
|
5271
5283
|
const schema = HAZO_FILES_NAMING_TABLE_SCHEMA[dbType];
|
|
5272
5284
|
const defaultName = HAZO_FILES_NAMING_TABLE_SCHEMA.tableName;
|
|
5273
5285
|
return {
|
|
@@ -5304,6 +5316,7 @@ var HAZO_FILES_MIGRATION_V3 = {
|
|
|
5304
5316
|
]
|
|
5305
5317
|
};
|
|
5306
5318
|
function getMigrationV3ForTable(tableName, dbType) {
|
|
5319
|
+
validateTableName(tableName);
|
|
5307
5320
|
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5308
5321
|
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5309
5322
|
return {
|
package/dist/server/index.d.mts
CHANGED
|
@@ -685,9 +685,9 @@ declare class FileManager {
|
|
|
685
685
|
*/
|
|
686
686
|
initialize(config?: HazoFilesConfig): Promise<void>;
|
|
687
687
|
/**
|
|
688
|
-
*
|
|
688
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
689
689
|
*/
|
|
690
|
-
initializeSync(
|
|
690
|
+
initializeSync(_config?: HazoFilesConfig): void;
|
|
691
691
|
/**
|
|
692
692
|
* Check if manager is initialized
|
|
693
693
|
*/
|
package/dist/server/index.d.ts
CHANGED
|
@@ -685,9 +685,9 @@ declare class FileManager {
|
|
|
685
685
|
*/
|
|
686
686
|
initialize(config?: HazoFilesConfig): Promise<void>;
|
|
687
687
|
/**
|
|
688
|
-
*
|
|
688
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
689
689
|
*/
|
|
690
|
-
initializeSync(
|
|
690
|
+
initializeSync(_config?: HazoFilesConfig): void;
|
|
691
691
|
/**
|
|
692
692
|
* Check if manager is initialized
|
|
693
693
|
*/
|
package/dist/server/index.js
CHANGED
|
@@ -1098,6 +1098,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1098
1098
|
if (!stats || stats.isDirectory()) {
|
|
1099
1099
|
throw new FileNotFoundError(virtualPath);
|
|
1100
1100
|
}
|
|
1101
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
1102
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
1103
|
+
}
|
|
1101
1104
|
this.validateExtension(newName);
|
|
1102
1105
|
const parentDir = path2.dirname(fullPath);
|
|
1103
1106
|
const newFullPath = path2.join(parentDir, newName);
|
|
@@ -1112,7 +1115,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1112
1115
|
const item = await this.statToItem(newFullPath, newStats);
|
|
1113
1116
|
return this.successResult(item);
|
|
1114
1117
|
} catch (error) {
|
|
1115
|
-
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError) {
|
|
1118
|
+
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError || error instanceof InvalidPathError) {
|
|
1116
1119
|
return this.errorResult(error.message);
|
|
1117
1120
|
}
|
|
1118
1121
|
return this.errorResult(`Failed to rename file: ${error.message}`);
|
|
@@ -1126,6 +1129,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1126
1129
|
if (!stats || !stats.isDirectory()) {
|
|
1127
1130
|
throw new DirectoryNotFoundError(virtualPath);
|
|
1128
1131
|
}
|
|
1132
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
1133
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
1134
|
+
}
|
|
1129
1135
|
const parentDir = path2.dirname(fullPath);
|
|
1130
1136
|
const newFullPath = path2.join(parentDir, newName);
|
|
1131
1137
|
if (!options.overwrite) {
|
|
@@ -1139,7 +1145,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
1139
1145
|
const item = await this.statToItem(newFullPath, newStats);
|
|
1140
1146
|
return this.successResult(item);
|
|
1141
1147
|
} catch (error) {
|
|
1142
|
-
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError) {
|
|
1148
|
+
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError || error instanceof InvalidPathError) {
|
|
1143
1149
|
return this.errorResult(error.message);
|
|
1144
1150
|
}
|
|
1145
1151
|
return this.errorResult(`Failed to rename folder: ${error.message}`);
|
|
@@ -1281,9 +1287,12 @@ var GoogleDriveAuth = class {
|
|
|
1281
1287
|
*/
|
|
1282
1288
|
async exchangeCodeForTokens(code) {
|
|
1283
1289
|
const { tokens } = await this.oauth2Client.getToken(code);
|
|
1290
|
+
if (!tokens.access_token) {
|
|
1291
|
+
throw new Error("No access token received from Google OAuth");
|
|
1292
|
+
}
|
|
1284
1293
|
this.tokens = {
|
|
1285
1294
|
accessToken: tokens.access_token,
|
|
1286
|
-
refreshToken: tokens.refresh_token,
|
|
1295
|
+
refreshToken: tokens.refresh_token ?? this.tokens?.refreshToken ?? "",
|
|
1287
1296
|
expiryDate: tokens.expiry_date || void 0,
|
|
1288
1297
|
scope: tokens.scope || void 0
|
|
1289
1298
|
};
|
|
@@ -1468,7 +1477,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1468
1477
|
const segments = normalized.split("/").filter(Boolean);
|
|
1469
1478
|
let currentParentId = this.rootFolderId;
|
|
1470
1479
|
for (const segment of segments) {
|
|
1471
|
-
const
|
|
1480
|
+
const escapedSegment = segment.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1481
|
+
const query = `name='${escapedSegment}' and '${currentParentId}' in parents and trashed=false`;
|
|
1472
1482
|
const response = await this.drive.files.list({
|
|
1473
1483
|
q: query,
|
|
1474
1484
|
fields: "files(id, name, mimeType)",
|
|
@@ -1536,7 +1546,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1536
1546
|
if (!parentId) {
|
|
1537
1547
|
throw new DirectoryNotFoundError(parentPath);
|
|
1538
1548
|
}
|
|
1539
|
-
const
|
|
1549
|
+
const escapedFolderName = folderName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1550
|
+
const existingQuery = `name='${escapedFolderName}' and '${parentId}' in parents and mimeType='${FOLDER_MIME_TYPE}' and trashed=false`;
|
|
1540
1551
|
const existingResponse = await this.drive.files.list({
|
|
1541
1552
|
q: existingQuery,
|
|
1542
1553
|
fields: "files(id)",
|
|
@@ -1599,7 +1610,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1599
1610
|
throw new DirectoryNotFoundError(parentPath);
|
|
1600
1611
|
}
|
|
1601
1612
|
if (!options.overwrite) {
|
|
1602
|
-
const
|
|
1613
|
+
const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1614
|
+
const existingQuery = `name='${escapedFileName}' and '${parentId}' in parents and trashed=false`;
|
|
1603
1615
|
const existingResponse = await this.drive.files.list({
|
|
1604
1616
|
q: existingQuery,
|
|
1605
1617
|
fields: "files(id)",
|
|
@@ -2554,20 +2566,12 @@ var FileManager = class {
|
|
|
2554
2566
|
this.initialized = true;
|
|
2555
2567
|
}
|
|
2556
2568
|
/**
|
|
2557
|
-
*
|
|
2569
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
2558
2570
|
*/
|
|
2559
|
-
initializeSync(
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
if (config) {
|
|
2564
|
-
this.config = config;
|
|
2565
|
-
} else if (this.options.config) {
|
|
2566
|
-
this.config = this.options.config;
|
|
2567
|
-
} else {
|
|
2568
|
-
this.config = loadConfig(this.options.configPath);
|
|
2569
|
-
}
|
|
2570
|
-
this.module = createModule(this.config.provider);
|
|
2571
|
+
initializeSync(_config) {
|
|
2572
|
+
throw new Error(
|
|
2573
|
+
"initializeSync is not supported. Storage modules require async initialization. Use initialize() instead."
|
|
2574
|
+
);
|
|
2571
2575
|
}
|
|
2572
2576
|
/**
|
|
2573
2577
|
* Check if manager is initialized
|
|
@@ -4009,7 +4013,7 @@ var TrackedFileManager = class extends FileManager {
|
|
|
4009
4013
|
}
|
|
4010
4014
|
let buffer;
|
|
4011
4015
|
if (typeof downloadResult.data === "string") {
|
|
4012
|
-
|
|
4016
|
+
return null;
|
|
4013
4017
|
} else if (downloadResult.data instanceof Buffer) {
|
|
4014
4018
|
buffer = downloadResult.data;
|
|
4015
4019
|
} else {
|
|
@@ -5392,7 +5396,13 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
5392
5396
|
"content_tag"
|
|
5393
5397
|
]
|
|
5394
5398
|
};
|
|
5399
|
+
function validateTableName(tableName) {
|
|
5400
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(tableName)) {
|
|
5401
|
+
throw new Error(`Invalid table name: ${tableName}`);
|
|
5402
|
+
}
|
|
5403
|
+
}
|
|
5395
5404
|
function getSchemaForTable(tableName, dbType) {
|
|
5405
|
+
validateTableName(tableName);
|
|
5396
5406
|
const schema = HAZO_FILES_TABLE_SCHEMA[dbType];
|
|
5397
5407
|
const defaultName = HAZO_FILES_TABLE_SCHEMA.tableName;
|
|
5398
5408
|
return {
|
|
@@ -5462,6 +5472,7 @@ WHERE file_refs IS NULL OR ref_count IS NULL OR status IS NULL`
|
|
|
5462
5472
|
]
|
|
5463
5473
|
};
|
|
5464
5474
|
function getMigrationForTable(tableName, dbType) {
|
|
5475
|
+
validateTableName(tableName);
|
|
5465
5476
|
const migration = HAZO_FILES_MIGRATION_V2[dbType];
|
|
5466
5477
|
const defaultName = HAZO_FILES_MIGRATION_V2.tableName;
|
|
5467
5478
|
return {
|
|
@@ -5521,6 +5532,7 @@ var HAZO_FILES_NAMING_TABLE_SCHEMA = {
|
|
|
5521
5532
|
]
|
|
5522
5533
|
};
|
|
5523
5534
|
function getNamingSchemaForTable(tableName, dbType) {
|
|
5535
|
+
validateTableName(tableName);
|
|
5524
5536
|
const schema = HAZO_FILES_NAMING_TABLE_SCHEMA[dbType];
|
|
5525
5537
|
const defaultName = HAZO_FILES_NAMING_TABLE_SCHEMA.tableName;
|
|
5526
5538
|
return {
|
|
@@ -5557,6 +5569,7 @@ var HAZO_FILES_MIGRATION_V3 = {
|
|
|
5557
5569
|
]
|
|
5558
5570
|
};
|
|
5559
5571
|
function getMigrationV3ForTable(tableName, dbType) {
|
|
5572
|
+
validateTableName(tableName);
|
|
5560
5573
|
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5561
5574
|
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5562
5575
|
return {
|
|
@@ -5606,6 +5619,9 @@ async function migrateToV3(executor, dbType, tableName) {
|
|
|
5606
5619
|
try {
|
|
5607
5620
|
require("server-only");
|
|
5608
5621
|
} catch {
|
|
5622
|
+
console.warn(
|
|
5623
|
+
'[hazo_files/server] "server-only" package not installed. Install it to prevent this module from being bundled in client code.'
|
|
5624
|
+
);
|
|
5609
5625
|
}
|
|
5610
5626
|
// Annotate the CommonJS export names for ESM import in node:
|
|
5611
5627
|
0 && (module.exports = {
|
package/dist/server/index.mjs
CHANGED
|
@@ -920,6 +920,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
920
920
|
if (!stats || stats.isDirectory()) {
|
|
921
921
|
throw new FileNotFoundError(virtualPath);
|
|
922
922
|
}
|
|
923
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
924
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
925
|
+
}
|
|
923
926
|
this.validateExtension(newName);
|
|
924
927
|
const parentDir = path2.dirname(fullPath);
|
|
925
928
|
const newFullPath = path2.join(parentDir, newName);
|
|
@@ -934,7 +937,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
934
937
|
const item = await this.statToItem(newFullPath, newStats);
|
|
935
938
|
return this.successResult(item);
|
|
936
939
|
} catch (error) {
|
|
937
|
-
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError) {
|
|
940
|
+
if (error instanceof FileNotFoundError || error instanceof FileExistsError || error instanceof InvalidExtensionError || error instanceof InvalidPathError) {
|
|
938
941
|
return this.errorResult(error.message);
|
|
939
942
|
}
|
|
940
943
|
return this.errorResult(`Failed to rename file: ${error.message}`);
|
|
@@ -948,6 +951,9 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
948
951
|
if (!stats || !stats.isDirectory()) {
|
|
949
952
|
throw new DirectoryNotFoundError(virtualPath);
|
|
950
953
|
}
|
|
954
|
+
if (newName.includes("/") || newName.includes("\\") || newName.includes("..")) {
|
|
955
|
+
throw new InvalidPathError(newName, 'Name must not contain path separators or ".."');
|
|
956
|
+
}
|
|
951
957
|
const parentDir = path2.dirname(fullPath);
|
|
952
958
|
const newFullPath = path2.join(parentDir, newName);
|
|
953
959
|
if (!options.overwrite) {
|
|
@@ -961,7 +967,7 @@ var LocalStorageModule = class extends BaseStorageModule {
|
|
|
961
967
|
const item = await this.statToItem(newFullPath, newStats);
|
|
962
968
|
return this.successResult(item);
|
|
963
969
|
} catch (error) {
|
|
964
|
-
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError) {
|
|
970
|
+
if (error instanceof DirectoryNotFoundError || error instanceof DirectoryExistsError || error instanceof InvalidPathError) {
|
|
965
971
|
return this.errorResult(error.message);
|
|
966
972
|
}
|
|
967
973
|
return this.errorResult(`Failed to rename folder: ${error.message}`);
|
|
@@ -1103,9 +1109,12 @@ var GoogleDriveAuth = class {
|
|
|
1103
1109
|
*/
|
|
1104
1110
|
async exchangeCodeForTokens(code) {
|
|
1105
1111
|
const { tokens } = await this.oauth2Client.getToken(code);
|
|
1112
|
+
if (!tokens.access_token) {
|
|
1113
|
+
throw new Error("No access token received from Google OAuth");
|
|
1114
|
+
}
|
|
1106
1115
|
this.tokens = {
|
|
1107
1116
|
accessToken: tokens.access_token,
|
|
1108
|
-
refreshToken: tokens.refresh_token,
|
|
1117
|
+
refreshToken: tokens.refresh_token ?? this.tokens?.refreshToken ?? "",
|
|
1109
1118
|
expiryDate: tokens.expiry_date || void 0,
|
|
1110
1119
|
scope: tokens.scope || void 0
|
|
1111
1120
|
};
|
|
@@ -1290,7 +1299,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1290
1299
|
const segments = normalized.split("/").filter(Boolean);
|
|
1291
1300
|
let currentParentId = this.rootFolderId;
|
|
1292
1301
|
for (const segment of segments) {
|
|
1293
|
-
const
|
|
1302
|
+
const escapedSegment = segment.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1303
|
+
const query = `name='${escapedSegment}' and '${currentParentId}' in parents and trashed=false`;
|
|
1294
1304
|
const response = await this.drive.files.list({
|
|
1295
1305
|
q: query,
|
|
1296
1306
|
fields: "files(id, name, mimeType)",
|
|
@@ -1358,7 +1368,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1358
1368
|
if (!parentId) {
|
|
1359
1369
|
throw new DirectoryNotFoundError(parentPath);
|
|
1360
1370
|
}
|
|
1361
|
-
const
|
|
1371
|
+
const escapedFolderName = folderName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1372
|
+
const existingQuery = `name='${escapedFolderName}' and '${parentId}' in parents and mimeType='${FOLDER_MIME_TYPE}' and trashed=false`;
|
|
1362
1373
|
const existingResponse = await this.drive.files.list({
|
|
1363
1374
|
q: existingQuery,
|
|
1364
1375
|
fields: "files(id)",
|
|
@@ -1421,7 +1432,8 @@ var GoogleDriveModule = class extends BaseStorageModule {
|
|
|
1421
1432
|
throw new DirectoryNotFoundError(parentPath);
|
|
1422
1433
|
}
|
|
1423
1434
|
if (!options.overwrite) {
|
|
1424
|
-
const
|
|
1435
|
+
const escapedFileName = fileName.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1436
|
+
const existingQuery = `name='${escapedFileName}' and '${parentId}' in parents and trashed=false`;
|
|
1425
1437
|
const existingResponse = await this.drive.files.list({
|
|
1426
1438
|
q: existingQuery,
|
|
1427
1439
|
fields: "files(id)",
|
|
@@ -2376,20 +2388,12 @@ var FileManager = class {
|
|
|
2376
2388
|
this.initialized = true;
|
|
2377
2389
|
}
|
|
2378
2390
|
/**
|
|
2379
|
-
*
|
|
2391
|
+
* @deprecated Storage modules require async initialization. Use initialize() instead.
|
|
2380
2392
|
*/
|
|
2381
|
-
initializeSync(
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
if (config) {
|
|
2386
|
-
this.config = config;
|
|
2387
|
-
} else if (this.options.config) {
|
|
2388
|
-
this.config = this.options.config;
|
|
2389
|
-
} else {
|
|
2390
|
-
this.config = loadConfig(this.options.configPath);
|
|
2391
|
-
}
|
|
2392
|
-
this.module = createModule(this.config.provider);
|
|
2393
|
+
initializeSync(_config) {
|
|
2394
|
+
throw new Error(
|
|
2395
|
+
"initializeSync is not supported. Storage modules require async initialization. Use initialize() instead."
|
|
2396
|
+
);
|
|
2393
2397
|
}
|
|
2394
2398
|
/**
|
|
2395
2399
|
* Check if manager is initialized
|
|
@@ -3831,7 +3835,7 @@ var TrackedFileManager = class extends FileManager {
|
|
|
3831
3835
|
}
|
|
3832
3836
|
let buffer;
|
|
3833
3837
|
if (typeof downloadResult.data === "string") {
|
|
3834
|
-
|
|
3838
|
+
return null;
|
|
3835
3839
|
} else if (downloadResult.data instanceof Buffer) {
|
|
3836
3840
|
buffer = downloadResult.data;
|
|
3837
3841
|
} else {
|
|
@@ -5214,7 +5218,13 @@ var HAZO_FILES_TABLE_SCHEMA = {
|
|
|
5214
5218
|
"content_tag"
|
|
5215
5219
|
]
|
|
5216
5220
|
};
|
|
5221
|
+
function validateTableName(tableName) {
|
|
5222
|
+
if (!/^[a-z_][a-z0-9_]*$/i.test(tableName)) {
|
|
5223
|
+
throw new Error(`Invalid table name: ${tableName}`);
|
|
5224
|
+
}
|
|
5225
|
+
}
|
|
5217
5226
|
function getSchemaForTable(tableName, dbType) {
|
|
5227
|
+
validateTableName(tableName);
|
|
5218
5228
|
const schema = HAZO_FILES_TABLE_SCHEMA[dbType];
|
|
5219
5229
|
const defaultName = HAZO_FILES_TABLE_SCHEMA.tableName;
|
|
5220
5230
|
return {
|
|
@@ -5284,6 +5294,7 @@ WHERE file_refs IS NULL OR ref_count IS NULL OR status IS NULL`
|
|
|
5284
5294
|
]
|
|
5285
5295
|
};
|
|
5286
5296
|
function getMigrationForTable(tableName, dbType) {
|
|
5297
|
+
validateTableName(tableName);
|
|
5287
5298
|
const migration = HAZO_FILES_MIGRATION_V2[dbType];
|
|
5288
5299
|
const defaultName = HAZO_FILES_MIGRATION_V2.tableName;
|
|
5289
5300
|
return {
|
|
@@ -5343,6 +5354,7 @@ var HAZO_FILES_NAMING_TABLE_SCHEMA = {
|
|
|
5343
5354
|
]
|
|
5344
5355
|
};
|
|
5345
5356
|
function getNamingSchemaForTable(tableName, dbType) {
|
|
5357
|
+
validateTableName(tableName);
|
|
5346
5358
|
const schema = HAZO_FILES_NAMING_TABLE_SCHEMA[dbType];
|
|
5347
5359
|
const defaultName = HAZO_FILES_NAMING_TABLE_SCHEMA.tableName;
|
|
5348
5360
|
return {
|
|
@@ -5379,6 +5391,7 @@ var HAZO_FILES_MIGRATION_V3 = {
|
|
|
5379
5391
|
]
|
|
5380
5392
|
};
|
|
5381
5393
|
function getMigrationV3ForTable(tableName, dbType) {
|
|
5394
|
+
validateTableName(tableName);
|
|
5382
5395
|
const migration = HAZO_FILES_MIGRATION_V3[dbType];
|
|
5383
5396
|
const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
|
|
5384
5397
|
return {
|
|
@@ -5428,6 +5441,9 @@ async function migrateToV3(executor, dbType, tableName) {
|
|
|
5428
5441
|
try {
|
|
5429
5442
|
__require("server-only");
|
|
5430
5443
|
} catch {
|
|
5444
|
+
console.warn(
|
|
5445
|
+
'[hazo_files/server] "server-only" package not installed. Install it to prevent this module from being bundled in client code.'
|
|
5446
|
+
);
|
|
5431
5447
|
}
|
|
5432
5448
|
export {
|
|
5433
5449
|
ALL_SYSTEM_VARIABLES,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
-- hazo_files initial database schema (V1)
|
|
2
|
+
-- Supports both PostgreSQL and SQLite
|
|
3
|
+
--
|
|
4
|
+
-- This creates the main file metadata tracking table.
|
|
5
|
+
-- Use hazo_files HAZO_FILES_TABLE_SCHEMA export for programmatic access.
|
|
6
|
+
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- SQLite version
|
|
9
|
+
-- ============================================
|
|
10
|
+
-- CREATE TABLE IF NOT EXISTS hazo_files (
|
|
11
|
+
-- id TEXT PRIMARY KEY,
|
|
12
|
+
-- filename TEXT NOT NULL,
|
|
13
|
+
-- file_type TEXT NOT NULL,
|
|
14
|
+
-- file_data TEXT DEFAULT '{}',
|
|
15
|
+
-- created_at TEXT NOT NULL,
|
|
16
|
+
-- changed_at TEXT NOT NULL,
|
|
17
|
+
-- file_path TEXT NOT NULL,
|
|
18
|
+
-- storage_type TEXT NOT NULL,
|
|
19
|
+
-- file_hash TEXT,
|
|
20
|
+
-- file_size INTEGER,
|
|
21
|
+
-- file_changed_at TEXT
|
|
22
|
+
-- );
|
|
23
|
+
--
|
|
24
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path);
|
|
25
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_storage ON hazo_files (storage_type);
|
|
26
|
+
-- CREATE UNIQUE INDEX IF NOT EXISTS idx_hazo_files_path_storage ON hazo_files (file_path, storage_type);
|
|
27
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_hash ON hazo_files (file_hash);
|
|
28
|
+
|
|
29
|
+
-- ============================================
|
|
30
|
+
-- PostgreSQL version (active)
|
|
31
|
+
-- ============================================
|
|
32
|
+
CREATE TABLE IF NOT EXISTS hazo_files (
|
|
33
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
34
|
+
filename TEXT NOT NULL,
|
|
35
|
+
file_type TEXT NOT NULL,
|
|
36
|
+
file_data TEXT DEFAULT '{}',
|
|
37
|
+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
38
|
+
changed_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
|
|
39
|
+
file_path TEXT NOT NULL,
|
|
40
|
+
storage_type TEXT NOT NULL,
|
|
41
|
+
file_hash TEXT,
|
|
42
|
+
file_size BIGINT,
|
|
43
|
+
file_changed_at TIMESTAMP WITH TIME ZONE
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path);
|
|
47
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_storage ON hazo_files (storage_type);
|
|
48
|
+
CREATE UNIQUE INDEX IF NOT EXISTS idx_hazo_files_path_storage ON hazo_files (file_path, storage_type);
|
|
49
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_hash ON hazo_files (file_hash);
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
-- hazo_files V2 migration - Reference Tracking
|
|
2
|
+
-- Adds columns for file references, soft-delete, scoping, and provenance.
|
|
3
|
+
-- Idempotent - safe to run multiple times.
|
|
4
|
+
|
|
5
|
+
-- ============================================
|
|
6
|
+
-- SQLite version
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- ALTER TABLE hazo_files ADD COLUMN file_refs TEXT DEFAULT '[]';
|
|
9
|
+
-- ALTER TABLE hazo_files ADD COLUMN ref_count INTEGER DEFAULT 0;
|
|
10
|
+
-- ALTER TABLE hazo_files ADD COLUMN status TEXT DEFAULT 'active';
|
|
11
|
+
-- ALTER TABLE hazo_files ADD COLUMN scope_id TEXT;
|
|
12
|
+
-- ALTER TABLE hazo_files ADD COLUMN uploaded_by TEXT;
|
|
13
|
+
-- ALTER TABLE hazo_files ADD COLUMN storage_verified_at TEXT;
|
|
14
|
+
-- ALTER TABLE hazo_files ADD COLUMN deleted_at TEXT;
|
|
15
|
+
-- ALTER TABLE hazo_files ADD COLUMN original_filename TEXT;
|
|
16
|
+
--
|
|
17
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status);
|
|
18
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id);
|
|
19
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count);
|
|
20
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at);
|
|
21
|
+
--
|
|
22
|
+
-- Backfill:
|
|
23
|
+
-- UPDATE hazo_files SET
|
|
24
|
+
-- file_refs = COALESCE(file_refs, '[]'),
|
|
25
|
+
-- ref_count = COALESCE(ref_count, 0),
|
|
26
|
+
-- status = COALESCE(status, 'active')
|
|
27
|
+
-- WHERE file_refs IS NULL OR ref_count IS NULL OR status IS NULL;
|
|
28
|
+
|
|
29
|
+
-- ============================================
|
|
30
|
+
-- PostgreSQL version (active)
|
|
31
|
+
-- ============================================
|
|
32
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS file_refs TEXT DEFAULT '[]';
|
|
33
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS ref_count INTEGER DEFAULT 0;
|
|
34
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS status TEXT DEFAULT 'active';
|
|
35
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS scope_id UUID;
|
|
36
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS uploaded_by UUID;
|
|
37
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS storage_verified_at TIMESTAMP WITH TIME ZONE;
|
|
38
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS deleted_at TIMESTAMP WITH TIME ZONE;
|
|
39
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS original_filename TEXT;
|
|
40
|
+
|
|
41
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status);
|
|
42
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id);
|
|
43
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count);
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at);
|
|
45
|
+
|
|
46
|
+
-- Backfill defaults for existing records
|
|
47
|
+
UPDATE hazo_files SET
|
|
48
|
+
file_refs = COALESCE(file_refs, '[]'),
|
|
49
|
+
ref_count = COALESCE(ref_count, 0),
|
|
50
|
+
status = COALESCE(status, 'active')
|
|
51
|
+
WHERE file_refs IS NULL OR ref_count IS NULL OR status IS NULL;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
-- hazo_files V3 migration - Content Tagging
|
|
2
|
+
-- Adds content_tag column for LLM-based document classification.
|
|
3
|
+
-- Idempotent - safe to run multiple times.
|
|
4
|
+
|
|
5
|
+
-- ============================================
|
|
6
|
+
-- SQLite version
|
|
7
|
+
-- ============================================
|
|
8
|
+
-- ALTER TABLE hazo_files ADD COLUMN content_tag TEXT;
|
|
9
|
+
-- CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag);
|
|
10
|
+
|
|
11
|
+
-- ============================================
|
|
12
|
+
-- PostgreSQL version (active)
|
|
13
|
+
-- ============================================
|
|
14
|
+
ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT;
|
|
15
|
+
CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hazo_files",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"description": "File management including integration to cloud files",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,7 +25,8 @@
|
|
|
25
25
|
"files": [
|
|
26
26
|
"dist",
|
|
27
27
|
"docs",
|
|
28
|
-
"
|
|
28
|
+
"config",
|
|
29
|
+
"migrations",
|
|
29
30
|
"README.md",
|
|
30
31
|
".env.example"
|
|
31
32
|
],
|
|
@@ -65,10 +66,7 @@
|
|
|
65
66
|
},
|
|
66
67
|
"homepage": "https://github.com/pub12/hazo_files#readme",
|
|
67
68
|
"dependencies": {
|
|
68
|
-
"
|
|
69
|
-
"googleapis": "^140.0.1",
|
|
70
|
-
"ini": "^4.1.3",
|
|
71
|
-
"xxhash-wasm": "^1.1.0"
|
|
69
|
+
"ini": "^4.1.3"
|
|
72
70
|
},
|
|
73
71
|
"devDependencies": {
|
|
74
72
|
"@dnd-kit/core": "^6.3.1",
|
|
@@ -78,23 +76,29 @@
|
|
|
78
76
|
"@types/node": "^20.10.0",
|
|
79
77
|
"@types/react": "^18.2.45",
|
|
80
78
|
"@types/react-dom": "^18.2.18",
|
|
79
|
+
"dropbox": "^10.34.0",
|
|
80
|
+
"googleapis": "^140.0.1",
|
|
81
81
|
"hazo_llm_api": "^1.2.7",
|
|
82
82
|
"react": "^18.2.0",
|
|
83
83
|
"react-dom": "^18.2.0",
|
|
84
84
|
"tsup": "^8.0.1",
|
|
85
85
|
"typescript": "^5.3.3",
|
|
86
|
-
"vitest": "^4.0.15"
|
|
86
|
+
"vitest": "^4.0.15",
|
|
87
|
+
"xxhash-wasm": "^1.1.0"
|
|
87
88
|
},
|
|
88
89
|
"peerDependencies": {
|
|
89
90
|
"@dnd-kit/core": "^6.0.0",
|
|
90
91
|
"@dnd-kit/sortable": ">=8.0.0",
|
|
91
92
|
"@dnd-kit/utilities": "^3.0.0",
|
|
93
|
+
"dropbox": "^10.0.0",
|
|
94
|
+
"googleapis": "^140.0.0",
|
|
92
95
|
"hazo_connect": ">=2.0.0",
|
|
93
96
|
"hazo_llm_api": ">=1.0.0",
|
|
94
97
|
"hazo_logs": ">=1.0.0",
|
|
95
98
|
"react": "^18.0.0",
|
|
96
99
|
"react-dom": "^18.0.0",
|
|
97
|
-
"server-only": ">=0.0.1"
|
|
100
|
+
"server-only": ">=0.0.1",
|
|
101
|
+
"xxhash-wasm": "^1.0.0"
|
|
98
102
|
},
|
|
99
103
|
"peerDependenciesMeta": {
|
|
100
104
|
"@dnd-kit/core": {
|
|
@@ -106,6 +110,12 @@
|
|
|
106
110
|
"@dnd-kit/utilities": {
|
|
107
111
|
"optional": true
|
|
108
112
|
},
|
|
113
|
+
"dropbox": {
|
|
114
|
+
"optional": true
|
|
115
|
+
},
|
|
116
|
+
"googleapis": {
|
|
117
|
+
"optional": true
|
|
118
|
+
},
|
|
109
119
|
"hazo_connect": {
|
|
110
120
|
"optional": true
|
|
111
121
|
},
|
|
@@ -117,6 +127,9 @@
|
|
|
117
127
|
},
|
|
118
128
|
"server-only": {
|
|
119
129
|
"optional": true
|
|
130
|
+
},
|
|
131
|
+
"xxhash-wasm": {
|
|
132
|
+
"optional": true
|
|
120
133
|
}
|
|
121
134
|
}
|
|
122
135
|
}
|