{"_id":"@eclosion-tech/syntropy-blob-storage","name":"@eclosion-tech/syntropy-blob-storage","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@eclosion-tech/syntropy-blob-storage","version":"0.1.0","description":"Shared blob storage abstraction with S3-compatible and UploadThing-style helpers","author":{"name":"Eclosion Tech"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/EclosionTech/Syntropy.git","directory":"packages/blob-storage"},"publishConfig":{"access":"public"},"main":"./dist/index.js","module":"./dist/index.mjs","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.mjs","require":"./dist/index.js"}},"dependencies":{"@aws-sdk/client-s3":"^3.995.0","@aws-sdk/s3-request-presigner":"^3.995.0"},"devDependencies":{"@types/node":"^20","@vitest/coverage-v8":"^3.0.4","tsup":"^8.3.5","typescript":"^5","vitest":"^3.0.4"},"keywords":["syntropy","blob","storage","s3","upload"],"scripts":{"build":"tsup src/index.ts --format cjs,esm --dts","dev":"tsup src/index.ts --format cjs,esm --dts --watch","test":"vitest run","test:unit":"vitest run","test:coverage":"vitest run --coverage","test:watch":"vitest","lint":"echo 'No ESLint config - skipping' && exit 0","clean":"rm -rf dist node_modules"},"_id":"@eclosion-tech/syntropy-blob-storage@0.1.0","bugs":{"url":"https://github.com/EclosionTech/Syntropy/issues"},"homepage":"https://github.com/EclosionTech/Syntropy#readme","_integrity":"sha512-gMwlKZ3t+rSJSMnFORPKijAr9u2XBDVPYO86DYU2U7SLoY/1CGeq1RgU6lX8dqzXOqJthTNiK390agD5i785LA==","_resolved":"/private/var/folders/t1/6v7q8q594qs4dmv1bqymvnvh0000gn/T/02d1b247dd7836b9bb69178ec00c83a9/eclosion-tech-syntropy-blob-storage-0.1.0.tgz","_from":"file:eclosion-tech-syntropy-blob-storage-0.1.0.tgz","_nodeVersion":"22.15.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-gMwlKZ3t+rSJSMnFORPKijAr9u2XBDVPYO86DYU2U7SLoY/1CGeq1RgU6lX8dqzXOqJthTNiK390agD5i785LA==","shasum":"ab777e1d44bb015c22865f01c9a65b7611171c3a","tarball":"https://registry.npmjs.org/@eclosion-tech/syntropy-blob-storage/-/syntropy-blob-storage-0.1.0.tgz","fileCount":6,"unpackedSize":53976,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDZF6LrWFHfkBmTRF12ThRspJS6/Q68dupyxDrHtXiYGgIhAIhCtU+UlRqCW0m/3D+ZwxLcQNvrM7JZi5iyHiIMQD9t"}]},"_npmUser":{"name":"ktomanelli","email":"kara@eclosion.tech"},"directories":{},"maintainers":[{"name":"jptomanelli","email":"jptomanelli@gmail.com"},{"name":"ktomanelli","email":"kara@eclosion.tech"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/syntropy-blob-storage_0.1.0_1772250647679_0.21817795233891824"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-28T03:50:47.524Z","0.1.0":"2026-02-28T03:50:47.857Z","modified":"2026-02-28T03:50:48.214Z"},"maintainers":[{"name":"jptomanelli","email":"jptomanelli@gmail.com"},{"name":"ktomanelli","email":"kara@eclosion.tech"}],"description":"Shared blob storage abstraction with S3-compatible and UploadThing-style helpers","homepage":"https://github.com/EclosionTech/Syntropy#readme","keywords":["syntropy","blob","storage","s3","upload"],"repository":{"type":"git","url":"git+https://github.com/EclosionTech/Syntropy.git","directory":"packages/blob-storage"},"author":{"name":"Eclosion Tech"},"bugs":{"url":"https://github.com/EclosionTech/Syntropy/issues"},"license":"MIT","readme":"# @eclosion-tech/syntropy-blob-storage\n\nShared blob storage abstractions for Syntropy and sibling projects.\n\n## Features\n\n- Provider-agnostic `BlobStorage` interface.\n- `S3BlobStorage` adapter for AWS S3 and S3-compatible providers.\n- `InMemoryBlobStorage` adapter for tests and local development.\n- UploadThing-style typed upload routing with `createUploadRouter` and `BlobUploadService`.\n- First-class presigned upload and download URL support.\n\n## Install\n\n```bash\npnpm add @eclosion-tech/syntropy-blob-storage\n```\n\n## Core storage usage\n\n```ts\nimport { S3BlobStorage } from \"@eclosion-tech/syntropy-blob-storage\";\n\nconst storage = new S3BlobStorage({\n  bucket: process.env.S3_BUCKET!,\n  region: process.env.S3_REGION ?? \"us-east-1\",\n  endpoint: process.env.S3_ENDPOINT, // Optional for S3-compatible providers\n  forcePathStyle: process.env.S3_FORCE_PATH_STYLE === \"true\",\n  keyPrefix: process.env.S3_KEY_PREFIX, // Optional (example: \"production\")\n  publicBaseUrl: process.env.S3_PUBLIC_BASE_URL, // Optional\n});\n\nawait storage.put({\n  key: \"source-maps/web/1.2.3/main.js.map\",\n  body: Buffer.from(\"...\"),\n  contentType: \"application/json\",\n});\n\nconst file = await storage.get(\"source-maps/web/1.2.3/main.js.map\");\n```\n\n## UploadThing-style upload routes\n\n```ts\nimport {\n  BlobUploadService,\n  S3BlobStorage,\n  createUploadRouter,\n} from \"@eclosion-tech/syntropy-blob-storage\";\n\ntype AvatarInput = { userId: string };\ntype InvoiceInput = { orgId: string; invoiceId: string };\n\nconst storage = new S3BlobStorage({\n  bucket: process.env.S3_BUCKET!,\n  region: process.env.S3_REGION ?? \"us-east-1\",\n  endpoint: process.env.S3_ENDPOINT,\n  forcePathStyle: true,\n});\n\nconst routes = createUploadRouter({\n  avatar: {\n    accept: [\"image/*\"],\n    maxSizeBytes: 5 * 1024 * 1024,\n    buildKey: ({ fileName, input }: { fileName: string; input: AvatarInput }) =>\n      `avatars/${input.userId}/${fileName}`,\n  },\n  invoicePdf: {\n    accept: [\"application/pdf\"],\n    maxSizeBytes: 25 * 1024 * 1024,\n    buildKey: ({ fileName, input }: { fileName: string; input: InvoiceInput }) =>\n      `invoices/${input.orgId}/${input.invoiceId}-${fileName}`,\n  },\n});\n\nconst uploadService = new BlobUploadService(storage, routes, {\n  keyPrefix: \"tenant-assets\",\n});\n\nconst signed = await uploadService.createUploadUrl({\n  route: \"avatar\",\n  input: { userId: \"u_123\" },\n  fileName: \"profile.png\",\n  contentType: \"image/png\",\n  fileSize: 120_000,\n});\n\n// Client uploads with signed.url and signed.headers\n// Then your server can acknowledge completion:\nawait uploadService.completeUpload({\n  route: \"avatar\",\n  key: signed.key,\n  input: { userId: \"u_123\" },\n  contentType: \"image/png\",\n  fileSize: 120_000,\n});\n```\n\n## Next.js route handler pattern\n\n```ts\n// app/api/uploads/route.ts\nimport { NextRequest, NextResponse } from \"next/server\";\nimport { BlobUploadService, S3BlobStorage, createUploadRouter } from \"@eclosion-tech/syntropy-blob-storage\";\n\nconst storage = new S3BlobStorage({\n  bucket: process.env.S3_BUCKET!,\n  region: process.env.S3_REGION ?? \"us-east-1\",\n  endpoint: process.env.S3_ENDPOINT,\n  forcePathStyle: process.env.S3_FORCE_PATH_STYLE === \"true\",\n});\n\nconst uploadService = new BlobUploadService(\n  storage,\n  createUploadRouter({\n    media: {\n      accept: [\"image/*\", \"video/*\"],\n      maxSizeBytes: 100 * 1024 * 1024,\n      buildKey: ({ fileName, input }: { fileName: string; input: { orgId: string } }) =>\n        `media/${input.orgId}/${Date.now()}-${fileName}`,\n    },\n  })\n);\n\nexport async function POST(request: NextRequest) {\n  const body = await request.json();\n\n  const signed = await uploadService.createUploadUrl({\n    route: \"media\",\n    input: { orgId: body.orgId },\n    fileName: body.fileName,\n    contentType: body.contentType,\n    fileSize: body.fileSize,\n  });\n\n  return NextResponse.json(signed);\n}\n```\n\n## Supported environments\n\n- AWS S3\n- Cloudflare R2 (set `endpoint`, often `forcePathStyle: true`)\n- MinIO (set `endpoint` and `forcePathStyle: true`)\n- Any S3-compatible object store\n\n## Testing\n\nUse the in-memory adapter when you do not want network calls:\n\n```ts\nimport { InMemoryBlobStorage } from \"@eclosion-tech/syntropy-blob-storage\";\n\nconst storage = new InMemoryBlobStorage();\n```\n\n## Exports\n\n- `BlobStorage` types and method contracts\n- `S3BlobStorage`\n- `InMemoryBlobStorage`\n- `BlobUploadService`\n- `createUploadRouter`\n- error types: `BlobStorageError`, `InvalidUploadRouteError`, `UploadValidationError`\n","readmeFilename":"README.md","_rev":"1-b624825a7d55f6deaa586bfdf6c71ff4"}