@artemjs/vfskit-front 1.1.0 → 1.2.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/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
  <h1 align="center">vfskit</h1>
6
6
 
7
7
  <p align="center">
8
- <img src="https://img.shields.io/badge/version-1.1.0-7c8cff?style=flat-square" alt="version">
8
+ <img src="https://img.shields.io/badge/version-1.2.0-7c8cff?style=flat-square" alt="version">
9
9
  <img src="https://img.shields.io/badge/license-MIT-56e6c4?style=flat-square" alt="license">
10
10
  <img src="https://img.shields.io/badge/TypeScript-strict-3178c6?style=flat-square" alt="typescript">
11
11
  <img src="https://img.shields.io/badge/module-ESM-f0db4f?style=flat-square" alt="esm">
@@ -26,8 +26,8 @@ becomes a structured file system with files and metadata.
26
26
 
27
27
  It ships in two faces under one brand:
28
28
 
29
- - **`/vfskit`** (npm, Node) - the full kit: core + memory + node-fs + s3 + encrypt + cache + serve + remote.
30
- - **`/vfskit-front`** (npm + jsDelivr, browser) - core + memory + encrypt + cache + a remote client.
29
+ - **`@artemjs/vfskit`** (npm, Node) - the full kit: core + memory + node-fs + s3 + sqlite + kv + encrypt + cache + serve + remote.
30
+ - **`@artemjs/vfskit-front`** (npm + jsDelivr, browser) - core + memory + opfs + kv + encrypt + cache + a remote client.
31
31
 
32
32
  Both expose **identical API names**, so your code looks the same on either side.
33
33
 
@@ -105,9 +105,23 @@ await fs.write('/hello.txt', 'hi')
105
105
  | `memory()` | anywhere | native | reference implementation; synchronous `watch` |
106
106
  | `nodeFs(dir)` | Node | sidecar `.vfskit/meta.json` | rooted at `dir`; native streaming; `watch` via `fs.watch` |
107
107
  | `s3({ client, prefix?, pollMs? })` | Node | native object metadata | inject any `S3Like` client; POSIX dirs emulated with markers; `watch` by polling |
108
+ | `sqlite(file)` | Node | native column | whole VFS in one SQLite file via built-in `node:sqlite`; conditional writes |
109
+ | `opfs(root?)` | browser | sidecar manifest | persistent Origin Private File System; native file streaming |
110
+ | `kv({ store, prefix? })` | anywhere | native record | over any async key-value store; ships `memKv()` and `localStorageKv()` |
108
111
 
109
112
  Every adapter passes the same conformance suite, so a new one "just works" once it does too.
110
113
 
114
+ ```ts
115
+ // browser-persistent storage, no server
116
+ import { opfs, kv, localStorageKv } from '@artemjs/vfskit-front'
117
+ const disk = opfs() // Origin Private File System
118
+ const ls = kv({ store: localStorageKv() }) // or back any KV: Redis, Cloudflare KV, Deno KV
119
+
120
+ // node: a whole file system in one .db
121
+ import { sqlite } from '@artemjs/vfskit'
122
+ const db = sqlite('./app.db')
123
+ ```
124
+
111
125
  ## Bring your own storage
112
126
 
113
127
  vfskit is just an interface. To put *any* backend behind the same API, write a function that
package/dist/index.d.ts CHANGED
@@ -35,6 +35,23 @@ declare function collect(s: ReadableStream<Uint8Array>): Promise<Uint8Array>;
35
35
 
36
36
  declare function memory(): VFS;
37
37
 
