@uploadista/data-store-filesystem 1.0.0-beta.2 → 1.0.0-beta.3
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/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -5
- package/src/file-store.ts +3 -3
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:fs`);c=s(c);let l=require(`node:fs/promises`);l=s(l);let u=require(`node:path`);u=s(u);let d=require(`@uploadista/core/errors`),f=require(`@uploadista/core/types`),p=require(`@uploadista/observability`),m=require(`effect`);const h=e=>m.Effect.tryPromise({try:()=>l.default.mkdir(e,{mode:`0777`,recursive:!0}),catch:e=>e instanceof Error&&`code`in e&&e.code===`EEXIST`?new d.UploadistaError({code:`UNKNOWN_ERROR`,status:200,body:`Directory already exists`,details:`Directory already exists`}):new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create directory`,details:`Directory creation failed: ${String(e)}`})}).pipe(m.Effect.orElse(()=>m.Effect.void)),g=(e,t)=>m.Effect.sync(()=>c.default.createWriteStream(e,{flags:`r+`,start:t})),_=({writeStream:e,bytesReceived:t,onProgress:n})=>r=>m.Effect.gen(function*(){yield*m.Effect.async(t=>{e.write(r,e=>{t(e?m.Effect.fail(new d.UploadistaError({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to write chunk`,details:`Chunk write failed: ${String(e)}`})):m.Effect.succeed(void 0))})}),yield*m.Ref.update(t,e=>e+r.length),n?.(r.length)}),v=e=>m.Effect.async(t=>{e.end(e=>{t(e?m.Effect.fail(new d.UploadistaError({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to close write stream`,details:`Stream close failed: ${String(e)}`})):m.Effect.succeed(void 0))})}),y=e=>m.Effect.sync(()=>{e.destroyed||e.destroy()}),b=({directory:e,deliveryUrl:t})=>m.Effect.gen(function*(){yield*h(e);let n=yield*f.UploadFileKVStore,r=()=>({supportsParallelUploads:!1,supportsConcatenation:!1,supportsDeferredLength:!0,supportsResumableUploads:!0,supportsTransactionalUploads:!1,supportsStreamingRead:!0,supportsStreamingWrite:!0,maxConcurrentUploads:1,minChunkSize:void 0,maxChunkSize:void 0,maxParts:void 0,optimalChunkSize:1024*1024,requiresOrderedChunks:!0,requiresMimeTypeValidation:!0,maxValidationSize:void 0});return{bucket:e,create:r=>{let i=(r.metadata?.fileName?.toString())?.split(`.`).pop(),a=r.id.split(`/`).slice(0,-1),o=u.default.join(e,i?`${r.id}.${i}`:r.id);return m.Effect.gen(function*(){yield*(0,p.filesystemUploadRequestsTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(1)),yield*(0,p.filesystemFileSizeHistogram)(m.Effect.succeed(r.size||0)),yield*m.Effect.tryPromise({try:()=>l.default.mkdir(u.default.join(e,...a),{recursive:!0}),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`create`,e,{upload_id:r.id,path:o})),new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(e)}`}))}),yield*m.Effect.tryPromise({try:()=>l.default.writeFile(o,``),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`create`,e,{upload_id:r.id,path:o})),new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file`,details:`File creation failed: ${String(e)}`}))});let s=i?`${r.id}.${i}`:r.id;return r.storage={id:s,type:r.storage.type,path:o,bucket:e},r.url=`${t}/${s}`,yield*n.set(r.id,r),r})},remove:t=>m.Effect.gen(function*(){let r=(yield*n.get(t)).storage.path||u.default.join(e,t);yield*m.Effect.tryPromise({try:()=>l.default.unlink(r),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`remove`,e,{upload_id:t,path:r})),d.UploadistaError.fromCode(`FILE_NOT_FOUND`))}),yield*n.delete(t),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(-1))}),write:({file_id:t,stream:r,offset:i},{onProgress:a})=>(0,p.withFilesystemUploadMetrics)(t,(0,p.withFilesystemTimingMetrics)(p.filesystemUploadDurationHistogram,m.Effect.gen(function*(){let o=Date.now(),s=yield*n.get(t),c=s.storage.path||u.default.join(e,t),l=yield*m.Ref.make(0);try{let e=yield*m.Effect.acquireUseRelease(g(c,i),e=>m.Effect.gen(function*(){let t=m.Sink.forEach(_({writeStream:e,bytesReceived:l,onProgress:a}));yield*(0,p.filesystemUploadPartsTotal)(m.Effect.succeed(1)),yield*m.Stream.run(r,t),yield*v(e);let n=yield*m.Ref.get(l);return yield*(0,p.filesystemPartSizeHistogram)(m.Effect.succeed(n)),i+n}),y);return s.size&&e===s.size&&(yield*(0,p.logFilesystemUploadCompletion)(t,{fileSize:s.size,totalDurationMs:Date.now()-o,partsCount:1,averagePartSize:s.size,throughputBps:s.size/(Date.now()-o),retryCount:0}),yield*(0,p.filesystemUploadSuccessTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(-1))),e}catch(e){throw m.Effect.runSync((0,p.trackFilesystemError)(`write`,e,{upload_id:t,path:c,offset:i})),e}}))),getUpload:t=>m.Effect.gen(function*(){let r=yield*n.get(t),i=r.storage.path||u.default.join(e,t),a=yield*m.Effect.tryPromise({try:()=>l.default.stat(i),catch:()=>d.UploadistaError.fromCode(`FILE_NOT_FOUND`)});return{...r,offset:a.size,size:r.size}}),read:t=>m.Effect.gen(function*(){let r=(yield*n.get(t)).storage.path||u.default.join(e,t),i=yield*m.Effect.tryPromise({try:()=>l.default.readFile(r),catch:()=>d.UploadistaError.fromCode(`FILE_READ_ERROR`)});return new Uint8Array(i)}),readStream:(t,r)=>m.Effect.gen(function*(){let i=(yield*n.get(t)).storage.path||u.default.join(e,t),a={...f.DEFAULT_STREAMING_CONFIG,...r};yield*m.Effect.tryPromise({try:()=>l.default.access(i,c.default.constants.R_OK),catch:()=>d.UploadistaError.fromCode(`FILE_NOT_FOUND`)});let o=c.default.createReadStream(i,{highWaterMark:a.chunkSize});return m.Stream.async(e=>(o.on(`data`,t=>{let n=typeof t==`string`?Buffer.from(t):t;e.single(new Uint8Array(n))}),o.on(`end`,()=>{e.end()}),o.on(`error`,t=>{e.fail(new d.UploadistaError({code:`FILE_READ_ERROR`,status:500,body:`Failed to read file stream`,details:`Stream read failed: ${String(t)}`}))}),m.Effect.sync(()=>{o.destroyed||o.destroy()})))}),writeStream:(t,n)=>(0,p.withFilesystemTimingMetrics)(p.filesystemUploadDurationHistogram,m.Effect.gen(function*(){let r=Date.now(),i=t.split(`/`).slice(0,-1),a=u.default.join(e,t);yield*(0,p.filesystemUploadRequestsTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(1)),i.length>0&&(yield*m.Effect.tryPromise({try:()=>l.default.mkdir(u.default.join(e,...i),{recursive:!0}),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`writeStream`,e,{upload_id:t,path:a})),new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(e)}`}))}));let o=yield*m.Ref.make(0),s=yield*m.Effect.acquireUseRelease((e=>m.Effect.sync(()=>c.default.createWriteStream(e,{flags:`w`})))(a),e=>m.Effect.gen(function*(){let t=m.Sink.forEach(_({writeStream:e,bytesReceived:o}));yield*(0,p.filesystemUploadPartsTotal)(m.Effect.succeed(1)),yield*m.Stream.run(n.stream,t),yield*v(e);let r=yield*m.Ref.get(o);return yield*(0,p.filesystemPartSizeHistogram)(m.Effect.succeed(r)),r}),y);return yield*(0,p.logFilesystemUploadCompletion)(t,{fileSize:s,totalDurationMs:Date.now()-r,partsCount:1,averagePartSize:s,throughputBps:s/Math.max(1,Date.now()-r),retryCount:0}),yield*(0,p.filesystemUploadSuccessTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(-1)),yield*(0,p.filesystemFileSizeHistogram)(m.Effect.succeed(s)),{id:t,size:s,path:a,bucket:e}})),getCapabilities:r,validateUploadStrategy:e=>{let t=r();switch(e){case`parallel`:return m.Effect.succeed(t.supportsParallelUploads);case`single`:return m.Effect.succeed(!0);default:return m.Effect.succeed(!1)}}}});exports.fileStore=b;
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,a)=>(a=n==null?{}:e(i(n)),o(r||!n||!n.__esModule?t(a,`default`,{value:n,enumerable:!0}):a,n));let c=require(`node:fs`);c=s(c);let l=require(`node:fs/promises`);l=s(l);let u=require(`node:path`);u=s(u);let d=require(`@uploadista/core/errors`),f=require(`@uploadista/core/types`),p=require(`@uploadista/observability`),m=require(`effect`);const h=e=>m.Effect.tryPromise({try:()=>l.default.mkdir(e,{mode:`0777`,recursive:!0}),catch:e=>e instanceof Error&&`code`in e&&e.code===`EEXIST`?new d.UploadistaError({code:`UNKNOWN_ERROR`,status:200,body:`Directory already exists`,details:`Directory already exists`}):new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create directory`,details:`Directory creation failed: ${String(e)}`})}).pipe(m.Effect.orElse(()=>m.Effect.void)),g=(e,t)=>m.Effect.sync(()=>c.default.createWriteStream(e,{flags:`r+`,start:t})),_=({writeStream:e,bytesReceived:t,onProgress:n})=>r=>m.Effect.gen(function*(){yield*m.Effect.async(t=>{e.write(r,e=>{t(e?m.Effect.fail(new d.UploadistaError({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to write chunk`,details:`Chunk write failed: ${String(e)}`})):m.Effect.succeed(void 0))})}),yield*m.Ref.update(t,e=>e+r.length),yield*n?.(r.length)??m.Effect.void}),v=e=>m.Effect.async(t=>{e.end(e=>{t(e?m.Effect.fail(new d.UploadistaError({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to close write stream`,details:`Stream close failed: ${String(e)}`})):m.Effect.succeed(void 0))})}),y=e=>m.Effect.sync(()=>{e.destroyed||e.destroy()}),b=({directory:e,deliveryUrl:t})=>m.Effect.gen(function*(){yield*h(e);let n=yield*f.UploadFileKVStore,r=()=>({supportsParallelUploads:!1,supportsConcatenation:!1,supportsDeferredLength:!0,supportsResumableUploads:!0,supportsTransactionalUploads:!1,supportsStreamingRead:!0,supportsStreamingWrite:!0,maxConcurrentUploads:1,minChunkSize:void 0,maxChunkSize:void 0,maxParts:void 0,optimalChunkSize:1024*1024,requiresOrderedChunks:!0,requiresMimeTypeValidation:!0,maxValidationSize:void 0});return{bucket:e,create:r=>{let i=(r.metadata?.fileName?.toString())?.split(`.`).pop(),a=r.id.split(`/`).slice(0,-1),o=u.default.join(e,i?`${r.id}.${i}`:r.id);return m.Effect.gen(function*(){yield*(0,p.filesystemUploadRequestsTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(1)),yield*(0,p.filesystemFileSizeHistogram)(m.Effect.succeed(r.size||0)),yield*m.Effect.tryPromise({try:()=>l.default.mkdir(u.default.join(e,...a),{recursive:!0}),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`create`,e,{upload_id:r.id,path:o})),new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(e)}`}))}),yield*m.Effect.tryPromise({try:()=>l.default.writeFile(o,``),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`create`,e,{upload_id:r.id,path:o})),new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file`,details:`File creation failed: ${String(e)}`}))});let s=i?`${r.id}.${i}`:r.id;return r.storage={id:s,type:r.storage.type,path:o,bucket:e},r.url=`${t}/${s}`,yield*n.set(r.id,r),r})},remove:t=>m.Effect.gen(function*(){let r=(yield*n.get(t)).storage.path||u.default.join(e,t);yield*m.Effect.tryPromise({try:()=>l.default.unlink(r),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`remove`,e,{upload_id:t,path:r})),d.UploadistaError.fromCode(`FILE_NOT_FOUND`))}),yield*n.delete(t),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(-1))}),write:({file_id:t,stream:r,offset:i},{onProgress:a})=>(0,p.withFilesystemUploadMetrics)(t,(0,p.withFilesystemTimingMetrics)(p.filesystemUploadDurationHistogram,m.Effect.gen(function*(){let o=Date.now(),s=yield*n.get(t),c=s.storage.path||u.default.join(e,t),l=yield*m.Ref.make(0);try{let e=yield*m.Effect.acquireUseRelease(g(c,i),e=>m.Effect.gen(function*(){let t=m.Sink.forEach(_({writeStream:e,bytesReceived:l,onProgress:a}));yield*(0,p.filesystemUploadPartsTotal)(m.Effect.succeed(1)),yield*m.Stream.run(r,t),yield*v(e);let n=yield*m.Ref.get(l);return yield*(0,p.filesystemPartSizeHistogram)(m.Effect.succeed(n)),i+n}),y);return s.size&&e===s.size&&(yield*(0,p.logFilesystemUploadCompletion)(t,{fileSize:s.size,totalDurationMs:Date.now()-o,partsCount:1,averagePartSize:s.size,throughputBps:s.size/(Date.now()-o),retryCount:0}),yield*(0,p.filesystemUploadSuccessTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(-1))),e}catch(e){throw m.Effect.runSync((0,p.trackFilesystemError)(`write`,e,{upload_id:t,path:c,offset:i})),e}}))),getUpload:t=>m.Effect.gen(function*(){let r=yield*n.get(t),i=r.storage.path||u.default.join(e,t),a=yield*m.Effect.tryPromise({try:()=>l.default.stat(i),catch:()=>d.UploadistaError.fromCode(`FILE_NOT_FOUND`)});return{...r,offset:a.size,size:r.size}}),read:t=>m.Effect.gen(function*(){let r=(yield*n.get(t)).storage.path||u.default.join(e,t),i=yield*m.Effect.tryPromise({try:()=>l.default.readFile(r),catch:()=>d.UploadistaError.fromCode(`FILE_READ_ERROR`)});return new Uint8Array(i)}),readStream:(t,r)=>m.Effect.gen(function*(){let i=(yield*n.get(t)).storage.path||u.default.join(e,t),a={...f.DEFAULT_STREAMING_CONFIG,...r};yield*m.Effect.tryPromise({try:()=>l.default.access(i,c.default.constants.R_OK),catch:()=>d.UploadistaError.fromCode(`FILE_NOT_FOUND`)});let o=c.default.createReadStream(i,{highWaterMark:a.chunkSize});return m.Stream.async(e=>(o.on(`data`,t=>{let n=typeof t==`string`?Buffer.from(t):t;e.single(new Uint8Array(n))}),o.on(`end`,()=>{e.end()}),o.on(`error`,t=>{e.fail(new d.UploadistaError({code:`FILE_READ_ERROR`,status:500,body:`Failed to read file stream`,details:`Stream read failed: ${String(t)}`}))}),m.Effect.sync(()=>{o.destroyed||o.destroy()})))}),writeStream:(t,n)=>(0,p.withFilesystemTimingMetrics)(p.filesystemUploadDurationHistogram,m.Effect.gen(function*(){let r=Date.now(),i=t.split(`/`).slice(0,-1),a=u.default.join(e,t);yield*(0,p.filesystemUploadRequestsTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(1)),i.length>0&&(yield*m.Effect.tryPromise({try:()=>l.default.mkdir(u.default.join(e,...i),{recursive:!0}),catch:e=>(m.Effect.runSync((0,p.trackFilesystemError)(`writeStream`,e,{upload_id:t,path:a})),new d.UploadistaError({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(e)}`}))}));let o=yield*m.Ref.make(0),s=yield*m.Effect.acquireUseRelease((e=>m.Effect.sync(()=>c.default.createWriteStream(e,{flags:`w`})))(a),e=>m.Effect.gen(function*(){let t=m.Sink.forEach(_({writeStream:e,bytesReceived:o}));yield*(0,p.filesystemUploadPartsTotal)(m.Effect.succeed(1)),yield*m.Stream.run(n.stream,t),yield*v(e);let r=yield*m.Ref.get(o);return yield*(0,p.filesystemPartSizeHistogram)(m.Effect.succeed(r)),r}),y);return yield*(0,p.logFilesystemUploadCompletion)(t,{fileSize:s,totalDurationMs:Date.now()-r,partsCount:1,averagePartSize:s,throughputBps:s/Math.max(1,Date.now()-r),retryCount:0}),yield*(0,p.filesystemUploadSuccessTotal)(m.Effect.succeed(1)),yield*(0,p.filesystemActiveUploadsGauge)(m.Effect.succeed(-1)),yield*(0,p.filesystemFileSizeHistogram)(m.Effect.succeed(s)),{id:t,size:s,path:a,bucket:e}})),getCapabilities:r,validateUploadStrategy:e=>{let t=r();switch(e){case`parallel`:return m.Effect.succeed(t.supportsParallelUploads);case`single`:return m.Effect.succeed(!0);default:return m.Effect.succeed(!1)}}}});exports.fileStore=b;
|
package/dist/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import e from"node:fs";import t from"node:fs/promises";import n from"node:path";import{UploadistaError as r}from"@uploadista/core/errors";import{DEFAULT_STREAMING_CONFIG as i,UploadFileKVStore as a}from"@uploadista/core/types";import{filesystemActiveUploadsGauge as o,filesystemFileSizeHistogram as s,filesystemPartSizeHistogram as c,filesystemUploadDurationHistogram as l,filesystemUploadPartsTotal as u,filesystemUploadRequestsTotal as d,filesystemUploadSuccessTotal as f,logFilesystemUploadCompletion as p,trackFilesystemError as m,withFilesystemTimingMetrics as h,withFilesystemUploadMetrics as g}from"@uploadista/observability";import{Effect as _,Ref as v,Sink as y,Stream as b}from"effect";const x=e=>_.tryPromise({try:()=>t.mkdir(e,{mode:`0777`,recursive:!0}),catch:e=>e instanceof Error&&`code`in e&&e.code===`EEXIST`?new r({code:`UNKNOWN_ERROR`,status:200,body:`Directory already exists`,details:`Directory already exists`}):new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create directory`,details:`Directory creation failed: ${String(e)}`})}).pipe(_.orElse(()=>_.void)),S=(t,n)=>_.sync(()=>e.createWriteStream(t,{flags:`r+`,start:n})),C=({writeStream:e,bytesReceived:t,onProgress:n})=>i=>_.gen(function*(){yield*_.async(t=>{e.write(i,e=>{t(e?_.fail(new r({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to write chunk`,details:`Chunk write failed: ${String(e)}`})):_.succeed(void 0))})}),yield*v.update(t,e=>e+i.length),n?.(i.length)}),w=e=>_.async(t=>{e.end(e=>{t(e?_.fail(new r({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to close write stream`,details:`Stream close failed: ${String(e)}`})):_.succeed(void 0))})}),T=e=>_.sync(()=>{e.destroyed||e.destroy()}),E=({directory:E,deliveryUrl:D})=>_.gen(function*(){yield*x(E);let O=yield*a,k=()=>({supportsParallelUploads:!1,supportsConcatenation:!1,supportsDeferredLength:!0,supportsResumableUploads:!0,supportsTransactionalUploads:!1,supportsStreamingRead:!0,supportsStreamingWrite:!0,maxConcurrentUploads:1,minChunkSize:void 0,maxChunkSize:void 0,maxParts:void 0,optimalChunkSize:1024*1024,requiresOrderedChunks:!0,requiresMimeTypeValidation:!0,maxValidationSize:void 0});return{bucket:E,create:e=>{let i=(e.metadata?.fileName?.toString())?.split(`.`).pop(),a=e.id.split(`/`).slice(0,-1),c=n.join(E,i?`${e.id}.${i}`:e.id);return _.gen(function*(){yield*d(_.succeed(1)),yield*o(_.succeed(1)),yield*s(_.succeed(e.size||0)),yield*_.tryPromise({try:()=>t.mkdir(n.join(E,...a),{recursive:!0}),catch:t=>(_.runSync(m(`create`,t,{upload_id:e.id,path:c})),new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(t)}`}))}),yield*_.tryPromise({try:()=>t.writeFile(c,``),catch:t=>(_.runSync(m(`create`,t,{upload_id:e.id,path:c})),new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file`,details:`File creation failed: ${String(t)}`}))});let l=i?`${e.id}.${i}`:e.id;return e.storage={id:l,type:e.storage.type,path:c,bucket:E},e.url=`${D}/${l}`,yield*O.set(e.id,e),e})},remove:e=>_.gen(function*(){let i=(yield*O.get(e)).storage.path||n.join(E,e);yield*_.tryPromise({try:()=>t.unlink(i),catch:t=>(_.runSync(m(`remove`,t,{upload_id:e,path:i})),r.fromCode(`FILE_NOT_FOUND`))}),yield*O.delete(e),yield*o(_.succeed(-1))}),write:({file_id:e,stream:t,offset:r},{onProgress:i})=>g(e,h(l,_.gen(function*(){let a=Date.now(),s=yield*O.get(e),l=s.storage.path||n.join(E,e),d=yield*v.make(0);try{let n=yield*_.acquireUseRelease(S(l,r),e=>_.gen(function*(){let n=y.forEach(C({writeStream:e,bytesReceived:d,onProgress:i}));yield*u(_.succeed(1)),yield*b.run(t,n),yield*w(e);let a=yield*v.get(d);return yield*c(_.succeed(a)),r+a}),T);return s.size&&n===s.size&&(yield*p(e,{fileSize:s.size,totalDurationMs:Date.now()-a,partsCount:1,averagePartSize:s.size,throughputBps:s.size/(Date.now()-a),retryCount:0}),yield*f(_.succeed(1)),yield*o(_.succeed(-1))),n}catch(t){throw _.runSync(m(`write`,t,{upload_id:e,path:l,offset:r})),t}}))),getUpload:e=>_.gen(function*(){let i=yield*O.get(e),a=i.storage.path||n.join(E,e),o=yield*_.tryPromise({try:()=>t.stat(a),catch:()=>r.fromCode(`FILE_NOT_FOUND`)});return{...i,offset:o.size,size:i.size}}),read:e=>_.gen(function*(){let i=(yield*O.get(e)).storage.path||n.join(E,e),a=yield*_.tryPromise({try:()=>t.readFile(i),catch:()=>r.fromCode(`FILE_READ_ERROR`)});return new Uint8Array(a)}),readStream:(a,o)=>_.gen(function*(){let s=(yield*O.get(a)).storage.path||n.join(E,a),c={...i,...o};yield*_.tryPromise({try:()=>t.access(s,e.constants.R_OK),catch:()=>r.fromCode(`FILE_NOT_FOUND`)});let l=e.createReadStream(s,{highWaterMark:c.chunkSize});return b.async(e=>(l.on(`data`,t=>{let n=typeof t==`string`?Buffer.from(t):t;e.single(new Uint8Array(n))}),l.on(`end`,()=>{e.end()}),l.on(`error`,t=>{e.fail(new r({code:`FILE_READ_ERROR`,status:500,body:`Failed to read file stream`,details:`Stream read failed: ${String(t)}`}))}),_.sync(()=>{l.destroyed||l.destroy()})))}),writeStream:(i,a)=>h(l,_.gen(function*(){let l=Date.now(),h=i.split(`/`).slice(0,-1),g=n.join(E,i);yield*d(_.succeed(1)),yield*o(_.succeed(1)),h.length>0&&(yield*_.tryPromise({try:()=>t.mkdir(n.join(E,...h),{recursive:!0}),catch:e=>(_.runSync(m(`writeStream`,e,{upload_id:i,path:g})),new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(e)}`}))}));let x=yield*v.make(0),S=yield*_.acquireUseRelease((t=>_.sync(()=>e.createWriteStream(t,{flags:`w`})))(g),e=>_.gen(function*(){let t=y.forEach(C({writeStream:e,bytesReceived:x}));yield*u(_.succeed(1)),yield*b.run(a.stream,t),yield*w(e);let n=yield*v.get(x);return yield*c(_.succeed(n)),n}),T);return yield*p(i,{fileSize:S,totalDurationMs:Date.now()-l,partsCount:1,averagePartSize:S,throughputBps:S/Math.max(1,Date.now()-l),retryCount:0}),yield*f(_.succeed(1)),yield*o(_.succeed(-1)),yield*s(_.succeed(S)),{id:i,size:S,path:g,bucket:E}})),getCapabilities:k,validateUploadStrategy:e=>{let t=k();switch(e){case`parallel`:return _.succeed(t.supportsParallelUploads);case`single`:return _.succeed(!0);default:return _.succeed(!1)}}}});export{E as fileStore};
|
|
1
|
+
import e from"node:fs";import t from"node:fs/promises";import n from"node:path";import{UploadistaError as r}from"@uploadista/core/errors";import{DEFAULT_STREAMING_CONFIG as i,UploadFileKVStore as a}from"@uploadista/core/types";import{filesystemActiveUploadsGauge as o,filesystemFileSizeHistogram as s,filesystemPartSizeHistogram as c,filesystemUploadDurationHistogram as l,filesystemUploadPartsTotal as u,filesystemUploadRequestsTotal as d,filesystemUploadSuccessTotal as f,logFilesystemUploadCompletion as p,trackFilesystemError as m,withFilesystemTimingMetrics as h,withFilesystemUploadMetrics as g}from"@uploadista/observability";import{Effect as _,Ref as v,Sink as y,Stream as b}from"effect";const x=e=>_.tryPromise({try:()=>t.mkdir(e,{mode:`0777`,recursive:!0}),catch:e=>e instanceof Error&&`code`in e&&e.code===`EEXIST`?new r({code:`UNKNOWN_ERROR`,status:200,body:`Directory already exists`,details:`Directory already exists`}):new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create directory`,details:`Directory creation failed: ${String(e)}`})}).pipe(_.orElse(()=>_.void)),S=(t,n)=>_.sync(()=>e.createWriteStream(t,{flags:`r+`,start:n})),C=({writeStream:e,bytesReceived:t,onProgress:n})=>i=>_.gen(function*(){yield*_.async(t=>{e.write(i,e=>{t(e?_.fail(new r({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to write chunk`,details:`Chunk write failed: ${String(e)}`})):_.succeed(void 0))})}),yield*v.update(t,e=>e+i.length),yield*n?.(i.length)??_.void}),w=e=>_.async(t=>{e.end(e=>{t(e?_.fail(new r({code:`FILE_WRITE_ERROR`,status:500,body:`Failed to close write stream`,details:`Stream close failed: ${String(e)}`})):_.succeed(void 0))})}),T=e=>_.sync(()=>{e.destroyed||e.destroy()}),E=({directory:E,deliveryUrl:D})=>_.gen(function*(){yield*x(E);let O=yield*a,k=()=>({supportsParallelUploads:!1,supportsConcatenation:!1,supportsDeferredLength:!0,supportsResumableUploads:!0,supportsTransactionalUploads:!1,supportsStreamingRead:!0,supportsStreamingWrite:!0,maxConcurrentUploads:1,minChunkSize:void 0,maxChunkSize:void 0,maxParts:void 0,optimalChunkSize:1024*1024,requiresOrderedChunks:!0,requiresMimeTypeValidation:!0,maxValidationSize:void 0});return{bucket:E,create:e=>{let i=(e.metadata?.fileName?.toString())?.split(`.`).pop(),a=e.id.split(`/`).slice(0,-1),c=n.join(E,i?`${e.id}.${i}`:e.id);return _.gen(function*(){yield*d(_.succeed(1)),yield*o(_.succeed(1)),yield*s(_.succeed(e.size||0)),yield*_.tryPromise({try:()=>t.mkdir(n.join(E,...a),{recursive:!0}),catch:t=>(_.runSync(m(`create`,t,{upload_id:e.id,path:c})),new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(t)}`}))}),yield*_.tryPromise({try:()=>t.writeFile(c,``),catch:t=>(_.runSync(m(`create`,t,{upload_id:e.id,path:c})),new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file`,details:`File creation failed: ${String(t)}`}))});let l=i?`${e.id}.${i}`:e.id;return e.storage={id:l,type:e.storage.type,path:c,bucket:E},e.url=`${D}/${l}`,yield*O.set(e.id,e),e})},remove:e=>_.gen(function*(){let i=(yield*O.get(e)).storage.path||n.join(E,e);yield*_.tryPromise({try:()=>t.unlink(i),catch:t=>(_.runSync(m(`remove`,t,{upload_id:e,path:i})),r.fromCode(`FILE_NOT_FOUND`))}),yield*O.delete(e),yield*o(_.succeed(-1))}),write:({file_id:e,stream:t,offset:r},{onProgress:i})=>g(e,h(l,_.gen(function*(){let a=Date.now(),s=yield*O.get(e),l=s.storage.path||n.join(E,e),d=yield*v.make(0);try{let n=yield*_.acquireUseRelease(S(l,r),e=>_.gen(function*(){let n=y.forEach(C({writeStream:e,bytesReceived:d,onProgress:i}));yield*u(_.succeed(1)),yield*b.run(t,n),yield*w(e);let a=yield*v.get(d);return yield*c(_.succeed(a)),r+a}),T);return s.size&&n===s.size&&(yield*p(e,{fileSize:s.size,totalDurationMs:Date.now()-a,partsCount:1,averagePartSize:s.size,throughputBps:s.size/(Date.now()-a),retryCount:0}),yield*f(_.succeed(1)),yield*o(_.succeed(-1))),n}catch(t){throw _.runSync(m(`write`,t,{upload_id:e,path:l,offset:r})),t}}))),getUpload:e=>_.gen(function*(){let i=yield*O.get(e),a=i.storage.path||n.join(E,e),o=yield*_.tryPromise({try:()=>t.stat(a),catch:()=>r.fromCode(`FILE_NOT_FOUND`)});return{...i,offset:o.size,size:i.size}}),read:e=>_.gen(function*(){let i=(yield*O.get(e)).storage.path||n.join(E,e),a=yield*_.tryPromise({try:()=>t.readFile(i),catch:()=>r.fromCode(`FILE_READ_ERROR`)});return new Uint8Array(a)}),readStream:(a,o)=>_.gen(function*(){let s=(yield*O.get(a)).storage.path||n.join(E,a),c={...i,...o};yield*_.tryPromise({try:()=>t.access(s,e.constants.R_OK),catch:()=>r.fromCode(`FILE_NOT_FOUND`)});let l=e.createReadStream(s,{highWaterMark:c.chunkSize});return b.async(e=>(l.on(`data`,t=>{let n=typeof t==`string`?Buffer.from(t):t;e.single(new Uint8Array(n))}),l.on(`end`,()=>{e.end()}),l.on(`error`,t=>{e.fail(new r({code:`FILE_READ_ERROR`,status:500,body:`Failed to read file stream`,details:`Stream read failed: ${String(t)}`}))}),_.sync(()=>{l.destroyed||l.destroy()})))}),writeStream:(i,a)=>h(l,_.gen(function*(){let l=Date.now(),h=i.split(`/`).slice(0,-1),g=n.join(E,i);yield*d(_.succeed(1)),yield*o(_.succeed(1)),h.length>0&&(yield*_.tryPromise({try:()=>t.mkdir(n.join(E,...h),{recursive:!0}),catch:e=>(_.runSync(m(`writeStream`,e,{upload_id:i,path:g})),new r({code:`UNKNOWN_ERROR`,status:500,body:`Failed to create file directory`,details:`Directory creation failed: ${String(e)}`}))}));let x=yield*v.make(0),S=yield*_.acquireUseRelease((t=>_.sync(()=>e.createWriteStream(t,{flags:`w`})))(g),e=>_.gen(function*(){let t=y.forEach(C({writeStream:e,bytesReceived:x}));yield*u(_.succeed(1)),yield*b.run(a.stream,t),yield*w(e);let n=yield*v.get(x);return yield*c(_.succeed(n)),n}),T);return yield*p(i,{fileSize:S,totalDurationMs:Date.now()-l,partsCount:1,averagePartSize:S,throughputBps:S/Math.max(1,Date.now()-l),retryCount:0}),yield*f(_.succeed(1)),yield*o(_.succeed(-1)),yield*s(_.succeed(S)),{id:i,size:S,path:g,bucket:E}})),getCapabilities:k,validateUploadStrategy:e=>{let t=k();switch(e){case`parallel`:return _.succeed(t.supportsParallelUploads);case`single`:return _.succeed(!0);default:return _.succeed(!1)}}}});export{E as fileStore};
|
|
2
2
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["uploadRequestsTotal","activeUploadsGauge","fileSizeHistogram","withUploadMetrics","withTimingMetrics","uploadDurationHistogram","uploadPartsTotal","partSizeHistogram","uploadSuccessTotal"],"sources":["../src/file-store.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport fsProm from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { UploadistaError } from \"@uploadista/core/errors\";\nimport type {\n DataStore,\n DataStoreCapabilities,\n DataStoreWriteOptions,\n StreamingConfig,\n StreamWriteOptions,\n StreamWriteResult,\n UploadFile,\n UploadStrategy,\n} from \"@uploadista/core/types\";\nimport {\n DEFAULT_STREAMING_CONFIG,\n UploadFileKVStore,\n} from \"@uploadista/core/types\";\nimport {\n filesystemActiveUploadsGauge as activeUploadsGauge,\n filesystemFileSizeHistogram as fileSizeHistogram,\n logFilesystemUploadCompletion,\n filesystemPartSizeHistogram as partSizeHistogram,\n trackFilesystemError,\n filesystemUploadDurationHistogram as uploadDurationHistogram,\n filesystemUploadPartsTotal as uploadPartsTotal,\n filesystemUploadRequestsTotal as uploadRequestsTotal,\n filesystemUploadSuccessTotal as uploadSuccessTotal,\n withFilesystemTimingMetrics as withTimingMetrics,\n withFilesystemUploadMetrics as withUploadMetrics,\n} from \"@uploadista/observability\";\nimport { Effect, Ref, Sink, Stream } from \"effect\";\n\nexport type FileStoreOptions = {\n directory: string;\n deliveryUrl: string;\n};\n\nconst MASK = \"0777\";\nconst IGNORED_MKDIR_ERROR = \"EEXIST\";\n// const FILE_DOESNT_EXIST = \"ENOENT\";\n\nconst checkOrCreateDirectory = (directory: string) =>\n Effect.tryPromise({\n try: () => fsProm.mkdir(directory, { mode: MASK, recursive: true }),\n catch: (error) => {\n if (\n error instanceof Error &&\n \"code\" in error &&\n error.code === IGNORED_MKDIR_ERROR\n ) {\n // Directory already exists, not an error\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 200,\n body: \"Directory already exists\",\n details: \"Directory already exists\",\n });\n }\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create directory\",\n details: `Directory creation failed: ${String(error)}`,\n });\n },\n }).pipe(Effect.orElse(() => Effect.void));\n\nconst createWriteStream = (file_path: string, offset: number) =>\n Effect.sync(() =>\n fs.createWriteStream(file_path, {\n flags: \"r+\",\n start: offset,\n }),\n );\n\nconst writeChunk =\n ({\n writeStream,\n bytesReceived,\n onProgress,\n }: {\n writeStream: fs.WriteStream;\n bytesReceived: Ref.Ref<number>;\n onProgress?: (chunkSize: number) => void;\n }) =>\n (chunk: Uint8Array) =>\n Effect.gen(function* () {\n yield* Effect.async<void, UploadistaError>((resume) => {\n writeStream.write(chunk, (err) => {\n if (err) {\n resume(\n Effect.fail(\n new UploadistaError({\n code: \"FILE_WRITE_ERROR\",\n status: 500,\n body: \"Failed to write chunk\",\n details: `Chunk write failed: ${String(err)}`,\n }),\n ),\n );\n } else {\n resume(Effect.succeed(void 0));\n }\n });\n });\n\n yield* Ref.update(bytesReceived, (size) => size + chunk.length);\n onProgress?.(chunk.length);\n });\n\nconst endWriteStream = (writeStream: fs.WriteStream) =>\n Effect.async<void, UploadistaError>((resume) => {\n writeStream.end((err: Error | null | undefined) => {\n if (err) {\n resume(\n Effect.fail(\n new UploadistaError({\n code: \"FILE_WRITE_ERROR\",\n status: 500,\n body: \"Failed to close write stream\",\n details: `Stream close failed: ${String(err)}`,\n }),\n ),\n );\n } else {\n resume(Effect.succeed(void 0));\n }\n });\n });\n\nconst destroyWriteStream = (writeStream: fs.WriteStream) =>\n Effect.sync(() => {\n if (!writeStream.destroyed) {\n writeStream.destroy();\n }\n });\n/**\n * A data store that stores files in the filesystem.\n * @param options - The options for the file store.\n * @returns A data store that stores files in the filesystem.\n */\nexport const fileStore = ({ directory, deliveryUrl }: FileStoreOptions) =>\n Effect.gen(function* () {\n yield* checkOrCreateDirectory(directory);\n const kvStore = yield* UploadFileKVStore;\n\n const getCapabilities = (): DataStoreCapabilities => {\n return {\n supportsParallelUploads: false, // Filesystem operations are sequential\n supportsConcatenation: false, // No native concatenation support\n supportsDeferredLength: true, // Supports deferred length via writeStream\n supportsResumableUploads: true, // Can write at specific offsets\n supportsTransactionalUploads: false,\n supportsStreamingRead: true, // Supports streaming reads via Node.js fs streams\n supportsStreamingWrite: true, // Supports streaming writes via writeStream\n maxConcurrentUploads: 1, // Sequential writes only\n minChunkSize: undefined,\n maxChunkSize: undefined,\n maxParts: undefined,\n optimalChunkSize: 1024 * 1024, // 1MB default\n requiresOrderedChunks: true, // Sequential offset-based writes\n requiresMimeTypeValidation: true,\n maxValidationSize: undefined, // no size limit\n };\n };\n\n const validateUploadStrategy = (\n strategy: UploadStrategy,\n ): Effect.Effect<boolean, never> => {\n const capabilities = getCapabilities();\n\n switch (strategy) {\n case \"parallel\":\n return Effect.succeed(capabilities.supportsParallelUploads);\n case \"single\":\n return Effect.succeed(true);\n default:\n return Effect.succeed(false);\n }\n };\n\n return {\n bucket: directory,\n create: (\n file: UploadFile,\n ): Effect.Effect<UploadFile, UploadistaError> => {\n const fileName = file.metadata?.fileName?.toString();\n const fileExtension = fileName?.split(\".\").pop();\n\n const dirs = file.id.split(\"/\").slice(0, -1);\n const filePath = path.join(\n directory,\n fileExtension ? `${file.id}.${fileExtension}` : file.id,\n );\n\n return Effect.gen(function* () {\n yield* uploadRequestsTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(1));\n yield* fileSizeHistogram(Effect.succeed(file.size || 0));\n\n yield* Effect.tryPromise({\n try: () =>\n fsProm.mkdir(path.join(directory, ...dirs), {\n recursive: true,\n }),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"create\", error, {\n upload_id: file.id,\n path: filePath,\n }),\n );\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create file directory\",\n details: `Directory creation failed: ${String(error)}`,\n });\n },\n });\n\n yield* Effect.tryPromise({\n try: () => fsProm.writeFile(filePath, \"\"),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"create\", error, {\n upload_id: file.id,\n path: filePath,\n }),\n );\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create file\",\n details: `File creation failed: ${String(error)}`,\n });\n },\n });\n\n const fileId = fileExtension\n ? `${file.id}.${fileExtension}`\n : file.id;\n file.storage = {\n id: fileId,\n type: file.storage.type,\n path: filePath,\n bucket: directory,\n };\n file.url = `${deliveryUrl}/${fileId}`;\n\n // Store file metadata in KV store\n yield* kvStore.set(file.id, file);\n\n return file;\n });\n },\n remove: (file_id: string): Effect.Effect<void, UploadistaError> => {\n return Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(file_id);\n const file_path =\n uploadFile.storage.path || path.join(directory, file_id);\n\n yield* Effect.tryPromise({\n try: () => fsProm.unlink(file_path),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"remove\", error, {\n upload_id: file_id,\n path: file_path,\n }),\n );\n return UploadistaError.fromCode(\"FILE_NOT_FOUND\");\n },\n });\n\n yield* kvStore.delete(file_id);\n yield* activeUploadsGauge(Effect.succeed(-1));\n });\n },\n write: (\n { file_id, stream, offset }: DataStoreWriteOptions,\n { onProgress }: { onProgress?: (chunkSize: number) => void },\n ): Effect.Effect<number, UploadistaError> => {\n return withUploadMetrics(\n file_id,\n withTimingMetrics(\n uploadDurationHistogram,\n Effect.gen(function* () {\n const startTime = Date.now();\n // Get the upload file from KV store to retrieve the actual file path\n const uploadFile = yield* kvStore.get(file_id);\n const file_path =\n uploadFile.storage.path || path.join(directory, file_id);\n\n const bytesReceived = yield* Ref.make(0);\n\n try {\n const result = yield* Effect.acquireUseRelease(\n createWriteStream(file_path, offset),\n (writeStream) =>\n Effect.gen(function* () {\n const sink = Sink.forEach(\n writeChunk({ writeStream, bytesReceived, onProgress }),\n );\n\n yield* uploadPartsTotal(Effect.succeed(1));\n yield* Stream.run(stream, sink);\n yield* endWriteStream(writeStream);\n\n const totalBytes = yield* Ref.get(bytesReceived);\n yield* partSizeHistogram(Effect.succeed(totalBytes));\n return offset + totalBytes;\n }),\n destroyWriteStream,\n );\n\n // Check if upload is complete\n if (uploadFile.size && result === uploadFile.size) {\n yield* logFilesystemUploadCompletion(file_id, {\n fileSize: uploadFile.size,\n totalDurationMs: Date.now() - startTime,\n partsCount: 1,\n averagePartSize: uploadFile.size,\n throughputBps: uploadFile.size / (Date.now() - startTime),\n retryCount: 0,\n });\n yield* uploadSuccessTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(-1));\n }\n\n return result;\n } catch (error) {\n Effect.runSync(\n trackFilesystemError(\"write\", error, {\n upload_id: file_id,\n path: file_path,\n offset,\n }),\n );\n throw error;\n }\n }),\n ),\n );\n },\n getUpload: (id: string) =>\n Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(id);\n\n // For filesystem, get the actual file size from disk\n const file_path = uploadFile.storage.path || path.join(directory, id);\n const stats = yield* Effect.tryPromise({\n try: () => fsProm.stat(file_path),\n catch: () => UploadistaError.fromCode(\"FILE_NOT_FOUND\"),\n });\n\n return {\n ...uploadFile,\n offset: stats.size,\n size: uploadFile.size,\n };\n }),\n read: (id: string) =>\n Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(id);\n const file_path = uploadFile.storage.path || path.join(directory, id);\n\n const buffer = yield* Effect.tryPromise({\n try: () => fsProm.readFile(file_path),\n catch: () => UploadistaError.fromCode(\"FILE_READ_ERROR\"),\n });\n\n return new Uint8Array(buffer);\n }),\n /**\n * Reads file content as a stream of chunks for memory-efficient processing.\n * Uses Node.js fs.createReadStream under the hood.\n *\n * @param id - The unique identifier of the file to read\n * @param config - Optional streaming configuration (chunk size)\n * @returns An Effect that resolves to a Stream of byte chunks\n */\n readStream: (id: string, config?: StreamingConfig) =>\n Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(id);\n const file_path = uploadFile.storage.path || path.join(directory, id);\n\n // Merge config with defaults\n const effectiveConfig = {\n ...DEFAULT_STREAMING_CONFIG,\n ...config,\n };\n\n // Verify file exists\n yield* Effect.tryPromise({\n try: () => fsProm.access(file_path, fs.constants.R_OK),\n catch: () => UploadistaError.fromCode(\"FILE_NOT_FOUND\"),\n });\n\n // Create a Node.js readable stream with the configured chunk size\n const nodeStream = fs.createReadStream(file_path, {\n highWaterMark: effectiveConfig.chunkSize,\n });\n\n // Convert Node.js stream to Effect Stream\n return Stream.async<Uint8Array, UploadistaError>((emit) => {\n nodeStream.on(\"data\", (chunk: Buffer | string) => {\n // Handle both Buffer and string (though readStream should return Buffer)\n const buffer =\n typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n emit.single(new Uint8Array(buffer));\n });\n\n nodeStream.on(\"end\", () => {\n emit.end();\n });\n\n nodeStream.on(\"error\", (error) => {\n emit.fail(\n new UploadistaError({\n code: \"FILE_READ_ERROR\",\n status: 500,\n body: \"Failed to read file stream\",\n details: `Stream read failed: ${String(error)}`,\n }),\n );\n });\n\n // Cleanup function when stream is interrupted\n return Effect.sync(() => {\n if (!nodeStream.destroyed) {\n nodeStream.destroy();\n }\n });\n });\n }),\n /**\n * Writes file content from a stream without knowing the final size upfront.\n * Creates the file and streams content directly to disk.\n *\n * @param fileId - The unique identifier for the file\n * @param options - Stream write options including the Effect Stream\n * @returns StreamWriteResult with final size after stream completes\n */\n writeStream: (\n fileId: string,\n options: StreamWriteOptions,\n ): Effect.Effect<StreamWriteResult, UploadistaError> =>\n withTimingMetrics(\n uploadDurationHistogram,\n Effect.gen(function* () {\n const startTime = Date.now();\n\n // Determine file path\n const dirs = fileId.split(\"/\").slice(0, -1);\n const filePath = path.join(directory, fileId);\n\n yield* uploadRequestsTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(1));\n\n // Create directory structure if needed\n if (dirs.length > 0) {\n yield* Effect.tryPromise({\n try: () =>\n fsProm.mkdir(path.join(directory, ...dirs), {\n recursive: true,\n }),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"writeStream\", error, {\n upload_id: fileId,\n path: filePath,\n }),\n );\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create file directory\",\n details: `Directory creation failed: ${String(error)}`,\n });\n },\n });\n }\n\n const bytesWritten = yield* Ref.make(0);\n\n // Create write stream helper for streaming write\n const createNewWriteStream = (targetPath: string) =>\n Effect.sync(() =>\n fs.createWriteStream(targetPath, {\n flags: \"w\", // Create new file or truncate existing\n }),\n );\n\n const result = yield* Effect.acquireUseRelease(\n createNewWriteStream(filePath),\n (writeStream) =>\n Effect.gen(function* () {\n const sink = Sink.forEach(\n writeChunk({\n writeStream,\n bytesReceived: bytesWritten,\n }),\n );\n\n yield* uploadPartsTotal(Effect.succeed(1));\n yield* Stream.run(options.stream, sink);\n yield* endWriteStream(writeStream);\n\n const totalBytes = yield* Ref.get(bytesWritten);\n yield* partSizeHistogram(Effect.succeed(totalBytes));\n\n return totalBytes;\n }),\n destroyWriteStream,\n );\n\n // Log completion and update metrics\n yield* logFilesystemUploadCompletion(fileId, {\n fileSize: result,\n totalDurationMs: Date.now() - startTime,\n partsCount: 1,\n averagePartSize: result,\n throughputBps: result / Math.max(1, Date.now() - startTime),\n retryCount: 0,\n });\n yield* uploadSuccessTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(-1));\n yield* fileSizeHistogram(Effect.succeed(result));\n\n return {\n id: fileId,\n size: result,\n path: filePath,\n bucket: directory,\n } satisfies StreamWriteResult;\n }),\n ),\n getCapabilities,\n validateUploadStrategy,\n } as DataStore<UploadFile>;\n });\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["uploadRequestsTotal","activeUploadsGauge","fileSizeHistogram","withUploadMetrics","withTimingMetrics","uploadDurationHistogram","uploadPartsTotal","partSizeHistogram","uploadSuccessTotal"],"sources":["../src/file-store.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport fsProm from \"node:fs/promises\";\nimport path from \"node:path\";\nimport { UploadistaError } from \"@uploadista/core/errors\";\nimport type {\n DataStore,\n DataStoreCapabilities,\n DataStoreWriteOptions,\n StreamingConfig,\n StreamWriteOptions,\n StreamWriteResult,\n UploadFile,\n UploadStrategy,\n} from \"@uploadista/core/types\";\nimport {\n DEFAULT_STREAMING_CONFIG,\n UploadFileKVStore,\n} from \"@uploadista/core/types\";\nimport {\n filesystemActiveUploadsGauge as activeUploadsGauge,\n filesystemFileSizeHistogram as fileSizeHistogram,\n logFilesystemUploadCompletion,\n filesystemPartSizeHistogram as partSizeHistogram,\n trackFilesystemError,\n filesystemUploadDurationHistogram as uploadDurationHistogram,\n filesystemUploadPartsTotal as uploadPartsTotal,\n filesystemUploadRequestsTotal as uploadRequestsTotal,\n filesystemUploadSuccessTotal as uploadSuccessTotal,\n withFilesystemTimingMetrics as withTimingMetrics,\n withFilesystemUploadMetrics as withUploadMetrics,\n} from \"@uploadista/observability\";\nimport { Effect, Ref, Sink, Stream } from \"effect\";\n\nexport type FileStoreOptions = {\n directory: string;\n deliveryUrl: string;\n};\n\nconst MASK = \"0777\";\nconst IGNORED_MKDIR_ERROR = \"EEXIST\";\n// const FILE_DOESNT_EXIST = \"ENOENT\";\n\nconst checkOrCreateDirectory = (directory: string) =>\n Effect.tryPromise({\n try: () => fsProm.mkdir(directory, { mode: MASK, recursive: true }),\n catch: (error) => {\n if (\n error instanceof Error &&\n \"code\" in error &&\n error.code === IGNORED_MKDIR_ERROR\n ) {\n // Directory already exists, not an error\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 200,\n body: \"Directory already exists\",\n details: \"Directory already exists\",\n });\n }\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create directory\",\n details: `Directory creation failed: ${String(error)}`,\n });\n },\n }).pipe(Effect.orElse(() => Effect.void));\n\nconst createWriteStream = (file_path: string, offset: number) =>\n Effect.sync(() =>\n fs.createWriteStream(file_path, {\n flags: \"r+\",\n start: offset,\n }),\n );\n\nconst writeChunk =\n ({\n writeStream,\n bytesReceived,\n onProgress,\n }: {\n writeStream: fs.WriteStream;\n bytesReceived: Ref.Ref<number>;\n onProgress?: (chunkSize: number) => Effect.Effect<void>;\n }) =>\n (chunk: Uint8Array) =>\n Effect.gen(function* () {\n yield* Effect.async<void, UploadistaError>((resume) => {\n writeStream.write(chunk, (err) => {\n if (err) {\n resume(\n Effect.fail(\n new UploadistaError({\n code: \"FILE_WRITE_ERROR\",\n status: 500,\n body: \"Failed to write chunk\",\n details: `Chunk write failed: ${String(err)}`,\n }),\n ),\n );\n } else {\n resume(Effect.succeed(void 0));\n }\n });\n });\n\n yield* Ref.update(bytesReceived, (size) => size + chunk.length);\n yield* (onProgress?.(chunk.length) ?? Effect.void);\n });\n\nconst endWriteStream = (writeStream: fs.WriteStream) =>\n Effect.async<void, UploadistaError>((resume) => {\n writeStream.end((err: Error | null | undefined) => {\n if (err) {\n resume(\n Effect.fail(\n new UploadistaError({\n code: \"FILE_WRITE_ERROR\",\n status: 500,\n body: \"Failed to close write stream\",\n details: `Stream close failed: ${String(err)}`,\n }),\n ),\n );\n } else {\n resume(Effect.succeed(void 0));\n }\n });\n });\n\nconst destroyWriteStream = (writeStream: fs.WriteStream) =>\n Effect.sync(() => {\n if (!writeStream.destroyed) {\n writeStream.destroy();\n }\n });\n/**\n * A data store that stores files in the filesystem.\n * @param options - The options for the file store.\n * @returns A data store that stores files in the filesystem.\n */\nexport const fileStore = ({ directory, deliveryUrl }: FileStoreOptions) =>\n Effect.gen(function* () {\n yield* checkOrCreateDirectory(directory);\n const kvStore = yield* UploadFileKVStore;\n\n const getCapabilities = (): DataStoreCapabilities => {\n return {\n supportsParallelUploads: false, // Filesystem operations are sequential\n supportsConcatenation: false, // No native concatenation support\n supportsDeferredLength: true, // Supports deferred length via writeStream\n supportsResumableUploads: true, // Can write at specific offsets\n supportsTransactionalUploads: false,\n supportsStreamingRead: true, // Supports streaming reads via Node.js fs streams\n supportsStreamingWrite: true, // Supports streaming writes via writeStream\n maxConcurrentUploads: 1, // Sequential writes only\n minChunkSize: undefined,\n maxChunkSize: undefined,\n maxParts: undefined,\n optimalChunkSize: 1024 * 1024, // 1MB default\n requiresOrderedChunks: true, // Sequential offset-based writes\n requiresMimeTypeValidation: true,\n maxValidationSize: undefined, // no size limit\n };\n };\n\n const validateUploadStrategy = (\n strategy: UploadStrategy,\n ): Effect.Effect<boolean, never> => {\n const capabilities = getCapabilities();\n\n switch (strategy) {\n case \"parallel\":\n return Effect.succeed(capabilities.supportsParallelUploads);\n case \"single\":\n return Effect.succeed(true);\n default:\n return Effect.succeed(false);\n }\n };\n\n return {\n bucket: directory,\n create: (\n file: UploadFile,\n ): Effect.Effect<UploadFile, UploadistaError> => {\n const fileName = file.metadata?.fileName?.toString();\n const fileExtension = fileName?.split(\".\").pop();\n\n const dirs = file.id.split(\"/\").slice(0, -1);\n const filePath = path.join(\n directory,\n fileExtension ? `${file.id}.${fileExtension}` : file.id,\n );\n\n return Effect.gen(function* () {\n yield* uploadRequestsTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(1));\n yield* fileSizeHistogram(Effect.succeed(file.size || 0));\n\n yield* Effect.tryPromise({\n try: () =>\n fsProm.mkdir(path.join(directory, ...dirs), {\n recursive: true,\n }),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"create\", error, {\n upload_id: file.id,\n path: filePath,\n }),\n );\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create file directory\",\n details: `Directory creation failed: ${String(error)}`,\n });\n },\n });\n\n yield* Effect.tryPromise({\n try: () => fsProm.writeFile(filePath, \"\"),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"create\", error, {\n upload_id: file.id,\n path: filePath,\n }),\n );\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create file\",\n details: `File creation failed: ${String(error)}`,\n });\n },\n });\n\n const fileId = fileExtension\n ? `${file.id}.${fileExtension}`\n : file.id;\n file.storage = {\n id: fileId,\n type: file.storage.type,\n path: filePath,\n bucket: directory,\n };\n file.url = `${deliveryUrl}/${fileId}`;\n\n // Store file metadata in KV store\n yield* kvStore.set(file.id, file);\n\n return file;\n });\n },\n remove: (file_id: string): Effect.Effect<void, UploadistaError> => {\n return Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(file_id);\n const file_path =\n uploadFile.storage.path || path.join(directory, file_id);\n\n yield* Effect.tryPromise({\n try: () => fsProm.unlink(file_path),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"remove\", error, {\n upload_id: file_id,\n path: file_path,\n }),\n );\n return UploadistaError.fromCode(\"FILE_NOT_FOUND\");\n },\n });\n\n yield* kvStore.delete(file_id);\n yield* activeUploadsGauge(Effect.succeed(-1));\n });\n },\n write: (\n { file_id, stream, offset }: DataStoreWriteOptions,\n { onProgress }: { onProgress?: (chunkSize: number) => Effect.Effect<void> },\n ): Effect.Effect<number, UploadistaError> => {\n return withUploadMetrics(\n file_id,\n withTimingMetrics(\n uploadDurationHistogram,\n Effect.gen(function* () {\n const startTime = Date.now();\n // Get the upload file from KV store to retrieve the actual file path\n const uploadFile = yield* kvStore.get(file_id);\n const file_path =\n uploadFile.storage.path || path.join(directory, file_id);\n\n const bytesReceived = yield* Ref.make(0);\n\n try {\n const result = yield* Effect.acquireUseRelease(\n createWriteStream(file_path, offset),\n (writeStream) =>\n Effect.gen(function* () {\n const sink = Sink.forEach(\n writeChunk({ writeStream, bytesReceived, onProgress }),\n );\n\n yield* uploadPartsTotal(Effect.succeed(1));\n yield* Stream.run(stream, sink);\n yield* endWriteStream(writeStream);\n\n const totalBytes = yield* Ref.get(bytesReceived);\n yield* partSizeHistogram(Effect.succeed(totalBytes));\n return offset + totalBytes;\n }),\n destroyWriteStream,\n );\n\n // Check if upload is complete\n if (uploadFile.size && result === uploadFile.size) {\n yield* logFilesystemUploadCompletion(file_id, {\n fileSize: uploadFile.size,\n totalDurationMs: Date.now() - startTime,\n partsCount: 1,\n averagePartSize: uploadFile.size,\n throughputBps: uploadFile.size / (Date.now() - startTime),\n retryCount: 0,\n });\n yield* uploadSuccessTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(-1));\n }\n\n return result;\n } catch (error) {\n Effect.runSync(\n trackFilesystemError(\"write\", error, {\n upload_id: file_id,\n path: file_path,\n offset,\n }),\n );\n throw error;\n }\n }),\n ),\n );\n },\n getUpload: (id: string) =>\n Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(id);\n\n // For filesystem, get the actual file size from disk\n const file_path = uploadFile.storage.path || path.join(directory, id);\n const stats = yield* Effect.tryPromise({\n try: () => fsProm.stat(file_path),\n catch: () => UploadistaError.fromCode(\"FILE_NOT_FOUND\"),\n });\n\n return {\n ...uploadFile,\n offset: stats.size,\n size: uploadFile.size,\n };\n }),\n read: (id: string) =>\n Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(id);\n const file_path = uploadFile.storage.path || path.join(directory, id);\n\n const buffer = yield* Effect.tryPromise({\n try: () => fsProm.readFile(file_path),\n catch: () => UploadistaError.fromCode(\"FILE_READ_ERROR\"),\n });\n\n return new Uint8Array(buffer);\n }),\n /**\n * Reads file content as a stream of chunks for memory-efficient processing.\n * Uses Node.js fs.createReadStream under the hood.\n *\n * @param id - The unique identifier of the file to read\n * @param config - Optional streaming configuration (chunk size)\n * @returns An Effect that resolves to a Stream of byte chunks\n */\n readStream: (id: string, config?: StreamingConfig) =>\n Effect.gen(function* () {\n const uploadFile = yield* kvStore.get(id);\n const file_path = uploadFile.storage.path || path.join(directory, id);\n\n // Merge config with defaults\n const effectiveConfig = {\n ...DEFAULT_STREAMING_CONFIG,\n ...config,\n };\n\n // Verify file exists\n yield* Effect.tryPromise({\n try: () => fsProm.access(file_path, fs.constants.R_OK),\n catch: () => UploadistaError.fromCode(\"FILE_NOT_FOUND\"),\n });\n\n // Create a Node.js readable stream with the configured chunk size\n const nodeStream = fs.createReadStream(file_path, {\n highWaterMark: effectiveConfig.chunkSize,\n });\n\n // Convert Node.js stream to Effect Stream\n return Stream.async<Uint8Array, UploadistaError>((emit) => {\n nodeStream.on(\"data\", (chunk: Buffer | string) => {\n // Handle both Buffer and string (though readStream should return Buffer)\n const buffer =\n typeof chunk === \"string\" ? Buffer.from(chunk) : chunk;\n emit.single(new Uint8Array(buffer));\n });\n\n nodeStream.on(\"end\", () => {\n emit.end();\n });\n\n nodeStream.on(\"error\", (error) => {\n emit.fail(\n new UploadistaError({\n code: \"FILE_READ_ERROR\",\n status: 500,\n body: \"Failed to read file stream\",\n details: `Stream read failed: ${String(error)}`,\n }),\n );\n });\n\n // Cleanup function when stream is interrupted\n return Effect.sync(() => {\n if (!nodeStream.destroyed) {\n nodeStream.destroy();\n }\n });\n });\n }),\n /**\n * Writes file content from a stream without knowing the final size upfront.\n * Creates the file and streams content directly to disk.\n *\n * @param fileId - The unique identifier for the file\n * @param options - Stream write options including the Effect Stream\n * @returns StreamWriteResult with final size after stream completes\n */\n writeStream: (\n fileId: string,\n options: StreamWriteOptions,\n ): Effect.Effect<StreamWriteResult, UploadistaError> =>\n withTimingMetrics(\n uploadDurationHistogram,\n Effect.gen(function* () {\n const startTime = Date.now();\n\n // Determine file path\n const dirs = fileId.split(\"/\").slice(0, -1);\n const filePath = path.join(directory, fileId);\n\n yield* uploadRequestsTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(1));\n\n // Create directory structure if needed\n if (dirs.length > 0) {\n yield* Effect.tryPromise({\n try: () =>\n fsProm.mkdir(path.join(directory, ...dirs), {\n recursive: true,\n }),\n catch: (error) => {\n Effect.runSync(\n trackFilesystemError(\"writeStream\", error, {\n upload_id: fileId,\n path: filePath,\n }),\n );\n return new UploadistaError({\n code: \"UNKNOWN_ERROR\",\n status: 500,\n body: \"Failed to create file directory\",\n details: `Directory creation failed: ${String(error)}`,\n });\n },\n });\n }\n\n const bytesWritten = yield* Ref.make(0);\n\n // Create write stream helper for streaming write\n const createNewWriteStream = (targetPath: string) =>\n Effect.sync(() =>\n fs.createWriteStream(targetPath, {\n flags: \"w\", // Create new file or truncate existing\n }),\n );\n\n const result = yield* Effect.acquireUseRelease(\n createNewWriteStream(filePath),\n (writeStream) =>\n Effect.gen(function* () {\n const sink = Sink.forEach(\n writeChunk({\n writeStream,\n bytesReceived: bytesWritten,\n }),\n );\n\n yield* uploadPartsTotal(Effect.succeed(1));\n yield* Stream.run(options.stream, sink);\n yield* endWriteStream(writeStream);\n\n const totalBytes = yield* Ref.get(bytesWritten);\n yield* partSizeHistogram(Effect.succeed(totalBytes));\n\n return totalBytes;\n }),\n destroyWriteStream,\n );\n\n // Log completion and update metrics\n yield* logFilesystemUploadCompletion(fileId, {\n fileSize: result,\n totalDurationMs: Date.now() - startTime,\n partsCount: 1,\n averagePartSize: result,\n throughputBps: result / Math.max(1, Date.now() - startTime),\n retryCount: 0,\n });\n yield* uploadSuccessTotal(Effect.succeed(1));\n yield* activeUploadsGauge(Effect.succeed(-1));\n yield* fileSizeHistogram(Effect.succeed(result));\n\n return {\n id: fileId,\n size: result,\n path: filePath,\n bucket: directory,\n } satisfies StreamWriteResult;\n }),\n ),\n getCapabilities,\n validateUploadStrategy,\n } as DataStore<UploadFile>;\n });\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@uploadista/data-store-filesystem",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.3",
|
|
5
5
|
"description": "File system data store for Uploadista",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "Uploadista",
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
}
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@uploadista/
|
|
18
|
-
"@uploadista/
|
|
17
|
+
"@uploadista/observability": "1.0.0-beta.3",
|
|
18
|
+
"@uploadista/core": "1.0.0-beta.3"
|
|
19
19
|
},
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"effect": "^3.0.0"
|
|
@@ -26,8 +26,8 @@
|
|
|
26
26
|
"effect": "3.19.19",
|
|
27
27
|
"tsdown": "0.20.3",
|
|
28
28
|
"vitest": "4.0.18",
|
|
29
|
-
"@uploadista/kv-store-memory": "1.0.0-beta.
|
|
30
|
-
"@uploadista/typescript-config": "1.0.0-beta.
|
|
29
|
+
"@uploadista/kv-store-memory": "1.0.0-beta.3",
|
|
30
|
+
"@uploadista/typescript-config": "1.0.0-beta.3"
|
|
31
31
|
},
|
|
32
32
|
"scripts": {
|
|
33
33
|
"build": "tsc --noEmit && tsdown",
|
package/src/file-store.ts
CHANGED
|
@@ -82,7 +82,7 @@ const writeChunk =
|
|
|
82
82
|
}: {
|
|
83
83
|
writeStream: fs.WriteStream;
|
|
84
84
|
bytesReceived: Ref.Ref<number>;
|
|
85
|
-
onProgress?: (chunkSize: number) => void
|
|
85
|
+
onProgress?: (chunkSize: number) => Effect.Effect<void>;
|
|
86
86
|
}) =>
|
|
87
87
|
(chunk: Uint8Array) =>
|
|
88
88
|
Effect.gen(function* () {
|
|
@@ -106,7 +106,7 @@ const writeChunk =
|
|
|
106
106
|
});
|
|
107
107
|
|
|
108
108
|
yield* Ref.update(bytesReceived, (size) => size + chunk.length);
|
|
109
|
-
onProgress?.(chunk.length);
|
|
109
|
+
yield* (onProgress?.(chunk.length) ?? Effect.void);
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
const endWriteStream = (writeStream: fs.WriteStream) =>
|
|
@@ -280,7 +280,7 @@ export const fileStore = ({ directory, deliveryUrl }: FileStoreOptions) =>
|
|
|
280
280
|
},
|
|
281
281
|
write: (
|
|
282
282
|
{ file_id, stream, offset }: DataStoreWriteOptions,
|
|
283
|
-
{ onProgress }: { onProgress?: (chunkSize: number) => void },
|
|
283
|
+
{ onProgress }: { onProgress?: (chunkSize: number) => Effect.Effect<void> },
|
|
284
284
|
): Effect.Effect<number, UploadistaError> => {
|
|
285
285
|
return withUploadMetrics(
|
|
286
286
|
file_id,
|