v0.21

storage

introduction

dframework provides a file system abstraction through the Storage facade. it allows you to easily read, write, and manage files across multiple configured "disks". the storage service automatically manages path traversal protection, symlinking public assets, concurrent operation queuing, and even disk space verification.

configuration

the storage configuration is defined within the app.storage.disks configuration namespace. a typical configuration might include a local disk for private files, a public disk for assets meant to be publicly accessible, and a secure disk for encrypted files.

1storage: {
2 disks: {
3 local: {
4 root: './storage/local', // private files, not web accessible
5 },
6 public: {
7 root: './storage/public', // web accessible files
8 },
9 secure: {
10 root: './storage/secure',
11 encryptionKey: Env.value('APP_KEY'), // enables automatic aes-256-gcm encryption
12 },
13 },
14},

dframework automatically creates a symbolic link from public/storage to storage/public upon boot, allowing files stored on the public disk to be served directly to the browser.

obtaining a disk

you interact with the file system by calling the disk method on the Storage facade. if you do not specify a disk name, the framework defaults to the public disk.

1import { Storage } from 'dframework';
2
3const disk = Storage.disk('local');

storing files

to write a file to the disk, use the put method. it accepts the relative file path as the first argument, and the file contents (as a string or buffer) as the second argument. the method will automatically create any necessary directories.

1await Storage.disk().put('avatars/1.jpg', imageBuffer);
2await Storage.disk('local').put('exports/report.csv', 'id,name\n1,tarou');

file uploads

the put method is fully aware of multipart file objects generated during http uploads. you can pass the uploaded file object directly to the put method, and the framework will stream it to its destination without requiring manual buffer extraction.

1// assuming `req.files.avatar` is a multiparty file upload
2const path = await Storage.disk().put('avatars/user.jpg', req.files.avatar);

retrieving files

use the get method to read a file's contents. by default, it returns a raw Buffer. if you prefer a string, you can pass the desired encoding (like utf8) as the second argument.

1const raw = await Storage.disk('local').get('documents/contract.pdf');
2const text = await Storage.disk('local').get('reports/summary.txt', 'utf8');

you can verify if a file exists on the disk using the exists method.

1if (await Storage.disk().exists('avatars/1.jpg')) {
2 // file exists
3}

if you need the absolute server path to a file on the disk, use the path method.

1const absolutePath = Storage.disk('local').path('exports/report.csv');

deleting files

to remove a file, pass the path to the delete method. it returns true if the file was deleted, and false if the file did not exist.

1await Storage.disk().delete('avatars/1.jpg');

file management

the framework provides convenience methods for moving and copying files within the same disk.

1// rename or move a file
2await Storage.disk().move('temp/draft.txt', 'published/final.txt');
3
4// duplicate a file
5await Storage.disk().copy('templates/invoice.pdf', 'invoices/123.pdf');

encryption

disks configured with an encryptionKey automatically encrypt on write and decrypt on read. the algorithm is aes-256-gcm. the key is derived from the provided string via sha256. the encryption key is never stored in the file. it lives only in the environment configuration (APP_KEY). the api is identical to unencrypted disks.

the on disk format is: 12 byte iv + 16 byte auth tag + ciphertext. the auth tag provides tamper detection. reads will fail if the file has been modified.

1// write, encrypted transparently
2await Storage.disk('secure').put('keys/api-key.txt', 'my-secret-value');
3
4// read, decrypted transparently
5const value = await Storage.disk('secure').get('keys/api-key.txt', 'utf8');

disk space protection

to prevent the application from crashing due to disk exhaustion, the storage service performs active health checks before writing data. before any put operation, the framework verifies that the target disk has sufficient space for the payload plus a one hundred megabyte safety buffer. if space is insufficient, the operation is safely aborted.