38
+ type DirH = FileSystemDirectoryHandle;
39
+ declare function opfs(root?: DirH): VFS;
40
+
41
+ interface KvStore {
42
+ get(key: string): Promise<string | null | undefined>;
43
+ set(key: string, value: string): Promise<void>;
44
+ delete(key: string): Promise<void>;
45
+ list(prefix: string): Promise<string[]>;
46
+ }
47
+ interface KvOpts {
48
+ store: KvStore;
49
+ prefix?: string;
50
+ }
51
+ declare function memKv(): KvStore;
52
+ declare function localStorageKv(ls?: Storage): KvStore;
53
+ declare function kv(opts: KvOpts): VFS;
54
+
38
55
  interface EncryptOpts {
39
56
  key?: Uint8Array;
40
57
  passphrase?: string;
@@ -89,4 +106,4 @@ interface SocketLike {
89
106
  }
90
107
  declare function wsTransport(url: string, factory?: () => SocketLike): Transport;
91
108
 
92
- export { BRAND, BytesLike, type CacheOpts, type CacheStore, Capabilities, type EncryptOpts, type ErrorCode, type FetchLike, ReadOpts, type RemoteOpts, type SocketLike, type Transport, Unsubscribe, VFS, VfsError, WatchCb, WriteOpts, alreadyExists, basename, cache, collect, concat, conflict, dirname, encrypt, httpTransport, io, isADirectory, isVfsError, join, memory, normalize, notADirectory, notFound, permissionDenied, readStream, remote, segments, toBytes, toText, unsupported, writeStream, wsTransport };
109
+ export { BRAND, BytesLike, type CacheOpts, type CacheStore, Capabilities, type EncryptOpts, type ErrorCode, type FetchLike, type KvOpts, type KvStore, ReadOpts, type RemoteOpts, type SocketLike, type Transport, Unsubscribe, VFS, VfsError, WatchCb, WriteOpts, alreadyExists, basename, cache, collect, concat, conflict, dirname, encrypt, httpTransport, io, isADirectory, isVfsError, join, kv, localStorageKv, memKv, memory, normalize, notADirectory, notFound, opfs, permissionDenied, readStream, remote, segments, toBytes, toText, unsupported, writeStream, wsTransport };
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- function f(t){let a=[];for(let o of t.split("/"))if(!(o===""||o===".")){if(o===".."){a.pop();continue}a.push(o)}return"/"+a.join("/")}function tt(...t){return f(t.join("/"))}function E(t){let a=f(t),o=a.lastIndexOf("/");return o<=0?"/":a.slice(0,o)}function et(t){let a=f(t);return a.slice(a.lastIndexOf("/")+1)}function rt(t){return f(t).split("/").filter(Boolean)}var D=Symbol.for("vfskit.VfsError"),w=class extends Error{code;path;[D]=!0;constructor(a,o,n){super(o),this.name="VfsError",this.code=a,this.path=n}},O=t=>new w("NOT_FOUND",`not found: ${t}`,t),x=t=>new w("ALREADY_EXISTS",`already exists: ${t}`,t),S=t=>new w("NOT_A_DIRECTORY",`not a directory: ${t}`,t),C=t=>new w("IS_A_DIRECTORY",`is a directory: ${t}`,t),at=t=>new w("PERMISSION_DENIED",`permission denied: ${t}`,t),T=t=>new w("UNSUPPORTED",`unsupported: ${t}`),R=t=>new w("CONFLICT",`version conflict: ${t}`,t),g=(t,a)=>new w("IO",t,a);function ot(t){return typeof t=="object"&&t!==null&&t[D]===!0}var L=new TextEncoder,B=new TextDecoder;function h(t){return typeof t=="string"?L.encode(t):t instanceof Uint8Array?t:new Uint8Array(t)}function st(t){return B.decode(t)}function b(t){let a=0;for(let i of t)a+=i.length;let o=new Uint8Array(a),n=0;for(let i of t)o.set(i,n),n+=i.length;return o}async function yt(t,a,o){if(t.readStream)return t.readStream(a,o);let n=await t.read(a,o);return new ReadableStream({start(i){i.enqueue(n),i.close()}})}async function dt(t,a,o){if(t.writeStream)return t.writeStream(a,o);let n=[];return new WritableStream({write(i){n.push(i)},async close(){await t.write(a,b(n),o)}})}async function ut(t){let a=[],o=t.getReader();for(;;){let{done:n,value:i}=await o.read();if(n)break;i&&a.push(i)}return b(a)}var W={streaming:!1,watch:!0,atomicMove:!0,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0};function _(){let t=()=>Date.now(),a=0,o=()=>String(++a),n=new Map([["/",{type:"dir",meta:{},mtime:t(),ctime:t()}]]),i=new Set,c=(r,s)=>s===r||s.startsWith(r==="/"?"/":r+"/"),m=(r,s)=>{for(let e of i)c(e.base,s)&&e.cb({type:r,path:s})},d=r=>{let s=n.get(r);if(!s)throw O(r);return s},p=r=>{let s=E(r),e=n.get(s);if(!e)throw O(s);if(e.type!=="dir")throw S(s)};return{capabilities:()=>W,async read(r){let s=f(r),e=d(s);if(e.type==="dir")throw C(s);return e.data.slice()},async write(r,s,e){let y=f(r);p(y);let l=n.get(y);if(l&&l.type==="dir")throw C(y);if(e?.ifAbsent&&l)throw x(y);if(e?.ifMatch!==void 0&&l?.version!==e.ifMatch)throw R(y);let u=l?l.ctime:t();n.set(y,{type:"file",data:h(s).slice(),meta:e?.meta?{...e.meta}:l?.meta??{},ctime:u,mtime:t(),version:o()}),m(l?"update":"create",y)},async list(r,s){let e=f(r);if(d(e).type!=="dir")throw S(e);let l=[];for(let[u,I]of n)u===e||!c(e,u)||!s?.recursive&&E(u)!==e||l.push({name:u.slice(u.lastIndexOf("/")+1),path:u,type:I.type});return l},async stat(r){let s=f(r),e=d(s);return{type:e.type,size:e.type==="file"?e.data.length:0,mtime:e.mtime,ctime:e.ctime,meta:{...e.meta},version:e.type==="file"?e.version:void 0}},async exists(r){return n.has(f(r))},async mkdir(r,s){let e=f(r);if(n.has(e)){if(s?.recursive)return;throw x(e)}if(s?.recursive){let y="";for(let l of e.split("/").filter(Boolean)){y+="/"+l;let u=n.get(y);if(u){if(u.type!=="dir")throw S(y);continue}n.set(y,{type:"dir",meta:{},mtime:t(),ctime:t()}),m("create",y)}return}p(e),n.set(e,{type:"dir",meta:{},mtime:t(),ctime:t()}),m("create",e)},async remove(r,s){let e=f(r);d(e);let y=[...n.keys()].filter(l=>l!==e&&c(e,l));if(y.length&&!s?.recursive)throw g("directory not empty",e);for(let l of[...y,e])n.delete(l),m("remove",l)},async move(r,s){let e=f(r),y=f(s);if(d(e),p(y),n.has(y))throw x(y);if(c(e,y))throw g("cannot move into itself",y);for(let l of[...n.keys()].filter(u=>u===e||c(e,u))){let u=n.get(l);n.delete(l),n.set(y+l.slice(e.length),u)}m("remove",e),m("create",y)},async copy(r,s){let e=f(r),y=f(s);if(d(e),p(y),n.has(y))throw x(y);for(let l of[...n.keys()].filter(u=>u===e||c(e,u))){let u=n.get(l);n.set(y+l.slice(e.length),u.type==="file"?{type:"file",data:u.data.slice(),meta:{...u.meta},mtime:u.mtime,ctime:u.ctime,version:o()}:{type:"dir",meta:{...u.meta},mtime:u.mtime,ctime:u.ctime})}m("create",y)},async getMeta(r){return{...d(f(r)).meta}},async setMeta(r,s){let e=d(f(r));e.meta={...s},e.mtime=t()},watch(r,s){let e={base:f(r),cb:s};return i.add(e),()=>{i.delete(e)}}}}var v=new Uint8Array([86,75,1]),M=47,k=globalThis.crypto.subtle;function P(t){let a=new Uint8Array(t);return globalThis.crypto.getRandomValues(a),a}async function q(t,a){let o=await k.importKey("raw",new TextEncoder().encode(t),"PBKDF2",!1,["deriveKey"]);return k.deriveKey({name:"PBKDF2",salt:a.slice(),iterations:21e4,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}function K(t,a){if(!a.key&&!a.passphrase)throw new Error("encrypt: key or passphrase required");let o=a.key?k.importKey("raw",a.key.slice(),"AES-GCM",!1,["encrypt","decrypt"]):null,n=c=>o??q(a.passphrase,c),i={...t.capabilities(),streaming:!1,randomAccess:!1};return{...t,readStream:void 0,writeStream:void 0,capabilities:()=>i,async stat(c){let m=await t.stat(c);return m.type==="file"?{...m,size:Math.max(0,m.size-M)}:m},async write(c,m,d){let p=P(16),r=P(12),s=await n(p),e=new Uint8Array(await k.encrypt({name:"AES-GCM",iv:r},s,h(m).slice()));await t.write(c,b([v,p,r,e]),d)},async read(c,m){let d=await t.read(c,m);if(d.length<M||d[0]!==v[0]||d[1]!==v[1]||d[2]!==v[2])throw g("invalid ciphertext",c);let p=await n(d.slice(3,19)),r;try{r=await k.decrypt({name:"AES-GCM",iv:d.slice(19,31)},p,d.slice(31))}catch{throw g("decryption failed",c)}return new Uint8Array(r)}}}function Y(t,a={}){let o=a.store??new Map,n=a.ttlMs??0,i=()=>n?Date.now():0,c=()=>n?Date.now()+n:1/0,m=(p,r)=>r===p||r.startsWith(p==="/"?"/":p+"/"),d=p=>{for(let r of[...o.keys()])m(p,r)&&o.delete(r)};return{...t,readStream:void 0,writeStream:void 0,async read(p,r){let s=f(p),e=o.get(s);if(e&&e.exp>i())return e.data.slice();let y=await t.read(s,r);return o.set(s,{data:y.slice(),exp:c()}),y.slice()},async write(p,r,s){let e=f(p),y=h(r).slice();await t.write(e,y,s),o.set(e,{data:y,exp:c()})},async remove(p,r){let s=f(p);await t.remove(s,r),d(s)},async move(p,r){let s=f(p),e=f(r);await t.move(s,e),d(s),d(e)},async copy(p,r){let s=f(p),e=f(r);await t.copy(s,e),d(e)}}}var $=new TextEncoder,z=new TextDecoder,A=new Uint8Array(0);function G(t,a=A){let o=$.encode(JSON.stringify(t)),n=new Uint8Array(4+o.length+a.length);return new DataView(n.buffer).setUint32(0,o.length),n.set(o,4),n.set(a,4+o.length),n}function j(t){let o=new DataView(t.buffer,t.byteOffset,t.byteLength).getUint32(0);return{header:JSON.parse(z.decode(t.subarray(4,4+o))),data:t.subarray(4+o)}}function F(t,a,o=[],n){return G({m:t,p:a,a:o},n)}function N(t){let{header:a,data:o}=j(t);return a.ok?{ok:!0,value:a.v,data:o}:{ok:!1,data:A,code:a.c,message:a.e,path:a.p}}function U(t,a,o=A){let n=new Uint8Array(5+o.length);return n[0]=t,new DataView(n.buffer).setUint32(1,a),n.set(o,5),n}function V(t){let a=new DataView(t.buffer,t.byteOffset,t.byteLength);return{type:t[0],id:a.getUint32(1),payload:t.subarray(5)}}function J(t){let a=t.request?t:t.transport,o=t.capabilities??{streaming:!1,watch:!!a.watch,atomicMove:!1,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0},n=async(i,c,m,d)=>{let p=m?[...m]:[];for(;p.length&&p[p.length-1]===void 0;)p.pop();let r=N(await a.request(F(i,c,p,d)));if(!r.ok)throw new w(r.code,r.message??"",r.path);return r};return{capabilities:()=>o,async read(i,c){return(await n("read",i,[c])).data},async write(i,c,m){await n("write",i,[m],h(c))},async list(i,c){return(await n("list",i,[c])).value},async stat(i){return(await n("stat",i)).value},async exists(i){return(await n("exists",i)).value},async mkdir(i,c){await n("mkdir",i,[c])},async remove(i,c){await n("remove",i,[c])},async move(i,c){await n("move",i,[c])},async copy(i,c){await n("copy",i,[c])},async getMeta(i){return(await n("getMeta",i)).value},async setMeta(i,c){await n("setMeta",i,[c])},watch(i,c){if(!a.watch)throw T("watch");return a.watch(i,c)}}}function H(t,a){let o=a??((n,i)=>fetch(n,i));return{async request(n){let i=await o(t,{method:"POST",body:n});return new Uint8Array(await i.arrayBuffer())}}}var X=new TextEncoder,Q=new TextDecoder;function Z(t,a){let o=a?a():new WebSocket(t);try{o.binaryType="arraybuffer"}catch{}let n=new Map,i=new Map,c=0,m=o.readyState===1?Promise.resolve():new Promise(d=>{o.addEventListener?o.addEventListener("open",()=>d()):o.onopen=()=>d()});return o.onmessage=d=>{let{type:p,id:r,payload:s}=V(new Uint8Array(d.data));p===1?(n.get(r)?.(s),n.delete(r)):p===4&&i.get(r)?.(JSON.parse(Q.decode(s)))},{async request(d){await m;let p=++c;return new Promise(r=>{n.set(p,r),o.send(U(0,p,d))})},watch(d,p){let r=++c;return i.set(r,p),m.then(()=>o.send(U(2,r,X.encode(JSON.stringify({path:d}))))),()=>{i.delete(r),m.then(()=>o.send(U(3,r,A)))}}}}export{D as BRAND,w as VfsError,x as alreadyExists,et as basename,Y as cache,ut as collect,b as concat,R as conflict,E as dirname,K as encrypt,H as httpTransport,g as io,C as isADirectory,ot as isVfsError,tt as join,_ as memory,f as normalize,S as notADirectory,O as notFound,at as permissionDenied,yt as readStream,J as remote,rt as segments,h as toBytes,st as toText,T as unsupported,dt as writeStream,Z as wsTransport};
1
+ function f(t){let a=[];for(let o of t.split("/"))if(!(o===""||o===".")){if(o===".."){a.pop();continue}a.push(o)}return"/"+a.join("/")}function ht(...t){return f(t.join("/"))}function A(t){let a=f(t),o=a.lastIndexOf("/");return o<=0?"/":a.slice(0,o)}function C(t){let a=f(t);return a.slice(a.lastIndexOf("/")+1)}function R(t){return f(t).split("/").filter(Boolean)}var B=Symbol.for("vfskit.VfsError"),x=class extends Error{code;path;[B]=!0;constructor(a,o,i){super(o),this.name="VfsError",this.code=a,this.path=i}},b=t=>new x("NOT_FOUND",`not found: ${t}`,t),O=t=>new x("ALREADY_EXISTS",`already exists: ${t}`,t),U=t=>new x("NOT_A_DIRECTORY",`not a directory: ${t}`,t),E=t=>new x("IS_A_DIRECTORY",`is a directory: ${t}`,t),vt=t=>new x("PERMISSION_DENIED",`permission denied: ${t}`,t),V=t=>new x("UNSUPPORTED",`unsupported: ${t}`),W=t=>new x("CONFLICT",`version conflict: ${t}`,t),k=(t,a)=>new x("IO",t,a);function bt(t){return typeof t=="object"&&t!==null&&t[B]===!0}var Y=new TextEncoder,j=new TextDecoder;function S(t){return typeof t=="string"?Y.encode(t):t instanceof Uint8Array?t:new Uint8Array(t)}function xt(t){return j.decode(t)}function P(t){let a=0;for(let p of t)a+=p.length;let o=new Uint8Array(a),i=0;for(let p of t)o.set(p,i),i+=p.length;return o}async function Ot(t,a,o){if(t.readStream)return t.readStream(a,o);let i=await t.read(a,o);return new ReadableStream({start(p){p.enqueue(i),p.close()}})}async function Ut(t,a,o){if(t.writeStream)return t.writeStream(a,o);let i=[];return new WritableStream({write(p){i.push(p)},async close(){await t.write(a,P(i),o)}})}async function Et(t){let a=[],o=t.getReader();for(;;){let{done:i,value:p}=await o.read();if(i)break;p&&a.push(p)}return P(a)}var $={streaming:!1,watch:!0,atomicMove:!0,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0};function G(){let t=()=>Date.now(),a=0,o=()=>String(++a),i=new Map([["/",{type:"dir",meta:{},mtime:t(),ctime:t()}]]),p=new Set,u=(y,n)=>n===y||n.startsWith(y==="/"?"/":y+"/"),m=(y,n)=>{for(let e of p)u(e.base,n)&&e.cb({type:y,path:n})},w=y=>{let n=i.get(y);if(!n)throw b(y);return n},l=y=>{let n=A(y),e=i.get(n);if(!e)throw b(n);if(e.type!=="dir")throw U(n)};return{capabilities:()=>$,async read(y){let n=f(y),e=w(n);if(e.type==="dir")throw E(n);return e.data.slice()},async write(y,n,e){let r=f(y);l(r);let s=i.get(r);if(s&&s.type==="dir")throw E(r);if(e?.ifAbsent&&s)throw O(r);if(e?.ifMatch!==void 0&&s?.version!==e.ifMatch)throw W(r);let c=s?s.ctime:t();i.set(r,{type:"file",data:S(n).slice(),meta:e?.meta?{...e.meta}:s?.meta??{},ctime:c,mtime:t(),version:o()}),m(s?"update":"create",r)},async list(y,n){let e=f(y);if(w(e).type!=="dir")throw U(e);let s=[];for(let[c,d]of i)c===e||!u(e,c)||!n?.recursive&&A(c)!==e||s.push({name:c.slice(c.lastIndexOf("/")+1),path:c,type:d.type});return s},async stat(y){let n=f(y),e=w(n);return{type:e.type,size:e.type==="file"?e.data.length:0,mtime:e.mtime,ctime:e.ctime,meta:{...e.meta},version:e.type==="file"?e.version:void 0}},async exists(y){return i.has(f(y))},async mkdir(y,n){let e=f(y);if(i.has(e)){if(n?.recursive)return;throw O(e)}if(n?.recursive){let r="";for(let s of e.split("/").filter(Boolean)){r+="/"+s;let c=i.get(r);if(c){if(c.type!=="dir")throw U(r);continue}i.set(r,{type:"dir",meta:{},mtime:t(),ctime:t()}),m("create",r)}return}l(e),i.set(e,{type:"dir",meta:{},mtime:t(),ctime:t()}),m("create",e)},async remove(y,n){let e=f(y);w(e);let r=[...i.keys()].filter(s=>s!==e&&u(e,s));if(r.length&&!n?.recursive)throw k("directory not empty",e);for(let s of[...r,e])i.delete(s),m("remove",s)},async move(y,n){let e=f(y),r=f(n);if(w(e),l(r),i.has(r))throw O(r);if(u(e,r))throw k("cannot move into itself",r);for(let s of[...i.keys()].filter(c=>c===e||u(e,c))){let c=i.get(s);i.delete(s),i.set(r+s.slice(e.length),c)}m("remove",e),m("create",r)},async copy(y,n){let e=f(y),r=f(n);if(w(e),l(r),i.has(r))throw O(r);for(let s of[...i.keys()].filter(c=>c===e||u(e,c))){let c=i.get(s);i.set(r+s.slice(e.length),c.type==="file"?{type:"file",data:c.data.slice(),meta:{...c.meta},mtime:c.mtime,ctime:c.ctime,version:o()}:{type:"dir",meta:{...c.meta},mtime:c.mtime,ctime:c.ctime})}m("create",r)},async getMeta(y){return{...w(f(y)).meta}},async setMeta(y,n){let e=w(f(y));e.meta={...n},e.mtime=t()},watch(y,n){let e={base:f(y),cb:n};return p.add(e),()=>{p.delete(e)}}}}var X=t=>t.values(),Q={streaming:!0,watch:!1,atomicMove:!1,nativeMeta:!1,randomAccess:!1,conditionalWrite:!1};function D(t,a){let o=a.name;throw o==="NotFoundError"?b(t):o==="TypeMismatchError"?U(t):o==="InvalidModificationError"?k("not empty",t):k(String(a?.message??a),t)}function Z(t){let a=t?Promise.resolve(t):navigator.storage.getDirectory(),o=n=>n==="/.vfskit"||n.startsWith("/.vfskit/"),i=async(n,e)=>{let r=await a;for(let s of n)r=await r.getDirectoryHandle(s,{create:e});return r},p=n=>i(R(A(n)),!1),u=async n=>{if(f(n)==="/")return"dir";let e=await p(n).catch(()=>null);if(!e)return null;let r=C(n);try{return await e.getFileHandle(r),"file"}catch{}try{return await e.getDirectoryHandle(r),"dir"}catch{return null}},m=async()=>{try{let e=await(await(await i([".vfskit"],!1)).getFileHandle("meta.json")).getFile();return JSON.parse(await e.text())}catch{return{}}},w=async n=>{let s=(await(await(await i([".vfskit"],!0)).getFileHandle("meta.json",{create:!0})).createWritable()).getWriter();await s.write(S(JSON.stringify(n))),await s.close()},l=async n=>{let e=await m();n(e)&&await w(e)},y=async(n,e)=>(await p(n)).getFileHandle(C(n),{create:e});return{capabilities:()=>Q,async read(n){let e=f(n);if(e==="/")throw E(e);try{return new Uint8Array(await(await(await y(e,!1)).getFile()).arrayBuffer())}catch(r){if(await u(e)==="dir")throw E(e);D(e,r)}},async write(n,e,r){let s=f(n);if(await u(s)==="dir")throw E(s);try{let d=(await(await y(s,!0)).createWritable()).getWriter();await d.write(S(e)),await d.close()}catch(c){D(A(s),c)}r?.meta&&await l(c=>(c[s]=r.meta,!0))},async list(n,e){let r=f(n),s=[],c=async d=>{let h=await i(R(d),!1);for await(let g of X(h)){let v=f(d+"/"+g.name);if(o(v))continue;let M=g.kind==="directory"?"dir":"file";s.push({name:g.name,path:v,type:M}),e?.recursive&&M==="dir"&&await c(v)}};try{await c(r)}catch(d){if(await u(r)==="file")throw U(r);D(r,d)}return s},async stat(n){let e=f(n),r=await u(e);if(r===null)throw b(e);let s=0;return r==="file"&&(s=(await(await y(e,!1)).getFile()).size),{type:r,size:s,mtime:0,ctime:0,meta:(await m())[e]??{}}},async exists(n){return await u(n)!==null},async mkdir(n,e){let r=f(n);if(await u(r)!==null){if(e?.recursive)return;throw O(r)}if(e?.recursive){let c="";for(let d of R(r)){c+="/"+d;let h=await u(c);if(h==="file")throw U(c);h===null&&await i(R(c),!0)}return}try{await(await p(r)).getDirectoryHandle(C(r),{create:!0})}catch(c){D(A(r),c)}},async remove(n,e){let r=f(n);if(await u(r)===null)throw b(r);let c=await p(r);try{await c.removeEntry(C(r),{recursive:!!e?.recursive})}catch(d){D(r,d)}await l(d=>{let h=!1;for(let g of Object.keys(d))(g===r||g.startsWith(r+"/"))&&(delete d[g],h=!0);return h})},async move(n,e){await this.copy(n,e),await this.remove(n,{recursive:!0})},async copy(n,e){let r=f(n),s=f(e);if(await u(r)===null)throw b(r);if(await u(s)!==null)throw O(s);if(s===r||s.startsWith(r+"/"))throw k("cannot copy into itself",s);if(await p(s).catch(()=>null)===null)throw b(A(s));let d=await m(),h=!1,g=async(v,M)=>{if(await u(v)==="file")await this.write(M,await this.read(v)),d[v]&&(d[M]=d[v],h=!0);else{await this.mkdir(M),d[v]&&(d[M]=d[v],h=!0);for(let L of await this.list(v))await g(L.path,M+"/"+L.name)}};await g(r,s),h&&await w(d)},async getMeta(n){let e=f(n);if(await u(e)===null)throw b(e);return(await m())[e]??{}},async setMeta(n,e){let r=f(n);if(await u(r)===null)throw b(r);await l(s=>(s[r]=e,!0))},async readStream(n){let e=f(n);try{return(await(await y(e,!1)).getFile()).stream()}catch(r){if(await u(e)==="dir")throw E(e);D(e,r)}},async writeStream(n){let e=f(n);if(await u(e)==="dir")throw E(e);try{return await(await y(e,!0)).createWritable()}catch(r){D(A(e),r)}},watch(){return()=>{}}}}var tt={streaming:!1,watch:!1,atomicMove:!1,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0};function et(t){let a="";for(let o=0;o<t.length;o+=32768)a+=String.fromCharCode(...t.subarray(o,o+32768));return btoa(a)}function K(t){let a=atob(t),o=new Uint8Array(a.length);for(let i=0;i<a.length;i++)o[i]=a.charCodeAt(i);return o}function rt(){let t=new Map;return{async get(a){return t.get(a)},async set(a,o){t.set(a,o)},async delete(a){t.delete(a)},async list(a){return[...t.keys()].filter(o=>o.startsWith(a))}}}function nt(t=globalThis.localStorage){return{async get(a){return t.getItem(a)},async set(a,o){t.setItem(a,o)},async delete(a){t.removeItem(a)},async list(a){let o=[];for(let i=0;i<t.length;i++){let p=t.key(i);p&&p.startsWith(a)&&o.push(p)}return o}}}function at(t){let a=t.store,o=(t.prefix?t.prefix+":":"")+"vfs:",i=r=>o+f(r),p=r=>r.slice(o.length),u=(r,s)=>s===r||s.startsWith(r==="/"?"/":r+"/"),m=async r=>{let s=await a.get(i(r));return s?JSON.parse(s):void 0},w=(r,s)=>a.set(i(r),JSON.stringify(s)),l=async r=>{let s=await m(r);if(!s)throw b(r);return s},y=async r=>(await a.list(i(r)===o+"/"?o+"/":i(r)+"/")).map(p).filter(s=>s!==r&&u(r,s)),n=async r=>{let s=A(r),c=await m(s);if(!c)throw b(s);if(c.t!=="dir")throw U(s)},e=(async()=>{await a.get(i("/"))||await w("/",{t:"dir",m:{},v:0})})();return{capabilities:()=>tt,async read(r){await e;let s=f(r),c=await l(s);if(c.t==="dir")throw E(s);return K(c.d??"")},async write(r,s,c){await e;let d=f(r);await n(d);let h=await m(d);if(h?.t==="dir")throw E(d);if(c?.ifAbsent&&h)throw O(d);if(c?.ifMatch!==void 0&&String(h?.v??"")!==c.ifMatch)throw W(d);await w(d,{t:"file",d:et(S(s)),m:c?.meta??h?.m??{},v:(h?.v??0)+1})},async list(r,s){await e;let c=f(r);if((await l(c)).t!=="dir")throw U(c);let h=[];for(let g of await y(c)){if(!s?.recursive&&A(g)!==c)continue;let v=await m(g);v&&h.push({name:g.slice(g.lastIndexOf("/")+1),path:g,type:v.t})}return h},async stat(r){await e;let s=f(r),c=await l(s);return{type:c.t,size:c.t==="file"?K(c.d??"").length:0,mtime:0,ctime:0,meta:{...c.m},version:c.t==="file"?String(c.v):void 0}},async exists(r){return await e,!!await m(f(r))},async mkdir(r,s){await e;let c=f(r);if(await m(c)){if(s?.recursive)return;throw O(c)}if(s?.recursive){let h="";for(let g of c.split("/").filter(Boolean)){h+="/"+g;let v=await m(h);if(v?.t==="file")throw U(h);v||await w(h,{t:"dir",m:{},v:0})}return}await n(c),await w(c,{t:"dir",m:{},v:0})},async remove(r,s){await e;let c=f(r);await l(c);let d=await y(c);if(d.length&&!s?.recursive)throw k("directory not empty",c);for(let h of[...d,c])await a.delete(i(h))},async move(r,s){await this.copy(r,s),await this.remove(r,{recursive:!0})},async copy(r,s){await e;let c=f(r),d=f(s);if(await l(c),await n(d),await m(d))throw O(d);if(u(c,d))throw k("cannot copy into itself",d);for(let h of[c,...await y(c)]){let g=await m(h);g&&await w(d+h.slice(c.length),{...g,v:(g.v??0)+1})}},async getMeta(r){return await e,{...(await l(f(r))).m}},async setMeta(r,s){await e;let c=f(r),d=await l(c);await w(c,{...d,m:{...s}})},watch(){return()=>{}}}}var N=new Uint8Array([86,75,1]),H=47,F=globalThis.crypto.subtle;function _(t){let a=new Uint8Array(t);return globalThis.crypto.getRandomValues(a),a}async function it(t,a){let o=await F.importKey("raw",new TextEncoder().encode(t),"PBKDF2",!1,["deriveKey"]);return F.deriveKey({name:"PBKDF2",salt:a.slice(),iterations:21e4,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}function st(t,a){if(!a.key&&!a.passphrase)throw new Error("encrypt: key or passphrase required");let o=a.key?F.importKey("raw",a.key.slice(),"AES-GCM",!1,["encrypt","decrypt"]):null,i=u=>o??it(a.passphrase,u),p={...t.capabilities(),streaming:!1,randomAccess:!1};return{...t,readStream:void 0,writeStream:void 0,capabilities:()=>p,async stat(u){let m=await t.stat(u);return m.type==="file"?{...m,size:Math.max(0,m.size-H)}:m},async write(u,m,w){let l=_(16),y=_(12),n=await i(l),e=new Uint8Array(await F.encrypt({name:"AES-GCM",iv:y},n,S(m).slice()));await t.write(u,P([N,l,y,e]),w)},async read(u,m){let w=await t.read(u,m);if(w.length<H||w[0]!==N[0]||w[1]!==N[1]||w[2]!==N[2])throw k("invalid ciphertext",u);let l=await i(w.slice(3,19)),y;try{y=await F.decrypt({name:"AES-GCM",iv:w.slice(19,31)},l,w.slice(31))}catch{throw k("decryption failed",u)}return new Uint8Array(y)}}}function ot(t,a={}){let o=a.store??new Map,i=a.ttlMs??0,p=()=>i?Date.now():0,u=()=>i?Date.now()+i:1/0,m=(l,y)=>y===l||y.startsWith(l==="/"?"/":l+"/"),w=l=>{for(let y of[...o.keys()])m(l,y)&&o.delete(y)};return{...t,readStream:void 0,writeStream:void 0,async read(l,y){let n=f(l),e=o.get(n);if(e&&e.exp>p())return e.data.slice();let r=await t.read(n,y);return o.set(n,{data:r.slice(),exp:u()}),r.slice()},async write(l,y,n){let e=f(l),r=S(y).slice();await t.write(e,r,n),o.set(e,{data:r,exp:u()})},async remove(l,y){let n=f(l);await t.remove(n,y),w(n)},async move(l,y){let n=f(l),e=f(y);await t.move(n,e),w(n),w(e)},async copy(l,y){let n=f(l),e=f(y);await t.copy(n,e),w(e)}}}var ct=new TextEncoder,yt=new TextDecoder,T=new Uint8Array(0);function pt(t,a=T){let o=ct.encode(JSON.stringify(t)),i=new Uint8Array(4+o.length+a.length);return new DataView(i.buffer).setUint32(0,o.length),i.set(o,4),i.set(a,4+o.length),i}function ut(t){let o=new DataView(t.buffer,t.byteOffset,t.byteLength).getUint32(0);return{header:JSON.parse(yt.decode(t.subarray(4,4+o))),data:t.subarray(4+o)}}function z(t,a,o=[],i){return pt({m:t,p:a,a:o},i)}function q(t){let{header:a,data:o}=ut(t);return a.ok?{ok:!0,value:a.v,data:o}:{ok:!1,data:T,code:a.c,message:a.e,path:a.p}}function I(t,a,o=T){let i=new Uint8Array(5+o.length);return i[0]=t,new DataView(i.buffer).setUint32(1,a),i.set(o,5),i}function J(t){let a=new DataView(t.buffer,t.byteOffset,t.byteLength);return{type:t[0],id:a.getUint32(1),payload:t.subarray(5)}}function ft(t){let a=t.request?t:t.transport,o=t.capabilities??{streaming:!1,watch:!!a.watch,atomicMove:!1,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0},i=async(p,u,m,w)=>{let l=m?[...m]:[];for(;l.length&&l[l.length-1]===void 0;)l.pop();let y=q(await a.request(z(p,u,l,w)));if(!y.ok)throw new x(y.code,y.message??"",y.path);return y};return{capabilities:()=>o,async read(p,u){return(await i("read",p,[u])).data},async write(p,u,m){await i("write",p,[m],S(u))},async list(p,u){return(await i("list",p,[u])).value},async stat(p){return(await i("stat",p)).value},async exists(p){return(await i("exists",p)).value},async mkdir(p,u){await i("mkdir",p,[u])},async remove(p,u){await i("remove",p,[u])},async move(p,u){await i("move",p,[u])},async copy(p,u){await i("copy",p,[u])},async getMeta(p){return(await i("getMeta",p)).value},async setMeta(p,u){await i("setMeta",p,[u])},watch(p,u){if(!a.watch)throw V("watch");return a.watch(p,u)}}}function lt(t,a){let o=a??((i,p)=>fetch(i,p));return{async request(i){let p=await o(t,{method:"POST",body:i});return new Uint8Array(await p.arrayBuffer())}}}var dt=new TextEncoder,wt=new TextDecoder;function mt(t,a){let o=a?a():new WebSocket(t);try{o.binaryType="arraybuffer"}catch{}let i=new Map,p=new Map,u=0,m=o.readyState===1?Promise.resolve():new Promise(w=>{o.addEventListener?o.addEventListener("open",()=>w()):o.onopen=()=>w()});return o.onmessage=w=>{let{type:l,id:y,payload:n}=J(new Uint8Array(w.data));l===1?(i.get(y)?.(n),i.delete(y)):l===4&&p.get(y)?.(JSON.parse(wt.decode(n)))},{async request(w){await m;let l=++u;return new Promise(y=>{i.set(l,y),o.send(I(0,l,w))})},watch(w,l){let y=++u;return p.set(y,l),m.then(()=>o.send(I(2,y,dt.encode(JSON.stringify({path:w}))))),()=>{p.delete(y),m.then(()=>o.send(I(3,y,T)))}}}}export{B as BRAND,x as VfsError,O as alreadyExists,C as basename,ot as cache,Et as collect,P as concat,W as conflict,A as dirname,st as encrypt,lt as httpTransport,k as io,E as isADirectory,bt as isVfsError,ht as join,at as kv,nt as localStorageKv,rt as memKv,G as memory,f as normalize,U as notADirectory,b as notFound,Z as opfs,vt as permissionDenied,Ot as readStream,ft as remote,R as segments,S as toBytes,xt as toText,V as unsupported,Ut as writeStream,mt as wsTransport};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artemjs/vfskit-front",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Universal VFS abstraction for the browser - in-memory + encryption + a remote client that drives any vfskit backend. Frontend kit, served via jsDelivr.",
5
5
  "keywords": [
6
6
  "vfs",
@@ -52,7 +52,9 @@
52
52
  "@vfskit/transport-http": "*",
53
53
  "@vfskit/transport-ws": "*",
54
54
  "tsup": "^8.3.0",
55
- "@vfskit/cache": "*"
55
+ "@vfskit/cache": "*",
56
+ "@vfskit/kv": "*",
57
+ "@vfskit/opfs": "*"
56
58
  },
57
59
  "publishConfig": {
58
60
  "access": "public"