Skip to main content

stacks-storage

Use when working with file storage in Stacks - the Storage facade (put/get/delete/copy/move/list), StorageAdapter interface, local and S3 disk configurations, file uploads (UploadedFile class), file operations (read/write/copy/move/delete/hash/glob/zip), visibility management, checksums, MIME types, temporary URLs, or filesystem configuration. Covers @stacksjs/storage and config/filesystems.ts.

소스 정보

저장소
stacksjs/stacks
최근 소스 활동
2026년 9월 10일 00:51
감지된 SKILL.md 언어
영어
스타
631
포크
20

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
stacks-storage
description
Use when working with file storage in Stacks - the Storage facade (put/get/delete/copy/move/list), StorageAdapter interface, local and S3 disk configurations, file uploads (UploadedFile class), file operations (read/write/copy/move/delete/hash/glob/zip), visibility management, checksums, MIME types, temporary URLs, or filesystem configuration. Covers @stacksjs/storage and config/filesystems.ts.
license
MIT
compatibility
Bun >= 1.3.0, TypeScript
allowed-tools
Read Edit Write Bash Grep Glob
# Stacks Storage File system abstraction with a Laravel-style Storage facade, local/S3/Azure adapters, file upload handling, and low-level file utilities. ## Key Paths - Core package: `storage/framework/core/storage/src/` - Storage facade: `storage/framework/core/storage/src/facade.ts` - Uploaded file: `storage/framework/core/storage/src/uploaded-file.ts` - Types: `storage/framework/core/storage/src/types.ts` - Filesystem config types: `storage/framework/core/storage/src/types/filesystem.ts` - Local adapter: `storage/framework/core/storage/src/adapters/local.ts` - S3 adapter: `storage/framework/core/storage/src/adapters/s3.ts` - Azure adapter: `storage/framework/core/storage/src/adapters/azure.ts` - Azure request signing: `storage/framework/core/storage/src/azure-signing.ts` - Memory adapter: `storage/framework/core/storage/src/adapters/memory.ts` - Bun adapter: `storage/framework/core/storage/src/adapters/bun.ts` - File utilities: `storage/framework/core/storage/src/files.ts` - Folder utilities: `storage/framework/core/storage/src/folders.ts` - Copy: `storage/framework/core/storage/src/copy.ts` - Move: `storage/framework/core/storage/src/move.ts` - Delete: `storage/framework/core/storage/src/delete.ts` - Hash: `storage/framework/core/storage/src/hash.ts` - Glob: `storage/framework/core/storage/src/glob.ts` - Zip: `storage/framework/core/storage/src/zip.ts` - Helpers: `storage/framework/core/storage/src/helpers.ts` - Configuration: `config/filesystems.ts` ## Package Exports ```typescript // Storage facade (Laravel-style) import { Storage, StorageManager } from '@stacksjs/storage' // File uploads import { UploadedFile, uploadedFile, uploadedFiles } from '@stacksjs/storage' // Adapters import { createLocalStorage, LocalStorageAdapter } from '@stacksjs/storage' import { createS3Storage, S3StorageAdapter } from '@stacksjs/storage' // Config helpers import { localDisk, s3Disk, azureDisk, r2Disk, gcsDisk, filebaseDisk, backblazeDisk, hetznerDisk, configFromEnv } from '@stacksjs/storage' // Types import type { StorageAdapter, FileContents, StatEntry, DirectoryEntry, DirectoryListing } from '@stacksjs/storage' import type { ListOptions, PublicUrlOptions, TemporaryUrlOptions, ChecksumOptions, MimeTypeOptions } from '@stacksjs/storage' import type { DiskConfig, FilesystemConfig, LocalDiskConfig, S3DiskConfig } from '@stacksjs/storage' import { Visibility, createDirectoryListing, normalizeExpiryToMilliseconds, normalizeExpiryToDate, isFile, isDirectory } from '@stacksjs/storage' // Low-level file operations import { copy, copyFile, copyFolder } from '@stacksjs/storage' import { move, rename } from '@stacksjs/storage' import { del, deleteFile, deleteFolder, deleteEmptyFolder, deleteEmptyFolders, deleteGlob, isDirectoryEmpty } from '@stacksjs/storage' import { readJsonFile, readPackageJson, readTextFile, writeFile, writeJsonFile, writeTextFile, put, get, getFiles, deleteFiles, hasFiles } from '@stacksjs/storage' import { isFolder, isDir, doesFolderExist, createFolder, getFolders } from '@stacksjs/storage' import { glob, globSync } from '@stacksjs/storage' import { hashDirectory, hashPath, hashPaths } from '@stacksjs/storage' import { zip, unzip, archive, unarchive, compress, decompress, gzipSync, gunzipSync, deflateSync, inflateSync } from '@stacksjs/storage' import * as storage from '@stacksjs/storage' ``` ## Storage Facade (StorageManager) The `Storage` singleton is a pre-instantiated `StorageManager`. It lazily builds its config from `@stacksjs/config` (which reads `config/filesystems.ts` and env vars). ### Basic Operations ```typescript import { Storage } from '@stacksjs/storage' // Write a file (to default disk) await Storage.put('file.txt', 'Hello World') await Storage.put('data.bin', new Uint8Array([1, 2, 3])) // Read a file as string const content = await Storage.get('file.txt') // Check existence const exists = await Storage.exists('file.txt') const missing = await Storage.missing('file.txt') // Delete await Storage.delete('file.txt') // Copy and move await Storage.copy('source.txt', 'dest.txt') await Storage.move('old.txt', 'new.txt') // File info const size = await Storage.size('file.txt') // bytes const modified = await Storage.lastModified('file.txt') // ms timestamp const mime = await Storage.mimeType('file.txt') const hash = await Storage.checksum('file.txt', 'sha256') // 'md5' | 'sha1' | 'sha256' // Public URL const url = await Storage.url('file.txt') // Directories await Storage.makeDirectory('uploads/images') await Storage.deleteDirectory('uploads/old') // List files (returns AsyncIterable<{ path, type }>) for await (const entry of Storage.files('uploads/')) { console.log(entry.path, entry.type) // 'file' or 'directory' } // Recursive listing for await (const entry of Storage.allFiles('uploads/')) { console.log(entry.path) } ``` ### Using Named Disks ```typescript // Use a specific disk await Storage.disk('s3').write('uploads/file.txt', contents) await Storage.disk('public').write('images/logo.png', imageData) await Storage.disk('local').readToString('config.json') // Disk instances are cached -- subsequent calls return the same adapter ``` ### Configuring Disks ```typescript // Initialize with custom config (overrides env-based config) Storage.init({ default: 's3', disks: { custom: { driver: 'local', root: '/custom/path' }, }, }) // Add/update a disk at runtime Storage.configure('backups', { driver: 's3', bucket: 'my-backups', region: 'eu-west-1' }) // Change default disk Storage.setDefaultDisk('s3') // Query configuration Storage.getDefaultDisk() // 'local' Storage.getConfiguredDisks() // ['local', 'public', 's3'] Storage.getDiskConfig('s3') // S3DiskConfig object // Reset all caches (useful for testing) Storage.reset() ``` ### Built-in Disk Configurations The facade auto-configures these disks from `config/filesystems.ts`: - **`local`** -- `driver: 'local'`, root: `<project>/storage/app`, visibility: from config (default `'private'`) - **`public`** -- `driver: 'local'`, root: `<project>/public`, url: `<appUrl>/storage`, visibility: `'public'` - **`s3`** -- only added if `s3.bucket` is configured in filesystems config ## StorageAdapter Interface All adapters implement this interface: ```typescript interface StorageAdapter { write(path: string, contents: FileContents): Promise<void> read(path: string): Promise<FileContents> readToString(path: string): Promise<string> readToBuffer(path: string): Promise<Buffer> readToUint8Array(path: string): Promise<Uint8Array> deleteFile(path: string): Promise<void> deleteDirectory(path: string): Promise<void> createDirectory(path: string): Promise<void> moveFile(from: string, to: string): Promise<void> copyFile(from: string, to: string): Promise<void> stat(path: string): Promise<StatEntry> list(path: string, options?: ListOptions): DirectoryListing // AsyncIterable changeVisibility(path: string, visibility: Visibility): Promise<void> visibility(path: string): Promise<Visibility> fileExists(path: string): Promise<boolean> directoryExists(path: string): Promise<boolean> publicUrl(path: string, options?: PublicUrlOptions): Promise<string> temporaryUrl(path: string, options: TemporaryUrlOptions): Promise<string> checksum(path: string, options?: ChecksumOptions): Promise<string> mimeType(path: string, options?: MimeTypeOptions): Promise<string> lastModified(path: string): Promise<number> fileSize(path: string): Promise<number> } ``` ### Local Adapter (`LocalStorageAdapter`) ```typescript import { createLocalStorage } from '@stacksjs/storage' const local = createLocalStorage({ root: './storage' }) ``` - Uses Node.js `fs/promises` for file operations - Path traversal protection: throws if resolved path escapes the root directory - `write()` auto-creates parent directories with `mkdir({ recursive: true })` - `moveFile()` uses `fs.rename()`; `copyFile()` uses `fs.copyFile()` - `checksum()` uses `Bun.CryptoHasher` (default algorithm: `sha256`) - `temporaryUrl()` generates HMAC-signed URLs using `APP_KEY` env var - `changeVisibility()` is a no-op (Node.js doesn't map to public/private simply) - `visibility()` always returns `'private'` - MIME type detection is extension-based (supports txt, html, css, js, json, xml, pdf, zip, jpg, jpeg, png, gif, svg, mp4, mp3, wav) - `list()` returns an async iterable; supports `deep: true` for recursive listing ### S3 Adapter (`S3StorageAdapter`) ```typescript import { createS3Storage, S3StorageAdapter } from '@stacksjs/storage' import { S3Client } from '@stacksjs/ts-cloud' const client = new S3Client('us-east-1') const s3 = new S3StorageAdapter(client, { bucket: 'my-bucket', region: 'us-east-1', prefix: 'uploads/' }) ``` - Uses `@stacksjs/ts-cloud` S3Client for AWS operations - Supports key prefix: all paths are prefixed with `config.prefix` - `write()` auto-detects MIME type from extension and sets `contentType` - `createDirectory()` is a no-op (S3 directories are implicit) - `moveFile()` = `copyFile()` + `deleteFile()` - `deleteDirectory()` lists all objects with prefix and deletes them in bulk - `temporaryUrl()` uses `client.getSignedUrl()` for pre-signed URLs - `checksum()` downloads the file content and hashes with `Bun.CryptoHasher` - `publicUrl()` defaults to `https://<bucket>.s3.<region>.amazonaws.com/<key>` - `list()` supports pagination via continuation tokens; `deep: true` uses `listAllObjects()` - `fileExists()` uses `headObject()` and catches 404/NoSuchKey/NotFound errors Every S3-COMPATIBLE provider is an `s3` disk with a different endpoint, and has a preset that fills it in: `r2Disk(bucket, accountId)`, `gcsDisk(bucket)`, `filebaseDisk(bucket)`, `backblazeDisk(bucket, region)`, `hetznerDisk(bucket, location)`. R2 additionally needs `url` set, because its API host never serves public objects. ### Azure Adapter (`AzureBlobStorageAdapter`) ```typescript import { azureDisk, AzureBlobStorageAdapter } from '@stacksjs/storage' const azure = new AzureBlobStorageAdapter({ account: 'mystorageaccount', accountKey: process.env.AZURE_STORAGE_ACCOUNT_KEY, container: 'uploads', prefix: 'tenant-7', }) ``` Its own driver rather than an `s3` disk with an endpoint, because Azure is the one provider with no S3-compatible API. - Speaks the blob REST API over `fetch`; no SDK, and no lazy-client dance - Shared Key authorization for server-side calls, service SAS for signed URLs, both in `azure-signing.ts` and tested there directly - `changeVisibility()` THROWS -- Azure has no per-blob ACL. Public access is a container property, so `visibility()` reports the container's level, and a per-object grant is `signedUrl()` - `putStream()` stages blocks (Put Block) and commits one Put Block List, so the write is atomic; block ids are zero-padded to a fixed width because Azure rejects unequal-length ids - `getStream()` is genuinely incremental, unlike the S3 adapter's buffered one - `createDirectory()` is a no-op and `deleteDirectory()` deletes by prefix, same as S3 - `endpoint` defaults to `https://<account>.blob.core.windows.net`; point it at `http://127.0.0.1:10000/devstoreaccount1` for Azurite - A `sasToken` disk can read and write but cannot sign URLs -- minting a SAS needs the account key, and re-serving the configured token would hand out its full grant ## File Uploads (UploadedFile) ```typescript import { UploadedFile, uploadedFile, uploadedFiles } from '@stacksjs/storage' // Create from native File object const file = uploadedFile(nativeFile) const files = uploadedFiles([file1, file2]) // Properties file.name // original filename file.extension // lowercase extension without dot file.mimeType // client MIME type file.size // size in bytes file.file // underlying File object // Content access const buffer = await file.arrayBuffer() const bytes = await file.bytes() // Uint8Array const text = await file.text() // Validation file.isValid() // size > 0 file.isImage() // MIME starts with 'image/' file.isVideo() // MIME starts with 'video/' file.isAudio() // MIME starts with 'audio/' file.isPdf() // MIME is 'application/pdf' file.isOneOf(['image/*', 'application/pdf']) // wildcard match // Name helpers file.hashName() // UUID-based hash filename: 'a1b2c3d4...f5.jpg' file.hashName('png') // override extension: 'a1b2c3d4...f5.png'
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기