Skip to main content
Glama
filesystem.ts3.16 kB
// SPDX-FileCopyrightText: Copyright Orangebot, Inc. and Medplum contributors // SPDX-License-Identifier: Apache-2.0 import type { Binary } from '@medplum/fhirtypes'; import { createReadStream, createWriteStream, readFileSync } from 'node:fs'; import { access, copyFile, mkdir } from 'node:fs/promises'; import { resolve, sep } from 'node:path'; import type { Readable } from 'node:stream'; import { pipeline } from 'node:stream'; import { getLogger } from '../logger'; import { BaseBinaryStorage } from './base'; import { generatePresignedUrl } from './presign'; import type { BinarySource } from './types'; import { checkFileMetadata } from './utils'; /** * The FileSystemStorage class stores binary blobs on the file system. * Files are stored in <baseDir>/binary.id/binary.meta.versionId. */ export class FileSystemStorage extends BaseBinaryStorage { private readonly baseDir: string; constructor(baseDir: string) { super(); this.baseDir = baseDir; } writeBinary( binary: Binary, filename: string | undefined, contentType: string | undefined, stream: BinarySource ): Promise<void> { checkFileMetadata(filename, contentType); return this.writeFile(this.getKey(binary), contentType, stream); } async writeFile(key: string, _contentType: string | undefined, input: BinarySource): Promise<void> { const fullPath = this.getPath(key); await this.ensureDirForFileExists(fullPath); const writeStream = createWriteStream(fullPath, { flags: 'w' }); return new Promise((resolve, reject) => { pipeline(input, writeStream, (err) => { if (err) { reject(err); } else { resolve(); } }); }); } async readFile(key: string): Promise<Readable> { const filePath = this.getPath(key); try { await access(filePath); return createReadStream(filePath); } catch (err: any) { getLogger().debug('File not found', err); throw new Error('File not found'); } } readFileByUrlForTests(url: URL): string { const [_empty, _storage, binaryId, versionId] = url.pathname.split('/'); const binary: Binary = { resourceType: 'Binary', id: binaryId, meta: { versionId: versionId }, contentType: 'test', }; return readFileSync(this.getPath(this.getKey(binary)), 'utf8'); } async copyFile(sourceKey: string, destinationKey: string): Promise<void> { const sourcePath = this.getPath(sourceKey); const destinationPath = this.getPath(destinationKey); await this.ensureDirForFileExists(destinationPath); await copyFile(sourcePath, destinationPath); } async getPresignedUrl(binary: Binary): Promise<string> { return generatePresignedUrl(binary); } private async ensureDirForFileExists(filePath: string): Promise<void> { const dir = filePath.substring(0, filePath.lastIndexOf(sep)); await this.ensureDirExists(dir); } private async ensureDirExists(dirPath: string): Promise<void> { await mkdir(dirPath, { recursive: true }); } private getPath(key: string): string { return resolve(this.baseDir, key.replace('binary/', '')); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/medplum/medplum'

If you have feedback or need assistance with the MCP directory API, please join our Discord server