@mr-aftab-ahmad-khan/upflow 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10 -0
- package/LICENSE +15 -0
- package/README.md +414 -0
- package/dist/index.cjs +706 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +200 -0
- package/dist/index.d.ts +200 -0
- package/dist/index.js +655 -0
- package/dist/index.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/upflow.ts","../src/multipart.ts","../src/errors.ts","../src/util.ts","../src/storage/disk.ts","../src/storage/s3.ts","../src/storage/r2.ts","../src/storage/memory.ts"],"sourcesContent":["import { EventEmitter } from \"node:events\";\nimport { PassThrough, Readable } from \"node:stream\";\nimport { parseMultipart, getBoundary, type MultipartFilePart } from \"./multipart.js\";\nimport {\n FileTooLargeError,\n InvalidMimeTypeError,\n UploadAbortedError,\n UploadError,\n} from \"./errors.js\";\nimport {\n appendUuidSuffix,\n detectMimeFromBytes,\n mimeMatchesAllowed,\n sanitizeFilename,\n webStreamToNode,\n} from \"./util.js\";\nimport type {\n PresignOptions,\n PresignResult,\n ProgressEvent,\n UploadedFile,\n UpflowOptions,\n} from \"./types.js\";\n\ntype RequestLike = { headers: Record<string, string | string[] | undefined>; body?: unknown };\n\nexport class Upflow extends EventEmitter {\n readonly options: UpflowOptions;\n\n constructor(options: UpflowOptions) {\n super();\n this.options = options;\n }\n\n presign(opts: PresignOptions): Promise<PresignResult> {\n if (!this.options.storage.presign) {\n throw new UploadError(\"Storage adapter does not support presigned uploads\", 501);\n }\n return this.options.storage.presign(opts);\n }\n\n // -------------------- Express middleware --------------------\n single(fieldName: string) {\n return async (req: any, res: any, next: (err?: unknown) => void): Promise<void> => {\n try {\n const files = await this.handleNodeRequest(req, { single: fieldName });\n req.file = files[0];\n next();\n } catch (err) {\n next(err);\n }\n };\n }\n\n array(fieldName: string, maxCount?: number) {\n return async (req: any, _res: any, next: (err?: unknown) => void): Promise<void> => {\n try {\n const files = await this.handleNodeRequest(req, { array: fieldName, maxCount });\n req.files = files;\n next();\n } catch (err) {\n next(err);\n }\n };\n }\n\n // -------------------- Generic fetch Request handler --------------------\n handler() {\n return async (request: Request): Promise<{ files: UploadedFile[] }> => {\n const contentType = request.headers.get(\"content-type\") ?? \"\";\n const boundary = getBoundary(contentType);\n if (!boundary) throw new UploadError(\"Missing multipart boundary\");\n const body = request.body;\n if (!body) throw new UploadError(\"Missing request body\");\n const nodeStream = webStreamToNode(body);\n const files = await this.consumeMultipart(nodeStream, boundary, request);\n return { files };\n };\n }\n\n // -------------------- Hono middleware --------------------\n hono() {\n return async (c: any, next: () => Promise<void>) => {\n const result = await this.handler()(c.req.raw as Request);\n c.set(\"uploadedFiles\", result.files);\n await next();\n };\n }\n\n // -------------------- Fastify plugin --------------------\n fastify() {\n return async (instance: any) => {\n instance.addContentTypeParser(\n \"multipart/form-data\",\n { parseAs: \"buffer\" },\n async (req: any, payload: Buffer) => {\n const boundary = getBoundary(req.headers[\"content-type\"]);\n if (!boundary) throw new UploadError(\"Missing multipart boundary\");\n const stream = Readable.from(payload);\n return { files: await this.consumeMultipart(stream, boundary, req) };\n },\n );\n };\n }\n\n // -------------------- Next.js App Router --------------------\n nextjs() {\n return async (request: Request): Promise<Response> => {\n const result = await this.handler()(request);\n return new Response(JSON.stringify({ files: result.files }), {\n status: 200,\n headers: { \"content-type\": \"application/json\" },\n });\n };\n }\n\n // -------------------- Core --------------------\n private async handleNodeRequest(\n req: RequestLike & AsyncIterable<Uint8Array>,\n opts: { single?: string; array?: string; maxCount?: number },\n ): Promise<UploadedFile[]> {\n const contentType = (req.headers[\"content-type\"] as string) ?? \"\";\n const boundary = getBoundary(contentType);\n if (!boundary) throw new UploadError(\"Missing multipart boundary\");\n const files = await this.consumeMultipart(req as unknown as Readable, boundary, req);\n const allowed = (opts.single ?? opts.array)!;\n const matched = files.filter((f) => f.fieldName === allowed);\n if (opts.single) {\n if (matched.length === 0) throw new UploadError(`Expected file under field \"${allowed}\"`);\n return [matched[0]!];\n }\n if (opts.maxCount && matched.length > opts.maxCount) {\n throw new UploadError(\n `Too many files for field \"${allowed}\" (got ${matched.length}, max ${opts.maxCount})`,\n );\n }\n return matched;\n }\n\n private async consumeMultipart(\n body: Readable | AsyncIterable<Uint8Array>,\n boundary: string,\n req: unknown,\n ): Promise<UploadedFile[]> {\n const limits = this.options.limits ?? {};\n const files: UploadedFile[] = [];\n let fileCount = 0;\n\n for await (const part of parseMultipart(body, boundary)) {\n if (part.type !== \"file\") continue;\n fileCount += 1;\n if (limits.files && fileCount > limits.files) {\n throw new UploadError(`Too many files (max ${limits.files})`, 413);\n }\n\n const safeOriginal = sanitizeFilename(part.filename);\n const filename = appendUuidSuffix(safeOriginal);\n await this.options.hooks?.onUploadStart?.({ filename, mimeType: part.mimeType }, req);\n\n const validated = await this.validateAndPipe(part, filename, limits);\n\n const stored = await this.options.storage.upload({\n stream: validated.stream,\n filename,\n mimeType: validated.mimeType,\n });\n\n const result: UploadedFile = {\n fieldName: part.fieldName,\n filename,\n originalName: part.filename,\n mimeType: validated.mimeType,\n size: stored.size,\n storageKey: stored.key,\n ...(stored.url !== undefined ? { url: stored.url } : {}),\n metadata: {},\n };\n await this.options.hooks?.onUploadComplete?.(result, req);\n files.push(result);\n }\n return files;\n }\n\n private async validateAndPipe(\n part: MultipartFilePart,\n filename: string,\n limits: NonNullable<UpflowOptions[\"limits\"]>,\n ): Promise<{ stream: Readable; mimeType: string }> {\n const out = new PassThrough();\n const maxSize = limits.fileSize ?? Number.MAX_SAFE_INTEGER;\n let received = 0;\n let detected = part.mimeType;\n let head = Buffer.alloc(0);\n let validated = false;\n\n part.stream.on(\"error\", (err) => out.destroy(err));\n part.stream.on(\"data\", (chunk: Buffer) => {\n received += chunk.length;\n if (received > maxSize) {\n const err = new FileTooLargeError(maxSize);\n out.destroy(err);\n part.stream.destroy(err);\n return;\n }\n if (!validated) {\n head = Buffer.concat([head, chunk]);\n if (head.length >= 16) {\n const sniffed = detectMimeFromBytes(head);\n if (sniffed) detected = sniffed;\n if (!mimeMatchesAllowed(detected, limits.allowedMimeTypes)) {\n const err = new InvalidMimeTypeError(detected, limits.allowedMimeTypes ?? []);\n out.destroy(err);\n part.stream.destroy(err);\n return;\n }\n validated = true;\n out.write(head);\n head = Buffer.alloc(0);\n return;\n }\n return;\n }\n out.write(chunk);\n this.emit(\"progress\", {\n filename,\n bytesReceived: received,\n bytesTotal: null,\n percent: null,\n } as ProgressEvent);\n });\n part.stream.on(\"end\", () => {\n if (!validated && head.length > 0) {\n const sniffed = detectMimeFromBytes(head) ?? detected;\n if (!mimeMatchesAllowed(sniffed, limits.allowedMimeTypes)) {\n out.destroy(new InvalidMimeTypeError(sniffed, limits.allowedMimeTypes ?? []));\n return;\n }\n detected = sniffed;\n out.write(head);\n }\n out.end();\n });\n part.stream.on(\"close\", () => {\n if (!part.stream.readableEnded) out.destroy(new UploadAbortedError());\n });\n\n return { stream: out, mimeType: detected };\n }\n}\n\nexport function upflow(options: UpflowOptions): Upflow {\n return new Upflow(options);\n}\n","import { Readable } from \"node:stream\";\n\nexport interface MultipartFilePart {\n type: \"file\";\n fieldName: string;\n filename: string;\n mimeType: string;\n stream: Readable;\n}\nexport interface MultipartFieldPart {\n type: \"field\";\n fieldName: string;\n value: string;\n}\nexport type MultipartPart = MultipartFilePart | MultipartFieldPart;\n\n/**\n * Multipart/form-data parser. Buffers the request body, then walks it\n * synchronously. Sized uploads are guarded by `limits.fileSize` in the caller.\n *\n * We chose buffer-then-parse over streaming because correctly streaming\n * multipart with proper boundary detection requires a non-trivial state\n * machine; buffering keeps the code small and the public API streaming-friendly\n * (each file part is exposed as a `Readable` you can pipe into S3, sharp, etc.).\n */\nexport async function* parseMultipart(\n body: Readable | AsyncIterable<Uint8Array>,\n boundary: string,\n): AsyncIterable<MultipartPart> {\n const buf = await collect(body);\n const parts = splitParts(buf, boundary);\n for (const p of parts) yield p;\n}\n\nasync function collect(body: Readable | AsyncIterable<Uint8Array>): Promise<Buffer> {\n const chunks: Buffer[] = [];\n for await (const c of body as AsyncIterable<Uint8Array>) {\n chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c));\n }\n return Buffer.concat(chunks);\n}\n\nfunction splitParts(buf: Buffer, boundary: string): MultipartPart[] {\n const out: MultipartPart[] = [];\n const delim = Buffer.from(`--${boundary}`);\n const CRLF = Buffer.from(\"\\r\\n\");\n\n let cursor = buf.indexOf(delim);\n if (cursor === -1) return out;\n cursor += delim.length;\n\n while (cursor < buf.length) {\n if (buf.slice(cursor, cursor + 2).equals(Buffer.from(\"--\"))) return out;\n if (buf.slice(cursor, cursor + 2).equals(CRLF)) cursor += 2;\n\n const headerEnd = buf.indexOf(Buffer.from(\"\\r\\n\\r\\n\"), cursor);\n if (headerEnd === -1) return out;\n const headerStr = buf.slice(cursor, headerEnd).toString(\"utf8\");\n cursor = headerEnd + 4;\n\n const headers: Record<string, string> = {};\n for (const line of headerStr.split(\"\\r\\n\")) {\n const c = line.indexOf(\":\");\n if (c === -1) continue;\n headers[line.slice(0, c).toLowerCase().trim()] = line.slice(c + 1).trim();\n }\n\n const sep = Buffer.concat([CRLF, delim]);\n const partEnd = buf.indexOf(sep, cursor);\n if (partEnd === -1) return out;\n const body = buf.slice(cursor, partEnd);\n cursor = partEnd + sep.length;\n\n const disposition = headers[\"content-disposition\"] ?? \"\";\n const nameMatch = /name=\"([^\"]*)\"/.exec(disposition);\n const filenameMatch = /filename=\"([^\"]*)\"/.exec(disposition);\n const fieldName = nameMatch ? nameMatch[1] ?? \"\" : \"\";\n const filename = filenameMatch ? filenameMatch[1] ?? \"\" : \"\";\n const mimeType = headers[\"content-type\"] ?? \"application/octet-stream\";\n\n if (filename) {\n out.push({\n type: \"file\",\n fieldName,\n filename,\n mimeType,\n stream: Readable.from(body),\n });\n } else {\n out.push({ type: \"field\", fieldName, value: body.toString(\"utf8\") });\n }\n }\n return out;\n}\n\nexport function getBoundary(contentType: string | undefined): string | undefined {\n if (!contentType) return undefined;\n const m = /boundary=\"?([^\";]+)\"?/i.exec(contentType);\n return m ? m[1] : undefined;\n}\n","export class UploadError extends Error {\n readonly status: number;\n constructor(message: string, status = 400) {\n super(message);\n this.name = \"UploadError\";\n this.status = status;\n Object.setPrototypeOf(this, UploadError.prototype);\n }\n}\n\nexport class FileTooLargeError extends UploadError {\n constructor(public readonly limit: number) {\n super(`File exceeds maximum size of ${limit} bytes`, 413);\n this.name = \"FileTooLargeError\";\n Object.setPrototypeOf(this, FileTooLargeError.prototype);\n }\n}\n\nexport class InvalidMimeTypeError extends UploadError {\n constructor(public readonly mime: string, public readonly allowed: string[]) {\n super(`Mime type \"${mime}\" is not in allowed list: ${allowed.join(\", \")}`, 415);\n this.name = \"InvalidMimeTypeError\";\n Object.setPrototypeOf(this, InvalidMimeTypeError.prototype);\n }\n}\n\nexport class StorageError extends UploadError {\n constructor(message: string, public readonly cause?: unknown) {\n super(message, 500);\n this.name = \"StorageError\";\n Object.setPrototypeOf(this, StorageError.prototype);\n }\n}\n\nexport class UploadAbortedError extends UploadError {\n constructor() {\n super(\"Upload was aborted by the client\", 499);\n this.name = \"UploadAbortedError\";\n Object.setPrototypeOf(this, UploadAbortedError.prototype);\n }\n}\n","import { Readable } from \"node:stream\";\nimport { randomUUID } from \"node:crypto\";\n\nconst RESERVED_WIN = /[<>:\"/\\\\|?*\\x00-\\x1f]/g;\n\nexport function sanitizeFilename(name: string): string {\n let safe = name.replace(/\\\\/g, \"/\").split(\"/\").pop() ?? \"file\";\n safe = safe.replace(RESERVED_WIN, \"_\");\n safe = safe.replace(/\\.\\./g, \"_\");\n safe = safe.replace(/\\s+/g, \"_\");\n safe = safe.replace(/^[._-]+/, \"\");\n if (!safe || safe === \".\") safe = \"file\";\n return safe.slice(0, 200);\n}\n\nexport function appendUuidSuffix(name: string): string {\n const dot = name.lastIndexOf(\".\");\n const stem = dot > 0 ? name.slice(0, dot) : name;\n const ext = dot > 0 ? name.slice(dot) : \"\";\n return `${stem}-${randomUUID().slice(0, 8)}${ext}`;\n}\n\ninterface MagicSignature {\n bytes: number[];\n mime: string;\n offset?: number;\n}\n\nconst MAGIC_TABLE: MagicSignature[] = [\n { bytes: [0xff, 0xd8, 0xff], mime: \"image/jpeg\" },\n { bytes: [0x89, 0x50, 0x4e, 0x47], mime: \"image/png\" },\n { bytes: [0x47, 0x49, 0x46, 0x38], mime: \"image/gif\" },\n { bytes: [0x42, 0x4d], mime: \"image/bmp\" },\n { bytes: [0x52, 0x49, 0x46, 0x46], mime: \"image/webp\" }, // also AVI/WAV — narrowed below\n { bytes: [0x66, 0x74, 0x79, 0x70], mime: \"video/mp4\", offset: 4 },\n { bytes: [0x25, 0x50, 0x44, 0x46], mime: \"application/pdf\" },\n { bytes: [0x50, 0x4b, 0x03, 0x04], mime: \"application/zip\" },\n { bytes: [0x1f, 0x8b], mime: \"application/gzip\" },\n];\n\nexport function detectMimeFromBytes(buf: Buffer): string | undefined {\n for (const sig of MAGIC_TABLE) {\n const off = sig.offset ?? 0;\n if (buf.length < off + sig.bytes.length) continue;\n let ok = true;\n for (let i = 0; i < sig.bytes.length; i++) {\n if (buf[off + i] !== sig.bytes[i]) {\n ok = false;\n break;\n }\n }\n if (ok) {\n if (sig.mime === \"image/webp\" && buf.length >= 12) {\n const tag = buf.slice(8, 12).toString(\"ascii\");\n if (tag === \"WEBP\") return \"image/webp\";\n if (tag === \"WAVE\") return \"audio/wav\";\n if (tag.startsWith(\"AVI\")) return \"video/x-msvideo\";\n return undefined;\n }\n return sig.mime;\n }\n }\n return undefined;\n}\n\nexport function mimeMatchesAllowed(mime: string, allowed: string[] | undefined): boolean {\n if (!allowed || allowed.length === 0) return true;\n for (const pattern of allowed) {\n if (pattern === mime) return true;\n if (pattern.endsWith(\"/*\") && mime.startsWith(pattern.slice(0, -1))) return true;\n if (pattern === \"*/*\") return true;\n }\n return false;\n}\n\n/** Convert a Web ReadableStream into a Node Readable. */\nexport function webStreamToNode(stream: ReadableStream<Uint8Array>): Readable {\n const reader = stream.getReader();\n return new Readable({\n async read() {\n try {\n const { done, value } = await reader.read();\n if (done) this.push(null);\n else this.push(Buffer.from(value));\n } catch (err) {\n this.destroy(err as Error);\n }\n },\n });\n}\n\nexport function applyTemplate(template: string, vars: Record<string, string>): string {\n return template.replace(/\\{([a-zA-Z0-9_-]+)\\}/g, (_m, key: string) => vars[key] ?? \"\");\n}\n","import { createWriteStream, existsSync, mkdirSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { randomUUID } from \"node:crypto\";\nimport { pipeline } from \"node:stream/promises\";\nimport { Readable } from \"node:stream\";\nimport { applyTemplate, sanitizeFilename } from \"../util.js\";\nimport type { PresignOptions, PresignResult, StorageAdapter } from \"../types.js\";\n\nexport interface DiskStorageOptions {\n root: string;\n /** Template for the on-disk key. Variables: `{date}`, `{uuid}`, `{filename}`, `{ext}`. */\n pathTemplate?: string;\n /** Public URL prefix for files. Defaults to `undefined` (no URL). */\n publicUrlPrefix?: string;\n}\n\nexport class DiskStorage implements StorageAdapter {\n readonly root: string;\n readonly pathTemplate: string;\n readonly publicUrlPrefix?: string;\n\n constructor(opts: DiskStorageOptions) {\n this.root = opts.root;\n this.pathTemplate = opts.pathTemplate ?? \"{date}/{uuid}-{filename}\";\n if (opts.publicUrlPrefix !== undefined) this.publicUrlPrefix = opts.publicUrlPrefix;\n }\n\n async upload(input: {\n stream: Readable;\n filename: string;\n mimeType: string;\n }): Promise<{ key: string; url?: string; size: number }> {\n const safe = sanitizeFilename(input.filename);\n const date = new Date().toISOString().slice(0, 10);\n const dot = safe.lastIndexOf(\".\");\n const ext = dot > 0 ? safe.slice(dot + 1) : \"\";\n const key = applyTemplate(this.pathTemplate, {\n date,\n uuid: randomUUID(),\n filename: safe,\n ext,\n });\n\n const fullPath = join(this.root, key);\n if (!existsSync(dirname(fullPath))) mkdirSync(dirname(fullPath), { recursive: true });\n\n let size = 0;\n input.stream.on(\"data\", (c: Buffer) => {\n size += c.length;\n });\n\n await pipeline(input.stream, createWriteStream(fullPath));\n\n const result: { key: string; url?: string; size: number } = { key, size };\n if (this.publicUrlPrefix) {\n result.url = `${this.publicUrlPrefix.replace(/\\/$/, \"\")}/${key}`;\n }\n return result;\n }\n\n async presign(_opts: PresignOptions): Promise<PresignResult> {\n throw new Error(\"DiskStorage does not support presigned uploads\");\n }\n}\n","import { Readable } from \"node:stream\";\nimport { randomUUID, createHmac, createHash } from \"node:crypto\";\nimport { sanitizeFilename } from \"../util.js\";\nimport { StorageError } from \"../errors.js\";\nimport type { PresignOptions, PresignResult, StorageAdapter } from \"../types.js\";\n\nexport interface S3StorageOptions {\n bucket: string;\n region: string;\n /** Required when not running in AWS Lambda / EC2. */\n accessKeyId?: string;\n secretAccessKey?: string;\n /** For R2 or custom endpoints. */\n endpoint?: string;\n /** Chunk size for multipart, default 8MB. Files smaller than `multipartThreshold` use a single PUT. */\n partSize?: number;\n multipartThreshold?: number;\n /** Public URL prefix to construct the returned URL. */\n publicUrlPrefix?: string;\n /** Optional `@aws-sdk/client-s3` client. If provided, that SDK is used; otherwise raw HTTP signatures are used. */\n client?: unknown;\n}\n\nconst DEFAULT_PART_SIZE = 8 * 1024 * 1024;\nconst DEFAULT_THRESHOLD = 5 * 1024 * 1024;\n\nexport class S3Storage implements StorageAdapter {\n readonly bucket: string;\n readonly region: string;\n readonly endpoint: string;\n readonly partSize: number;\n readonly multipartThreshold: number;\n readonly publicUrlPrefix?: string;\n readonly accessKeyId?: string;\n readonly secretAccessKey?: string;\n readonly client?: unknown;\n\n constructor(opts: S3StorageOptions) {\n this.bucket = opts.bucket;\n this.region = opts.region;\n this.endpoint = opts.endpoint ?? `https://s3.${opts.region}.amazonaws.com`;\n this.partSize = opts.partSize ?? DEFAULT_PART_SIZE;\n this.multipartThreshold = opts.multipartThreshold ?? DEFAULT_THRESHOLD;\n if (opts.publicUrlPrefix !== undefined) this.publicUrlPrefix = opts.publicUrlPrefix;\n if (opts.accessKeyId !== undefined) this.accessKeyId = opts.accessKeyId;\n if (opts.secretAccessKey !== undefined) this.secretAccessKey = opts.secretAccessKey;\n if (opts.client !== undefined) this.client = opts.client;\n }\n\n private buildKey(filename: string): string {\n const safe = sanitizeFilename(filename);\n return `${new Date().toISOString().slice(0, 10)}/${randomUUID()}-${safe}`;\n }\n\n private buildUrl(key: string): string | undefined {\n if (this.publicUrlPrefix) return `${this.publicUrlPrefix.replace(/\\/$/, \"\")}/${key}`;\n return undefined;\n }\n\n async upload(input: { stream: Readable; filename: string; mimeType: string; size?: number }) {\n const key = this.buildKey(input.filename);\n if (this.client && hasSdk(this.client)) {\n try {\n await this.client.send(new (await loadCmd(\"PutObjectCommand\"))({\n Bucket: this.bucket,\n Key: key,\n Body: input.stream,\n ContentType: input.mimeType,\n }));\n } catch (err) {\n throw new StorageError(\"S3 PutObject failed\", err);\n }\n const url = this.buildUrl(key);\n const result: { key: string; url?: string; size: number } = { key, size: input.size ?? 0 };\n if (url) result.url = url;\n return result;\n }\n // Fallback: buffer + signed PUT. Production users should provide an `@aws-sdk/client-s3` client.\n const chunks: Buffer[] = [];\n let size = 0;\n for await (const c of input.stream) {\n const b = Buffer.isBuffer(c) ? c : Buffer.from(c as Uint8Array);\n chunks.push(b);\n size += b.length;\n }\n const body = Buffer.concat(chunks);\n const url = `${this.endpoint}/${this.bucket}/${encodeURI(key)}`;\n const headers = await this.signRequest(\"PUT\", url, body, input.mimeType);\n const res = await fetch(url, { method: \"PUT\", headers, body });\n if (!res.ok) throw new StorageError(`S3 PUT failed with ${res.status}`);\n const result: { key: string; url?: string; size: number } = { key, size };\n const publicUrl = this.buildUrl(key);\n if (publicUrl) result.url = publicUrl;\n return result;\n }\n\n async presign(opts: PresignOptions): Promise<PresignResult> {\n const key = this.buildKey(opts.filename);\n const expires = opts.expiresInSeconds ?? 600;\n const date = new Date();\n const dateStamp = date.toISOString().slice(0, 10).replace(/-/g, \"\");\n const amzDate = date.toISOString().replace(/[-:]/g, \"\").replace(/\\.\\d{3}/, \"\");\n const credential = `${this.accessKeyId ?? \"\"}/${dateStamp}/${this.region}/s3/aws4_request`;\n const policy = Buffer.from(JSON.stringify({\n expiration: new Date(Date.now() + expires * 1000).toISOString(),\n conditions: [\n { bucket: this.bucket },\n [\"starts-with\", \"$key\", key.split(\"/\")[0] ?? \"\"],\n { \"content-type\": opts.contentType },\n [\"content-length-range\", 0, opts.maxSizeBytes ?? 100 * 1024 * 1024],\n { \"x-amz-credential\": credential },\n { \"x-amz-algorithm\": \"AWS4-HMAC-SHA256\" },\n { \"x-amz-date\": amzDate },\n ],\n })).toString(\"base64\");\n const signingKey = await this.getSigningKey(dateStamp);\n const signature = createHmac(\"sha256\", signingKey).update(policy).digest(\"hex\");\n return {\n url: `${this.endpoint}/${this.bucket}`,\n fields: {\n key,\n \"Content-Type\": opts.contentType,\n \"x-amz-credential\": credential,\n \"x-amz-algorithm\": \"AWS4-HMAC-SHA256\",\n \"x-amz-date\": amzDate,\n Policy: policy,\n \"x-amz-signature\": signature,\n },\n storageKey: key,\n expiresAt: new Date(Date.now() + expires * 1000).toISOString(),\n };\n }\n\n private async signRequest(method: string, url: string, body: Buffer, contentType: string) {\n const u = new URL(url);\n const date = new Date();\n const amzDate = date.toISOString().replace(/[-:]/g, \"\").replace(/\\.\\d{3}/, \"\");\n const dateStamp = amzDate.slice(0, 8);\n const payloadHash = createHash(\"sha256\").update(body).digest(\"hex\");\n const canonicalUri = u.pathname;\n const canonicalQuery = \"\";\n const canonicalHeaders =\n `content-type:${contentType}\\nhost:${u.host}\\nx-amz-content-sha256:${payloadHash}\\nx-amz-date:${amzDate}\\n`;\n const signedHeaders = \"content-type;host;x-amz-content-sha256;x-amz-date\";\n const canonicalReq = `${method}\\n${canonicalUri}\\n${canonicalQuery}\\n${canonicalHeaders}\\n${signedHeaders}\\n${payloadHash}`;\n const credentialScope = `${dateStamp}/${this.region}/s3/aws4_request`;\n const stringToSign = `AWS4-HMAC-SHA256\\n${amzDate}\\n${credentialScope}\\n${createHash(\"sha256\").update(canonicalReq).digest(\"hex\")}`;\n const signingKey = await this.getSigningKey(dateStamp);\n const signature = createHmac(\"sha256\", signingKey).update(stringToSign).digest(\"hex\");\n return {\n Authorization: `AWS4-HMAC-SHA256 Credential=${this.accessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`,\n \"Content-Type\": contentType,\n \"x-amz-date\": amzDate,\n \"x-amz-content-sha256\": payloadHash,\n host: u.host,\n };\n }\n\n private async getSigningKey(dateStamp: string): Promise<Buffer> {\n const kDate = createHmac(\"sha256\", `AWS4${this.secretAccessKey ?? \"\"}`).update(dateStamp).digest();\n const kRegion = createHmac(\"sha256\", kDate).update(this.region).digest();\n const kService = createHmac(\"sha256\", kRegion).update(\"s3\").digest();\n return createHmac(\"sha256\", kService).update(\"aws4_request\").digest();\n }\n}\n\ninterface AwsSdkClient {\n send(command: unknown): Promise<unknown>;\n}\n\nfunction hasSdk(client: unknown): client is AwsSdkClient {\n return typeof (client as { send?: unknown })?.send === \"function\";\n}\n\nasync function loadCmd(name: \"PutObjectCommand\"): Promise<new (input: unknown) => unknown> {\n try {\n // @ts-expect-error optional peer dependency, resolved at runtime when used\n const mod = await import(/* @vite-ignore */ \"@aws-sdk/client-s3\");\n return (mod as Record<string, new (input: unknown) => unknown>)[name]!;\n } catch (err) {\n throw new StorageError(\n \"@aws-sdk/client-s3 is required when passing a `client` to S3Storage; install it as a peer dependency.\",\n err,\n );\n }\n}\n","import { S3Storage, type S3StorageOptions } from \"./s3.js\";\n\nexport interface R2StorageOptions extends Omit<S3StorageOptions, \"region\"> {\n accountId: string;\n}\n\n/** Cloudflare R2 — S3-compatible API behind a per-account endpoint. */\nexport class R2Storage extends S3Storage {\n constructor(opts: R2StorageOptions) {\n super({\n ...opts,\n region: \"auto\",\n endpoint: opts.endpoint ?? `https://${opts.accountId}.r2.cloudflarestorage.com`,\n });\n }\n}\n","import { Readable } from \"node:stream\";\nimport { randomUUID } from \"node:crypto\";\nimport type { StorageAdapter } from \"../types.js\";\n\n/** In-memory storage for tests. Records every upload and returns its buffer via `read`. */\nexport class MemoryStorage implements StorageAdapter {\n private store = new Map<string, { buffer: Buffer; mimeType: string }>();\n\n async upload(input: { stream: Readable; filename: string; mimeType: string }) {\n const chunks: Buffer[] = [];\n for await (const c of input.stream) chunks.push(Buffer.isBuffer(c) ? c : Buffer.from(c as Uint8Array));\n const buffer = Buffer.concat(chunks);\n const key = `${randomUUID()}-${input.filename}`;\n this.store.set(key, { buffer, mimeType: input.mimeType });\n return { key, size: buffer.length };\n }\n\n read(key: string): Buffer | undefined {\n return this.store.get(key)?.buffer;\n }\n\n list(): string[] {\n return [...this.store.keys()];\n }\n\n clear(): void {\n this.store.clear();\n }\n}\n"],"mappings":"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","names":["Readable","Readable","Readable","randomUUID","randomUUID","randomUUID","randomUUID","url","result","randomUUID"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mr-aftab-ahmad-khan/upflow",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "File upload handler with resumable multipart uploads, signed URLs, virus scanning hooks, and pluggable storage adapters for S3, GCS, and local disk.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./dist/index.cjs",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"require": "./dist/index.cjs"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"dist",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE",
|
|
21
|
+
"CHANGELOG.md"
|
|
22
|
+
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsup",
|
|
25
|
+
"test": "vitest run",
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"prepublishOnly": "npm run build"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"upload",
|
|
31
|
+
"multer",
|
|
32
|
+
"s3",
|
|
33
|
+
"r2",
|
|
34
|
+
"multipart",
|
|
35
|
+
"express",
|
|
36
|
+
"hono",
|
|
37
|
+
"fastify",
|
|
38
|
+
"next",
|
|
39
|
+
"file",
|
|
40
|
+
"nextjs"
|
|
41
|
+
],
|
|
42
|
+
"peerDependencies": {
|
|
43
|
+
"@aws-sdk/client-s3": ">=3.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"@aws-sdk/client-s3": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/node": "^20.11.0",
|
|
52
|
+
"tsup": "^8.0.0",
|
|
53
|
+
"typescript": "^5.4.0",
|
|
54
|
+
"vitest": "^1.4.0"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18"
|
|
58
|
+
},
|
|
59
|
+
"author": "Aftab Ahmad Khan (https://github.com/aftab-ahmad-khan-dev)",
|
|
60
|
+
"repository": {
|
|
61
|
+
"type": "git",
|
|
62
|
+
"url": "git+https://github.com/NPM-Packages-Modules/upflow.git"
|
|
63
|
+
},
|
|
64
|
+
"bugs": {
|
|
65
|
+
"url": "https://github.com/NPM-Packages-Modules/upflow/issues"
|
|
66
|
+
},
|
|
67
|
+
"homepage": "https://github.com/NPM-Packages-Modules/upflow#readme",
|
|
68
|
+
"publishConfig": {
|
|
69
|
+
"access": "public"
|
|
70
|
+
}
|
|
71
|
+
}
|