diff --git a/biome.json b/biome.json index 344e858..6dcecc7 100644 --- a/biome.json +++ b/biome.json @@ -2,7 +2,7 @@ "$schema": "https://biomejs.dev/schemas/1.9.3/schema.json", "files": { "maxSize": 2097152, - "ignore": ["*.vue", "vendor"] + "ignore": ["*.vue", "coverage", "node_modules", "vendor"] }, "formatter": { "indentStyle": "tab", diff --git a/storage-s3/CHANGELOG.md b/storage-s3/CHANGELOG.md new file mode 100644 index 0000000..8be8814 --- /dev/null +++ b/storage-s3/CHANGELOG.md @@ -0,0 +1,5 @@ +# Storage S3 Changelog + +## 2024-12-18 - 0.0.1 + +- feat: added basic setup diff --git a/storage-s3/storage.ts b/storage-s3/storage.ts index c8458be..9393d0e 100644 --- a/storage-s3/storage.ts +++ b/storage-s3/storage.ts @@ -1,25 +1,64 @@ +/** + * Storage module for S3 with common operations using MinIO. + * @module + */ + // import packages import { Buffer } from 'node:buffer' -import process from 'node:process' +import { getRequiredEnv } from '@frytg/check-required-env/get' +// @deno-types="minio/dist/esm/minio.d.mts" +import type { BucketItem, BucketItemStat, BucketStream, Client } from 'minio' // load utils import { minioClient } from './s3.ts' -// get object from s3 -export const getObject = (path: string, parseJson = false, throwError = true) => +/** + * The MinIO client. + * @type {Client} + */ +export const client: Client = minioClient + +/** + * Retrieves an object from S3. + * @param {string} path - The path to the object in S3. + * @param {object} options - The options for the operation. + * @param {boolean} options.parseJson - Whether to parse the object as JSON. Defaults to `false`. + * @param {boolean} options.throwError - Whether to throw an error if the object does not exist. Defaults to `true`. + * @param {string} options.bucketName - The name of the bucket to retrieve the object from. Defaults to env `S3_BUCKET_NAME`. + * @returns {Promise} + * + * @see https://min.io/docs/minio/linux/developers/javascript/API.html#getObject + * + * @example + * ```ts + * import { getObject } from '@frytg/storage-s3' + * + * const object = await getObject('path/to/object.json', { parseJson: true }) + * console.log(object) + * ``` + */ +export const getObject = ( + path: string, + { + parseJson = false, + throwError = true, + bucketName = getRequiredEnv('S3_BUCKET_NAME'), + }: { + parseJson?: boolean + throwError?: boolean + bucketName?: string + }, +): Promise => new Promise((resolve, reject) => { - const chunks = [] + const chunks: Buffer[] = [] minioClient - .getObject(process.env.S3_BUCKET_NAME, path) + .getObject(bucketName, path) .then((stream) => { stream.on('data', (chunk) => chunks.push(chunk)) stream.on('end', () => { const buffer = Buffer.concat(chunks) - if (parseJson) { - resolve(JSON.parse(buffer.toString('utf-8'))) - } else { - resolve(buffer) - } + if (parseJson) resolve(JSON.parse(buffer.toString('utf-8'))) + else resolve(buffer) }) stream.on('error', reject) }) @@ -29,8 +68,25 @@ export const getObject = (path: string, parseJson = false, throwError = true) => }) }) -// upload object to s3 -export const uploadObject = async (path: string, data) => { +/** + * Uploads an object to S3. + * @param {string} path - The path to the object in S3. + * @param {Buffer | string} data - The data to upload. Can be a Buffer or a string. If JSON is passed, it will be stringified. + * @param {object} options - The options for the operation. + * @param {string} options.bucketName - The name of the bucket to upload the object to. Defaults to env `S3_BUCKET_NAME`. + * @returns {Promise} + * + * @see https://min.io/docs/minio/linux/developers/javascript/API.html#putObject + */ +export const uploadObject = async ( + path: string, + data: Buffer | string, + { + bucketName = getRequiredEnv('S3_BUCKET_NAME'), + }: { + bucketName?: string + }, +): Promise => { // convert data to string if it's an object or array let dataString = data if (typeof data === 'object' || Array.isArray(data)) { @@ -38,32 +94,64 @@ export const uploadObject = async (path: string, data) => { } // upload object - await minioClient.putObject(process.env.S3_BUCKET_NAME, path, dataString) + await minioClient.putObject(bucketName, path, dataString) } -// check if object exists in s3 -export const objectExists = async (path: string) => { +/** + * Checks if an object exists in S3. + * @param {string} path - The path to the object in S3. + * @param {object} options - The options for the operation. + * @param {string} options.bucketName - The name of the bucket to check for the object. Defaults to env `S3_BUCKET_NAME`. + * @returns {Promise} + * + * @see https://min.io/docs/minio/linux/developers/javascript/API.html#statObject + */ +export const objectExists = async ( + path: string, + { + bucketName = getRequiredEnv('S3_BUCKET_NAME'), + }: { + bucketName?: string + }, +): Promise> => { try { - const result = await minioClient.statObject(process.env.S3_BUCKET_NAME, path) + // explicitly awaiting the result to avoid unhandled promise rejection + const result: BucketItemStat = await minioClient.statObject(bucketName, path) return result } catch (_error) { return false } } -// convert a readable stream to a string -const readableStreamToString = (stream: ReadableStream) => { - return new Promise((resolve, reject) => { - const chunks = [] +// convert a readable stream to a string (for listObjects) +const readableStreamForListObjects = (stream: BucketStream): Promise => + new Promise((resolve, reject) => { + const chunks: BucketItem[] = [] stream.on('data', (chunk) => chunks.push(chunk)) stream.on('end', () => resolve(chunks)) stream.on('error', reject) }) -} -// list objects in s3 with v2 -export const listObjects = async (prefix: string, recursive = false) => { - const result = await minioClient.listObjectsV2(process.env.S3_BUCKET_NAME, prefix, recursive) - const data = await readableStreamToString(result) - return data +/** + * Lists objects in S3. + * @param {string} prefix - The prefix to filter the objects by. + * @param {object} options - The options for the operation. + * @param {boolean} options.recursive - Whether to list recursively. Defaults to `false`. + * @param {string} options.bucketName - The name of the bucket to list the objects from. Defaults to env `S3_BUCKET_NAME`. + * @returns {Promise} + * + * @see https://min.io/docs/minio/linux/developers/javascript/API.html#listObjectsV2 + */ +export const listObjects = async ( + prefix: string, + { + recursive = false, + bucketName = getRequiredEnv('S3_BUCKET_NAME'), + }: { + recursive?: boolean + bucketName?: string + }, +): Promise => { + const result: BucketStream = await minioClient.listObjectsV2(bucketName, prefix, recursive) + return readableStreamForListObjects(result) }