chore: initial commit
This commit is contained in:
commit
cd7ea67942
44 changed files with 3621 additions and 0 deletions
121
dist/client.d.ts
vendored
Normal file
121
dist/client.d.ts
vendored
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
import { S3Client } from '@aws-sdk/client-s3';
|
||||
import type { MinioConfig, GenerateUploadUrlOptions, GenerateDownloadUrlOptions, PresignedUrl, UploadOptions, ListOptions, ListResult, ObjectInfo, CopyOptions, ExistsResult } from './types.js';
|
||||
/**
|
||||
* MinIO Client - Pure TypeScript wrapper around AWS S3 SDK
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { MinioClient } from '@lilith/minio'
|
||||
*
|
||||
* const client = new MinioClient({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* })
|
||||
*
|
||||
* // Generate presigned upload URL
|
||||
* const { url, expiresAt } = await client.generateUploadUrl({
|
||||
* key: 'uploads/image.png',
|
||||
* contentType: 'image/png',
|
||||
* })
|
||||
*
|
||||
* // Upload directly
|
||||
* await client.upload({
|
||||
* key: 'uploads/data.json',
|
||||
* body: Buffer.from(JSON.stringify({ hello: 'world' })),
|
||||
* contentType: 'application/json',
|
||||
* })
|
||||
*
|
||||
* // Download
|
||||
* const buffer = await client.download('uploads/data.json')
|
||||
* ```
|
||||
*/
|
||||
export declare class MinioClient {
|
||||
private readonly s3Client;
|
||||
private readonly bucket;
|
||||
private readonly config;
|
||||
constructor(config: MinioConfig);
|
||||
/**
|
||||
* Get the underlying S3Client for advanced operations
|
||||
*/
|
||||
getS3Client(): S3Client;
|
||||
/**
|
||||
* Get the default bucket name
|
||||
*/
|
||||
getBucket(): string;
|
||||
/**
|
||||
* Get the config
|
||||
*/
|
||||
getConfig(): MinioConfig;
|
||||
/**
|
||||
* Generate a presigned URL for uploading an object
|
||||
*/
|
||||
generateUploadUrl(options: GenerateUploadUrlOptions): Promise<PresignedUrl>;
|
||||
/**
|
||||
* Generate a presigned URL for downloading an object
|
||||
*/
|
||||
generateDownloadUrl(options: GenerateDownloadUrlOptions): Promise<PresignedUrl>;
|
||||
/**
|
||||
* Generate a simple download URL (convenience method)
|
||||
*/
|
||||
getDownloadUrl(key: string, expiresIn?: number): Promise<string>;
|
||||
/**
|
||||
* Upload data directly to MinIO
|
||||
*/
|
||||
upload(options: UploadOptions): Promise<void>;
|
||||
/**
|
||||
* Upload a buffer with automatic content-type detection
|
||||
*/
|
||||
uploadBuffer(key: string, buffer: Buffer, contentType?: string): Promise<void>;
|
||||
/**
|
||||
* Download an object as a Buffer
|
||||
*/
|
||||
download(key: string): Promise<Buffer>;
|
||||
/**
|
||||
* Download an object as a string
|
||||
*/
|
||||
downloadString(key: string, encoding?: BufferEncoding): Promise<string>;
|
||||
/**
|
||||
* Download an object as JSON
|
||||
*/
|
||||
downloadJson<T = unknown>(key: string): Promise<T>;
|
||||
/**
|
||||
* Delete an object
|
||||
*/
|
||||
delete(key: string): Promise<void>;
|
||||
/**
|
||||
* Delete multiple objects
|
||||
*/
|
||||
deleteMany(keys: string[]): Promise<void>;
|
||||
/**
|
||||
* Check if an object exists
|
||||
*/
|
||||
exists(key: string): Promise<ExistsResult>;
|
||||
/**
|
||||
* List objects in the bucket
|
||||
*/
|
||||
list(options?: ListOptions): Promise<ListResult>;
|
||||
/**
|
||||
* List all objects with a prefix (handles pagination)
|
||||
*/
|
||||
listAll(prefix?: string): Promise<ObjectInfo[]>;
|
||||
/**
|
||||
* Copy an object within MinIO
|
||||
*/
|
||||
copy(options: CopyOptions): Promise<void>;
|
||||
/**
|
||||
* Move an object (copy + delete)
|
||||
*/
|
||||
move(sourceKey: string, destinationKey: string): Promise<void>;
|
||||
/**
|
||||
* Get object metadata without downloading the content
|
||||
*/
|
||||
getMetadata(key: string): Promise<ExistsResult['metadata']>;
|
||||
}
|
||||
/**
|
||||
* Create a MinioClient instance
|
||||
*/
|
||||
export declare function createMinioClient(config: MinioConfig): MinioClient;
|
||||
//# sourceMappingURL=client.d.ts.map
|
||||
1
dist/client.d.ts.map
vendored
Normal file
1
dist/client.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,QAAQ,EAOT,MAAM,oBAAoB,CAAA;AAG3B,OAAO,KAAK,EACV,WAAW,EACX,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACb,MAAM,YAAY,CAAA;AAGnB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAU;IACnC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAExB,MAAM,EAAE,WAAW;IAmB/B;;OAEG;IACH,WAAW,IAAI,QAAQ;IAIvB;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACH,SAAS,IAAI,WAAW;IAIxB;;OAEG;IACG,iBAAiB,CACrB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,YAAY,CAAC;IAgBxB;;OAEG;IACG,mBAAmB,CACvB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,YAAY,CAAC;IAgBxB;;OAEG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAKpE;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAkBnD;;OAEG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAQhB;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqB5C;;OAEG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,cAAwB,GAAG,OAAO,CAAC,MAAM,CAAC;IAKtF;;OAEG;IACG,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAKxD;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASxC;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAY/C;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAqChD;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IA+BtD;;OAEG;IACG,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAiBrD;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAgB/C;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASpE;;OAEG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;CASlE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,WAAW,CAElE"}
|
||||
314
dist/client.js
vendored
Normal file
314
dist/client.js
vendored
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
import { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, HeadObjectCommand, ListObjectsV2Command, CopyObjectCommand, } from '@aws-sdk/client-s3';
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
|
||||
import { buildEndpointUrl, validateConfig, getMimeType } from './index.js';
|
||||
/**
|
||||
* MinIO Client - Pure TypeScript wrapper around AWS S3 SDK
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { MinioClient } from '@lilith/minio'
|
||||
*
|
||||
* const client = new MinioClient({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* })
|
||||
*
|
||||
* // Generate presigned upload URL
|
||||
* const { url, expiresAt } = await client.generateUploadUrl({
|
||||
* key: 'uploads/image.png',
|
||||
* contentType: 'image/png',
|
||||
* })
|
||||
*
|
||||
* // Upload directly
|
||||
* await client.upload({
|
||||
* key: 'uploads/data.json',
|
||||
* body: Buffer.from(JSON.stringify({ hello: 'world' })),
|
||||
* contentType: 'application/json',
|
||||
* })
|
||||
*
|
||||
* // Download
|
||||
* const buffer = await client.download('uploads/data.json')
|
||||
* ```
|
||||
*/
|
||||
export class MinioClient {
|
||||
s3Client;
|
||||
bucket;
|
||||
config;
|
||||
constructor(config) {
|
||||
validateConfig(config);
|
||||
this.config = config;
|
||||
this.bucket = config.bucket;
|
||||
const endpointUrl = buildEndpointUrl(config);
|
||||
this.s3Client = new S3Client({
|
||||
endpoint: endpointUrl,
|
||||
region: config.region ?? 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: config.accessKey,
|
||||
secretAccessKey: config.secretKey,
|
||||
},
|
||||
forcePathStyle: true, // Required for MinIO
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Get the underlying S3Client for advanced operations
|
||||
*/
|
||||
getS3Client() {
|
||||
return this.s3Client;
|
||||
}
|
||||
/**
|
||||
* Get the default bucket name
|
||||
*/
|
||||
getBucket() {
|
||||
return this.bucket;
|
||||
}
|
||||
/**
|
||||
* Get the config
|
||||
*/
|
||||
getConfig() {
|
||||
return { ...this.config };
|
||||
}
|
||||
/**
|
||||
* Generate a presigned URL for uploading an object
|
||||
*/
|
||||
async generateUploadUrl(options) {
|
||||
const expiresIn = options.expiresIn ?? 3600;
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: options.key,
|
||||
ContentType: options.contentType,
|
||||
Metadata: options.metadata,
|
||||
});
|
||||
const url = await getSignedUrl(this.s3Client, command, { expiresIn });
|
||||
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
|
||||
return { url, expiresAt };
|
||||
}
|
||||
/**
|
||||
* Generate a presigned URL for downloading an object
|
||||
*/
|
||||
async generateDownloadUrl(options) {
|
||||
const expiresIn = options.expiresIn ?? 3600;
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: options.key,
|
||||
ResponseContentDisposition: options.responseContentDisposition,
|
||||
ResponseContentType: options.responseContentType,
|
||||
});
|
||||
const url = await getSignedUrl(this.s3Client, command, { expiresIn });
|
||||
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
|
||||
return { url, expiresAt };
|
||||
}
|
||||
/**
|
||||
* Generate a simple download URL (convenience method)
|
||||
*/
|
||||
async getDownloadUrl(key, expiresIn = 3600) {
|
||||
const result = await this.generateDownloadUrl({ key, expiresIn });
|
||||
return result.url;
|
||||
}
|
||||
/**
|
||||
* Upload data directly to MinIO
|
||||
*/
|
||||
async upload(options) {
|
||||
const body = typeof options.body === 'string'
|
||||
? Buffer.from(options.body)
|
||||
: options.body;
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: options.key,
|
||||
Body: body,
|
||||
ContentType: options.contentType,
|
||||
Metadata: options.metadata,
|
||||
CacheControl: options.cacheControl,
|
||||
});
|
||||
await this.s3Client.send(command);
|
||||
}
|
||||
/**
|
||||
* Upload a buffer with automatic content-type detection
|
||||
*/
|
||||
async uploadBuffer(key, buffer, contentType) {
|
||||
await this.upload({
|
||||
key,
|
||||
body: buffer,
|
||||
contentType: contentType ?? getMimeType(key),
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Download an object as a Buffer
|
||||
*/
|
||||
async download(key) {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
});
|
||||
const response = await this.s3Client.send(command);
|
||||
if (!response.Body) {
|
||||
throw new Error(`Empty response body for key: ${key}`);
|
||||
}
|
||||
// Convert stream to buffer
|
||||
const chunks = [];
|
||||
for await (const chunk of response.Body) {
|
||||
chunks.push(chunk);
|
||||
}
|
||||
return Buffer.concat(chunks);
|
||||
}
|
||||
/**
|
||||
* Download an object as a string
|
||||
*/
|
||||
async downloadString(key, encoding = 'utf-8') {
|
||||
const buffer = await this.download(key);
|
||||
return buffer.toString(encoding);
|
||||
}
|
||||
/**
|
||||
* Download an object as JSON
|
||||
*/
|
||||
async downloadJson(key) {
|
||||
const str = await this.downloadString(key);
|
||||
return JSON.parse(str);
|
||||
}
|
||||
/**
|
||||
* Delete an object
|
||||
*/
|
||||
async delete(key) {
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
});
|
||||
await this.s3Client.send(command);
|
||||
}
|
||||
/**
|
||||
* Delete multiple objects
|
||||
*/
|
||||
async deleteMany(keys) {
|
||||
// S3 batch delete has a limit of 1000 keys
|
||||
const batches = [];
|
||||
for (let i = 0; i < keys.length; i += 1000) {
|
||||
batches.push(keys.slice(i, i + 1000));
|
||||
}
|
||||
for (const batch of batches) {
|
||||
await Promise.all(batch.map((key) => this.delete(key)));
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if an object exists
|
||||
*/
|
||||
async exists(key) {
|
||||
try {
|
||||
const command = new HeadObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
});
|
||||
const response = await this.s3Client.send(command);
|
||||
return {
|
||||
exists: true,
|
||||
metadata: {
|
||||
contentType: response.ContentType,
|
||||
contentLength: response.ContentLength,
|
||||
lastModified: response.LastModified,
|
||||
etag: response.ETag,
|
||||
},
|
||||
};
|
||||
}
|
||||
catch (error) {
|
||||
// Check if it's a "not found" error
|
||||
if (error instanceof Error &&
|
||||
(error.name === 'NotFound' || error.name === '404')) {
|
||||
return { exists: false };
|
||||
}
|
||||
// Check for S3 error codes
|
||||
const s3Error = error;
|
||||
if (s3Error.$metadata?.httpStatusCode === 404) {
|
||||
return { exists: false };
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* List objects in the bucket
|
||||
*/
|
||||
async list(options) {
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: this.bucket,
|
||||
Prefix: options?.prefix,
|
||||
MaxKeys: options?.maxKeys ?? 1000,
|
||||
ContinuationToken: options?.continuationToken,
|
||||
Delimiter: options?.delimiter,
|
||||
});
|
||||
const response = await this.s3Client.send(command);
|
||||
const objects = (response.Contents ?? []).map((obj) => ({
|
||||
key: obj.Key,
|
||||
lastModified: obj.LastModified,
|
||||
size: obj.Size,
|
||||
etag: obj.ETag,
|
||||
storageClass: obj.StorageClass,
|
||||
}));
|
||||
const prefixes = (response.CommonPrefixes ?? [])
|
||||
.map((p) => p.Prefix)
|
||||
.filter((p) => p !== undefined);
|
||||
return {
|
||||
objects,
|
||||
prefixes,
|
||||
isTruncated: response.IsTruncated ?? false,
|
||||
nextContinuationToken: response.NextContinuationToken,
|
||||
};
|
||||
}
|
||||
/**
|
||||
* List all objects with a prefix (handles pagination)
|
||||
*/
|
||||
async listAll(prefix) {
|
||||
const allObjects = [];
|
||||
let continuationToken;
|
||||
do {
|
||||
const result = await this.list({
|
||||
prefix,
|
||||
continuationToken,
|
||||
});
|
||||
allObjects.push(...result.objects);
|
||||
continuationToken = result.nextContinuationToken;
|
||||
} while (continuationToken);
|
||||
return allObjects;
|
||||
}
|
||||
/**
|
||||
* Copy an object within MinIO
|
||||
*/
|
||||
async copy(options) {
|
||||
const sourceBucket = options.sourceBucket ?? this.bucket;
|
||||
const destinationBucket = options.destinationBucket ?? this.bucket;
|
||||
const command = new CopyObjectCommand({
|
||||
Bucket: destinationBucket,
|
||||
Key: options.destinationKey,
|
||||
CopySource: encodeURIComponent(`${sourceBucket}/${options.sourceKey}`),
|
||||
Metadata: options.metadata,
|
||||
ContentType: options.contentType,
|
||||
MetadataDirective: options.metadata ? 'REPLACE' : 'COPY',
|
||||
});
|
||||
await this.s3Client.send(command);
|
||||
}
|
||||
/**
|
||||
* Move an object (copy + delete)
|
||||
*/
|
||||
async move(sourceKey, destinationKey) {
|
||||
await this.copy({
|
||||
sourceKey,
|
||||
destinationKey,
|
||||
});
|
||||
await this.delete(sourceKey);
|
||||
}
|
||||
/**
|
||||
* Get object metadata without downloading the content
|
||||
*/
|
||||
async getMetadata(key) {
|
||||
const result = await this.exists(key);
|
||||
if (!result.exists) {
|
||||
throw new Error(`Object not found: ${key}`);
|
||||
}
|
||||
return result.metadata;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a MinioClient instance
|
||||
*/
|
||||
export function createMinioClient(config) {
|
||||
return new MinioClient(config);
|
||||
}
|
||||
128
dist/config.d.ts
vendored
Normal file
128
dist/config.d.ts
vendored
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
import type { MinioConfig } from './types.js';
|
||||
/**
|
||||
* Build MinioConfig from environment variables
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Uses MINIO_* environment variables
|
||||
* const config = buildConfigFromEnv()
|
||||
*
|
||||
* // With custom prefix
|
||||
* const config = buildConfigFromEnv({ prefix: 'STORAGE_' })
|
||||
* ```
|
||||
*/
|
||||
export declare function buildConfigFromEnv(options?: {
|
||||
/**
|
||||
* Environment variable prefix
|
||||
* @default 'MINIO_'
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* Default bucket if MINIO_BUCKET not set
|
||||
*/
|
||||
defaultBucket?: string;
|
||||
/**
|
||||
* Environment object (default: process.env)
|
||||
*/
|
||||
env?: Record<string, string | undefined>;
|
||||
}): MinioConfig;
|
||||
/**
|
||||
* Build MinioConfig from a URL
|
||||
*
|
||||
* Use this when you have a MinIO URL from service discovery or configuration.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const config = buildConfigFromServiceAddresses({
|
||||
* minioUrl: process.env.MINIO_URL ?? 'http://localhost:9000',
|
||||
* accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
* secretKey: process.env.MINIO_SECRET_KEY,
|
||||
* bucket: 'my-bucket',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export declare function buildConfigFromServiceAddresses(options: {
|
||||
/**
|
||||
* MinIO service URL from service-addresses (e.g., 'http://localhost:9000')
|
||||
*/
|
||||
minioUrl: string;
|
||||
/**
|
||||
* Access key
|
||||
*/
|
||||
accessKey: string;
|
||||
/**
|
||||
* Secret key
|
||||
*/
|
||||
secretKey: string;
|
||||
/**
|
||||
* Bucket name
|
||||
*/
|
||||
bucket: string;
|
||||
/**
|
||||
* Region
|
||||
* @default 'us-east-1'
|
||||
*/
|
||||
region?: string;
|
||||
}): MinioConfig;
|
||||
/**
|
||||
* Build the full endpoint URL from config
|
||||
*/
|
||||
export declare function buildEndpointUrl(config: MinioConfig): string;
|
||||
/**
|
||||
* Validate MinioConfig
|
||||
*/
|
||||
export declare function validateConfig(config: MinioConfig): void;
|
||||
/**
|
||||
* Create a config object with defaults applied
|
||||
*/
|
||||
export declare function withDefaults(config: MinioConfig): Required<MinioConfig>;
|
||||
/**
|
||||
* Options for getMinioConfig
|
||||
*/
|
||||
export interface GetMinioConfigOptions {
|
||||
/**
|
||||
* MinIO access key (falls back to MINIO_ACCESS_KEY env var)
|
||||
*/
|
||||
accessKey?: string;
|
||||
/**
|
||||
* MinIO secret key (falls back to MINIO_SECRET_KEY env var)
|
||||
*/
|
||||
secretKey?: string;
|
||||
/**
|
||||
* Bucket name (defaults to featureId)
|
||||
*/
|
||||
bucket?: string;
|
||||
/**
|
||||
* Use SSL/TLS (falls back to MINIO_USE_SSL env var)
|
||||
* @default false
|
||||
*/
|
||||
useSSL?: boolean;
|
||||
}
|
||||
/**
|
||||
* Get MinIO configuration for a feature using service-registry
|
||||
*
|
||||
* This function looks up the MinIO service for a feature in the service registry
|
||||
* and builds a MinioConfig object with the resolved port and credentials.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getMinioConfig } from '@lilith/minio'
|
||||
*
|
||||
* // Basic usage - looks up 'seo.minio' service
|
||||
* const config = getMinioConfig('seo')
|
||||
*
|
||||
* // With custom options
|
||||
* const config = getMinioConfig('seo', {
|
||||
* bucket: 'seo-assets',
|
||||
* useSSL: true,
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param featureId - The feature ID (e.g., 'seo', 'landing')
|
||||
* @param options - Optional configuration overrides
|
||||
* @returns MinioConfig object ready for use with MinioClient
|
||||
* @throws Error if no MinIO service found for the feature
|
||||
* @throws Error if credentials not provided and not in environment
|
||||
*/
|
||||
export declare function getMinioConfig(featureId: string, options?: GetMinioConfigOptions): Promise<MinioConfig>;
|
||||
//# sourceMappingURL=config.d.ts.map
|
||||
1
dist/config.d.ts.map
vendored
Normal file
1
dist/config.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE7C;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAC3C;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;OAEG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACzC,GAAG,WAAW,CAuCd;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,+BAA+B,CAAC,OAAO,EAAE;IACvD;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB,GAAG,WAAW,CAYd;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,GAAG,MAAM,CAe5D;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAoBxD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,WAAW,GAAG,QAAQ,CAAC,WAAW,CAAC,CAUvE;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,WAAW,CAAC,CAiCtB"}
|
||||
173
dist/config.js
vendored
Normal file
173
dist/config.js
vendored
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/**
|
||||
* Build MinioConfig from environment variables
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Uses MINIO_* environment variables
|
||||
* const config = buildConfigFromEnv()
|
||||
*
|
||||
* // With custom prefix
|
||||
* const config = buildConfigFromEnv({ prefix: 'STORAGE_' })
|
||||
* ```
|
||||
*/
|
||||
export function buildConfigFromEnv(options) {
|
||||
const prefix = options?.prefix ?? 'MINIO_';
|
||||
const env = options?.env ?? process.env;
|
||||
const endpoint = env[`${prefix}ENDPOINT`];
|
||||
const port = env[`${prefix}PORT`];
|
||||
const accessKey = env[`${prefix}ACCESS_KEY`];
|
||||
const secretKey = env[`${prefix}SECRET_KEY`];
|
||||
const bucket = env[`${prefix}BUCKET`] ?? options?.defaultBucket;
|
||||
const useSSL = env[`${prefix}USE_SSL`];
|
||||
const region = env[`${prefix}REGION`];
|
||||
if (!endpoint) {
|
||||
throw new Error(`${prefix}ENDPOINT environment variable is required`);
|
||||
}
|
||||
if (!accessKey) {
|
||||
throw new Error(`${prefix}ACCESS_KEY environment variable is required`);
|
||||
}
|
||||
if (!secretKey) {
|
||||
throw new Error(`${prefix}SECRET_KEY environment variable is required`);
|
||||
}
|
||||
if (!bucket) {
|
||||
throw new Error(`${prefix}BUCKET environment variable is required (or provide defaultBucket option)`);
|
||||
}
|
||||
return {
|
||||
endpoint,
|
||||
port: port ? parseInt(port, 10) : undefined,
|
||||
accessKey,
|
||||
secretKey,
|
||||
bucket,
|
||||
useSSL: useSSL === 'true',
|
||||
region: region ?? 'us-east-1',
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Build MinioConfig from a URL
|
||||
*
|
||||
* Use this when you have a MinIO URL from service discovery or configuration.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const config = buildConfigFromServiceAddresses({
|
||||
* minioUrl: process.env.MINIO_URL ?? 'http://localhost:9000',
|
||||
* accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
* secretKey: process.env.MINIO_SECRET_KEY,
|
||||
* bucket: 'my-bucket',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function buildConfigFromServiceAddresses(options) {
|
||||
const url = new URL(options.minioUrl);
|
||||
return {
|
||||
endpoint: url.hostname,
|
||||
port: url.port ? parseInt(url.port, 10) : undefined,
|
||||
accessKey: options.accessKey,
|
||||
secretKey: options.secretKey,
|
||||
bucket: options.bucket,
|
||||
useSSL: url.protocol === 'https:',
|
||||
region: options.region ?? 'us-east-1',
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Build the full endpoint URL from config
|
||||
*/
|
||||
export function buildEndpointUrl(config) {
|
||||
const protocol = config.useSSL ? 'https' : 'http';
|
||||
const port = config.port ?? (config.useSSL ? 443 : 9000);
|
||||
// Check if endpoint already includes port
|
||||
if (config.endpoint.includes(':')) {
|
||||
return `${protocol}://${config.endpoint}`;
|
||||
}
|
||||
// Don't include default ports
|
||||
if ((config.useSSL && port === 443) || (!config.useSSL && port === 80)) {
|
||||
return `${protocol}://${config.endpoint}`;
|
||||
}
|
||||
return `${protocol}://${config.endpoint}:${port}`;
|
||||
}
|
||||
/**
|
||||
* Validate MinioConfig
|
||||
*/
|
||||
export function validateConfig(config) {
|
||||
if (!config.endpoint) {
|
||||
throw new Error('MinIO endpoint is required');
|
||||
}
|
||||
if (!config.accessKey) {
|
||||
throw new Error('MinIO accessKey is required');
|
||||
}
|
||||
if (!config.secretKey) {
|
||||
throw new Error('MinIO secretKey is required');
|
||||
}
|
||||
if (!config.bucket) {
|
||||
throw new Error('MinIO bucket is required');
|
||||
}
|
||||
if (config.port !== undefined && (config.port < 1 || config.port > 65535)) {
|
||||
throw new Error('MinIO port must be between 1 and 65535');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Create a config object with defaults applied
|
||||
*/
|
||||
export function withDefaults(config) {
|
||||
return {
|
||||
endpoint: config.endpoint,
|
||||
accessKey: config.accessKey,
|
||||
secretKey: config.secretKey,
|
||||
bucket: config.bucket,
|
||||
region: config.region ?? 'us-east-1',
|
||||
useSSL: config.useSSL ?? false,
|
||||
port: config.port ?? (config.useSSL ? 443 : 9000),
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Get MinIO configuration for a feature using service-registry
|
||||
*
|
||||
* This function looks up the MinIO service for a feature in the service registry
|
||||
* and builds a MinioConfig object with the resolved port and credentials.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getMinioConfig } from '@lilith/minio'
|
||||
*
|
||||
* // Basic usage - looks up 'seo.minio' service
|
||||
* const config = getMinioConfig('seo')
|
||||
*
|
||||
* // With custom options
|
||||
* const config = getMinioConfig('seo', {
|
||||
* bucket: 'seo-assets',
|
||||
* useSSL: true,
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param featureId - The feature ID (e.g., 'seo', 'landing')
|
||||
* @param options - Optional configuration overrides
|
||||
* @returns MinioConfig object ready for use with MinioClient
|
||||
* @throws Error if no MinIO service found for the feature
|
||||
* @throws Error if credentials not provided and not in environment
|
||||
*/
|
||||
export async function getMinioConfig(featureId, options) {
|
||||
// Dynamic import to avoid circular dependencies and allow optional peer dep
|
||||
const { getServiceRegistry } = await import('@lilith/service-registry');
|
||||
const registry = getServiceRegistry();
|
||||
const minio = registry.getServiceByParts(featureId, 'minio');
|
||||
if (!minio) {
|
||||
throw new Error(`No MinIO service found for feature: ${featureId}. ` +
|
||||
`Expected service '${featureId}.minio' to be defined in the service registry.`);
|
||||
}
|
||||
const accessKey = options?.accessKey ?? process.env.MINIO_ACCESS_KEY;
|
||||
const secretKey = options?.secretKey ?? process.env.MINIO_SECRET_KEY;
|
||||
if (!accessKey || !secretKey) {
|
||||
throw new Error('MinIO credentials not found. ' +
|
||||
'Set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables, ' +
|
||||
'or provide accessKey and secretKey in options.');
|
||||
}
|
||||
return {
|
||||
endpoint: process.env.MINIO_HOST ?? 'localhost',
|
||||
port: minio.port,
|
||||
accessKey,
|
||||
secretKey,
|
||||
bucket: options?.bucket ?? featureId,
|
||||
useSSL: options?.useSSL ?? process.env.MINIO_USE_SSL === 'true',
|
||||
region: 'us-east-1',
|
||||
};
|
||||
}
|
||||
47
dist/index.d.ts
vendored
Normal file
47
dist/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @lilith/minio - MinIO object storage client
|
||||
*
|
||||
* Provides a TypeScript wrapper around AWS S3 SDK for MinIO operations,
|
||||
* plus NestJS integration with dynamic modules.
|
||||
*
|
||||
* @example Pure TypeScript usage
|
||||
* ```ts
|
||||
* import { MinioClient, buildConfigFromEnv } from '@lilith/minio'
|
||||
*
|
||||
* const config = buildConfigFromEnv()
|
||||
* const client = new MinioClient(config)
|
||||
*
|
||||
* // Upload
|
||||
* await client.upload({
|
||||
* key: 'images/photo.jpg',
|
||||
* body: imageBuffer,
|
||||
* contentType: 'image/jpeg',
|
||||
* })
|
||||
*
|
||||
* // Generate presigned URL
|
||||
* const { url } = await client.generateDownloadUrl({ key: 'images/photo.jpg' })
|
||||
* ```
|
||||
*
|
||||
* @example NestJS usage
|
||||
* ```ts
|
||||
* import { MinioModule } from '@lilith/minio/nestjs'
|
||||
*
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRoot({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*/
|
||||
export type { MinioConfig, GenerateUploadUrlOptions, GenerateDownloadUrlOptions, PresignedUrl, UploadOptions, ListOptions, ListResult, ObjectInfo, CopyOptions, ExistsResult, } from './types.js';
|
||||
export { MIME_TYPES, getMimeType } from './types.js';
|
||||
export { buildConfigFromEnv, buildConfigFromServiceAddresses, buildEndpointUrl, validateConfig, withDefaults, getMinioConfig, type GetMinioConfigOptions, } from './config.js';
|
||||
export { MinioClient, createMinioClient } from './client.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
dist/index.d.ts.map
vendored
Normal file
1
dist/index.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAyCG;AAGH,YAAY,EACV,WAAW,EACX,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,YAAY,CAAA;AAEnB,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAGpD,OAAO,EACL,kBAAkB,EAClB,+BAA+B,EAC/B,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,cAAc,EACd,KAAK,qBAAqB,GAC3B,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA"}
|
||||
47
dist/index.js
vendored
Normal file
47
dist/index.js
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* @lilith/minio - MinIO object storage client
|
||||
*
|
||||
* Provides a TypeScript wrapper around AWS S3 SDK for MinIO operations,
|
||||
* plus NestJS integration with dynamic modules.
|
||||
*
|
||||
* @example Pure TypeScript usage
|
||||
* ```ts
|
||||
* import { MinioClient, buildConfigFromEnv } from '@lilith/minio'
|
||||
*
|
||||
* const config = buildConfigFromEnv()
|
||||
* const client = new MinioClient(config)
|
||||
*
|
||||
* // Upload
|
||||
* await client.upload({
|
||||
* key: 'images/photo.jpg',
|
||||
* body: imageBuffer,
|
||||
* contentType: 'image/jpeg',
|
||||
* })
|
||||
*
|
||||
* // Generate presigned URL
|
||||
* const { url } = await client.generateDownloadUrl({ key: 'images/photo.jpg' })
|
||||
* ```
|
||||
*
|
||||
* @example NestJS usage
|
||||
* ```ts
|
||||
* import { MinioModule } from '@lilith/minio/nestjs'
|
||||
*
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRoot({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*/
|
||||
export { MIME_TYPES, getMimeType } from './types.js';
|
||||
// Config builders
|
||||
export { buildConfigFromEnv, buildConfigFromServiceAddresses, buildEndpointUrl, validateConfig, withDefaults, getMinioConfig, } from './config.js';
|
||||
// Client
|
||||
export { MinioClient, createMinioClient } from './client.js';
|
||||
12
dist/nestjs/constants.d.ts
vendored
Normal file
12
dist/nestjs/constants.d.ts
vendored
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Injection tokens for MinIO NestJS module
|
||||
*/
|
||||
/**
|
||||
* Injection token for MinioConfig
|
||||
*/
|
||||
export declare const MINIO_CONFIG: unique symbol;
|
||||
/**
|
||||
* Injection token for MinioService
|
||||
*/
|
||||
export declare const MINIO_SERVICE: unique symbol;
|
||||
//# sourceMappingURL=constants.d.ts.map
|
||||
1
dist/nestjs/constants.d.ts.map
vendored
Normal file
1
dist/nestjs/constants.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/nestjs/constants.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;GAEG;AACH,eAAO,MAAM,YAAY,eAAyB,CAAA;AAElD;;GAEG;AACH,eAAO,MAAM,aAAa,eAA0B,CAAA"}
|
||||
11
dist/nestjs/constants.js
vendored
Normal file
11
dist/nestjs/constants.js
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Injection tokens for MinIO NestJS module
|
||||
*/
|
||||
/**
|
||||
* Injection token for MinioConfig
|
||||
*/
|
||||
export const MINIO_CONFIG = Symbol('MINIO_CONFIG');
|
||||
/**
|
||||
* Injection token for MinioService
|
||||
*/
|
||||
export const MINIO_SERVICE = Symbol('MINIO_SERVICE');
|
||||
39
dist/nestjs/index.d.ts
vendored
Normal file
39
dist/nestjs/index.d.ts
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/**
|
||||
* NestJS integration for @lilith/minio
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { MinioModule, MinioService } from '@lilith/minio/nestjs'
|
||||
*
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRootAsync({
|
||||
* imports: [ConfigModule],
|
||||
* useFactory: (config: ConfigService) => ({
|
||||
* endpoint: config.get('MINIO_ENDPOINT'),
|
||||
* port: config.get('MINIO_PORT'),
|
||||
* accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
* secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
* bucket: config.get('MINIO_BUCKET'),
|
||||
* }),
|
||||
* inject: [ConfigService],
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
*
|
||||
* @Injectable()
|
||||
* export class UploadService {
|
||||
* constructor(private readonly minio: MinioService) {}
|
||||
*
|
||||
* async uploadImage(filename: string, buffer: Buffer) {
|
||||
* await this.minio.uploadBuffer(`images/${filename}`, buffer, 'image/jpeg')
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export { MINIO_CONFIG, MINIO_SERVICE } from './constants.js';
|
||||
export { MinioModule, InjectMinio, type MinioModuleOptions, type MinioModuleAsyncOptions, } from './minio.module.js';
|
||||
export { MinioService } from './minio.service.js';
|
||||
export type { MinioConfig, GenerateUploadUrlOptions, GenerateDownloadUrlOptions, PresignedUrl, UploadOptions, ListOptions, ListResult, ObjectInfo, CopyOptions, ExistsResult, } from '../types.js';
|
||||
//# sourceMappingURL=index.d.ts.map
|
||||
1
dist/nestjs/index.d.ts.map
vendored
Normal file
1
dist/nestjs/index.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/nestjs/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAGH,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAG5D,OAAO,EACL,WAAW,EACX,WAAW,EACX,KAAK,kBAAkB,EACvB,KAAK,uBAAuB,GAC7B,MAAM,mBAAmB,CAAA;AAG1B,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAGjD,YAAY,EACV,WAAW,EACX,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,aAAa,CAAA"}
|
||||
40
dist/nestjs/index.js
vendored
Normal file
40
dist/nestjs/index.js
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/**
|
||||
* NestJS integration for @lilith/minio
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { MinioModule, MinioService } from '@lilith/minio/nestjs'
|
||||
*
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRootAsync({
|
||||
* imports: [ConfigModule],
|
||||
* useFactory: (config: ConfigService) => ({
|
||||
* endpoint: config.get('MINIO_ENDPOINT'),
|
||||
* port: config.get('MINIO_PORT'),
|
||||
* accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
* secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
* bucket: config.get('MINIO_BUCKET'),
|
||||
* }),
|
||||
* inject: [ConfigService],
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
*
|
||||
* @Injectable()
|
||||
* export class UploadService {
|
||||
* constructor(private readonly minio: MinioService) {}
|
||||
*
|
||||
* async uploadImage(filename: string, buffer: Buffer) {
|
||||
* await this.minio.uploadBuffer(`images/${filename}`, buffer, 'image/jpeg')
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
// Constants
|
||||
export { MINIO_CONFIG, MINIO_SERVICE } from './constants.js';
|
||||
// Module
|
||||
export { MinioModule, InjectMinio, } from './minio.module.js';
|
||||
// Service
|
||||
export { MinioService } from './minio.service.js';
|
||||
123
dist/nestjs/minio.module.d.ts
vendored
Normal file
123
dist/nestjs/minio.module.d.ts
vendored
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
import { type DynamicModule } from '@nestjs/common';
|
||||
import type { MinioConfig } from '../types.js';
|
||||
/**
|
||||
* Options for async configuration
|
||||
*/
|
||||
export interface MinioModuleAsyncOptions {
|
||||
/**
|
||||
* Imports required for useFactory
|
||||
*/
|
||||
imports?: any[];
|
||||
/**
|
||||
* Factory function to create config
|
||||
*/
|
||||
useFactory: (...args: any[]) => MinioConfig | Promise<MinioConfig>;
|
||||
/**
|
||||
* Dependencies to inject into factory
|
||||
*/
|
||||
inject?: any[];
|
||||
/**
|
||||
* Whether to make module global
|
||||
* @default true
|
||||
*/
|
||||
isGlobal?: boolean;
|
||||
}
|
||||
/**
|
||||
* Options for forRoot
|
||||
*/
|
||||
export interface MinioModuleOptions extends MinioConfig {
|
||||
/**
|
||||
* Whether to make module global
|
||||
* @default true
|
||||
*/
|
||||
isGlobal?: boolean;
|
||||
}
|
||||
/**
|
||||
* NestJS Dynamic Module for MinIO
|
||||
*
|
||||
* @example Static configuration
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRoot({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*
|
||||
* @example Async configuration with ConfigService
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRootAsync({
|
||||
* imports: [ConfigModule],
|
||||
* useFactory: (config: ConfigService) => ({
|
||||
* endpoint: config.get('MINIO_ENDPOINT'),
|
||||
* port: config.get('MINIO_PORT'),
|
||||
* accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
* secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
* bucket: config.get('MINIO_BUCKET'),
|
||||
* }),
|
||||
* inject: [ConfigService],
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*
|
||||
* @example Using environment variables
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forEnv(),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*/
|
||||
export declare class MinioModule {
|
||||
/**
|
||||
* Configure module with static options
|
||||
*/
|
||||
static forRoot(options: MinioModuleOptions): DynamicModule;
|
||||
/**
|
||||
* Configure module with async factory
|
||||
*/
|
||||
static forRootAsync(options: MinioModuleAsyncOptions): DynamicModule;
|
||||
/**
|
||||
* Configure module from environment variables
|
||||
*
|
||||
* Uses MINIO_* environment variables:
|
||||
* - MINIO_ENDPOINT
|
||||
* - MINIO_PORT
|
||||
* - MINIO_ACCESS_KEY
|
||||
* - MINIO_SECRET_KEY
|
||||
* - MINIO_BUCKET
|
||||
* - MINIO_USE_SSL (optional)
|
||||
* - MINIO_REGION (optional)
|
||||
*/
|
||||
static forEnv(options?: {
|
||||
prefix?: string;
|
||||
defaultBucket?: string;
|
||||
isGlobal?: boolean;
|
||||
}): DynamicModule;
|
||||
}
|
||||
/**
|
||||
* Decorator to inject MinioService
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* @Injectable()
|
||||
* export class MyService {
|
||||
* constructor(@InjectMinio() private readonly minio: MinioService) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare function InjectMinio(): ParameterDecorator;
|
||||
//# sourceMappingURL=minio.module.d.ts.map
|
||||
1
dist/nestjs/minio.module.d.ts.map
vendored
Normal file
1
dist/nestjs/minio.module.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"minio.module.d.ts","sourceRoot":"","sources":["../../src/nestjs/minio.module.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,aAAa,EAAiB,MAAM,gBAAgB,CAAA;AAClF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAK9C;;GAEG;AACH,MAAM,WAAW,uBAAuB;IACtC;;OAEG;IACH,OAAO,CAAC,EAAE,GAAG,EAAE,CAAA;IAEf;;OAEG;IACH,UAAU,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;IAElE;;OAEG;IACH,MAAM,CAAC,EAAE,GAAG,EAAE,CAAA;IAEd;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAmB,SAAQ,WAAW;IACrD;;;OAGG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AACH,qBAEa,WAAW;IACtB;;OAEG;IACH,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,kBAAkB,GAAG,aAAa;IAgB1D;;OAEG;IACH,MAAM,CAAC,YAAY,CAAC,OAAO,EAAE,uBAAuB,GAAG,aAAa;IAkBpE;;;;;;;;;;;OAWG;IACH,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE;QACtB,MAAM,CAAC,EAAE,MAAM,CAAA;QACf,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;KACnB,GAAG,aAAa;CAelB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,IAAI,kBAAkB,CAmBhD"}
|
||||
147
dist/nestjs/minio.module.js
vendored
Normal file
147
dist/nestjs/minio.module.js
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var MinioModule_1;
|
||||
import { Module, Global } from '@nestjs/common';
|
||||
import { MinioService } from './minio.service.js';
|
||||
import { buildConfigFromEnv } from '../config.js';
|
||||
import { MINIO_CONFIG } from './constants.js';
|
||||
/**
|
||||
* NestJS Dynamic Module for MinIO
|
||||
*
|
||||
* @example Static configuration
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRoot({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*
|
||||
* @example Async configuration with ConfigService
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRootAsync({
|
||||
* imports: [ConfigModule],
|
||||
* useFactory: (config: ConfigService) => ({
|
||||
* endpoint: config.get('MINIO_ENDPOINT'),
|
||||
* port: config.get('MINIO_PORT'),
|
||||
* accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
* secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
* bucket: config.get('MINIO_BUCKET'),
|
||||
* }),
|
||||
* inject: [ConfigService],
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*
|
||||
* @example Using environment variables
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forEnv(),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*/
|
||||
let MinioModule = MinioModule_1 = class MinioModule {
|
||||
/**
|
||||
* Configure module with static options
|
||||
*/
|
||||
static forRoot(options) {
|
||||
const { isGlobal = true, ...config } = options;
|
||||
const configProvider = {
|
||||
provide: MINIO_CONFIG,
|
||||
useValue: config,
|
||||
};
|
||||
return {
|
||||
module: MinioModule_1,
|
||||
global: isGlobal,
|
||||
providers: [configProvider, MinioService],
|
||||
exports: [MinioService, MINIO_CONFIG],
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Configure module with async factory
|
||||
*/
|
||||
static forRootAsync(options) {
|
||||
const { isGlobal = true } = options;
|
||||
const asyncConfigProvider = {
|
||||
provide: MINIO_CONFIG,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject ?? [],
|
||||
};
|
||||
return {
|
||||
module: MinioModule_1,
|
||||
global: isGlobal,
|
||||
imports: options.imports ?? [],
|
||||
providers: [asyncConfigProvider, MinioService],
|
||||
exports: [MinioService, MINIO_CONFIG],
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Configure module from environment variables
|
||||
*
|
||||
* Uses MINIO_* environment variables:
|
||||
* - MINIO_ENDPOINT
|
||||
* - MINIO_PORT
|
||||
* - MINIO_ACCESS_KEY
|
||||
* - MINIO_SECRET_KEY
|
||||
* - MINIO_BUCKET
|
||||
* - MINIO_USE_SSL (optional)
|
||||
* - MINIO_REGION (optional)
|
||||
*/
|
||||
static forEnv(options) {
|
||||
const { isGlobal = true, prefix, defaultBucket } = options ?? {};
|
||||
const configProvider = {
|
||||
provide: MINIO_CONFIG,
|
||||
useFactory: () => buildConfigFromEnv({ prefix, defaultBucket }),
|
||||
};
|
||||
return {
|
||||
module: MinioModule_1,
|
||||
global: isGlobal,
|
||||
providers: [configProvider, MinioService],
|
||||
exports: [MinioService, MINIO_CONFIG],
|
||||
};
|
||||
}
|
||||
};
|
||||
MinioModule = MinioModule_1 = __decorate([
|
||||
Global(),
|
||||
Module({})
|
||||
], MinioModule);
|
||||
export { MinioModule };
|
||||
/**
|
||||
* Decorator to inject MinioService
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* @Injectable()
|
||||
* export class MyService {
|
||||
* constructor(@InjectMinio() private readonly minio: MinioService) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function InjectMinio() {
|
||||
return (target, propertyKey, parameterIndex) => {
|
||||
// The Inject decorator from @nestjs/common handles this
|
||||
// We just provide a semantic alias
|
||||
const existingParams = Reflect.getMetadata('design:paramtypes', target, propertyKey) ||
|
||||
[];
|
||||
existingParams[parameterIndex] = MinioService;
|
||||
Reflect.defineMetadata('design:paramtypes', existingParams, target, propertyKey);
|
||||
};
|
||||
}
|
||||
99
dist/nestjs/minio.service.d.ts
vendored
Normal file
99
dist/nestjs/minio.service.d.ts
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
import { OnModuleDestroy } from '@nestjs/common';
|
||||
import { MinioClient } from '../client.js';
|
||||
import type { MinioConfig, GenerateUploadUrlOptions, GenerateDownloadUrlOptions, PresignedUrl, UploadOptions, ListOptions, ListResult, ObjectInfo, CopyOptions, ExistsResult } from '../types.js';
|
||||
/**
|
||||
* NestJS injectable service wrapping MinioClient
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* @Injectable()
|
||||
* export class UploadService {
|
||||
* constructor(private readonly minio: MinioService) {}
|
||||
*
|
||||
* async createUploadUrl(filename: string) {
|
||||
* return this.minio.generateUploadUrl({
|
||||
* key: `uploads/${filename}`,
|
||||
* contentType: 'image/jpeg',
|
||||
* })
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export declare class MinioService implements OnModuleDestroy {
|
||||
private readonly client;
|
||||
constructor(config: MinioConfig);
|
||||
onModuleDestroy(): void;
|
||||
/**
|
||||
* Get the underlying MinioClient for advanced operations
|
||||
*/
|
||||
getClient(): MinioClient;
|
||||
/**
|
||||
* Get the default bucket name
|
||||
*/
|
||||
getBucket(): string;
|
||||
/**
|
||||
* Generate a presigned URL for uploading an object
|
||||
*/
|
||||
generateUploadUrl(options: GenerateUploadUrlOptions): Promise<PresignedUrl>;
|
||||
/**
|
||||
* Generate a presigned URL for downloading an object
|
||||
*/
|
||||
generateDownloadUrl(options: GenerateDownloadUrlOptions): Promise<PresignedUrl>;
|
||||
/**
|
||||
* Generate a simple download URL (convenience method)
|
||||
*/
|
||||
getDownloadUrl(key: string, expiresIn?: number): Promise<string>;
|
||||
/**
|
||||
* Upload data directly to MinIO
|
||||
*/
|
||||
upload(options: UploadOptions): Promise<void>;
|
||||
/**
|
||||
* Upload a buffer with automatic content-type detection
|
||||
*/
|
||||
uploadBuffer(key: string, buffer: Buffer, contentType?: string): Promise<void>;
|
||||
/**
|
||||
* Download an object as a Buffer
|
||||
*/
|
||||
download(key: string): Promise<Buffer>;
|
||||
/**
|
||||
* Download an object as a string
|
||||
*/
|
||||
downloadString(key: string, encoding?: BufferEncoding): Promise<string>;
|
||||
/**
|
||||
* Download an object as JSON
|
||||
*/
|
||||
downloadJson<T = unknown>(key: string): Promise<T>;
|
||||
/**
|
||||
* Delete an object
|
||||
*/
|
||||
delete(key: string): Promise<void>;
|
||||
/**
|
||||
* Delete multiple objects
|
||||
*/
|
||||
deleteMany(keys: string[]): Promise<void>;
|
||||
/**
|
||||
* Check if an object exists
|
||||
*/
|
||||
exists(key: string): Promise<ExistsResult>;
|
||||
/**
|
||||
* List objects in the bucket
|
||||
*/
|
||||
list(options?: ListOptions): Promise<ListResult>;
|
||||
/**
|
||||
* List all objects with a prefix (handles pagination)
|
||||
*/
|
||||
listAll(prefix?: string): Promise<ObjectInfo[]>;
|
||||
/**
|
||||
* Copy an object within MinIO
|
||||
*/
|
||||
copy(options: CopyOptions): Promise<void>;
|
||||
/**
|
||||
* Move an object (copy + delete)
|
||||
*/
|
||||
move(sourceKey: string, destinationKey: string): Promise<void>;
|
||||
/**
|
||||
* Get object metadata without downloading the content
|
||||
*/
|
||||
getMetadata(key: string): Promise<ExistsResult['metadata']>;
|
||||
}
|
||||
//# sourceMappingURL=minio.service.d.ts.map
|
||||
1
dist/nestjs/minio.service.d.ts.map
vendored
Normal file
1
dist/nestjs/minio.service.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"minio.service.d.ts","sourceRoot":"","sources":["../../src/nestjs/minio.service.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,eAAe,EAAE,MAAM,gBAAgB,CAAA;AACpE,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,KAAK,EACV,WAAW,EACX,wBAAwB,EACxB,0BAA0B,EAC1B,YAAY,EACZ,aAAa,EACb,WAAW,EACX,UAAU,EACV,UAAU,EACV,WAAW,EACX,YAAY,EACb,MAAM,aAAa,CAAA;AAGpB;;;;;;;;;;;;;;;;;GAiBG;AACH,qBACa,YAAa,YAAW,eAAe;IAClD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAa;gBAEF,MAAM,EAAE,WAAW;IAIrD,eAAe,IAAI,IAAI;IAKvB;;OAEG;IACH,SAAS,IAAI,WAAW;IAIxB;;OAEG;IACH,SAAS,IAAI,MAAM;IAInB;;OAEG;IACG,iBAAiB,CACrB,OAAO,EAAE,wBAAwB,GAChC,OAAO,CAAC,YAAY,CAAC;IAIxB;;OAEG;IACG,mBAAmB,CACvB,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,YAAY,CAAC;IAIxB;;OAEG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,SAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAIpE;;OAEG;IACG,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD;;OAEG;IACG,YAAY,CAChB,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,IAAI,CAAC;IAIhB;;OAEG;IACG,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAI5C;;OAEG;IACG,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,cAAwB,GAAG,OAAO,CAAC,MAAM,CAAC;IAItF;;OAEG;IACG,YAAY,CAAC,CAAC,GAAG,OAAO,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC;IAIxD;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIxC;;OAEG;IACG,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAIhD;;OAEG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;IAItD;;OAEG;IACG,OAAO,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC;IAIrD;;OAEG;IACG,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAI/C;;OAEG;IACG,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIpE;;OAEG;IACG,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;CAGlE"}
|
||||
157
dist/nestjs/minio.service.js
vendored
Normal file
157
dist/nestjs/minio.service.js
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
var __metadata = (this && this.__metadata) || function (k, v) {
|
||||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
||||
};
|
||||
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
||||
return function (target, key) { decorator(target, key, paramIndex); }
|
||||
};
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { MinioClient } from '../client.js';
|
||||
import { MINIO_CONFIG } from './constants.js';
|
||||
/**
|
||||
* NestJS injectable service wrapping MinioClient
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* @Injectable()
|
||||
* export class UploadService {
|
||||
* constructor(private readonly minio: MinioService) {}
|
||||
*
|
||||
* async createUploadUrl(filename: string) {
|
||||
* return this.minio.generateUploadUrl({
|
||||
* key: `uploads/${filename}`,
|
||||
* contentType: 'image/jpeg',
|
||||
* })
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
let MinioService = class MinioService {
|
||||
client;
|
||||
constructor(config) {
|
||||
this.client = new MinioClient(config);
|
||||
}
|
||||
onModuleDestroy() {
|
||||
// S3Client doesn't require explicit cleanup, but this hook
|
||||
// is available if needed for future connection pooling
|
||||
}
|
||||
/**
|
||||
* Get the underlying MinioClient for advanced operations
|
||||
*/
|
||||
getClient() {
|
||||
return this.client;
|
||||
}
|
||||
/**
|
||||
* Get the default bucket name
|
||||
*/
|
||||
getBucket() {
|
||||
return this.client.getBucket();
|
||||
}
|
||||
/**
|
||||
* Generate a presigned URL for uploading an object
|
||||
*/
|
||||
async generateUploadUrl(options) {
|
||||
return this.client.generateUploadUrl(options);
|
||||
}
|
||||
/**
|
||||
* Generate a presigned URL for downloading an object
|
||||
*/
|
||||
async generateDownloadUrl(options) {
|
||||
return this.client.generateDownloadUrl(options);
|
||||
}
|
||||
/**
|
||||
* Generate a simple download URL (convenience method)
|
||||
*/
|
||||
async getDownloadUrl(key, expiresIn = 3600) {
|
||||
return this.client.getDownloadUrl(key, expiresIn);
|
||||
}
|
||||
/**
|
||||
* Upload data directly to MinIO
|
||||
*/
|
||||
async upload(options) {
|
||||
return this.client.upload(options);
|
||||
}
|
||||
/**
|
||||
* Upload a buffer with automatic content-type detection
|
||||
*/
|
||||
async uploadBuffer(key, buffer, contentType) {
|
||||
return this.client.uploadBuffer(key, buffer, contentType);
|
||||
}
|
||||
/**
|
||||
* Download an object as a Buffer
|
||||
*/
|
||||
async download(key) {
|
||||
return this.client.download(key);
|
||||
}
|
||||
/**
|
||||
* Download an object as a string
|
||||
*/
|
||||
async downloadString(key, encoding = 'utf-8') {
|
||||
return this.client.downloadString(key, encoding);
|
||||
}
|
||||
/**
|
||||
* Download an object as JSON
|
||||
*/
|
||||
async downloadJson(key) {
|
||||
return this.client.downloadJson(key);
|
||||
}
|
||||
/**
|
||||
* Delete an object
|
||||
*/
|
||||
async delete(key) {
|
||||
return this.client.delete(key);
|
||||
}
|
||||
/**
|
||||
* Delete multiple objects
|
||||
*/
|
||||
async deleteMany(keys) {
|
||||
return this.client.deleteMany(keys);
|
||||
}
|
||||
/**
|
||||
* Check if an object exists
|
||||
*/
|
||||
async exists(key) {
|
||||
return this.client.exists(key);
|
||||
}
|
||||
/**
|
||||
* List objects in the bucket
|
||||
*/
|
||||
async list(options) {
|
||||
return this.client.list(options);
|
||||
}
|
||||
/**
|
||||
* List all objects with a prefix (handles pagination)
|
||||
*/
|
||||
async listAll(prefix) {
|
||||
return this.client.listAll(prefix);
|
||||
}
|
||||
/**
|
||||
* Copy an object within MinIO
|
||||
*/
|
||||
async copy(options) {
|
||||
return this.client.copy(options);
|
||||
}
|
||||
/**
|
||||
* Move an object (copy + delete)
|
||||
*/
|
||||
async move(sourceKey, destinationKey) {
|
||||
return this.client.move(sourceKey, destinationKey);
|
||||
}
|
||||
/**
|
||||
* Get object metadata without downloading the content
|
||||
*/
|
||||
async getMetadata(key) {
|
||||
return this.client.getMetadata(key);
|
||||
}
|
||||
};
|
||||
MinioService = __decorate([
|
||||
Injectable(),
|
||||
__param(0, Inject(MINIO_CONFIG)),
|
||||
__metadata("design:paramtypes", [Object])
|
||||
], MinioService);
|
||||
export { MinioService };
|
||||
241
dist/types.d.ts
vendored
Normal file
241
dist/types.d.ts
vendored
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
/**
|
||||
* MinIO Client Configuration
|
||||
*/
|
||||
export interface MinioConfig {
|
||||
/**
|
||||
* MinIO server endpoint (e.g., 'localhost:9000' or 'minio.example.com')
|
||||
*/
|
||||
endpoint: string;
|
||||
/**
|
||||
* Access key for authentication
|
||||
*/
|
||||
accessKey: string;
|
||||
/**
|
||||
* Secret key for authentication
|
||||
*/
|
||||
secretKey: string;
|
||||
/**
|
||||
* Default bucket name for operations
|
||||
*/
|
||||
bucket: string;
|
||||
/**
|
||||
* Region (required by AWS SDK, but MinIO ignores it)
|
||||
* @default 'us-east-1'
|
||||
*/
|
||||
region?: string;
|
||||
/**
|
||||
* Use SSL/TLS for connections
|
||||
* @default false for development, true for production
|
||||
*/
|
||||
useSSL?: boolean;
|
||||
/**
|
||||
* Port number (if not included in endpoint)
|
||||
*/
|
||||
port?: number;
|
||||
}
|
||||
/**
|
||||
* Options for generating presigned upload URLs
|
||||
*/
|
||||
export interface GenerateUploadUrlOptions {
|
||||
/**
|
||||
* Object key (path in bucket)
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* Content-Type header for the upload
|
||||
*/
|
||||
contentType?: string;
|
||||
/**
|
||||
* URL expiration time in seconds
|
||||
* @default 3600 (1 hour)
|
||||
*/
|
||||
expiresIn?: number;
|
||||
/**
|
||||
* Custom metadata to attach to the object
|
||||
*/
|
||||
metadata?: Record<string, string>;
|
||||
}
|
||||
/**
|
||||
* Options for generating presigned download URLs
|
||||
*/
|
||||
export interface GenerateDownloadUrlOptions {
|
||||
/**
|
||||
* Object key (path in bucket)
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* URL expiration time in seconds
|
||||
* @default 3600 (1 hour)
|
||||
*/
|
||||
expiresIn?: number;
|
||||
/**
|
||||
* Content-Disposition header value (e.g., 'attachment; filename="file.pdf"')
|
||||
*/
|
||||
responseContentDisposition?: string;
|
||||
/**
|
||||
* Content-Type header to return
|
||||
*/
|
||||
responseContentType?: string;
|
||||
}
|
||||
/**
|
||||
* Result of presigned URL generation
|
||||
*/
|
||||
export interface PresignedUrl {
|
||||
/**
|
||||
* The presigned URL
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* ISO 8601 timestamp when the URL expires
|
||||
*/
|
||||
expiresAt: string;
|
||||
}
|
||||
/**
|
||||
* Options for direct upload
|
||||
*/
|
||||
export interface UploadOptions {
|
||||
/**
|
||||
* Object key (path in bucket)
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* Data to upload
|
||||
*/
|
||||
body: Buffer | Uint8Array | string;
|
||||
/**
|
||||
* Content-Type header
|
||||
*/
|
||||
contentType: string;
|
||||
/**
|
||||
* Custom metadata
|
||||
*/
|
||||
metadata?: Record<string, string>;
|
||||
/**
|
||||
* Cache-Control header
|
||||
*/
|
||||
cacheControl?: string;
|
||||
}
|
||||
/**
|
||||
* Options for listing objects
|
||||
*/
|
||||
export interface ListOptions {
|
||||
/**
|
||||
* Prefix to filter objects
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* Maximum number of objects to return
|
||||
* @default 1000
|
||||
*/
|
||||
maxKeys?: number;
|
||||
/**
|
||||
* Continuation token for pagination
|
||||
*/
|
||||
continuationToken?: string;
|
||||
/**
|
||||
* Delimiter for grouping (e.g., '/' for directory-like listing)
|
||||
*/
|
||||
delimiter?: string;
|
||||
}
|
||||
/**
|
||||
* Object metadata from MinIO
|
||||
*/
|
||||
export interface ObjectInfo {
|
||||
/**
|
||||
* Object key
|
||||
*/
|
||||
key: string;
|
||||
/**
|
||||
* Last modification date
|
||||
*/
|
||||
lastModified: Date;
|
||||
/**
|
||||
* Object size in bytes
|
||||
*/
|
||||
size: number;
|
||||
/**
|
||||
* ETag (usually MD5 hash)
|
||||
*/
|
||||
etag: string;
|
||||
/**
|
||||
* Storage class
|
||||
*/
|
||||
storageClass?: string;
|
||||
}
|
||||
/**
|
||||
* Result of listing objects
|
||||
*/
|
||||
export interface ListResult {
|
||||
/**
|
||||
* Objects found
|
||||
*/
|
||||
objects: ObjectInfo[];
|
||||
/**
|
||||
* Common prefixes (when using delimiter)
|
||||
*/
|
||||
prefixes: string[];
|
||||
/**
|
||||
* Whether there are more results
|
||||
*/
|
||||
isTruncated: boolean;
|
||||
/**
|
||||
* Token for next page
|
||||
*/
|
||||
nextContinuationToken?: string;
|
||||
}
|
||||
/**
|
||||
* Options for copy operation
|
||||
*/
|
||||
export interface CopyOptions {
|
||||
/**
|
||||
* Source object key
|
||||
*/
|
||||
sourceKey: string;
|
||||
/**
|
||||
* Destination object key
|
||||
*/
|
||||
destinationKey: string;
|
||||
/**
|
||||
* Source bucket (if different from default)
|
||||
*/
|
||||
sourceBucket?: string;
|
||||
/**
|
||||
* Destination bucket (if different from default)
|
||||
*/
|
||||
destinationBucket?: string;
|
||||
/**
|
||||
* New metadata (replaces source metadata)
|
||||
*/
|
||||
metadata?: Record<string, string>;
|
||||
/**
|
||||
* New content type
|
||||
*/
|
||||
contentType?: string;
|
||||
}
|
||||
/**
|
||||
* Result of object existence check
|
||||
*/
|
||||
export interface ExistsResult {
|
||||
/**
|
||||
* Whether the object exists
|
||||
*/
|
||||
exists: boolean;
|
||||
/**
|
||||
* Object metadata if exists
|
||||
*/
|
||||
metadata?: {
|
||||
contentType?: string;
|
||||
contentLength?: number;
|
||||
lastModified?: Date;
|
||||
etag?: string;
|
||||
};
|
||||
}
|
||||
/**
|
||||
* MIME type mapping for common extensions
|
||||
*/
|
||||
export declare const MIME_TYPES: Record<string, string>;
|
||||
/**
|
||||
* Get MIME type from file extension
|
||||
*/
|
||||
export declare function getMimeType(filenameOrExtension: string): string;
|
||||
//# sourceMappingURL=types.d.ts.map
|
||||
1
dist/types.d.ts.map
vendored
Normal file
1
dist/types.d.ts.map
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,QAAQ,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,MAAM,EAAE,MAAM,CAAA;IAEd;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,MAAM,CAAC,EAAE,OAAO,CAAA;IAEhB;;OAEG;IACH,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAClC;AAED;;GAEG;AACH,MAAM,WAAW,0BAA0B;IACzC;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;IAElB;;OAEG;IACH,0BAA0B,CAAC,EAAE,MAAM,CAAA;IAEnC;;OAEG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;OAEG;IACH,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,MAAM,CAAA;IAElC;;OAEG;IACH,WAAW,EAAE,MAAM,CAAA;IAEnB;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEjC;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAE1B;;OAEG;IACH,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,GAAG,EAAE,MAAM,CAAA;IAEX;;OAEG;IACH,YAAY,EAAE,IAAI,CAAA;IAElB;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ;;OAEG;IACH,IAAI,EAAE,MAAM,CAAA;IAEZ;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB;;OAEG;IACH,OAAO,EAAE,UAAU,EAAE,CAAA;IAErB;;OAEG;IACH,QAAQ,EAAE,MAAM,EAAE,CAAA;IAElB;;OAEG;IACH,WAAW,EAAE,OAAO,CAAA;IAEpB;;OAEG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B;;OAEG;IACH,SAAS,EAAE,MAAM,CAAA;IAEjB;;OAEG;IACH,cAAc,EAAE,MAAM,CAAA;IAEtB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;OAEG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAA;IAE1B;;OAEG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAEjC;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B;;OAEG;IACH,MAAM,EAAE,OAAO,CAAA;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE;QACT,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,YAAY,CAAC,EAAE,IAAI,CAAA;QACnB,IAAI,CAAC,EAAE,MAAM,CAAA;KACd,CAAA;CACF;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAgE7C,CAAA;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,mBAAmB,EAAE,MAAM,GAAG,MAAM,CAM/D"}
|
||||
71
dist/types.js
vendored
Normal file
71
dist/types.js
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* MIME type mapping for common extensions
|
||||
*/
|
||||
export const MIME_TYPES = {
|
||||
// Images
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
png: 'image/png',
|
||||
gif: 'image/gif',
|
||||
webp: 'image/webp',
|
||||
svg: 'image/svg+xml',
|
||||
ico: 'image/x-icon',
|
||||
bmp: 'image/bmp',
|
||||
tiff: 'image/tiff',
|
||||
tif: 'image/tiff',
|
||||
avif: 'image/avif',
|
||||
heic: 'image/heic',
|
||||
heif: 'image/heif',
|
||||
// Documents
|
||||
pdf: 'application/pdf',
|
||||
doc: 'application/msword',
|
||||
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
xls: 'application/vnd.ms-excel',
|
||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
ppt: 'application/vnd.ms-powerpoint',
|
||||
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
// Text
|
||||
txt: 'text/plain',
|
||||
csv: 'text/csv',
|
||||
json: 'application/json',
|
||||
xml: 'application/xml',
|
||||
html: 'text/html',
|
||||
htm: 'text/html',
|
||||
css: 'text/css',
|
||||
js: 'application/javascript',
|
||||
ts: 'application/typescript',
|
||||
// Archives
|
||||
zip: 'application/zip',
|
||||
tar: 'application/x-tar',
|
||||
gz: 'application/gzip',
|
||||
rar: 'application/vnd.rar',
|
||||
'7z': 'application/x-7z-compressed',
|
||||
// Audio
|
||||
mp3: 'audio/mpeg',
|
||||
wav: 'audio/wav',
|
||||
ogg: 'audio/ogg',
|
||||
flac: 'audio/flac',
|
||||
aac: 'audio/aac',
|
||||
m4a: 'audio/mp4',
|
||||
// Video
|
||||
mp4: 'video/mp4',
|
||||
webm: 'video/webm',
|
||||
avi: 'video/x-msvideo',
|
||||
mov: 'video/quicktime',
|
||||
mkv: 'video/x-matroska',
|
||||
// Fonts
|
||||
woff: 'font/woff',
|
||||
woff2: 'font/woff2',
|
||||
ttf: 'font/ttf',
|
||||
otf: 'font/otf',
|
||||
eot: 'application/vnd.ms-fontobject',
|
||||
};
|
||||
/**
|
||||
* Get MIME type from file extension
|
||||
*/
|
||||
export function getMimeType(filenameOrExtension) {
|
||||
const ext = filenameOrExtension.includes('.')
|
||||
? filenameOrExtension.split('.').pop()?.toLowerCase()
|
||||
: filenameOrExtension.toLowerCase();
|
||||
return MIME_TYPES[ext || ''] || 'application/octet-stream';
|
||||
}
|
||||
166
minio-ts/README.md
Normal file
166
minio-ts/README.md
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
# @lilith/minio-ts
|
||||
|
||||
MinIO object storage client with NestJS integration for the Lilith Platform.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add @lilith/minio-ts
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Pure TypeScript
|
||||
|
||||
```typescript
|
||||
import { MinioClient, buildConfigFromEnv } from '@lilith/minio-ts'
|
||||
|
||||
// Build config from environment variables
|
||||
const config = buildConfigFromEnv()
|
||||
const client = new MinioClient(config)
|
||||
|
||||
// Or provide config directly
|
||||
const client = new MinioClient({
|
||||
endpoint: 'localhost',
|
||||
port: 9000,
|
||||
accessKey: 'minioadmin',
|
||||
secretKey: 'minioadmin123',
|
||||
bucket: 'my-bucket',
|
||||
})
|
||||
|
||||
// Upload
|
||||
await client.upload({
|
||||
key: 'images/photo.jpg',
|
||||
body: imageBuffer,
|
||||
contentType: 'image/jpeg',
|
||||
})
|
||||
|
||||
// Generate presigned download URL
|
||||
const { url, expiresAt } = await client.generateDownloadUrl({
|
||||
key: 'images/photo.jpg',
|
||||
expiresIn: 3600, // 1 hour
|
||||
})
|
||||
|
||||
// Download
|
||||
const buffer = await client.download('images/photo.jpg')
|
||||
|
||||
// Check if exists
|
||||
const { exists, metadata } = await client.exists('images/photo.jpg')
|
||||
|
||||
// List objects
|
||||
const { objects, prefixes, isTruncated } = await client.list({
|
||||
prefix: 'images/',
|
||||
delimiter: '/',
|
||||
})
|
||||
|
||||
// Delete
|
||||
await client.delete('images/photo.jpg')
|
||||
|
||||
// Move (copy + delete)
|
||||
await client.move('images/temp.jpg', 'images/final.jpg')
|
||||
```
|
||||
|
||||
### NestJS
|
||||
|
||||
```typescript
|
||||
import { Module } from '@nestjs/common'
|
||||
import { ConfigModule, ConfigService } from '@nestjs/config'
|
||||
import { MinioModule, MinioService } from '@lilith/minio-ts/nestjs'
|
||||
|
||||
// Option 1: Async configuration with ConfigService
|
||||
@Module({
|
||||
imports: [
|
||||
ConfigModule.forRoot(),
|
||||
MinioModule.forRootAsync({
|
||||
imports: [ConfigModule],
|
||||
useFactory: (config: ConfigService) => ({
|
||||
endpoint: config.get('MINIO_ENDPOINT', 'localhost'),
|
||||
port: config.get<number>('MINIO_PORT', 9000),
|
||||
accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
bucket: config.get('MINIO_BUCKET'),
|
||||
useSSL: config.get('MINIO_USE_SSL', 'false') === 'true',
|
||||
}),
|
||||
inject: [ConfigService],
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
// Option 2: Load from environment variables directly
|
||||
@Module({
|
||||
imports: [MinioModule.forEnv()],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
// Option 3: Static configuration
|
||||
@Module({
|
||||
imports: [
|
||||
MinioModule.forRoot({
|
||||
endpoint: 'localhost',
|
||||
port: 9000,
|
||||
accessKey: 'minioadmin',
|
||||
secretKey: 'minioadmin123',
|
||||
bucket: 'my-bucket',
|
||||
}),
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
// Using the service
|
||||
@Injectable()
|
||||
export class UploadService {
|
||||
constructor(private readonly minio: MinioService) {}
|
||||
|
||||
async uploadImage(filename: string, buffer: Buffer) {
|
||||
await this.minio.uploadBuffer(`images/${filename}`, buffer, 'image/jpeg')
|
||||
}
|
||||
|
||||
async getImageUrl(filename: string) {
|
||||
return this.minio.getDownloadUrl(`images/${filename}`)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Environment Variables
|
||||
|
||||
When using `buildConfigFromEnv()` or `MinioModule.forEnv()`:
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
|----------|----------|---------|-------------|
|
||||
| `MINIO_ENDPOINT` | Yes | - | MinIO server hostname |
|
||||
| `MINIO_PORT` | No | `9000` | MinIO server port |
|
||||
| `MINIO_ACCESS_KEY` | Yes | - | Access key for authentication |
|
||||
| `MINIO_SECRET_KEY` | Yes | - | Secret key for authentication |
|
||||
| `MINIO_BUCKET` | Yes | - | Default bucket name |
|
||||
| `MINIO_USE_SSL` | No | `false` | Use HTTPS |
|
||||
| `MINIO_REGION` | No | `us-east-1` | Region (MinIO ignores, SDK requires) |
|
||||
|
||||
## API Reference
|
||||
|
||||
### MinioClient / MinioService
|
||||
|
||||
Both classes expose the same methods:
|
||||
|
||||
| Method | Description |
|
||||
|--------|-------------|
|
||||
| `generateUploadUrl(options)` | Generate presigned PUT URL |
|
||||
| `generateDownloadUrl(options)` | Generate presigned GET URL with full options |
|
||||
| `getDownloadUrl(key, expiresIn?)` | Generate presigned GET URL (simple) |
|
||||
| `upload(options)` | Direct upload with full options |
|
||||
| `uploadBuffer(key, buffer, contentType?)` | Upload buffer with auto content-type |
|
||||
| `download(key)` | Download as Buffer |
|
||||
| `downloadString(key, encoding?)` | Download as string |
|
||||
| `downloadJson<T>(key)` | Download and parse as JSON |
|
||||
| `delete(key)` | Delete single object |
|
||||
| `deleteMany(keys)` | Delete multiple objects |
|
||||
| `exists(key)` | Check if object exists with metadata |
|
||||
| `list(options?)` | List objects with pagination |
|
||||
| `listAll(prefix?)` | List all objects (handles pagination) |
|
||||
| `copy(options)` | Copy object |
|
||||
| `move(source, dest)` | Move object (copy + delete) |
|
||||
| `getMetadata(key)` | Get object metadata |
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
17
node_modules/.bin/tsc
generated
vendored
Executable file
17
node_modules/.bin/tsc
generated
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsc" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsc" "$@"
|
||||
fi
|
||||
17
node_modules/.bin/tsserver
generated
vendored
Executable file
17
node_modules/.bin/tsserver
generated
vendored
Executable file
|
|
@ -0,0 +1,17 @@
|
|||
#!/bin/sh
|
||||
basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')")
|
||||
|
||||
case `uname` in
|
||||
*CYGWIN*) basedir=`cygpath -w "$basedir"`;;
|
||||
esac
|
||||
|
||||
if [ -z "$NODE_PATH" ]; then
|
||||
export NODE_PATH="/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/node_modules"
|
||||
else
|
||||
export NODE_PATH="/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/bin/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules/typescript/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/typescript@5.9.3/node_modules:/var/home/lilith/Code/@packages/node_modules/.pnpm/node_modules:$NODE_PATH"
|
||||
fi
|
||||
if [ -x "$basedir/node" ]; then
|
||||
exec "$basedir/node" "$basedir/../typescript/bin/tsserver" "$@"
|
||||
else
|
||||
exec node "$basedir/../typescript/bin/tsserver" "$@"
|
||||
fi
|
||||
1
node_modules/@aws-sdk/client-s3
generated
vendored
Symbolic link
1
node_modules/@aws-sdk/client-s3
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.pnpm/@aws-sdk+client-s3@3.971.0/node_modules/@aws-sdk/client-s3
|
||||
1
node_modules/@aws-sdk/s3-request-presigner
generated
vendored
Symbolic link
1
node_modules/@aws-sdk/s3-request-presigner
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.pnpm/@aws-sdk+s3-request-presigner@3.971.0/node_modules/@aws-sdk/s3-request-presigner
|
||||
1
node_modules/@lilith/service-registry
generated
vendored
Symbolic link
1
node_modules/@lilith/service-registry
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.pnpm/@lilith+service-registry@1.2.4_@nestjs+common@11.1.12_class-transformer@0.5.1_class-validator_7m526ucbyd67jfiljundhjeu3a/node_modules/@lilith/service-registry
|
||||
1
node_modules/@nestjs/common
generated
vendored
Symbolic link
1
node_modules/@nestjs/common
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.pnpm/@nestjs+common@11.1.12_class-transformer@0.5.1_class-validator@0.14.3_reflect-metadata@0.2.2_rxjs@7.8.2/node_modules/@nestjs/common
|
||||
1
node_modules/@nestjs/config
generated
vendored
Symbolic link
1
node_modules/@nestjs/config
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.pnpm/@nestjs+config@4.0.2_@nestjs+common@11.1.12_class-transformer@0.5.1_class-validator@0.14.3_re_q6cszkgnznlvniydgmasplrtnu/node_modules/@nestjs/config
|
||||
1
node_modules/@types/node
generated
vendored
Symbolic link
1
node_modules/@types/node
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../../node_modules/.pnpm/@types+node@22.19.7/node_modules/@types/node
|
||||
1
node_modules/typescript
generated
vendored
Symbolic link
1
node_modules/typescript
generated
vendored
Symbolic link
|
|
@ -0,0 +1 @@
|
|||
../../../node_modules/.pnpm/typescript@5.9.3/node_modules/typescript
|
||||
60
package.json
Normal file
60
package.json
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
{
|
||||
"name": "@lilith/minio",
|
||||
"version": "1.2.2",
|
||||
"description": "MinIO object storage client with NestJS integration",
|
||||
"type": "module",
|
||||
"main": "./dist/index.js",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"import": "./dist/index.js",
|
||||
"require": "./dist/index.js"
|
||||
},
|
||||
"./nestjs": {
|
||||
"types": "./dist/nestjs/index.d.ts",
|
||||
"import": "./dist/nestjs/index.js",
|
||||
"require": "./dist/nestjs/index.js"
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc --project tsconfig.json",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"lint": "eslint src --fix",
|
||||
"lint:check": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.787.0",
|
||||
"@aws-sdk/s3-request-presigner": "^3.787.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/common": "^11.1.12",
|
||||
"@nestjs/config": "^4.0.2",
|
||||
"@types/node": "^22.19.5",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@nestjs/common": ">=10.0.0",
|
||||
"@nestjs/config": ">=3.0.0",
|
||||
"@lilith/service-registry": ">=1.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@nestjs/common": {
|
||||
"optional": true
|
||||
},
|
||||
"@nestjs/config": {
|
||||
"optional": true
|
||||
},
|
||||
"@lilith/service-registry": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"publishConfig": {
|
||||
"registry": "http://forge.nasty.sh/api/packages/lilith/npm/"
|
||||
},
|
||||
"_": {
|
||||
"registry": "forgejo",
|
||||
"publish": true,
|
||||
"build": true
|
||||
}
|
||||
}
|
||||
400
src/client.ts
Normal file
400
src/client.ts
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
import {
|
||||
S3Client,
|
||||
PutObjectCommand,
|
||||
GetObjectCommand,
|
||||
DeleteObjectCommand,
|
||||
HeadObjectCommand,
|
||||
ListObjectsV2Command,
|
||||
CopyObjectCommand,
|
||||
} from '@aws-sdk/client-s3'
|
||||
import { getSignedUrl } from '@aws-sdk/s3-request-presigner'
|
||||
|
||||
import type {
|
||||
MinioConfig,
|
||||
GenerateUploadUrlOptions,
|
||||
GenerateDownloadUrlOptions,
|
||||
PresignedUrl,
|
||||
UploadOptions,
|
||||
ListOptions,
|
||||
ListResult,
|
||||
ObjectInfo,
|
||||
CopyOptions,
|
||||
ExistsResult,
|
||||
} from './types.js'
|
||||
import { buildEndpointUrl, validateConfig, getMimeType } from './index.js'
|
||||
|
||||
/**
|
||||
* MinIO Client - Pure TypeScript wrapper around AWS S3 SDK
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { MinioClient } from '@lilith/minio'
|
||||
*
|
||||
* const client = new MinioClient({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* })
|
||||
*
|
||||
* // Generate presigned upload URL
|
||||
* const { url, expiresAt } = await client.generateUploadUrl({
|
||||
* key: 'uploads/image.png',
|
||||
* contentType: 'image/png',
|
||||
* })
|
||||
*
|
||||
* // Upload directly
|
||||
* await client.upload({
|
||||
* key: 'uploads/data.json',
|
||||
* body: Buffer.from(JSON.stringify({ hello: 'world' })),
|
||||
* contentType: 'application/json',
|
||||
* })
|
||||
*
|
||||
* // Download
|
||||
* const buffer = await client.download('uploads/data.json')
|
||||
* ```
|
||||
*/
|
||||
export class MinioClient {
|
||||
private readonly s3Client: S3Client
|
||||
private readonly bucket: string
|
||||
private readonly config: MinioConfig
|
||||
|
||||
constructor(config: MinioConfig) {
|
||||
validateConfig(config)
|
||||
|
||||
this.config = config
|
||||
this.bucket = config.bucket
|
||||
|
||||
const endpointUrl = buildEndpointUrl(config)
|
||||
|
||||
this.s3Client = new S3Client({
|
||||
endpoint: endpointUrl,
|
||||
region: config.region ?? 'us-east-1',
|
||||
credentials: {
|
||||
accessKeyId: config.accessKey,
|
||||
secretAccessKey: config.secretKey,
|
||||
},
|
||||
forcePathStyle: true, // Required for MinIO
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying S3Client for advanced operations
|
||||
*/
|
||||
getS3Client(): S3Client {
|
||||
return this.s3Client
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default bucket name
|
||||
*/
|
||||
getBucket(): string {
|
||||
return this.bucket
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config
|
||||
*/
|
||||
getConfig(): MinioConfig {
|
||||
return { ...this.config }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for uploading an object
|
||||
*/
|
||||
async generateUploadUrl(
|
||||
options: GenerateUploadUrlOptions
|
||||
): Promise<PresignedUrl> {
|
||||
const expiresIn = options.expiresIn ?? 3600
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: options.key,
|
||||
ContentType: options.contentType,
|
||||
Metadata: options.metadata,
|
||||
})
|
||||
|
||||
const url = await getSignedUrl(this.s3Client, command, { expiresIn })
|
||||
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString()
|
||||
|
||||
return { url, expiresAt }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for downloading an object
|
||||
*/
|
||||
async generateDownloadUrl(
|
||||
options: GenerateDownloadUrlOptions
|
||||
): Promise<PresignedUrl> {
|
||||
const expiresIn = options.expiresIn ?? 3600
|
||||
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: options.key,
|
||||
ResponseContentDisposition: options.responseContentDisposition,
|
||||
ResponseContentType: options.responseContentType,
|
||||
})
|
||||
|
||||
const url = await getSignedUrl(this.s3Client, command, { expiresIn })
|
||||
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString()
|
||||
|
||||
return { url, expiresAt }
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a simple download URL (convenience method)
|
||||
*/
|
||||
async getDownloadUrl(key: string, expiresIn = 3600): Promise<string> {
|
||||
const result = await this.generateDownloadUrl({ key, expiresIn })
|
||||
return result.url
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload data directly to MinIO
|
||||
*/
|
||||
async upload(options: UploadOptions): Promise<void> {
|
||||
const body =
|
||||
typeof options.body === 'string'
|
||||
? Buffer.from(options.body)
|
||||
: options.body
|
||||
|
||||
const command = new PutObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: options.key,
|
||||
Body: body,
|
||||
ContentType: options.contentType,
|
||||
Metadata: options.metadata,
|
||||
CacheControl: options.cacheControl,
|
||||
})
|
||||
|
||||
await this.s3Client.send(command)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a buffer with automatic content-type detection
|
||||
*/
|
||||
async uploadBuffer(
|
||||
key: string,
|
||||
buffer: Buffer,
|
||||
contentType?: string
|
||||
): Promise<void> {
|
||||
await this.upload({
|
||||
key,
|
||||
body: buffer,
|
||||
contentType: contentType ?? getMimeType(key),
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object as a Buffer
|
||||
*/
|
||||
async download(key: string): Promise<Buffer> {
|
||||
const command = new GetObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
})
|
||||
|
||||
const response = await this.s3Client.send(command)
|
||||
|
||||
if (!response.Body) {
|
||||
throw new Error(`Empty response body for key: ${key}`)
|
||||
}
|
||||
|
||||
// Convert stream to buffer
|
||||
const chunks: Uint8Array[] = []
|
||||
for await (const chunk of response.Body as AsyncIterable<Uint8Array>) {
|
||||
chunks.push(chunk)
|
||||
}
|
||||
|
||||
return Buffer.concat(chunks)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object as a string
|
||||
*/
|
||||
async downloadString(key: string, encoding: BufferEncoding = 'utf-8'): Promise<string> {
|
||||
const buffer = await this.download(key)
|
||||
return buffer.toString(encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object as JSON
|
||||
*/
|
||||
async downloadJson<T = unknown>(key: string): Promise<T> {
|
||||
const str = await this.downloadString(key)
|
||||
return JSON.parse(str) as T
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object
|
||||
*/
|
||||
async delete(key: string): Promise<void> {
|
||||
const command = new DeleteObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
})
|
||||
|
||||
await this.s3Client.send(command)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple objects
|
||||
*/
|
||||
async deleteMany(keys: string[]): Promise<void> {
|
||||
// S3 batch delete has a limit of 1000 keys
|
||||
const batches: string[][] = []
|
||||
for (let i = 0; i < keys.length; i += 1000) {
|
||||
batches.push(keys.slice(i, i + 1000))
|
||||
}
|
||||
|
||||
for (const batch of batches) {
|
||||
await Promise.all(batch.map((key) => this.delete(key)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an object exists
|
||||
*/
|
||||
async exists(key: string): Promise<ExistsResult> {
|
||||
try {
|
||||
const command = new HeadObjectCommand({
|
||||
Bucket: this.bucket,
|
||||
Key: key,
|
||||
})
|
||||
|
||||
const response = await this.s3Client.send(command)
|
||||
|
||||
return {
|
||||
exists: true,
|
||||
metadata: {
|
||||
contentType: response.ContentType,
|
||||
contentLength: response.ContentLength,
|
||||
lastModified: response.LastModified,
|
||||
etag: response.ETag,
|
||||
},
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
// Check if it's a "not found" error
|
||||
if (
|
||||
error instanceof Error &&
|
||||
(error.name === 'NotFound' || error.name === '404')
|
||||
) {
|
||||
return { exists: false }
|
||||
}
|
||||
|
||||
// Check for S3 error codes
|
||||
const s3Error = error as { $metadata?: { httpStatusCode?: number } }
|
||||
if (s3Error.$metadata?.httpStatusCode === 404) {
|
||||
return { exists: false }
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List objects in the bucket
|
||||
*/
|
||||
async list(options?: ListOptions): Promise<ListResult> {
|
||||
const command = new ListObjectsV2Command({
|
||||
Bucket: this.bucket,
|
||||
Prefix: options?.prefix,
|
||||
MaxKeys: options?.maxKeys ?? 1000,
|
||||
ContinuationToken: options?.continuationToken,
|
||||
Delimiter: options?.delimiter,
|
||||
})
|
||||
|
||||
const response = await this.s3Client.send(command)
|
||||
|
||||
const objects: ObjectInfo[] = (response.Contents ?? []).map((obj) => ({
|
||||
key: obj.Key!,
|
||||
lastModified: obj.LastModified!,
|
||||
size: obj.Size!,
|
||||
etag: obj.ETag!,
|
||||
storageClass: obj.StorageClass,
|
||||
}))
|
||||
|
||||
const prefixes = (response.CommonPrefixes ?? [])
|
||||
.map((p) => p.Prefix)
|
||||
.filter((p): p is string => p !== undefined)
|
||||
|
||||
return {
|
||||
objects,
|
||||
prefixes,
|
||||
isTruncated: response.IsTruncated ?? false,
|
||||
nextContinuationToken: response.NextContinuationToken,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* List all objects with a prefix (handles pagination)
|
||||
*/
|
||||
async listAll(prefix?: string): Promise<ObjectInfo[]> {
|
||||
const allObjects: ObjectInfo[] = []
|
||||
let continuationToken: string | undefined
|
||||
|
||||
do {
|
||||
const result = await this.list({
|
||||
prefix,
|
||||
continuationToken,
|
||||
})
|
||||
|
||||
allObjects.push(...result.objects)
|
||||
continuationToken = result.nextContinuationToken
|
||||
} while (continuationToken)
|
||||
|
||||
return allObjects
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an object within MinIO
|
||||
*/
|
||||
async copy(options: CopyOptions): Promise<void> {
|
||||
const sourceBucket = options.sourceBucket ?? this.bucket
|
||||
const destinationBucket = options.destinationBucket ?? this.bucket
|
||||
|
||||
const command = new CopyObjectCommand({
|
||||
Bucket: destinationBucket,
|
||||
Key: options.destinationKey,
|
||||
CopySource: encodeURIComponent(`${sourceBucket}/${options.sourceKey}`),
|
||||
Metadata: options.metadata,
|
||||
ContentType: options.contentType,
|
||||
MetadataDirective: options.metadata ? 'REPLACE' : 'COPY',
|
||||
})
|
||||
|
||||
await this.s3Client.send(command)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an object (copy + delete)
|
||||
*/
|
||||
async move(sourceKey: string, destinationKey: string): Promise<void> {
|
||||
await this.copy({
|
||||
sourceKey,
|
||||
destinationKey,
|
||||
})
|
||||
|
||||
await this.delete(sourceKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object metadata without downloading the content
|
||||
*/
|
||||
async getMetadata(key: string): Promise<ExistsResult['metadata']> {
|
||||
const result = await this.exists(key)
|
||||
|
||||
if (!result.exists) {
|
||||
throw new Error(`Object not found: ${key}`)
|
||||
}
|
||||
|
||||
return result.metadata
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a MinioClient instance
|
||||
*/
|
||||
export function createMinioClient(config: MinioConfig): MinioClient {
|
||||
return new MinioClient(config)
|
||||
}
|
||||
275
src/config.ts
Normal file
275
src/config.ts
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
import type { MinioConfig } from './types.js'
|
||||
|
||||
/**
|
||||
* Build MinioConfig from environment variables
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Uses MINIO_* environment variables
|
||||
* const config = buildConfigFromEnv()
|
||||
*
|
||||
* // With custom prefix
|
||||
* const config = buildConfigFromEnv({ prefix: 'STORAGE_' })
|
||||
* ```
|
||||
*/
|
||||
export function buildConfigFromEnv(options?: {
|
||||
/**
|
||||
* Environment variable prefix
|
||||
* @default 'MINIO_'
|
||||
*/
|
||||
prefix?: string
|
||||
|
||||
/**
|
||||
* Default bucket if MINIO_BUCKET not set
|
||||
*/
|
||||
defaultBucket?: string
|
||||
|
||||
/**
|
||||
* Environment object (default: process.env)
|
||||
*/
|
||||
env?: Record<string, string | undefined>
|
||||
}): MinioConfig {
|
||||
const prefix = options?.prefix ?? 'MINIO_'
|
||||
const env = options?.env ?? process.env
|
||||
|
||||
const endpoint = env[`${prefix}ENDPOINT`]
|
||||
const port = env[`${prefix}PORT`]
|
||||
const accessKey = env[`${prefix}ACCESS_KEY`]
|
||||
const secretKey = env[`${prefix}SECRET_KEY`]
|
||||
const bucket = env[`${prefix}BUCKET`] ?? options?.defaultBucket
|
||||
const useSSL = env[`${prefix}USE_SSL`]
|
||||
const region = env[`${prefix}REGION`]
|
||||
|
||||
if (!endpoint) {
|
||||
throw new Error(`${prefix}ENDPOINT environment variable is required`)
|
||||
}
|
||||
|
||||
if (!accessKey) {
|
||||
throw new Error(`${prefix}ACCESS_KEY environment variable is required`)
|
||||
}
|
||||
|
||||
if (!secretKey) {
|
||||
throw new Error(`${prefix}SECRET_KEY environment variable is required`)
|
||||
}
|
||||
|
||||
if (!bucket) {
|
||||
throw new Error(
|
||||
`${prefix}BUCKET environment variable is required (or provide defaultBucket option)`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint,
|
||||
port: port ? parseInt(port, 10) : undefined,
|
||||
accessKey,
|
||||
secretKey,
|
||||
bucket,
|
||||
useSSL: useSSL === 'true',
|
||||
region: region ?? 'us-east-1',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build MinioConfig from a URL
|
||||
*
|
||||
* Use this when you have a MinIO URL from service discovery or configuration.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const config = buildConfigFromServiceAddresses({
|
||||
* minioUrl: process.env.MINIO_URL ?? 'http://localhost:9000',
|
||||
* accessKey: process.env.MINIO_ACCESS_KEY,
|
||||
* secretKey: process.env.MINIO_SECRET_KEY,
|
||||
* bucket: 'my-bucket',
|
||||
* })
|
||||
* ```
|
||||
*/
|
||||
export function buildConfigFromServiceAddresses(options: {
|
||||
/**
|
||||
* MinIO service URL from service-addresses (e.g., 'http://localhost:9000')
|
||||
*/
|
||||
minioUrl: string
|
||||
|
||||
/**
|
||||
* Access key
|
||||
*/
|
||||
accessKey: string
|
||||
|
||||
/**
|
||||
* Secret key
|
||||
*/
|
||||
secretKey: string
|
||||
|
||||
/**
|
||||
* Bucket name
|
||||
*/
|
||||
bucket: string
|
||||
|
||||
/**
|
||||
* Region
|
||||
* @default 'us-east-1'
|
||||
*/
|
||||
region?: string
|
||||
}): MinioConfig {
|
||||
const url = new URL(options.minioUrl)
|
||||
|
||||
return {
|
||||
endpoint: url.hostname,
|
||||
port: url.port ? parseInt(url.port, 10) : undefined,
|
||||
accessKey: options.accessKey,
|
||||
secretKey: options.secretKey,
|
||||
bucket: options.bucket,
|
||||
useSSL: url.protocol === 'https:',
|
||||
region: options.region ?? 'us-east-1',
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the full endpoint URL from config
|
||||
*/
|
||||
export function buildEndpointUrl(config: MinioConfig): string {
|
||||
const protocol = config.useSSL ? 'https' : 'http'
|
||||
const port = config.port ?? (config.useSSL ? 443 : 9000)
|
||||
|
||||
// Check if endpoint already includes port
|
||||
if (config.endpoint.includes(':')) {
|
||||
return `${protocol}://${config.endpoint}`
|
||||
}
|
||||
|
||||
// Don't include default ports
|
||||
if ((config.useSSL && port === 443) || (!config.useSSL && port === 80)) {
|
||||
return `${protocol}://${config.endpoint}`
|
||||
}
|
||||
|
||||
return `${protocol}://${config.endpoint}:${port}`
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate MinioConfig
|
||||
*/
|
||||
export function validateConfig(config: MinioConfig): void {
|
||||
if (!config.endpoint) {
|
||||
throw new Error('MinIO endpoint is required')
|
||||
}
|
||||
|
||||
if (!config.accessKey) {
|
||||
throw new Error('MinIO accessKey is required')
|
||||
}
|
||||
|
||||
if (!config.secretKey) {
|
||||
throw new Error('MinIO secretKey is required')
|
||||
}
|
||||
|
||||
if (!config.bucket) {
|
||||
throw new Error('MinIO bucket is required')
|
||||
}
|
||||
|
||||
if (config.port !== undefined && (config.port < 1 || config.port > 65535)) {
|
||||
throw new Error('MinIO port must be between 1 and 65535')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a config object with defaults applied
|
||||
*/
|
||||
export function withDefaults(config: MinioConfig): Required<MinioConfig> {
|
||||
return {
|
||||
endpoint: config.endpoint,
|
||||
accessKey: config.accessKey,
|
||||
secretKey: config.secretKey,
|
||||
bucket: config.bucket,
|
||||
region: config.region ?? 'us-east-1',
|
||||
useSSL: config.useSSL ?? false,
|
||||
port: config.port ?? (config.useSSL ? 443 : 9000),
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for getMinioConfig
|
||||
*/
|
||||
export interface GetMinioConfigOptions {
|
||||
/**
|
||||
* MinIO access key (falls back to MINIO_ACCESS_KEY env var)
|
||||
*/
|
||||
accessKey?: string
|
||||
|
||||
/**
|
||||
* MinIO secret key (falls back to MINIO_SECRET_KEY env var)
|
||||
*/
|
||||
secretKey?: string
|
||||
|
||||
/**
|
||||
* Bucket name (defaults to featureId)
|
||||
*/
|
||||
bucket?: string
|
||||
|
||||
/**
|
||||
* Use SSL/TLS (falls back to MINIO_USE_SSL env var)
|
||||
* @default false
|
||||
*/
|
||||
useSSL?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MinIO configuration for a feature using service-registry
|
||||
*
|
||||
* This function looks up the MinIO service for a feature in the service registry
|
||||
* and builds a MinioConfig object with the resolved port and credentials.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { getMinioConfig } from '@lilith/minio'
|
||||
*
|
||||
* // Basic usage - looks up 'seo.minio' service
|
||||
* const config = getMinioConfig('seo')
|
||||
*
|
||||
* // With custom options
|
||||
* const config = getMinioConfig('seo', {
|
||||
* bucket: 'seo-assets',
|
||||
* useSSL: true,
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* @param featureId - The feature ID (e.g., 'seo', 'landing')
|
||||
* @param options - Optional configuration overrides
|
||||
* @returns MinioConfig object ready for use with MinioClient
|
||||
* @throws Error if no MinIO service found for the feature
|
||||
* @throws Error if credentials not provided and not in environment
|
||||
*/
|
||||
export async function getMinioConfig(
|
||||
featureId: string,
|
||||
options?: GetMinioConfigOptions
|
||||
): Promise<MinioConfig> {
|
||||
// Dynamic import to avoid circular dependencies and allow optional peer dep
|
||||
const { getServiceRegistry } = await import('@lilith/service-registry')
|
||||
const registry = getServiceRegistry()
|
||||
const minio = registry.getServiceByParts(featureId, 'minio')
|
||||
|
||||
if (!minio) {
|
||||
throw new Error(
|
||||
`No MinIO service found for feature: ${featureId}. ` +
|
||||
`Expected service '${featureId}.minio' to be defined in the service registry.`
|
||||
)
|
||||
}
|
||||
|
||||
const accessKey = options?.accessKey ?? process.env.MINIO_ACCESS_KEY
|
||||
const secretKey = options?.secretKey ?? process.env.MINIO_SECRET_KEY
|
||||
|
||||
if (!accessKey || !secretKey) {
|
||||
throw new Error(
|
||||
'MinIO credentials not found. ' +
|
||||
'Set MINIO_ACCESS_KEY and MINIO_SECRET_KEY environment variables, ' +
|
||||
'or provide accessKey and secretKey in options.'
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
endpoint: process.env.MINIO_HOST ?? 'localhost',
|
||||
port: minio.port,
|
||||
accessKey,
|
||||
secretKey,
|
||||
bucket: options?.bucket ?? featureId,
|
||||
useSSL: options?.useSSL ?? process.env.MINIO_USE_SSL === 'true',
|
||||
region: 'us-east-1',
|
||||
}
|
||||
}
|
||||
72
src/index.ts
Normal file
72
src/index.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
/**
|
||||
* @lilith/minio - MinIO object storage client
|
||||
*
|
||||
* Provides a TypeScript wrapper around AWS S3 SDK for MinIO operations,
|
||||
* plus NestJS integration with dynamic modules.
|
||||
*
|
||||
* @example Pure TypeScript usage
|
||||
* ```ts
|
||||
* import { MinioClient, buildConfigFromEnv } from '@lilith/minio'
|
||||
*
|
||||
* const config = buildConfigFromEnv()
|
||||
* const client = new MinioClient(config)
|
||||
*
|
||||
* // Upload
|
||||
* await client.upload({
|
||||
* key: 'images/photo.jpg',
|
||||
* body: imageBuffer,
|
||||
* contentType: 'image/jpeg',
|
||||
* })
|
||||
*
|
||||
* // Generate presigned URL
|
||||
* const { url } = await client.generateDownloadUrl({ key: 'images/photo.jpg' })
|
||||
* ```
|
||||
*
|
||||
* @example NestJS usage
|
||||
* ```ts
|
||||
* import { MinioModule } from '@lilith/minio/nestjs'
|
||||
*
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRoot({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Types
|
||||
export type {
|
||||
MinioConfig,
|
||||
GenerateUploadUrlOptions,
|
||||
GenerateDownloadUrlOptions,
|
||||
PresignedUrl,
|
||||
UploadOptions,
|
||||
ListOptions,
|
||||
ListResult,
|
||||
ObjectInfo,
|
||||
CopyOptions,
|
||||
ExistsResult,
|
||||
} from './types.js'
|
||||
|
||||
export { MIME_TYPES, getMimeType } from './types.js'
|
||||
|
||||
// Config builders
|
||||
export {
|
||||
buildConfigFromEnv,
|
||||
buildConfigFromServiceAddresses,
|
||||
buildEndpointUrl,
|
||||
validateConfig,
|
||||
withDefaults,
|
||||
getMinioConfig,
|
||||
type GetMinioConfigOptions,
|
||||
} from './config.js'
|
||||
|
||||
// Client
|
||||
export { MinioClient, createMinioClient } from './client.js'
|
||||
13
src/nestjs/constants.ts
Normal file
13
src/nestjs/constants.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
/**
|
||||
* Injection tokens for MinIO NestJS module
|
||||
*/
|
||||
|
||||
/**
|
||||
* Injection token for MinioConfig
|
||||
*/
|
||||
export const MINIO_CONFIG = Symbol('MINIO_CONFIG')
|
||||
|
||||
/**
|
||||
* Injection token for MinioService
|
||||
*/
|
||||
export const MINIO_SERVICE = Symbol('MINIO_SERVICE')
|
||||
62
src/nestjs/index.ts
Normal file
62
src/nestjs/index.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* NestJS integration for @lilith/minio
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* import { MinioModule, MinioService } from '@lilith/minio/nestjs'
|
||||
*
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRootAsync({
|
||||
* imports: [ConfigModule],
|
||||
* useFactory: (config: ConfigService) => ({
|
||||
* endpoint: config.get('MINIO_ENDPOINT'),
|
||||
* port: config.get('MINIO_PORT'),
|
||||
* accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
* secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
* bucket: config.get('MINIO_BUCKET'),
|
||||
* }),
|
||||
* inject: [ConfigService],
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
*
|
||||
* @Injectable()
|
||||
* export class UploadService {
|
||||
* constructor(private readonly minio: MinioService) {}
|
||||
*
|
||||
* async uploadImage(filename: string, buffer: Buffer) {
|
||||
* await this.minio.uploadBuffer(`images/${filename}`, buffer, 'image/jpeg')
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
|
||||
// Constants
|
||||
export { MINIO_CONFIG, MINIO_SERVICE } from './constants.js'
|
||||
|
||||
// Module
|
||||
export {
|
||||
MinioModule,
|
||||
InjectMinio,
|
||||
type MinioModuleOptions,
|
||||
type MinioModuleAsyncOptions,
|
||||
} from './minio.module.js'
|
||||
|
||||
// Service
|
||||
export { MinioService } from './minio.service.js'
|
||||
|
||||
// Re-export types for convenience
|
||||
export type {
|
||||
MinioConfig,
|
||||
GenerateUploadUrlOptions,
|
||||
GenerateDownloadUrlOptions,
|
||||
PresignedUrl,
|
||||
UploadOptions,
|
||||
ListOptions,
|
||||
ListResult,
|
||||
ObjectInfo,
|
||||
CopyOptions,
|
||||
ExistsResult,
|
||||
} from '../types.js'
|
||||
199
src/nestjs/minio.module.ts
Normal file
199
src/nestjs/minio.module.ts
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { Module, Global, type DynamicModule, type Provider } from '@nestjs/common'
|
||||
import type { MinioConfig } from '../types.js'
|
||||
import { MinioService } from './minio.service.js'
|
||||
import { buildConfigFromEnv } from '../config.js'
|
||||
import { MINIO_CONFIG, MINIO_SERVICE } from './constants.js'
|
||||
|
||||
/**
|
||||
* Options for async configuration
|
||||
*/
|
||||
export interface MinioModuleAsyncOptions {
|
||||
/**
|
||||
* Imports required for useFactory
|
||||
*/
|
||||
imports?: any[]
|
||||
|
||||
/**
|
||||
* Factory function to create config
|
||||
*/
|
||||
useFactory: (...args: any[]) => MinioConfig | Promise<MinioConfig>
|
||||
|
||||
/**
|
||||
* Dependencies to inject into factory
|
||||
*/
|
||||
inject?: any[]
|
||||
|
||||
/**
|
||||
* Whether to make module global
|
||||
* @default true
|
||||
*/
|
||||
isGlobal?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for forRoot
|
||||
*/
|
||||
export interface MinioModuleOptions extends MinioConfig {
|
||||
/**
|
||||
* Whether to make module global
|
||||
* @default true
|
||||
*/
|
||||
isGlobal?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* NestJS Dynamic Module for MinIO
|
||||
*
|
||||
* @example Static configuration
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRoot({
|
||||
* endpoint: 'localhost',
|
||||
* port: 9000,
|
||||
* accessKey: 'minioadmin',
|
||||
* secretKey: 'minioadmin123',
|
||||
* bucket: 'my-bucket',
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*
|
||||
* @example Async configuration with ConfigService
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forRootAsync({
|
||||
* imports: [ConfigModule],
|
||||
* useFactory: (config: ConfigService) => ({
|
||||
* endpoint: config.get('MINIO_ENDPOINT'),
|
||||
* port: config.get('MINIO_PORT'),
|
||||
* accessKey: config.get('MINIO_ACCESS_KEY'),
|
||||
* secretKey: config.get('MINIO_SECRET_KEY'),
|
||||
* bucket: config.get('MINIO_BUCKET'),
|
||||
* }),
|
||||
* inject: [ConfigService],
|
||||
* }),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*
|
||||
* @example Using environment variables
|
||||
* ```ts
|
||||
* @Module({
|
||||
* imports: [
|
||||
* MinioModule.forEnv(),
|
||||
* ],
|
||||
* })
|
||||
* export class AppModule {}
|
||||
* ```
|
||||
*/
|
||||
@Global()
|
||||
@Module({})
|
||||
export class MinioModule {
|
||||
/**
|
||||
* Configure module with static options
|
||||
*/
|
||||
static forRoot(options: MinioModuleOptions): DynamicModule {
|
||||
const { isGlobal = true, ...config } = options
|
||||
|
||||
const configProvider: Provider = {
|
||||
provide: MINIO_CONFIG,
|
||||
useValue: config,
|
||||
}
|
||||
|
||||
return {
|
||||
module: MinioModule,
|
||||
global: isGlobal,
|
||||
providers: [configProvider, MinioService],
|
||||
exports: [MinioService, MINIO_CONFIG],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure module with async factory
|
||||
*/
|
||||
static forRootAsync(options: MinioModuleAsyncOptions): DynamicModule {
|
||||
const { isGlobal = true } = options
|
||||
|
||||
const asyncConfigProvider: Provider = {
|
||||
provide: MINIO_CONFIG,
|
||||
useFactory: options.useFactory,
|
||||
inject: options.inject ?? [],
|
||||
}
|
||||
|
||||
return {
|
||||
module: MinioModule,
|
||||
global: isGlobal,
|
||||
imports: options.imports ?? [],
|
||||
providers: [asyncConfigProvider, MinioService],
|
||||
exports: [MinioService, MINIO_CONFIG],
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Configure module from environment variables
|
||||
*
|
||||
* Uses MINIO_* environment variables:
|
||||
* - MINIO_ENDPOINT
|
||||
* - MINIO_PORT
|
||||
* - MINIO_ACCESS_KEY
|
||||
* - MINIO_SECRET_KEY
|
||||
* - MINIO_BUCKET
|
||||
* - MINIO_USE_SSL (optional)
|
||||
* - MINIO_REGION (optional)
|
||||
*/
|
||||
static forEnv(options?: {
|
||||
prefix?: string
|
||||
defaultBucket?: string
|
||||
isGlobal?: boolean
|
||||
}): DynamicModule {
|
||||
const { isGlobal = true, prefix, defaultBucket } = options ?? {}
|
||||
|
||||
const configProvider: Provider = {
|
||||
provide: MINIO_CONFIG,
|
||||
useFactory: () => buildConfigFromEnv({ prefix, defaultBucket }),
|
||||
}
|
||||
|
||||
return {
|
||||
module: MinioModule,
|
||||
global: isGlobal,
|
||||
providers: [configProvider, MinioService],
|
||||
exports: [MinioService, MINIO_CONFIG],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Decorator to inject MinioService
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* @Injectable()
|
||||
* export class MyService {
|
||||
* constructor(@InjectMinio() private readonly minio: MinioService) {}
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
export function InjectMinio(): ParameterDecorator {
|
||||
return (
|
||||
target: object,
|
||||
propertyKey: string | symbol | undefined,
|
||||
parameterIndex: number
|
||||
) => {
|
||||
// The Inject decorator from @nestjs/common handles this
|
||||
// We just provide a semantic alias
|
||||
const existingParams =
|
||||
Reflect.getMetadata('design:paramtypes', target, propertyKey as string) ||
|
||||
[]
|
||||
existingParams[parameterIndex] = MinioService
|
||||
Reflect.defineMetadata(
|
||||
'design:paramtypes',
|
||||
existingParams,
|
||||
target,
|
||||
propertyKey as string
|
||||
)
|
||||
}
|
||||
}
|
||||
181
src/nestjs/minio.service.ts
Normal file
181
src/nestjs/minio.service.ts
Normal file
|
|
@ -0,0 +1,181 @@
|
|||
import { Injectable, Inject, OnModuleDestroy } from '@nestjs/common'
|
||||
import { MinioClient } from '../client.js'
|
||||
import type {
|
||||
MinioConfig,
|
||||
GenerateUploadUrlOptions,
|
||||
GenerateDownloadUrlOptions,
|
||||
PresignedUrl,
|
||||
UploadOptions,
|
||||
ListOptions,
|
||||
ListResult,
|
||||
ObjectInfo,
|
||||
CopyOptions,
|
||||
ExistsResult,
|
||||
} from '../types.js'
|
||||
import { MINIO_CONFIG } from './constants.js'
|
||||
|
||||
/**
|
||||
* NestJS injectable service wrapping MinioClient
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* @Injectable()
|
||||
* export class UploadService {
|
||||
* constructor(private readonly minio: MinioService) {}
|
||||
*
|
||||
* async createUploadUrl(filename: string) {
|
||||
* return this.minio.generateUploadUrl({
|
||||
* key: `uploads/${filename}`,
|
||||
* contentType: 'image/jpeg',
|
||||
* })
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
@Injectable()
|
||||
export class MinioService implements OnModuleDestroy {
|
||||
private readonly client: MinioClient
|
||||
|
||||
constructor(@Inject(MINIO_CONFIG) config: MinioConfig) {
|
||||
this.client = new MinioClient(config)
|
||||
}
|
||||
|
||||
onModuleDestroy(): void {
|
||||
// S3Client doesn't require explicit cleanup, but this hook
|
||||
// is available if needed for future connection pooling
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying MinioClient for advanced operations
|
||||
*/
|
||||
getClient(): MinioClient {
|
||||
return this.client
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default bucket name
|
||||
*/
|
||||
getBucket(): string {
|
||||
return this.client.getBucket()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for uploading an object
|
||||
*/
|
||||
async generateUploadUrl(
|
||||
options: GenerateUploadUrlOptions
|
||||
): Promise<PresignedUrl> {
|
||||
return this.client.generateUploadUrl(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a presigned URL for downloading an object
|
||||
*/
|
||||
async generateDownloadUrl(
|
||||
options: GenerateDownloadUrlOptions
|
||||
): Promise<PresignedUrl> {
|
||||
return this.client.generateDownloadUrl(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a simple download URL (convenience method)
|
||||
*/
|
||||
async getDownloadUrl(key: string, expiresIn = 3600): Promise<string> {
|
||||
return this.client.getDownloadUrl(key, expiresIn)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload data directly to MinIO
|
||||
*/
|
||||
async upload(options: UploadOptions): Promise<void> {
|
||||
return this.client.upload(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a buffer with automatic content-type detection
|
||||
*/
|
||||
async uploadBuffer(
|
||||
key: string,
|
||||
buffer: Buffer,
|
||||
contentType?: string
|
||||
): Promise<void> {
|
||||
return this.client.uploadBuffer(key, buffer, contentType)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object as a Buffer
|
||||
*/
|
||||
async download(key: string): Promise<Buffer> {
|
||||
return this.client.download(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object as a string
|
||||
*/
|
||||
async downloadString(key: string, encoding: BufferEncoding = 'utf-8'): Promise<string> {
|
||||
return this.client.downloadString(key, encoding)
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object as JSON
|
||||
*/
|
||||
async downloadJson<T = unknown>(key: string): Promise<T> {
|
||||
return this.client.downloadJson<T>(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an object
|
||||
*/
|
||||
async delete(key: string): Promise<void> {
|
||||
return this.client.delete(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete multiple objects
|
||||
*/
|
||||
async deleteMany(keys: string[]): Promise<void> {
|
||||
return this.client.deleteMany(keys)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an object exists
|
||||
*/
|
||||
async exists(key: string): Promise<ExistsResult> {
|
||||
return this.client.exists(key)
|
||||
}
|
||||
|
||||
/**
|
||||
* List objects in the bucket
|
||||
*/
|
||||
async list(options?: ListOptions): Promise<ListResult> {
|
||||
return this.client.list(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* List all objects with a prefix (handles pagination)
|
||||
*/
|
||||
async listAll(prefix?: string): Promise<ObjectInfo[]> {
|
||||
return this.client.listAll(prefix)
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy an object within MinIO
|
||||
*/
|
||||
async copy(options: CopyOptions): Promise<void> {
|
||||
return this.client.copy(options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Move an object (copy + delete)
|
||||
*/
|
||||
async move(sourceKey: string, destinationKey: string): Promise<void> {
|
||||
return this.client.move(sourceKey, destinationKey)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get object metadata without downloading the content
|
||||
*/
|
||||
async getMetadata(key: string): Promise<ExistsResult['metadata']> {
|
||||
return this.client.getMetadata(key)
|
||||
}
|
||||
}
|
||||
354
src/types.ts
Normal file
354
src/types.ts
Normal file
|
|
@ -0,0 +1,354 @@
|
|||
/**
|
||||
* MinIO Client Configuration
|
||||
*/
|
||||
export interface MinioConfig {
|
||||
/**
|
||||
* MinIO server endpoint (e.g., 'localhost:9000' or 'minio.example.com')
|
||||
*/
|
||||
endpoint: string
|
||||
|
||||
/**
|
||||
* Access key for authentication
|
||||
*/
|
||||
accessKey: string
|
||||
|
||||
/**
|
||||
* Secret key for authentication
|
||||
*/
|
||||
secretKey: string
|
||||
|
||||
/**
|
||||
* Default bucket name for operations
|
||||
*/
|
||||
bucket: string
|
||||
|
||||
/**
|
||||
* Region (required by AWS SDK, but MinIO ignores it)
|
||||
* @default 'us-east-1'
|
||||
*/
|
||||
region?: string
|
||||
|
||||
/**
|
||||
* Use SSL/TLS for connections
|
||||
* @default false for development, true for production
|
||||
*/
|
||||
useSSL?: boolean
|
||||
|
||||
/**
|
||||
* Port number (if not included in endpoint)
|
||||
*/
|
||||
port?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for generating presigned upload URLs
|
||||
*/
|
||||
export interface GenerateUploadUrlOptions {
|
||||
/**
|
||||
* Object key (path in bucket)
|
||||
*/
|
||||
key: string
|
||||
|
||||
/**
|
||||
* Content-Type header for the upload
|
||||
*/
|
||||
contentType?: string
|
||||
|
||||
/**
|
||||
* URL expiration time in seconds
|
||||
* @default 3600 (1 hour)
|
||||
*/
|
||||
expiresIn?: number
|
||||
|
||||
/**
|
||||
* Custom metadata to attach to the object
|
||||
*/
|
||||
metadata?: Record<string, string>
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for generating presigned download URLs
|
||||
*/
|
||||
export interface GenerateDownloadUrlOptions {
|
||||
/**
|
||||
* Object key (path in bucket)
|
||||
*/
|
||||
key: string
|
||||
|
||||
/**
|
||||
* URL expiration time in seconds
|
||||
* @default 3600 (1 hour)
|
||||
*/
|
||||
expiresIn?: number
|
||||
|
||||
/**
|
||||
* Content-Disposition header value (e.g., 'attachment; filename="file.pdf"')
|
||||
*/
|
||||
responseContentDisposition?: string
|
||||
|
||||
/**
|
||||
* Content-Type header to return
|
||||
*/
|
||||
responseContentType?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of presigned URL generation
|
||||
*/
|
||||
export interface PresignedUrl {
|
||||
/**
|
||||
* The presigned URL
|
||||
*/
|
||||
url: string
|
||||
|
||||
/**
|
||||
* ISO 8601 timestamp when the URL expires
|
||||
*/
|
||||
expiresAt: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for direct upload
|
||||
*/
|
||||
export interface UploadOptions {
|
||||
/**
|
||||
* Object key (path in bucket)
|
||||
*/
|
||||
key: string
|
||||
|
||||
/**
|
||||
* Data to upload
|
||||
*/
|
||||
body: Buffer | Uint8Array | string
|
||||
|
||||
/**
|
||||
* Content-Type header
|
||||
*/
|
||||
contentType: string
|
||||
|
||||
/**
|
||||
* Custom metadata
|
||||
*/
|
||||
metadata?: Record<string, string>
|
||||
|
||||
/**
|
||||
* Cache-Control header
|
||||
*/
|
||||
cacheControl?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for listing objects
|
||||
*/
|
||||
export interface ListOptions {
|
||||
/**
|
||||
* Prefix to filter objects
|
||||
*/
|
||||
prefix?: string
|
||||
|
||||
/**
|
||||
* Maximum number of objects to return
|
||||
* @default 1000
|
||||
*/
|
||||
maxKeys?: number
|
||||
|
||||
/**
|
||||
* Continuation token for pagination
|
||||
*/
|
||||
continuationToken?: string
|
||||
|
||||
/**
|
||||
* Delimiter for grouping (e.g., '/' for directory-like listing)
|
||||
*/
|
||||
delimiter?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Object metadata from MinIO
|
||||
*/
|
||||
export interface ObjectInfo {
|
||||
/**
|
||||
* Object key
|
||||
*/
|
||||
key: string
|
||||
|
||||
/**
|
||||
* Last modification date
|
||||
*/
|
||||
lastModified: Date
|
||||
|
||||
/**
|
||||
* Object size in bytes
|
||||
*/
|
||||
size: number
|
||||
|
||||
/**
|
||||
* ETag (usually MD5 hash)
|
||||
*/
|
||||
etag: string
|
||||
|
||||
/**
|
||||
* Storage class
|
||||
*/
|
||||
storageClass?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of listing objects
|
||||
*/
|
||||
export interface ListResult {
|
||||
/**
|
||||
* Objects found
|
||||
*/
|
||||
objects: ObjectInfo[]
|
||||
|
||||
/**
|
||||
* Common prefixes (when using delimiter)
|
||||
*/
|
||||
prefixes: string[]
|
||||
|
||||
/**
|
||||
* Whether there are more results
|
||||
*/
|
||||
isTruncated: boolean
|
||||
|
||||
/**
|
||||
* Token for next page
|
||||
*/
|
||||
nextContinuationToken?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for copy operation
|
||||
*/
|
||||
export interface CopyOptions {
|
||||
/**
|
||||
* Source object key
|
||||
*/
|
||||
sourceKey: string
|
||||
|
||||
/**
|
||||
* Destination object key
|
||||
*/
|
||||
destinationKey: string
|
||||
|
||||
/**
|
||||
* Source bucket (if different from default)
|
||||
*/
|
||||
sourceBucket?: string
|
||||
|
||||
/**
|
||||
* Destination bucket (if different from default)
|
||||
*/
|
||||
destinationBucket?: string
|
||||
|
||||
/**
|
||||
* New metadata (replaces source metadata)
|
||||
*/
|
||||
metadata?: Record<string, string>
|
||||
|
||||
/**
|
||||
* New content type
|
||||
*/
|
||||
contentType?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Result of object existence check
|
||||
*/
|
||||
export interface ExistsResult {
|
||||
/**
|
||||
* Whether the object exists
|
||||
*/
|
||||
exists: boolean
|
||||
|
||||
/**
|
||||
* Object metadata if exists
|
||||
*/
|
||||
metadata?: {
|
||||
contentType?: string
|
||||
contentLength?: number
|
||||
lastModified?: Date
|
||||
etag?: string
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* MIME type mapping for common extensions
|
||||
*/
|
||||
export const MIME_TYPES: Record<string, string> = {
|
||||
// Images
|
||||
jpg: 'image/jpeg',
|
||||
jpeg: 'image/jpeg',
|
||||
png: 'image/png',
|
||||
gif: 'image/gif',
|
||||
webp: 'image/webp',
|
||||
svg: 'image/svg+xml',
|
||||
ico: 'image/x-icon',
|
||||
bmp: 'image/bmp',
|
||||
tiff: 'image/tiff',
|
||||
tif: 'image/tiff',
|
||||
avif: 'image/avif',
|
||||
heic: 'image/heic',
|
||||
heif: 'image/heif',
|
||||
|
||||
// Documents
|
||||
pdf: 'application/pdf',
|
||||
doc: 'application/msword',
|
||||
docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
|
||||
xls: 'application/vnd.ms-excel',
|
||||
xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
|
||||
ppt: 'application/vnd.ms-powerpoint',
|
||||
pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
|
||||
|
||||
// Text
|
||||
txt: 'text/plain',
|
||||
csv: 'text/csv',
|
||||
json: 'application/json',
|
||||
xml: 'application/xml',
|
||||
html: 'text/html',
|
||||
htm: 'text/html',
|
||||
css: 'text/css',
|
||||
js: 'application/javascript',
|
||||
ts: 'application/typescript',
|
||||
|
||||
// Archives
|
||||
zip: 'application/zip',
|
||||
tar: 'application/x-tar',
|
||||
gz: 'application/gzip',
|
||||
rar: 'application/vnd.rar',
|
||||
'7z': 'application/x-7z-compressed',
|
||||
|
||||
// Audio
|
||||
mp3: 'audio/mpeg',
|
||||
wav: 'audio/wav',
|
||||
ogg: 'audio/ogg',
|
||||
flac: 'audio/flac',
|
||||
aac: 'audio/aac',
|
||||
m4a: 'audio/mp4',
|
||||
|
||||
// Video
|
||||
mp4: 'video/mp4',
|
||||
webm: 'video/webm',
|
||||
avi: 'video/x-msvideo',
|
||||
mov: 'video/quicktime',
|
||||
mkv: 'video/x-matroska',
|
||||
|
||||
// Fonts
|
||||
woff: 'font/woff',
|
||||
woff2: 'font/woff2',
|
||||
ttf: 'font/ttf',
|
||||
otf: 'font/otf',
|
||||
eot: 'application/vnd.ms-fontobject',
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MIME type from file extension
|
||||
*/
|
||||
export function getMimeType(filenameOrExtension: string): string {
|
||||
const ext = filenameOrExtension.includes('.')
|
||||
? filenameOrExtension.split('.').pop()?.toLowerCase()
|
||||
: filenameOrExtension.toLowerCase()
|
||||
|
||||
return MIME_TYPES[ext || ''] || 'application/octet-stream'
|
||||
}
|
||||
20
tsconfig.json
Normal file
20
tsconfig.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"lib": ["ES2022"],
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./src",
|
||||
"resolveJsonModule": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue