opfs-worker
Version:
A robust TypeScript library for working with Origin Private File System (OPFS) through Web Workers
3 lines (2 loc) • 9.39 kB
JavaScript
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const m=require("comlink"),i=require("./helpers-Cvjm0f_r.cjs");class w{root;watchers=new Map;mountingPromise=null;broadcastChannel=null;options={root:"/",namespace:"",maxFileSize:50*1024*1024,hashAlgorithm:null,broadcastChannel:"opfs-worker"};async notifyChange(t){if(!this.options.broadcastChannel)return;const e=t.path;if(![...this.watchers.values()].some(s=>i.matchMinimatch(e,s.pattern)&&s.include.some(o=>o&&i.matchMinimatch(e,o))&&!s.exclude.some(o=>o&&i.matchMinimatch(e,o))))return;let r;if(this.options.hashAlgorithm)try{r=(await this.stat(e)).hash}catch{}try{this.broadcastChannel||(this.broadcastChannel=new BroadcastChannel(this.options.broadcastChannel));const s={namespace:this.options.namespace,timestamp:new Date().toISOString(),...t,...r&&{hash:r}};this.broadcastChannel.postMessage(s)}catch(s){console.warn("Failed to send event via BroadcastChannel:",s)}}constructor(t){i.checkOPFSSupport(),t&&this.setOptions(t)}async mount(){const t=this.options.root;return this.mountingPromise&&await this.mountingPromise,this.mountingPromise=new Promise(async(e,a)=>{try{const r=await navigator.storage.getDirectory();this.root=t==="/"?r:await this.getDirectoryHandle(t,!0,r),e(!0)}catch(r){a(new i.OPFSError("Failed to initialize OPFS","INIT_FAILED",t,r))}finally{this.mountingPromise=null}}),this.mountingPromise}async setOptions(t){t.hashAlgorithm!==void 0&&(this.options.hashAlgorithm=t.hashAlgorithm),t.maxFileSize!==void 0&&(this.options.maxFileSize=t.maxFileSize),t.broadcastChannel!==void 0&&(this.broadcastChannel&&this.options.broadcastChannel!==t.broadcastChannel&&(this.broadcastChannel.close(),this.broadcastChannel=null),this.options.broadcastChannel=t.broadcastChannel),t.namespace&&(this.options.namespace=t.namespace),t.root!==void 0&&(this.options.root=i.normalizePath(t.root),this.options.namespace||(this.options.namespace=`opfs-worker:${this.options.root}`),await this.mount())}async getDirectoryHandle(t,e=!1,a=this.root){const r=Array.isArray(t)?t:i.splitPath(t);let s=a;for(const o of r)s=await s.getDirectoryHandle(o,{create:e});return s}async getFileHandle(t,e=!1,a=this.root){const r=i.splitPath(t);if(r.length===0)throw new i.PathError("Path must not be empty",Array.isArray(t)?t.join("/"):t);const s=r.pop();return(await this.getDirectoryHandle(r,e,a)).getFileHandle(s,{create:e})}async index(){const t=new Map,e=async a=>{const r=await this.readDir(a);for(const s of r){const o=`${a==="/"?"":a}/${s.name}`;try{const n=await this.stat(o);t.set(o,n),n.isDirectory&&await e(o)}catch(n){console.warn(`Skipping broken entry: ${o}`,n)}}};return t.set("/",{kind:"directory",size:0,mtime:new Date(0).toISOString(),ctime:new Date(0).toISOString(),isFile:!1,isDirectory:!0}),await e("/"),t}async readFile(t,e){await this.mount(),e||(e=i.isBinaryFileExtension(t)?"binary":"utf-8");try{const a=await this.getFileHandle(t,!1,this.root),r=await i.readFileData(a,t);return e==="binary"?r:i.decodeBuffer(r,e)}catch(a){throw new i.FileNotFoundError(t,a)}}async writeFile(t,e,a){await this.mount();const r=await this.exists(t),s=await this.getFileHandle(t,!0);a||(a=typeof e!="string"||i.isBinaryFileExtension(t)?"binary":"utf-8"),await i.writeFileData(s,e,a,t),r?await this.notifyChange({path:t,type:"changed",isDirectory:!1}):await this.notifyChange({path:t,type:"added",isDirectory:!1})}async appendFile(t,e,a){await this.mount();const r=await this.getFileHandle(t,!0);a||(a=typeof e!="string"||i.isBinaryFileExtension(t)?"binary":"utf-8"),await i.writeFileData(r,e,a,t,{append:!0}),await this.notifyChange({path:t,type:"changed",isDirectory:!1})}async mkdir(t,e){await this.mount();const a=e?.recursive??!1,r=i.splitPath(t);let s=this.root;for(let o=0;o<r.length;o++){const n=r[o];try{s=await s.getDirectoryHandle(n,{create:a||o===r.length-1})}catch(c){throw c.name==="NotFoundError"?new i.OPFSError(`Parent directory does not exist: ${i.joinPath(r.slice(0,o+1))}`,"ENOENT",void 0,c):c.name==="TypeMismatchError"?new i.OPFSError(`Path segment is not a directory: ${n}`,"ENOTDIR",void 0,c):new i.OPFSError("Failed to create directory","MKDIR_FAILED",void 0,c)}}await this.notifyChange({path:t,type:"added",isDirectory:!0})}async stat(t){if(await this.mount(),t==="/")return{kind:"directory",size:0,mtime:new Date(0).toISOString(),ctime:new Date(0).toISOString(),isFile:!1,isDirectory:!0};const e=i.basename(t),a=await this.getDirectoryHandle(i.dirname(t),!1),r=this.options.hashAlgorithm!==null;try{const o=await(await a.getFileHandle(e,{create:!1})).getFile(),n={kind:"file",size:o.size,mtime:new Date(o.lastModified).toISOString(),ctime:new Date(o.lastModified).toISOString(),isFile:!0,isDirectory:!1};if(r&&this.options.hashAlgorithm)try{const c=await i.calculateFileHash(o,this.options.hashAlgorithm,this.options.maxFileSize);n.hash=c}catch(c){console.warn(`Failed to calculate hash for ${t}:`,c)}return n}catch(s){if(s.name!=="TypeMismatchError"&&s.name!=="NotFoundError")throw new i.OPFSError("Failed to stat (file)","STAT_FAILED",void 0,s)}try{return await a.getDirectoryHandle(e,{create:!1}),{kind:"directory",size:0,mtime:new Date(0).toISOString(),ctime:new Date(0).toISOString(),isFile:!1,isDirectory:!0}}catch(s){throw s.name==="NotFoundError"?new i.OPFSError(`No such file or directory: ${t}`,"ENOENT",void 0,s):new i.OPFSError("Failed to stat (directory)","STAT_FAILED",void 0,s)}}async readDir(t){await this.mount();const e=await this.getDirectoryHandle(t,!1),a=[];for await(const[r,s]of e.entries()){const o=s.kind==="file";a.push({name:r,kind:s.kind,isFile:o,isDirectory:!o})}return a}async exists(t){if(await this.mount(),t==="/")return!0;const e=i.basename(t);let a=null;try{a=await this.getDirectoryHandle(i.dirname(t),!1)}catch(r){throw(r.name==="NotFoundError"||r.name==="TypeMismatchError")&&(a=null),r}if(!a||!e)return!1;try{return await a.getFileHandle(e,{create:!1}),!0}catch(r){if(r.name!=="NotFoundError"&&r.name!=="TypeMismatchError")throw r;try{return await a.getDirectoryHandle(e,{create:!1}),!0}catch(s){if(s.name!=="NotFoundError"&&s.name!=="TypeMismatchError")throw s;return!1}}}async clear(t="/"){await this.mount();try{const e=await this.readDir(t);for(const a of e){const r=`${t==="/"?"":t}/${a.name}`;await this.remove(r,{recursive:!0})}await this.notifyChange({path:t,type:"changed",isDirectory:!0})}catch(e){throw e instanceof i.OPFSError?e:new i.OPFSError(`Failed to clear directory: ${t}`,"CLEAR_FAILED",void 0,e)}}async remove(t,e){if(await this.mount(),t==="/")throw new i.OPFSError("Cannot remove root directory","EROOT");const{recursive:a=!1,force:r=!1}=e||{},s=await this.getDirectoryHandle(i.dirname(t),!1);await i.removeEntry(s,t,{recursive:a,force:r}),await this.notifyChange({path:t,type:"removed",isDirectory:!1})}async realpath(t){await this.mount();try{const e=i.resolvePath(t);if(!await this.exists(e))throw new i.FileNotFoundError(e);return e}catch(e){throw e instanceof i.OPFSError?e:new i.OPFSError(`Failed to resolve path: ${t}`,"REALPATH_FAILED",void 0,e)}}async rename(t,e,a){await this.mount();try{const r=a?.overwrite??!1;if(!await this.exists(t))throw new i.FileNotFoundError(t);if(await this.exists(e)&&!r)throw new i.OPFSError(`Destination already exists: ${e}`,"EEXIST",void 0);await this.copy(t,e,{recursive:!0,overwrite:r}),await this.remove(t,{recursive:!0}),await this.notifyChange({path:t,type:"removed",isDirectory:!1}),await this.notifyChange({path:e,type:"added",isDirectory:!1})}catch(r){throw r instanceof i.OPFSError?r:new i.OPFSError(`Failed to rename from ${t} to ${e}`,"RENAME_FAILED",void 0,r)}}async copy(t,e,a){await this.mount();try{const r=a?.recursive??!1,s=a?.overwrite??!0;if(!await this.exists(t))throw new i.OPFSError(`Source does not exist: ${t}`,"ENOENT",void 0);if(await this.exists(e)&&!s)throw new i.OPFSError(`Destination already exists: ${e}`,"EEXIST",void 0);if((await this.stat(t)).isFile){const l=await this.readFile(t,"binary");await this.writeFile(e,l)}else{if(!r)throw new i.OPFSError(`Cannot copy directory without recursive option: ${t}`,"EISDIR",void 0);await this.mkdir(e,{recursive:!0});const l=await this.readDir(t);for(const h of l){const d=`${t}/${h.name}`,f=`${e}/${h.name}`;await this.copy(d,f,{recursive:!0,overwrite:s})}}}catch(r){throw r instanceof i.OPFSError?r:new i.OPFSError(`Failed to copy from ${t} to ${e}`,"CP_FAILED",void 0,r)}}async watch(t,e){if(!this.options.broadcastChannel)throw new i.OPFSError("This instance is not configured to send events. Please specify options.broadcastChannel to enable watching.","ENOENT");const a={pattern:i.normalizeMinimatch(t,e?.recursive??!0),include:Array.isArray(e?.include)?e.include:[e?.include??"**"],exclude:Array.isArray(e?.exclude)?e.exclude:[e?.exclude??""]};this.watchers.set(t,a)}unwatch(t){this.watchers.delete(t)}dispose(){this.broadcastChannel&&(this.broadcastChannel.close(),this.broadcastChannel=null),this.watchers.clear()}async sync(t,e){await this.mount();try{(e?.cleanBefore??!1)&&await this.clear("/");for(const[r,s]of t){const o=i.normalizePath(r);let n;s instanceof Blob?n=await i.convertBlobToUint8Array(s):n=s,await this.writeFile(o,n)}}catch(a){throw a instanceof i.OPFSError?a:new i.OPFSError("Failed to sync file system","SYNC_FAILED",void 0,a)}}}typeof globalThis<"u"&&globalThis.constructor.name==="DedicatedWorkerGlobalScope"&&m.expose(new w);exports.OPFSWorker=w;
//# sourceMappingURL=raw.cjs.map