hazo_files 1.4.2 → 1.4.4

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.
@@ -37,6 +37,8 @@ __export(index_exports, {
37
37
  DirectoryExistsError: () => DirectoryExistsError,
38
38
  DirectoryNotEmptyError: () => DirectoryNotEmptyError,
39
39
  DirectoryNotFoundError: () => DirectoryNotFoundError,
40
+ DropboxAuth: () => DropboxAuth,
41
+ DropboxModule: () => DropboxModule,
40
42
  FileExistsError: () => FileExistsError,
41
43
  FileManager: () => FileManager,
42
44
  FileMetadataService: () => FileMetadataService,
@@ -46,6 +48,7 @@ __export(index_exports, {
46
48
  GoogleDriveModule: () => GoogleDriveModule,
47
49
  HAZO_FILES_DEFAULT_TABLE_NAME: () => HAZO_FILES_DEFAULT_TABLE_NAME,
48
50
  HAZO_FILES_MIGRATION_V2: () => HAZO_FILES_MIGRATION_V2,
51
+ HAZO_FILES_MIGRATION_V3: () => HAZO_FILES_MIGRATION_V3,
49
52
  HAZO_FILES_NAMING_DEFAULT_TABLE_NAME: () => HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
50
53
  HAZO_FILES_NAMING_TABLE_SCHEMA: () => HAZO_FILES_NAMING_TABLE_SCHEMA,
51
54
  HAZO_FILES_TABLE_SCHEMA: () => HAZO_FILES_TABLE_SCHEMA,
@@ -73,6 +76,8 @@ __export(index_exports, {
73
76
  computeFileInfo: () => computeFileInfo,
74
77
  createAndInitializeModule: () => createAndInitializeModule,
75
78
  createBasicFileManager: () => createBasicFileManager,
79
+ createDropboxAuth: () => createDropboxAuth,
80
+ createDropboxModule: () => createDropboxModule,
76
81
  createEmptyFileDataStructure: () => createEmptyFileDataStructure,
77
82
  createEmptyNamingRuleSchema: () => createEmptyNamingRuleSchema,
78
83
  createFileItem: () => createFileItem,
@@ -117,6 +122,7 @@ __export(index_exports, {
117
122
  getFileMetadataValues: () => getFileMetadataValues,
118
123
  getMergedData: () => getMergedData,
119
124
  getMigrationForTable: () => getMigrationForTable,
125
+ getMigrationV3ForTable: () => getMigrationV3ForTable,
120
126
  getMimeType: () => getMimeType,
121
127
  getNameWithoutExtension: () => getNameWithoutExtension,
122
128
  getNamingSchemaForTable: () => getNamingSchemaForTable,
@@ -149,6 +155,7 @@ __export(index_exports, {
149
155
  loadConfig: () => loadConfig,
150
156
  loadConfigAsync: () => loadConfigAsync,
151
157
  migrateToV2: () => migrateToV2,
158
+ migrateToV3: () => migrateToV3,
152
159
  normalizePath: () => normalizePath,
153
160
  parseConfig: () => parseConfig,
154
161
  parseFileData: () => parseFileData,
@@ -204,6 +211,16 @@ function parseConfig(configContent) {
204
211
  rootFolderId: parsed.google_drive.root_folder_id || process.env.HAZO_GOOGLE_DRIVE_ROOT_FOLDER_ID
205
212
  };
206
213
  }
214
+ if (parsed.dropbox) {
215
+ config.dropbox = {
216
+ clientId: parsed.dropbox.client_id || process.env.HAZO_DROPBOX_CLIENT_ID || "",
217
+ clientSecret: parsed.dropbox.client_secret || process.env.HAZO_DROPBOX_CLIENT_SECRET || "",
218
+ redirectUri: parsed.dropbox.redirect_uri || process.env.HAZO_DROPBOX_REDIRECT_URI || "",
219
+ refreshToken: parsed.dropbox.refresh_token || process.env.HAZO_DROPBOX_REFRESH_TOKEN,
220
+ accessToken: parsed.dropbox.access_token || process.env.HAZO_DROPBOX_ACCESS_TOKEN,
221
+ rootPath: parsed.dropbox.root_path || process.env.HAZO_DROPBOX_ROOT_PATH
222
+ };
223
+ }
207
224
  return config;
208
225
  }
209
226
  function loadConfig(configPath) {
@@ -265,6 +282,18 @@ refresh_token =
265
282
  access_token =
266
283
  ; Optional: Root folder ID to use as base (empty = root of Drive)
267
284
  root_folder_id =
285
+
286
+ [dropbox]
287
+ ; Dropbox OAuth credentials
288
+ ; These can also be set via environment variables:
289
+ ; HAZO_DROPBOX_CLIENT_ID, HAZO_DROPBOX_CLIENT_SECRET, etc.
290
+ client_id =
291
+ client_secret =
292
+ redirect_uri = http://localhost:3000/api/auth/dropbox/callback
293
+ refresh_token =
294
+ access_token =
295
+ ; Optional: Root path to use as base (empty = root of Dropbox)
296
+ root_path =
268
297
  `;
269
298
  }
270
299
  async function saveConfig(config, configPath) {
@@ -291,6 +320,16 @@ async function saveConfig(config, configPath) {
291
320
  root_folder_id: config.google_drive.rootFolderId || ""
292
321
  };
293
322
  }
323
+ if (config.dropbox) {
324
+ iniConfig.dropbox = {
325
+ client_id: config.dropbox.clientId || "",
326
+ client_secret: config.dropbox.clientSecret || "",
327
+ redirect_uri: config.dropbox.redirectUri || "",
328
+ refresh_token: config.dropbox.refreshToken || "",
329
+ access_token: config.dropbox.accessToken || "",
330
+ root_path: config.dropbox.rootPath || ""
331
+ };
332
+ }
294
333
  const content = ini.stringify(iniConfig);
295
334
  await fs.promises.writeFile(resolvedPath, content, "utf-8");
296
335
  }
@@ -1828,10 +1867,641 @@ function createGoogleDriveModule() {
1828
1867
  return new GoogleDriveModule();
1829
1868
  }
1830
1869
 
1870
+ // src/modules/dropbox/index.ts
1871
+ var import_dropbox = require("dropbox");
1872
+
1873
+ // src/modules/dropbox/auth.ts
1874
+ var DROPBOX_AUTH_URL = "https://www.dropbox.com/oauth2/authorize";
1875
+ var DROPBOX_TOKEN_URL = "https://api.dropboxapi.com/oauth2/token";
1876
+ var DROPBOX_REVOKE_URL = "https://api.dropboxapi.com/2/auth/token/revoke";
1877
+ var DropboxAuth = class {
1878
+ constructor(config, callbacks = {}) {
1879
+ this.tokens = null;
1880
+ this.config = config;
1881
+ this.callbacks = callbacks;
1882
+ }
1883
+ /**
1884
+ * Generate the authorization URL for OAuth consent
1885
+ */
1886
+ getAuthUrl(state) {
1887
+ const params = new URLSearchParams({
1888
+ client_id: this.config.clientId,
1889
+ redirect_uri: this.config.redirectUri,
1890
+ response_type: "code",
1891
+ token_access_type: "offline"
1892
+ });
1893
+ if (state) {
1894
+ params.set("state", state);
1895
+ }
1896
+ return `${DROPBOX_AUTH_URL}?${params.toString()}`;
1897
+ }
1898
+ /**
1899
+ * Exchange authorization code for tokens
1900
+ */
1901
+ async exchangeCodeForTokens(code) {
1902
+ const response = await fetch(DROPBOX_TOKEN_URL, {
1903
+ method: "POST",
1904
+ headers: {
1905
+ "Content-Type": "application/x-www-form-urlencoded"
1906
+ },
1907
+ body: new URLSearchParams({
1908
+ code,
1909
+ grant_type: "authorization_code",
1910
+ client_id: this.config.clientId,
1911
+ client_secret: this.config.clientSecret,
1912
+ redirect_uri: this.config.redirectUri
1913
+ })
1914
+ });
1915
+ if (!response.ok) {
1916
+ const errorData = await response.text();
1917
+ throw new Error(`Failed to exchange code for tokens: ${errorData}`);
1918
+ }
1919
+ const data = await response.json();
1920
+ this.tokens = {
1921
+ accessToken: data.access_token,
1922
+ refreshToken: data.refresh_token,
1923
+ expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
1924
+ };
1925
+ if (this.callbacks.onTokensUpdated) {
1926
+ await this.callbacks.onTokensUpdated(this.tokens);
1927
+ }
1928
+ return this.tokens;
1929
+ }
1930
+ /**
1931
+ * Set tokens directly (e.g., from stored tokens)
1932
+ */
1933
+ async setTokens(tokens) {
1934
+ this.tokens = tokens;
1935
+ }
1936
+ /**
1937
+ * Load tokens from storage using callback
1938
+ */
1939
+ async loadStoredTokens() {
1940
+ if (!this.callbacks.getStoredTokens) {
1941
+ return false;
1942
+ }
1943
+ const tokens = await this.callbacks.getStoredTokens();
1944
+ if (tokens) {
1945
+ await this.setTokens(tokens);
1946
+ return true;
1947
+ }
1948
+ return false;
1949
+ }
1950
+ /**
1951
+ * Check if authenticated
1952
+ */
1953
+ isAuthenticated() {
1954
+ return this.tokens !== null && !!this.tokens.accessToken;
1955
+ }
1956
+ /**
1957
+ * Get current tokens
1958
+ */
1959
+ getTokens() {
1960
+ return this.tokens;
1961
+ }
1962
+ /**
1963
+ * Get current access token
1964
+ */
1965
+ getAccessToken() {
1966
+ return this.tokens?.accessToken ?? null;
1967
+ }
1968
+ /**
1969
+ * Refresh the access token
1970
+ */
1971
+ async refreshAccessToken() {
1972
+ if (!this.tokens?.refreshToken) {
1973
+ throw new Error("No refresh token available");
1974
+ }
1975
+ const response = await fetch(DROPBOX_TOKEN_URL, {
1976
+ method: "POST",
1977
+ headers: {
1978
+ "Content-Type": "application/x-www-form-urlencoded"
1979
+ },
1980
+ body: new URLSearchParams({
1981
+ grant_type: "refresh_token",
1982
+ refresh_token: this.tokens.refreshToken,
1983
+ client_id: this.config.clientId,
1984
+ client_secret: this.config.clientSecret
1985
+ })
1986
+ });
1987
+ if (!response.ok) {
1988
+ const errorData = await response.text();
1989
+ throw new Error(`Failed to refresh token: ${errorData}`);
1990
+ }
1991
+ const data = await response.json();
1992
+ this.tokens = {
1993
+ ...this.tokens,
1994
+ accessToken: data.access_token,
1995
+ expiryDate: data.expires_in ? Date.now() + data.expires_in * 1e3 : void 0
1996
+ };
1997
+ if (this.callbacks.onTokensUpdated) {
1998
+ await this.callbacks.onTokensUpdated(this.tokens);
1999
+ }
2000
+ return this.tokens;
2001
+ }
2002
+ /**
2003
+ * Revoke access (disconnect)
2004
+ */
2005
+ async revokeAccess() {
2006
+ if (this.tokens?.accessToken) {
2007
+ try {
2008
+ await fetch(DROPBOX_REVOKE_URL, {
2009
+ method: "POST",
2010
+ headers: {
2011
+ Authorization: `Bearer ${this.tokens.accessToken}`
2012
+ }
2013
+ });
2014
+ } catch {
2015
+ }
2016
+ }
2017
+ this.tokens = null;
2018
+ }
2019
+ /**
2020
+ * Check if token is expired or will expire soon
2021
+ */
2022
+ isTokenExpired(bufferSeconds = 300) {
2023
+ if (!this.tokens?.expiryDate) {
2024
+ return false;
2025
+ }
2026
+ const now = Date.now();
2027
+ const expiry = this.tokens.expiryDate;
2028
+ return now >= expiry - bufferSeconds * 1e3;
2029
+ }
2030
+ /**
2031
+ * Ensure valid access token (refresh if needed)
2032
+ */
2033
+ async ensureValidToken() {
2034
+ if (this.isTokenExpired()) {
2035
+ await this.refreshAccessToken();
2036
+ }
2037
+ }
2038
+ };
2039
+ function createDropboxAuth(config, callbacks) {
2040
+ return new DropboxAuth(config, callbacks);
2041
+ }
2042
+
2043
+ // src/modules/dropbox/index.ts
2044
+ var MAX_UPLOAD_SIZE = 150 * 1024 * 1024;
2045
+ var DropboxModule = class extends BaseStorageModule {
2046
+ constructor() {
2047
+ super(...arguments);
2048
+ this.provider = "dropbox";
2049
+ this.auth = null;
2050
+ this.dbx = null;
2051
+ this.rootPath = "";
2052
+ this.authCallbacks = {};
2053
+ }
2054
+ /**
2055
+ * Set authentication callbacks for token persistence
2056
+ */
2057
+ setAuthCallbacks(callbacks) {
2058
+ this.authCallbacks = callbacks;
2059
+ }
2060
+ async initialize(config) {
2061
+ await super.initialize(config);
2062
+ const dropboxConfig = this.getProviderConfig();
2063
+ if (!dropboxConfig.clientId || !dropboxConfig.clientSecret) {
2064
+ throw new AuthenticationError("dropbox", "Missing client ID or client secret");
2065
+ }
2066
+ this.auth = createDropboxAuth(
2067
+ {
2068
+ clientId: dropboxConfig.clientId,
2069
+ clientSecret: dropboxConfig.clientSecret,
2070
+ redirectUri: dropboxConfig.redirectUri
2071
+ },
2072
+ this.authCallbacks
2073
+ );
2074
+ if (dropboxConfig.refreshToken || dropboxConfig.accessToken) {
2075
+ await this.auth.setTokens({
2076
+ accessToken: dropboxConfig.accessToken || "",
2077
+ refreshToken: dropboxConfig.refreshToken || ""
2078
+ });
2079
+ }
2080
+ this.rootPath = dropboxConfig.rootPath || "";
2081
+ this.createDropboxClient();
2082
+ }
2083
+ /**
2084
+ * Create/recreate the Dropbox SDK client with the current access token
2085
+ */
2086
+ createDropboxClient() {
2087
+ const accessToken = this.auth?.getAccessToken();
2088
+ if (accessToken) {
2089
+ this.dbx = new import_dropbox.Dropbox({ accessToken });
2090
+ }
2091
+ }
2092
+ /**
2093
+ * Get the auth instance for OAuth flow
2094
+ */
2095
+ getAuth() {
2096
+ if (!this.auth) {
2097
+ throw new AuthenticationError("dropbox", "Module not initialized");
2098
+ }
2099
+ return this.auth;
2100
+ }
2101
+ /**
2102
+ * Check if user is authenticated
2103
+ */
2104
+ isAuthenticated() {
2105
+ return this.auth?.isAuthenticated() ?? false;
2106
+ }
2107
+ /**
2108
+ * Authenticate with provided tokens
2109
+ */
2110
+ async authenticate(tokens) {
2111
+ if (!this.auth) {
2112
+ throw new AuthenticationError("dropbox", "Module not initialized");
2113
+ }
2114
+ await this.auth.setTokens(tokens);
2115
+ this.createDropboxClient();
2116
+ }
2117
+ /**
2118
+ * Ensure authenticated before operations
2119
+ */
2120
+ async ensureAuthenticated() {
2121
+ this.ensureInitialized();
2122
+ if (!this.isAuthenticated()) {
2123
+ throw new AuthenticationError("dropbox", "Not authenticated. Please connect your Dropbox.");
2124
+ }
2125
+ await this.auth.ensureValidToken();
2126
+ this.createDropboxClient();
2127
+ }
2128
+ /**
2129
+ * Convert virtual path to Dropbox path
2130
+ * Virtual: /folder/file.txt -> Dropbox: /folder/file.txt (or /rootPath/folder/file.txt)
2131
+ * Dropbox root is empty string "", not "/"
2132
+ */
2133
+ toDropboxPath(virtualPath) {
2134
+ const normalized = this.normalizePath(virtualPath);
2135
+ if (this.rootPath) {
2136
+ if (normalized === "/") {
2137
+ return `/${this.rootPath}`;
2138
+ }
2139
+ return `/${this.rootPath}${normalized}`;
2140
+ }
2141
+ if (normalized === "/") {
2142
+ return "";
2143
+ }
2144
+ return normalized;
2145
+ }
2146
+ /**
2147
+ * Convert Dropbox metadata to FileSystemItem
2148
+ */
2149
+ metadataToItem(entry, virtualPath) {
2150
+ const isFolder2 = entry[".tag"] === "folder";
2151
+ const path3 = virtualPath || this.toVirtualPath(entry.path_display || entry.name);
2152
+ if (isFolder2) {
2153
+ return createFolderItem({
2154
+ id: entry.id,
2155
+ name: entry.name,
2156
+ path: path3,
2157
+ createdAt: /* @__PURE__ */ new Date(),
2158
+ modifiedAt: /* @__PURE__ */ new Date(),
2159
+ metadata: {
2160
+ dropboxId: entry.id,
2161
+ pathDisplay: entry.path_display
2162
+ }
2163
+ });
2164
+ }
2165
+ const fileEntry = entry;
2166
+ return createFileItem({
2167
+ id: fileEntry.id,
2168
+ name: fileEntry.name,
2169
+ path: path3,
2170
+ size: fileEntry.size,
2171
+ mimeType: getMimeType(fileEntry.name),
2172
+ createdAt: new Date(fileEntry.client_modified),
2173
+ modifiedAt: new Date(fileEntry.server_modified),
2174
+ metadata: {
2175
+ dropboxId: fileEntry.id,
2176
+ pathDisplay: fileEntry.path_display
2177
+ }
2178
+ });
2179
+ }
2180
+ /**
2181
+ * Convert a Dropbox path_display to virtual path
2182
+ */
2183
+ toVirtualPath(dropboxPath) {
2184
+ if (this.rootPath && dropboxPath.toLowerCase().startsWith(`/${this.rootPath.toLowerCase()}`)) {
2185
+ const stripped = dropboxPath.substring(this.rootPath.length + 1);
2186
+ return stripped || "/";
2187
+ }
2188
+ return dropboxPath || "/";
2189
+ }
2190
+ async createDirectory(virtualPath) {
2191
+ try {
2192
+ await this.ensureAuthenticated();
2193
+ const dbxPath = this.toDropboxPath(virtualPath);
2194
+ const response = await this.dbx.filesCreateFolderV2({
2195
+ path: dbxPath,
2196
+ autorename: false
2197
+ });
2198
+ const metadata = response.result.metadata;
2199
+ const item = this.metadataToItem(
2200
+ { ".tag": "folder", id: metadata.id, name: metadata.name, path_display: metadata.path_display, path_lower: metadata.path_lower },
2201
+ this.normalizePath(virtualPath)
2202
+ );
2203
+ return this.successResult(item);
2204
+ } catch (error) {
2205
+ if (error instanceof AuthenticationError) {
2206
+ return this.errorResult(error.message);
2207
+ }
2208
+ const errMsg = error.message || String(error);
2209
+ if (errMsg.includes("path/conflict")) {
2210
+ return this.errorResult(`Directory already exists: ${virtualPath}`);
2211
+ }
2212
+ return this.errorResult(`Failed to create directory: ${errMsg}`);
2213
+ }
2214
+ }
2215
+ async removeDirectory(virtualPath, recursive = false) {
2216
+ try {
2217
+ await this.ensureAuthenticated();
2218
+ const dbxPath = this.toDropboxPath(virtualPath);
2219
+ if (!recursive) {
2220
+ const listResponse = await this.dbx.filesListFolder({
2221
+ path: dbxPath,
2222
+ limit: 1
2223
+ });
2224
+ if (listResponse.result.entries.length > 0) {
2225
+ return this.errorResult(`Directory is not empty: ${virtualPath}`);
2226
+ }
2227
+ }
2228
+ await this.dbx.filesDeleteV2({ path: dbxPath });
2229
+ return this.successResult();
2230
+ } catch (error) {
2231
+ const errMsg = error.message || String(error);
2232
+ if (errMsg.includes("path_lookup/not_found") || errMsg.includes("not_found")) {
2233
+ return this.errorResult(`Directory not found: ${virtualPath}`);
2234
+ }
2235
+ return this.errorResult(`Failed to remove directory: ${errMsg}`);
2236
+ }
2237
+ }
2238
+ async uploadFile(source, remotePath, options = {}) {
2239
+ try {
2240
+ await this.ensureAuthenticated();
2241
+ const normalized = this.normalizePath(remotePath);
2242
+ const dbxPath = this.toDropboxPath(remotePath);
2243
+ let contents;
2244
+ if (typeof source === "string") {
2245
+ const fs3 = await import("fs");
2246
+ contents = await fs3.promises.readFile(source);
2247
+ } else if (Buffer.isBuffer(source)) {
2248
+ contents = source;
2249
+ } else {
2250
+ const chunks = [];
2251
+ const reader = source.getReader();
2252
+ let done = false;
2253
+ while (!done) {
2254
+ const result = await reader.read();
2255
+ done = result.done;
2256
+ if (result.value) {
2257
+ chunks.push(result.value);
2258
+ }
2259
+ }
2260
+ contents = Buffer.concat(chunks);
2261
+ }
2262
+ if (contents.length > MAX_UPLOAD_SIZE) {
2263
+ throw new FileTooLargeError(this.getBaseName(remotePath), contents.length, MAX_UPLOAD_SIZE);
2264
+ }
2265
+ if (!options.overwrite) {
2266
+ try {
2267
+ await this.dbx.filesGetMetadata({ path: dbxPath });
2268
+ throw new FileExistsError(remotePath);
2269
+ } catch (err) {
2270
+ if (err instanceof FileExistsError) throw err;
2271
+ }
2272
+ }
2273
+ const response = await this.dbx.filesUpload({
2274
+ path: dbxPath,
2275
+ contents,
2276
+ mode: options.overwrite ? { ".tag": "overwrite" } : { ".tag": "add" },
2277
+ autorename: false
2278
+ });
2279
+ if (options.onProgress) {
2280
+ options.onProgress(100, contents.length, contents.length);
2281
+ }
2282
+ const metadata = response.result;
2283
+ const item = this.metadataToItem(
2284
+ {
2285
+ ".tag": "file",
2286
+ id: metadata.id,
2287
+ name: metadata.name,
2288
+ path_display: metadata.path_display,
2289
+ path_lower: metadata.path_lower,
2290
+ size: metadata.size,
2291
+ client_modified: metadata.client_modified,
2292
+ server_modified: metadata.server_modified
2293
+ },
2294
+ normalized
2295
+ );
2296
+ return this.successResult(item);
2297
+ } catch (error) {
2298
+ if (error instanceof AuthenticationError || error instanceof FileExistsError || error instanceof FileTooLargeError) {
2299
+ return this.errorResult(error.message);
2300
+ }
2301
+ return this.errorResult(`Failed to upload file: ${error.message}`);
2302
+ }
2303
+ }
2304
+ async downloadFile(remotePath, localPath, options = {}) {
2305
+ try {
2306
+ await this.ensureAuthenticated();
2307
+ const dbxPath = this.toDropboxPath(remotePath);
2308
+ const response = await this.dbx.filesDownload({ path: dbxPath });
2309
+ const result = response.result;
2310
+ const buffer = Buffer.from(result.fileBinary);
2311
+ if (options.onProgress) {
2312
+ options.onProgress(100, buffer.length, buffer.length);
2313
+ }
2314
+ if (localPath) {
2315
+ const fs3 = await import("fs");
2316
+ const path3 = await import("path");
2317
+ await fs3.promises.mkdir(path3.dirname(localPath), { recursive: true });
2318
+ await fs3.promises.writeFile(localPath, buffer);
2319
+ return this.successResult(localPath);
2320
+ }
2321
+ return this.successResult(buffer);
2322
+ } catch (error) {
2323
+ const errMsg = error.message || String(error);
2324
+ if (errMsg.includes("path/not_found") || errMsg.includes("path_lookup/not_found")) {
2325
+ return this.errorResult(`File not found: ${remotePath}`);
2326
+ }
2327
+ return this.errorResult(`Failed to download file: ${errMsg}`);
2328
+ }
2329
+ }
2330
+ async moveItem(sourcePath, destinationPath, _options = {}) {
2331
+ try {
2332
+ await this.ensureAuthenticated();
2333
+ const fromPath = this.toDropboxPath(sourcePath);
2334
+ const toPath = this.toDropboxPath(destinationPath);
2335
+ const response = await this.dbx.filesMoveV2({
2336
+ from_path: fromPath,
2337
+ to_path: toPath,
2338
+ autorename: false
2339
+ });
2340
+ const metadata = response.result.metadata;
2341
+ const item = this.metadataToItem(metadata, this.normalizePath(destinationPath));
2342
+ return this.successResult(item);
2343
+ } catch (error) {
2344
+ const errMsg = error.message || String(error);
2345
+ if (errMsg.includes("not_found")) {
2346
+ return this.errorResult(`Item not found: ${sourcePath}`);
2347
+ }
2348
+ return this.errorResult(`Failed to move item: ${errMsg}`);
2349
+ }
2350
+ }
2351
+ async deleteFile(virtualPath) {
2352
+ try {
2353
+ await this.ensureAuthenticated();
2354
+ const dbxPath = this.toDropboxPath(virtualPath);
2355
+ await this.dbx.filesDeleteV2({ path: dbxPath });
2356
+ return this.successResult();
2357
+ } catch (error) {
2358
+ const errMsg = error.message || String(error);
2359
+ if (errMsg.includes("not_found")) {
2360
+ return this.errorResult(`File not found: ${virtualPath}`);
2361
+ }
2362
+ return this.errorResult(`Failed to delete file: ${errMsg}`);
2363
+ }
2364
+ }
2365
+ async renameFile(virtualPath, newName, _options = {}) {
2366
+ try {
2367
+ await this.ensureAuthenticated();
2368
+ const parentPath = this.getParentPath(virtualPath);
2369
+ const newVirtualPath = this.joinPath(parentPath, newName);
2370
+ const fromPath = this.toDropboxPath(virtualPath);
2371
+ const toPath = this.toDropboxPath(newVirtualPath);
2372
+ const response = await this.dbx.filesMoveV2({
2373
+ from_path: fromPath,
2374
+ to_path: toPath,
2375
+ autorename: false
2376
+ });
2377
+ const metadata = response.result.metadata;
2378
+ const item = this.metadataToItem(metadata, newVirtualPath);
2379
+ return this.successResult(item);
2380
+ } catch (error) {
2381
+ const errMsg = error.message || String(error);
2382
+ if (errMsg.includes("not_found")) {
2383
+ return this.errorResult(`File not found: ${virtualPath}`);
2384
+ }
2385
+ return this.errorResult(`Failed to rename file: ${errMsg}`);
2386
+ }
2387
+ }
2388
+ async renameFolder(virtualPath, newName, _options = {}) {
2389
+ try {
2390
+ await this.ensureAuthenticated();
2391
+ const parentPath = this.getParentPath(virtualPath);
2392
+ const newVirtualPath = this.joinPath(parentPath, newName);
2393
+ const fromPath = this.toDropboxPath(virtualPath);
2394
+ const toPath = this.toDropboxPath(newVirtualPath);
2395
+ const response = await this.dbx.filesMoveV2({
2396
+ from_path: fromPath,
2397
+ to_path: toPath,
2398
+ autorename: false
2399
+ });
2400
+ const metadata = response.result.metadata;
2401
+ const item = this.metadataToItem(metadata, newVirtualPath);
2402
+ return this.successResult(item);
2403
+ } catch (error) {
2404
+ const errMsg = error.message || String(error);
2405
+ if (errMsg.includes("not_found")) {
2406
+ return this.errorResult(`Folder not found: ${virtualPath}`);
2407
+ }
2408
+ return this.errorResult(`Failed to rename folder: ${errMsg}`);
2409
+ }
2410
+ }
2411
+ async listDirectory(virtualPath, options = {}) {
2412
+ try {
2413
+ await this.ensureAuthenticated();
2414
+ const dbxPath = this.toDropboxPath(virtualPath);
2415
+ const items = [];
2416
+ let hasMore = true;
2417
+ let cursor;
2418
+ const firstResponse = await this.dbx.filesListFolder({
2419
+ path: dbxPath,
2420
+ limit: 100
2421
+ });
2422
+ let entries = firstResponse.result.entries;
2423
+ hasMore = firstResponse.result.has_more;
2424
+ cursor = firstResponse.result.cursor;
2425
+ const processEntries = async (entryList) => {
2426
+ for (const entry of entryList) {
2427
+ if (!options.includeHidden && entry.name.startsWith(".")) {
2428
+ continue;
2429
+ }
2430
+ const itemPath = this.joinPath(virtualPath, entry.name);
2431
+ const item = this.metadataToItem(entry, itemPath);
2432
+ if (options.filter && !options.filter(item)) {
2433
+ continue;
2434
+ }
2435
+ items.push(item);
2436
+ if (options.recursive && entry[".tag"] === "folder") {
2437
+ const subResult = await this.listDirectory(itemPath, options);
2438
+ if (subResult.success && subResult.data) {
2439
+ items.push(...subResult.data);
2440
+ }
2441
+ }
2442
+ }
2443
+ };
2444
+ await processEntries(entries);
2445
+ while (hasMore && cursor) {
2446
+ const continueResponse = await this.dbx.filesListFolderContinue({ cursor });
2447
+ entries = continueResponse.result.entries;
2448
+ hasMore = continueResponse.result.has_more;
2449
+ cursor = continueResponse.result.cursor;
2450
+ await processEntries(entries);
2451
+ }
2452
+ return this.successResult(items);
2453
+ } catch (error) {
2454
+ const errMsg = error.message || String(error);
2455
+ if (errMsg.includes("path/not_found") || errMsg.includes("not_found")) {
2456
+ return this.errorResult(`Directory not found: ${virtualPath}`);
2457
+ }
2458
+ return this.errorResult(`Failed to list directory: ${errMsg}`);
2459
+ }
2460
+ }
2461
+ async getItem(virtualPath) {
2462
+ try {
2463
+ await this.ensureAuthenticated();
2464
+ const dbxPath = this.toDropboxPath(virtualPath);
2465
+ const response = await this.dbx.filesGetMetadata({ path: dbxPath });
2466
+ const metadata = response.result;
2467
+ const item = this.metadataToItem(metadata, this.normalizePath(virtualPath));
2468
+ return this.successResult(item);
2469
+ } catch (error) {
2470
+ const errMsg = error.message || String(error);
2471
+ if (errMsg.includes("not_found")) {
2472
+ return this.errorResult(`Item not found: ${virtualPath}`);
2473
+ }
2474
+ return this.errorResult(`Failed to get item: ${errMsg}`);
2475
+ }
2476
+ }
2477
+ async exists(virtualPath) {
2478
+ try {
2479
+ await this.ensureAuthenticated();
2480
+ const dbxPath = this.toDropboxPath(virtualPath);
2481
+ await this.dbx.filesGetMetadata({ path: dbxPath });
2482
+ return true;
2483
+ } catch {
2484
+ return false;
2485
+ }
2486
+ }
2487
+ async getFolderTree(path3 = "/", depth = 3) {
2488
+ try {
2489
+ await this.ensureAuthenticated();
2490
+ return super.getFolderTree(path3, depth);
2491
+ } catch (error) {
2492
+ return this.errorResult(`Failed to get folder tree: ${error.message}`);
2493
+ }
2494
+ }
2495
+ };
2496
+ function createDropboxModule() {
2497
+ return new DropboxModule();
2498
+ }
2499
+
1831
2500
  // src/modules/index.ts
1832
2501
  var moduleRegistry = {
1833
2502
  local: createLocalModule,
1834
- google_drive: createGoogleDriveModule
2503
+ google_drive: createGoogleDriveModule,
2504
+ dropbox: createDropboxModule
1835
2505
  };
1836
2506
  function getRegisteredProviders() {
1837
2507
  return Object.keys(moduleRegistry);
@@ -2397,6 +3067,7 @@ var FileMetadataService = class {
2397
3067
  if (input.scope_id !== void 0) record.scope_id = input.scope_id;
2398
3068
  if (input.uploaded_by !== void 0) record.uploaded_by = input.uploaded_by;
2399
3069
  if (input.original_filename !== void 0) record.original_filename = input.original_filename;
3070
+ if (input.content_tag !== void 0) record.content_tag = input.content_tag;
2400
3071
  const results = await this.crud.insert(record);
2401
3072
  this.logger?.debug?.("Recorded file upload", { path: input.file_path });
2402
3073
  return results[0] || null;
@@ -3774,10 +4445,18 @@ function createNamingConventionService(crudService, options) {
3774
4445
 
3775
4446
  // src/services/llm-extraction-service.ts
3776
4447
  var LLMExtractionService = class {
3777
- constructor(llmFactory, defaultProvider = "gemini") {
3778
- this.llmFactory = llmFactory;
4448
+ constructor(factoryConfig, defaultProvider = "gemini") {
4449
+ this.llmFactory = factoryConfig.create;
4450
+ this.cacheInvalidator = factoryConfig.invalidateCache;
3779
4451
  this.defaultProvider = defaultProvider;
3780
4452
  }
4453
+ /**
4454
+ * Invalidate the LLM prompt cache
4455
+ * Passthrough to hazo_llm_api's invalidate_prompt_cache when configured
4456
+ */
4457
+ invalidatePromptCache(area, key) {
4458
+ this.cacheInvalidator?.(area, key);
4459
+ }
3781
4460
  /**
3782
4461
  * Extract data from a document
3783
4462
  *
@@ -3904,8 +4583,8 @@ var LLMExtractionService = class {
3904
4583
  };
3905
4584
  }
3906
4585
  };
3907
- function createLLMExtractionService(llmFactory, defaultProvider) {
3908
- return new LLMExtractionService(llmFactory, defaultProvider);
4586
+ function createLLMExtractionService(factoryConfig, defaultProvider) {
4587
+ return new LLMExtractionService(factoryConfig, defaultProvider);
3909
4588
  }
3910
4589
 
3911
4590
  // src/common/naming-utils.ts
@@ -4231,10 +4910,11 @@ function generatePreviewName(pattern, userVariables, options = {}) {
4231
4910
 
4232
4911
  // src/services/upload-extract-service.ts
4233
4912
  var UploadExtractService = class {
4234
- constructor(fileManager, namingService, extractionService) {
4913
+ constructor(fileManager, namingService, extractionService, defaultContentTagConfig) {
4235
4914
  this.fileManager = fileManager;
4236
4915
  this.namingService = namingService;
4237
4916
  this.extractionService = extractionService;
4917
+ this.defaultContentTagConfig = defaultContentTagConfig;
4238
4918
  }
4239
4919
  /**
4240
4920
  * Upload a file with optional extraction and naming convention
@@ -4311,11 +4991,12 @@ var UploadExtractService = class {
4311
4991
  metadata.extraction_id = extractionData.id;
4312
4992
  metadata.extraction_source = extractionData.source;
4313
4993
  }
4994
+ const effectiveContentTagConfig = options.contentTagConfig ?? this.defaultContentTagConfig;
4995
+ const needsContentTagging = effectiveContentTagConfig?.content_tag_set_by_llm && this.extractionService && this.fileManager.isTrackingActive();
4314
4996
  const uploadResult = await this.fileManager.uploadFile(source, fullPath, {
4315
4997
  ...options,
4316
4998
  metadata,
4317
- awaitRecording: !!extractionData
4318
- // Await recording when extraction needs to be added
4999
+ awaitRecording: !!extractionData || !!needsContentTagging
4319
5000
  });
4320
5001
  if (!uploadResult.success) {
4321
5002
  return {
@@ -4339,13 +5020,23 @@ var UploadExtractService = class {
4339
5020
  );
4340
5021
  }
4341
5022
  }
5023
+ let contentTag;
5024
+ if (needsContentTagging && effectiveContentTagConfig) {
5025
+ contentTag = await this.performContentTagging(
5026
+ source,
5027
+ mimeType,
5028
+ effectiveContentTagConfig,
5029
+ fullPath
5030
+ );
5031
+ }
4342
5032
  return {
4343
5033
  success: true,
4344
5034
  file: uploadResult.data,
4345
5035
  extraction: extractionData,
4346
5036
  generatedPath: fullPath,
4347
5037
  generatedFolderPath: generatedFolderPath || void 0,
4348
- originalFileName
5038
+ originalFileName,
5039
+ contentTag
4349
5040
  };
4350
5041
  } catch (error) {
4351
5042
  const message = error instanceof Error ? error.message : String(error);
@@ -4443,6 +5134,76 @@ var UploadExtractService = class {
4443
5134
  folderPath: folderPath || void 0
4444
5135
  };
4445
5136
  }
5137
+ /**
5138
+ * Perform content tagging via LLM extraction.
5139
+ * Calls the LLM with the configured prompt, extracts the specified field,
5140
+ * and writes it to the content_tag column.
5141
+ */
5142
+ async performContentTagging(buffer, mimeType, config, filePath) {
5143
+ try {
5144
+ if (!this.extractionService) return void 0;
5145
+ const result = await this.extractionService.extract(buffer, mimeType, {
5146
+ promptArea: config.content_tag_prompt_area,
5147
+ promptKey: config.content_tag_prompt_key,
5148
+ promptVariables: config.content_tag_prompt_variables
5149
+ });
5150
+ if (!result.success || !result.data) return void 0;
5151
+ const tagValue = result.data[config.content_tag_prompt_return_fieldname];
5152
+ if (typeof tagValue !== "string" || !tagValue) return void 0;
5153
+ const metadataService = this.fileManager.getMetadataService();
5154
+ if (metadataService) {
5155
+ const storageType = this.fileManager.getProvider() || "local";
5156
+ const record = await metadataService.findByPath(filePath, storageType);
5157
+ if (record) {
5158
+ await metadataService.updateFields(record.id, { content_tag: tagValue });
5159
+ }
5160
+ }
5161
+ return tagValue;
5162
+ } catch {
5163
+ return void 0;
5164
+ }
5165
+ }
5166
+ /**
5167
+ * Manually tag a file's content via LLM.
5168
+ * Works with existing DB records, resolving the file path internally.
5169
+ *
5170
+ * @param fileId - Database record ID of the file
5171
+ * @param config - Content tag config (falls back to default if not provided)
5172
+ * @returns OperationResult with the tag value
5173
+ */
5174
+ async tagFileContent(fileId, config) {
5175
+ const effectiveConfig = config ?? this.defaultContentTagConfig;
5176
+ if (!effectiveConfig || !effectiveConfig.content_tag_set_by_llm) {
5177
+ return { success: false, error: "Content tagging is not configured or disabled" };
5178
+ }
5179
+ if (!this.extractionService) {
5180
+ return { success: false, error: "Extraction service not available" };
5181
+ }
5182
+ const metadataService = this.fileManager.getMetadataService();
5183
+ if (!metadataService) {
5184
+ return { success: false, error: "Metadata service not available (tracking not enabled)" };
5185
+ }
5186
+ const record = await metadataService.findById(fileId);
5187
+ if (!record) {
5188
+ return { success: false, error: `File record not found: ${fileId}` };
5189
+ }
5190
+ const downloadResult = await this.fileManager.downloadFile(record.file_path);
5191
+ if (!downloadResult.success || !downloadResult.data) {
5192
+ return { success: false, error: `Failed to download file: ${downloadResult.error}` };
5193
+ }
5194
+ const buffer = Buffer.isBuffer(downloadResult.data) ? downloadResult.data : Buffer.from(downloadResult.data);
5195
+ const mimeType = getMimeType(record.filename);
5196
+ const tagValue = await this.performContentTagging(
5197
+ buffer,
5198
+ mimeType,
5199
+ effectiveConfig,
5200
+ record.file_path
5201
+ );
5202
+ if (!tagValue) {
5203
+ return { success: false, error: "Content tagging did not produce a result" };
5204
+ }
5205
+ return { success: true, data: tagValue };
5206
+ }
4446
5207
  /**
4447
5208
  * Get the file manager
4448
5209
  */
@@ -4462,8 +5223,8 @@ var UploadExtractService = class {
4462
5223
  return this.extractionService;
4463
5224
  }
4464
5225
  };
4465
- function createUploadExtractService(fileManager, namingService, extractionService) {
4466
- return new UploadExtractService(fileManager, namingService, extractionService);
5226
+ function createUploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig) {
5227
+ return new UploadExtractService(fileManager, namingService, extractionService, defaultContentTagConfig);
4467
5228
  }
4468
5229
 
4469
5230
  // src/server/factory.ts
@@ -4479,7 +5240,8 @@ async function createHazoFilesServer(options = {}) {
4479
5240
  metadataTableName = "hazo_files",
4480
5241
  namingTableName = "hazo_files_naming",
4481
5242
  enableTracking = !!crudService,
4482
- trackDownloads = true
5243
+ trackDownloads = true,
5244
+ defaultContentTagConfig
4483
5245
  } = options;
4484
5246
  const fileManagerOptions = {
4485
5247
  config,
@@ -4508,14 +5270,17 @@ async function createHazoFilesServer(options = {}) {
4508
5270
  const uploadExtractService = new UploadExtractService(
4509
5271
  fileManager,
4510
5272
  namingService || void 0,
4511
- extractionService || void 0
5273
+ extractionService || void 0,
5274
+ defaultContentTagConfig
4512
5275
  );
5276
+ const invalidatePromptCache = extractionService ? (area, key) => extractionService.invalidatePromptCache(area, key) : void 0;
4513
5277
  return {
4514
5278
  fileManager,
4515
5279
  metadataService,
4516
5280
  namingService,
4517
5281
  extractionService,
4518
- uploadExtractService
5282
+ uploadExtractService,
5283
+ invalidatePromptCache
4519
5284
  };
4520
5285
  }
4521
5286
  async function createBasicFileManager(config, crudService) {
@@ -4554,7 +5319,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
4554
5319
  uploaded_by TEXT,
4555
5320
  storage_verified_at TEXT,
4556
5321
  deleted_at TEXT,
4557
- original_filename TEXT
5322
+ original_filename TEXT,
5323
+ content_tag TEXT
4558
5324
  )`,
4559
5325
  indexes: [
4560
5326
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
@@ -4564,7 +5330,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
4564
5330
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
4565
5331
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
4566
5332
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
4567
- "CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
5333
+ "CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
5334
+ "CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
4568
5335
  ]
4569
5336
  },
4570
5337
  postgres: {
@@ -4587,7 +5354,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
4587
5354
  uploaded_by UUID,
4588
5355
  storage_verified_at TIMESTAMP WITH TIME ZONE,
4589
5356
  deleted_at TIMESTAMP WITH TIME ZONE,
4590
- original_filename TEXT
5357
+ original_filename TEXT,
5358
+ content_tag TEXT
4591
5359
  )`,
4592
5360
  indexes: [
4593
5361
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_path ON hazo_files (file_path)",
@@ -4597,7 +5365,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
4597
5365
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_status ON hazo_files (status)",
4598
5366
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_scope ON hazo_files (scope_id)",
4599
5367
  "CREATE INDEX IF NOT EXISTS idx_hazo_files_ref_count ON hazo_files (ref_count)",
4600
- "CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)"
5368
+ "CREATE INDEX IF NOT EXISTS idx_hazo_files_deleted ON hazo_files (deleted_at)",
5369
+ "CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
4601
5370
  ]
4602
5371
  },
4603
5372
  columns: [
@@ -4619,7 +5388,8 @@ var HAZO_FILES_TABLE_SCHEMA = {
4619
5388
  "uploaded_by",
4620
5389
  "storage_verified_at",
4621
5390
  "deleted_at",
4622
- "original_filename"
5391
+ "original_filename",
5392
+ "content_tag"
4623
5393
  ]
4624
5394
  };
4625
5395
  function getSchemaForTable(tableName, dbType) {
@@ -4760,6 +5530,45 @@ function getNamingSchemaForTable(tableName, dbType) {
4760
5530
  )
4761
5531
  };
4762
5532
  }
5533
+ var HAZO_FILES_MIGRATION_V3 = {
5534
+ tableName: HAZO_FILES_DEFAULT_TABLE_NAME,
5535
+ sqlite: {
5536
+ alterStatements: [
5537
+ "ALTER TABLE hazo_files ADD COLUMN content_tag TEXT"
5538
+ ],
5539
+ indexes: [
5540
+ "CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
5541
+ ],
5542
+ backfill: ""
5543
+ // No backfill needed — column is nullable, defaults to NULL
5544
+ },
5545
+ postgres: {
5546
+ alterStatements: [
5547
+ "ALTER TABLE hazo_files ADD COLUMN IF NOT EXISTS content_tag TEXT"
5548
+ ],
5549
+ indexes: [
5550
+ "CREATE INDEX IF NOT EXISTS idx_hazo_files_content_tag ON hazo_files (content_tag)"
5551
+ ],
5552
+ backfill: ""
5553
+ // No backfill needed — column is nullable, defaults to NULL
5554
+ },
5555
+ newColumns: [
5556
+ "content_tag"
5557
+ ]
5558
+ };
5559
+ function getMigrationV3ForTable(tableName, dbType) {
5560
+ const migration = HAZO_FILES_MIGRATION_V3[dbType];
5561
+ const defaultName = HAZO_FILES_MIGRATION_V3.tableName;
5562
+ return {
5563
+ alterStatements: migration.alterStatements.map(
5564
+ (stmt) => stmt.replace(new RegExp(defaultName, "g"), tableName)
5565
+ ),
5566
+ indexes: migration.indexes.map(
5567
+ (idx) => idx.replace(new RegExp(defaultName, "g"), tableName)
5568
+ ),
5569
+ backfill: migration.backfill
5570
+ };
5571
+ }
4763
5572
 
4764
5573
  // src/migrations/add-reference-tracking.ts
4765
5574
  async function migrateToV2(executor, dbType, tableName) {
@@ -4779,6 +5588,20 @@ async function backfillV2Defaults(executor, dbType, tableName) {
4779
5588
  await executor.run(migration.backfill);
4780
5589
  }
4781
5590
 
5591
+ // src/migrations/add-content-tag.ts
5592
+ async function migrateToV3(executor, dbType, tableName) {
5593
+ const migration = tableName ? getMigrationV3ForTable(tableName, dbType) : HAZO_FILES_MIGRATION_V3[dbType];
5594
+ for (const stmt of migration.alterStatements) {
5595
+ try {
5596
+ await executor.run(stmt);
5597
+ } catch {
5598
+ }
5599
+ }
5600
+ for (const idx of migration.indexes) {
5601
+ await executor.run(idx);
5602
+ }
5603
+ }
5604
+
4782
5605
  // src/server/index.ts
4783
5606
  try {
4784
5607
  require("server-only");
@@ -4793,6 +5616,8 @@ try {
4793
5616
  DirectoryExistsError,
4794
5617
  DirectoryNotEmptyError,
4795
5618
  DirectoryNotFoundError,
5619
+ DropboxAuth,
5620
+ DropboxModule,
4796
5621
  FileExistsError,
4797
5622
  FileManager,
4798
5623
  FileMetadataService,
@@ -4802,6 +5627,7 @@ try {
4802
5627
  GoogleDriveModule,
4803
5628
  HAZO_FILES_DEFAULT_TABLE_NAME,
4804
5629
  HAZO_FILES_MIGRATION_V2,
5630
+ HAZO_FILES_MIGRATION_V3,
4805
5631
  HAZO_FILES_NAMING_DEFAULT_TABLE_NAME,
4806
5632
  HAZO_FILES_NAMING_TABLE_SCHEMA,
4807
5633
  HAZO_FILES_TABLE_SCHEMA,
@@ -4829,6 +5655,8 @@ try {
4829
5655
  computeFileInfo,
4830
5656
  createAndInitializeModule,
4831
5657
  createBasicFileManager,
5658
+ createDropboxAuth,
5659
+ createDropboxModule,
4832
5660
  createEmptyFileDataStructure,
4833
5661
  createEmptyNamingRuleSchema,
4834
5662
  createFileItem,
@@ -4873,6 +5701,7 @@ try {
4873
5701
  getFileMetadataValues,
4874
5702
  getMergedData,
4875
5703
  getMigrationForTable,
5704
+ getMigrationV3ForTable,
4876
5705
  getMimeType,
4877
5706
  getNameWithoutExtension,
4878
5707
  getNamingSchemaForTable,
@@ -4905,6 +5734,7 @@ try {
4905
5734
  loadConfig,
4906
5735
  loadConfigAsync,
4907
5736
  migrateToV2,
5737
+ migrateToV3,
4908
5738
  normalizePath,
4909
5739
  parseConfig,
4910
5740
  parseFileData,