import { CreatePreAuthUploadUrlPayload, FileSearchOptions, FileSearchResultSet, FileStorageBase, GetObjectParams, GetObjectMetadataParams, GetObjectStreamParams, ObjectStreamResult, StorageListResult, StorageObjectMetadata } from '../generic/FileStorageBase.js'; /** * Callback type for persisting refreshed tokens to the database. * This is called when a new refresh token is obtained (some providers issue new refresh tokens on each refresh). */ type TokenRefreshCallback = (newRefreshToken: string, newAccessToken?: string) => Promise; import { StorageProviderConfig } from '../generic/FileStorageBase.js'; /** * Configuration interface for SharePoint storage provider with OAuth2 refresh token flow. * Used when initializing the provider with user-specific OAuth credentials. * Extends StorageProviderConfig to include accountId and accountName. */ interface SharePointOAuthConfig extends StorageProviderConfig { /** OAuth2 Client ID (from Azure AD app registration) */ clientID?: string; /** OAuth2 Client Secret (from Azure AD app registration) */ clientSecret?: string; /** OAuth2 Refresh Token (obtained from OAuth flow, used to get new access tokens) */ refreshToken?: string; /** Azure AD Tenant ID (use 'common' for multi-tenant or 'consumers' for personal accounts) */ tenantID?: string; /** SharePoint Site ID (optional - can be determined from user's OneDrive if not specified) */ siteID?: string; /** Drive ID (document library ID - optional, defaults to user's OneDrive) */ driveID?: string; /** Optional root folder ID to restrict operations to a specific folder */ rootFolderID?: string; /** Callback to persist new tokens when they are refreshed */ onTokenRefresh?: TokenRefreshCallback; } /** * FileStorageBase implementation for Microsoft SharePoint using the Microsoft Graph API * * This provider allows working with files stored in SharePoint document libraries. * It uses the Microsoft Graph API and client credentials authentication flow to * securely access and manipulate SharePoint files and folders. * * @remarks * This implementation requires the following environment variables: * - STORAGE_SHAREPOINT_CLIENT_ID - Azure AD application (client) ID * - STORAGE_SHAREPOINT_CLIENT_SECRET - Azure AD application client secret * - STORAGE_SHAREPOINT_TENANT_ID - Azure AD tenant ID * - STORAGE_SHAREPOINT_SITE_ID - The SharePoint site ID * - STORAGE_SHAREPOINT_DRIVE_ID - The ID of the document library (drive) * - STORAGE_SHAREPOINT_ROOT_FOLDER_ID (optional) - ID of a subfolder to use as the root * * To use this provider, you need to: * 1. Register an Azure AD application with appropriate Microsoft Graph API permissions * (typically Files.ReadWrite.All and Sites.ReadWrite.All) * 2. Create a client secret for the application * 3. Grant admin consent for the permissions * 4. Find your SharePoint site ID and document library (drive) ID using the Microsoft Graph Explorer * * @example * ```typescript * // Set required environment variables before creating the provider * process.env.STORAGE_SHAREPOINT_CLIENT_ID = 'your-client-id'; * process.env.STORAGE_SHAREPOINT_CLIENT_SECRET = 'your-client-secret'; * process.env.STORAGE_SHAREPOINT_TENANT_ID = 'your-tenant-id'; * process.env.STORAGE_SHAREPOINT_SITE_ID = 'your-site-id'; * process.env.STORAGE_SHAREPOINT_DRIVE_ID = 'your-drive-id'; * * // Create the provider * const storage = new SharePointFileStorage(); * * // Upload a file * const fileContent = Buffer.from('Hello, SharePoint!'); * await storage.PutObject('documents/hello.txt', fileContent, 'text/plain'); * * // Download a file * const downloadedContent = await storage.GetObject('documents/hello.txt'); * * // Get a temporary download URL * const downloadUrl = await storage.CreatePreAuthDownloadUrl('documents/hello.txt'); * ``` */ export declare class SharePointFileStorage extends FileStorageBase { /** * The name of this storage provider */ protected readonly providerName = "SharePoint"; /** * Microsoft Graph API client */ private _client; /** * The ID of the SharePoint document library (drive) */ private _driveId; /** * The ID of the SharePoint site */ private _siteId; /** * Optional ID of a subfolder to use as the root folder (if specified) */ private _rootFolderId?; /** * OAuth2 Client ID (for per-user OAuth flow) */ private _clientID?; /** * OAuth2 Client Secret (for per-user OAuth flow) */ private _clientSecret?; /** * OAuth2 Refresh Token (for per-user OAuth flow) */ private _refreshToken?; /** * Azure AD Tenant ID */ private _tenantID?; /** * Callback for persisting refreshed tokens */ private _onTokenRefresh?; /** * Creates a new SharePointFileStorage instance * * This constructor reads configuration from environment variables if available. * If no environment variables are set, the provider can be initialized later * via the initialize() method with OAuth credentials from the database. */ constructor(); /** * Checks if SharePoint provider is properly configured. * Returns true if the Graph client is initialized and has required IDs. * Logs detailed error messages if configuration is incomplete. */ get IsConfigured(): boolean; /** * Initialize SharePoint storage provider with optional configuration. * * ## Standard Usage Pattern * * **ALWAYS call this method** after creating a provider instance. * * ### Simple Deployment (Environment Variables) * Constructor loads credentials from environment variables, then call * initialize() with no config to complete setup: * * @example * ```typescript * const storage = new SharePointFileStorage(); // Constructor loads env vars * await storage.initialize(); // No config - uses env vars * await storage.ListObjects('/'); // Ready to use * ``` * * ### Multi-Tenant Enterprise (Database) * Use infrastructure utility which handles credential decryption automatically: * * @example * ```typescript * const storage = await initializeDriverWithAccountCredentials({ * accountEntity: accountWithProvider.account, * providerEntity: accountWithProvider.provider, * contextUser * }); * await storage.ListObjects('/'); // Credentials already decrypted and initialized * ``` * * @param config - Configuration object containing OAuth2 credentials from database */ initialize(config?: SharePointOAuthConfig): Promise; /** * Gets the SharePoint item ID for a folder at the specified path * * This helper method navigates the folder hierarchy in SharePoint to find * the folder specified by the path, returning its item ID. * * @param path - The path to get the parent folder for (e.g., 'documents/reports') * @returns A Promise that resolves to the parent folder ID * @throws Error if any folder in the path doesn't exist * @private */ private _getParentFolderIdByPath; /** * Gets a SharePoint item by its path * * This helper method retrieves a SharePoint item (file or folder) using * its path. It handles path normalization and root folder redirection. * * @param path - The path of the item to retrieve (e.g., 'documents/reports/report.docx') * @returns A Promise that resolves to the SharePoint item * @throws Error if the item doesn't exist or cannot be accessed * @private */ private _getItemByPath; /** * Converts a SharePoint item to a StorageObjectMetadata object * * This helper method transforms the Microsoft Graph API item representation * into the standard StorageObjectMetadata format used by the FileStorageBase interface. * * @param item - The SharePoint item from the Microsoft Graph API * @returns A StorageObjectMetadata object representing the item * @private */ private _itemToMetadata; /** * Creates a pre-authenticated upload URL (not supported in SharePoint) * * This method is not supported for SharePoint storage as SharePoint doesn't provide * a way to generate pre-authenticated upload URLs like object storage services. * Instead, use the PutObject method for file uploads. * * @param objectName - The object name (path) to create a pre-auth URL for * @throws UnsupportedOperationError always, as this operation is not supported * @example * ```typescript * // This will throw an UnsupportedOperationError * try { * await storage.CreatePreAuthUploadUrl('documents/report.docx'); * } catch (error) { * if (error instanceof UnsupportedOperationError) { * console.log('Pre-authenticated upload URLs are not supported in SharePoint.'); * // Use PutObject instead * await storage.PutObject('documents/report.docx', fileContent, 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'); * } * } * ``` */ CreatePreAuthUploadUrl(objectName: string): Promise; /** * Creates a pre-authenticated download URL for an object * * This method generates a time-limited, publicly accessible URL that can be used * to download a file without authentication. The URL expires after 10 minutes. * * @param objectName - Path to the object to create a download URL for (e.g., 'documents/report.pdf') * @returns A Promise that resolves to the pre-authenticated download URL * @throws Error if the object doesn't exist or the URL creation fails * * @example * ```typescript * // Generate a pre-authenticated download URL that will work for 10 minutes * const downloadUrl = await storage.CreatePreAuthDownloadUrl('presentations/quarterly-update.pptx'); * console.log(`Download the file using this URL: ${downloadUrl}`); * * // You can share this URL with users who don't have SharePoint access * // The URL will expire after 10 minutes * ``` */ CreatePreAuthDownloadUrl(objectName: string): Promise; /** * Moves an object from one location to another * * This method moves a file or folder from one location in SharePoint to another. * It handles both renaming and changing the parent folder. * * @param oldObjectName - Current path of the object (e.g., 'old-folder/document.docx') * @param newObjectName - New path for the object (e.g., 'new-folder/renamed-document.docx') * @returns A Promise that resolves to true if successful, false otherwise * * @example * ```typescript * // Move a file to a different folder * const moveResult = await storage.MoveObject( * 'documents/old-report.pdf', * 'archive/2023/annual-report.pdf' * ); * * if (moveResult) { * console.log('File moved successfully'); * } else { * console.error('Failed to move file'); * } * ``` */ MoveObject(oldObjectName: string, newObjectName: string): Promise; /** * Deletes an object (file) from SharePoint * * This method permanently deletes a file from SharePoint storage. * Note that deleted files may be recoverable from the SharePoint recycle bin * depending on your SharePoint configuration. * * @param objectName - Path to the object to delete (e.g., 'documents/old-report.docx') * @returns A Promise that resolves to true if successful, false if an error occurs * * @remarks * - Returns true if the object doesn't exist (for idempotency) * - Handles 404 errors by returning true since the end result is the same * * @example * ```typescript * // Delete a file * const deleteResult = await storage.DeleteObject('temp/draft-document.docx'); * * if (deleteResult) { * console.log('File deleted successfully or already didn\'t exist'); * } else { * console.error('Failed to delete file'); * } * ``` */ DeleteObject(objectName: string): Promise; /** * Lists objects in a given directory (folder) * * This method retrieves all files and subfolders in the specified directory. * It returns both a list of object metadata and a list of directory prefixes. * * @param prefix - Path to the directory to list (e.g., 'documents/reports') * @param delimiter - Optional delimiter character (not used in this implementation) * @returns A Promise that resolves to a StorageListResult containing objects and prefixes * * @remarks * - The `objects` array in the result includes both files and folders * - The `prefixes` array includes only folder paths (with trailing slashes) * - Returns empty arrays if the directory doesn't exist or an error occurs * * @example * ```typescript * // List all files and folders in the 'documents' directory * const result = await storage.ListObjects('documents'); * * // Process files * for (const obj of result.objects) { * console.log(`Name: ${obj.name}, Size: ${obj.size}, Type: ${obj.isDirectory ? 'Folder' : 'File'}`); * } * * // Process subfolders * for (const prefix of result.prefixes) { * console.log(`Subfolder: ${prefix}`); * } * ``` */ ListObjects(prefix: string, delimiter?: string): Promise; /** * Creates a directory (folder) in SharePoint * * This method creates a new folder at the specified path. The parent directory * must already exist. * * @param directoryPath - Path where the directory should be created (e.g., 'documents/new-folder') * @returns A Promise that resolves to true if successful, false if an error occurs * * @remarks * - If a folder with the same name already exists, the operation will fail * - The parent directory must exist for the operation to succeed * - Trailing slashes in the path are automatically removed * * @example * ```typescript * // Create a new folder * const createResult = await storage.CreateDirectory('documents/2024-reports'); * * if (createResult) { * console.log('Folder created successfully'); * * // Now we can put files in this folder * await storage.PutObject( * 'documents/2024-reports/q1-results.xlsx', * fileContent, * 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' * ); * } else { * console.error('Failed to create folder'); * } * ``` */ CreateDirectory(directoryPath: string): Promise; /** * Deletes a directory (folder) and optionally its contents * * This method deletes a folder from SharePoint. By default, it will only delete * empty folders unless the recursive parameter is set to true. * * @param directoryPath - Path to the directory to delete (e.g., 'archive/old-reports') * @param recursive - If true, delete the directory and all its contents; if false, only delete if empty * @returns A Promise that resolves to true if successful, false if an error occurs * * @remarks * - If recursive=false and the directory contains files, the operation will fail * - SharePoint deleted items may be recoverable from the recycle bin depending on site settings * - Trailing slashes in the path are automatically removed * * @example * ```typescript * // Attempt to delete an empty folder * const deleteResult = await storage.DeleteDirectory('temp/empty-folder'); * * // Delete a folder and all its contents * const recursiveDeleteResult = await storage.DeleteDirectory('archive/old-data', true); * * if (recursiveDeleteResult) { * console.log('Folder and all its contents deleted successfully'); * } else { * console.error('Failed to delete folder'); * } * ``` */ DeleteDirectory(directoryPath: string, recursive?: boolean): Promise; /** * Gets metadata for a file or folder * * This method retrieves metadata information about a file or folder, such as * its name, size, content type, and last modified date. * * @param params - Object identifier (prefer objectId for performance, fallback to fullPath) * @returns A Promise that resolves to a StorageObjectMetadata object * @throws Error if the object doesn't exist or cannot be accessed * * @example * ```typescript * try { * // Fast path: Use objectId (SharePoint item ID) * const metadata = await storage.GetObjectMetadata({ objectId: '01BYE5RZ6QN3VYRVNHHFDK2QJODWDDFR4E' }); * * // Slow path: Use path * const metadata2 = await storage.GetObjectMetadata({ fullPath: 'presentations/quarterly-update.pptx' }); * * console.log(`Name: ${metadata.name}`); * console.log(`Size: ${metadata.size} bytes`); * console.log(`Content Type: ${metadata.contentType}`); * console.log(`Last Modified: ${metadata.lastModified}`); * console.log(`Is Directory: ${metadata.isDirectory}`); * } catch (error) { * console.error('Error getting metadata:', error.message); * } * ``` */ GetObjectMetadata(params: GetObjectMetadataParams): Promise; /** * Downloads a file's contents * * This method retrieves the raw content of a file as a Buffer. * * @param params - Object identifier (prefer objectId for performance, fallback to fullPath) * @returns A Promise that resolves to a Buffer containing the file's contents * @throws Error if the file doesn't exist or cannot be downloaded * * @remarks * - This method uses the Graph API's download URL to retrieve the file contents * - The method will throw an error if the object is a folder * - For large files, consider using CreatePreAuthDownloadUrl instead * * @example * ```typescript * try { * // Fast path: Use objectId (SharePoint item ID) * const fileContent = await storage.GetObject({ objectId: '01BYE5RZ6QN3VYRVNHHFDK2QJODWDDFR4E' }); * * // Slow path: Use path * const fileContent2 = await storage.GetObject({ fullPath: 'documents/notes.txt' }); * * // Convert Buffer to string for text files * const textContent = fileContent.toString('utf8'); * console.log('File content:', textContent); * * // For binary files, you can write the buffer to a local file * // or process it as needed * } catch (error) { * console.error('Error downloading file:', error.message); * } * ``` */ GetObject(params: GetObjectParams): Promise; /** * SharePoint supports ranged streaming: the Microsoft Graph driveItem `@microsoft.graph.downloadUrl` * is a short-lived pre-authenticated URL that honors the HTTP `Range` header. */ get SupportsStreaming(): boolean; /** * Streams a file's content from SharePoint, optionally honoring a byte range. * * Resolves the driveItem (fast path via objectId, slow path via fullPath), reads its * `@microsoft.graph.downloadUrl` (a short-lived pre-authenticated URL), and `fetch`es that URL * with the inclusive `Range` encoded via {@link BuildHttpRangeHeader}. The fetch response body is * a web `ReadableStream`, converted to a Node {@link Readable} via `Readable.fromWeb` so it is * never buffered fully in memory. `Content-Type`, `Content-Length`, and (for ranged reads) * `Content-Range` are read straight off the HTTP response headers, falling back to the item's * `size` for the total when the server doesn't return a `Content-Range`. * * @param params - Object identifier (prefer objectId) plus optional Range. * @returns A Promise resolving to an {@link ObjectStreamResult}. * @throws Error if the file doesn't exist or cannot be streamed. */ GetObjectStream(params: GetObjectStreamParams): Promise; /** * Parses an HTTP `Content-Range` header value (`bytes start-end/total`) into the structured * form used by {@link ObjectStreamResult.ContentRange}. * * @param contentRange - The raw `Content-Range` header value from the download response, if present. * @returns The parsed range, or undefined when no (valid) range header was returned. */ private _parseSharePointContentRange; /** * Uploads a file to SharePoint * * This method uploads a file to SharePoint at the specified path. It automatically * determines whether to use a simple upload or a chunked upload based on file size. * * @param objectName - Path where the file should be uploaded (e.g., 'documents/report.pdf') * @param data - Buffer containing the file content * @param contentType - Optional MIME type of the file (if not provided, it will be guessed from the filename) * @param metadata - Optional metadata to associate with the file (not used in SharePoint implementation) * @returns A Promise that resolves to true if successful, false if an error occurs * * @remarks * - Files smaller than 4MB use a simple upload * - Files 4MB or larger use a chunked upload session for better reliability * - Automatically creates the parent folder structure if it doesn't exist * - If a file with the same name exists, it will be replaced * * @example * ```typescript * // Create a text file * const textContent = Buffer.from('This is a sample document', 'utf8'); * const uploadResult = await storage.PutObject( * 'documents/sample.txt', * textContent, * 'text/plain' * ); * * // Upload a large file using chunked upload * const largeFileBuffer = fs.readFileSync('/path/to/large-presentation.pptx'); * const largeUploadResult = await storage.PutObject( * 'presentations/quarterly-results.pptx', * largeFileBuffer, * 'application/vnd.openxmlformats-officedocument.presentationml.presentation' * ); * * if (largeUploadResult) { * console.log('Large file uploaded successfully'); * } else { * console.error('Failed to upload large file'); * } * ``` */ PutObject(objectName: string, data: Buffer, contentType?: string, metadata?: Record): Promise; /** * Copies a file from one location to another * * This method creates a copy of a file at a new location. The original file * remains unchanged. * * @param sourceObjectName - Path to the source file (e.g., 'templates/report-template.docx') * @param destinationObjectName - Path where the copy should be created (e.g., 'documents/new-report.docx') * @returns A Promise that resolves to true if successful, false if an error occurs * * @remarks * - The parent folder of the destination must exist * - Both files and folders can be copied * - The operation is asynchronous in SharePoint and may not complete immediately * * @example * ```typescript * // Copy a file to a new location with a different name * const copyResult = await storage.CopyObject( * 'templates/financial-report.xlsx', * 'reports/2024/q1-financial-report.xlsx' * ); * * if (copyResult) { * console.log('File copied successfully'); * } else { * console.error('Failed to copy file'); * } * ``` */ CopyObject(sourceObjectName: string, destinationObjectName: string): Promise; /** * Checks if a file or folder exists * * This method verifies whether an object (file or folder) exists at the specified path. * * @param objectName - Path to check (e.g., 'documents/report.pdf') * @returns A Promise that resolves to true if the object exists, false otherwise * * @example * ```typescript * // Check if a file exists before attempting to download it * const exists = await storage.ObjectExists('presentations/quarterly-update.pptx'); * * if (exists) { * // File exists, proceed with download * const fileContent = await storage.GetObject('presentations/quarterly-update.pptx'); * // Process the file... * } else { * console.log('File does not exist'); * } * ``` */ ObjectExists(objectName: string): Promise; /** * Checks if a directory exists * * This method verifies whether a folder exists at the specified path. * Unlike ObjectExists, this method also checks that the item is a folder. * * @param directoryPath - Path to check (e.g., 'documents/reports') * @returns A Promise that resolves to true if the directory exists, false otherwise * * @remarks * - Returns false if the path exists but points to a file instead of a folder * - Trailing slashes in the path are automatically removed * * @example * ```typescript * // Check if a directory exists before creating a file in it * const dirExists = await storage.DirectoryExists('documents/reports'); * * if (!dirExists) { * // Create the directory first * await storage.CreateDirectory('documents/reports'); * } * * // Now we can safely put a file in this directory * await storage.PutObject('documents/reports/annual-summary.pdf', fileContent, 'application/pdf'); * ``` */ DirectoryExists(directoryPath: string): Promise; /** * Search files in SharePoint using Microsoft Graph Search API. * * This method provides powerful search capabilities using KQL (Keyword Query Language), * SharePoint's native query language. The search can target file names, metadata, and * optionally file contents. * * @param query - The search query string. Can be plain text or use KQL syntax for advanced queries. * @param options - Optional search configuration including filters, limits, and content search * @returns A Promise resolving to FileSearchResultSet with matched files and pagination info * * @remarks * **KQL Query Syntax Examples:** * - Simple text: `"quarterly report"` - searches for files containing these terms * - Boolean operators: `"budget AND 2024"`, `"draft OR final"`, `"report NOT internal"` * - Wildcards: `"proj*"` matches "project", "projection", etc. * - Property filters: `"FileType:pdf"`, `"Author:John Smith"`, `"Size>1000000"` * - Date filters: `"Created>=2024-01-01"`, `"LastModifiedTime<2024-12-31"` * - Proximity: `"project NEAR report"` - finds terms near each other * - Exact phrases: `"\"annual budget report\""` - exact phrase match * * **Additional Filtering:** * The method automatically adds KQL filters based on the provided options: * - `fileTypes`: Adds FileType filters (e.g., `FileType:pdf OR FileType:docx`) * - `modifiedAfter`/`modifiedBefore`: Adds LastModifiedTime filters * - `pathPrefix`: Adds Path filter to restrict search to a directory * - `searchContent`: When false, restricts search to filename only * * @example * ```typescript * // Simple text search in filenames * const results = await storage.SearchFiles('quarterly report', { * maxResults: 20 * }); * * // Search for PDFs only * const pdfResults = await storage.SearchFiles('budget', { * fileTypes: ['pdf'], * maxResults: 50 * }); * * // Search with date range * const recentResults = await storage.SearchFiles('meeting notes', { * modifiedAfter: new Date('2024-01-01'), * modifiedBefore: new Date('2024-12-31'), * searchContent: true * }); * * // Search within specific directory * const folderResults = await storage.SearchFiles('presentation', { * pathPrefix: 'documents/reports', * fileTypes: ['pptx', 'pdf'] * }); * * // Advanced KQL query * const advancedResults = await storage.SearchFiles( * 'FileType:xlsx AND Created>=2024-01-01 AND Author:"John Smith"', * { maxResults: 100 } * ); * ``` */ SearchFiles(query: string, options?: FileSearchOptions): Promise; /** * Builds a KQL (Keyword Query Language) query string from the base query and search options. * * This helper method constructs a properly formatted KQL query by combining the user's * search query with filters derived from FileSearchOptions. It handles file type filters, * date range filters, path restrictions, and content search options. * * @param baseQuery - The user's search query (plain text or KQL) * @param options - Optional search options to convert into KQL filters * @returns A complete KQL query string * @private */ private buildKQLQuery; /** * Transforms Microsoft Graph Search API response into FileSearchResultSet format. * * This helper method processes the raw search response from the Graph API, * extracting relevant file information and converting it to the standard * FileSearchResult format. It handles pagination info and calculates relevance scores. * * @param response - The raw response from Microsoft Graph Search API * @param maxResults - The maximum number of results requested * @returns A FileSearchResultSet with transformed results * @private */ private transformSearchResults; /** * Extracts the file path from a SharePoint resource object. * * This helper method processes the path information from a Graph API resource, * removing the drive and site prefixes to return just the file path relative * to the configured root folder. * * @param resource - The resource object from Graph API search results * @returns The relative file path * @private */ private extractPathFromResource; /** * Determines whether the search match was in the filename or content. * * This helper method analyzes the hit metadata to determine if the search * term was found in the filename versus the file content. * * @param hit - The search hit object from Graph API * @returns True if match was in filename, false if in content, undefined if unknown * @private */ private determineMatchLocation; } export {}; //# sourceMappingURL=SharePointFileStorage.d.ts.map