@artemjs/vfskit-front 1.1.0 → 1.2.1

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.1-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
@@ -1 +1 @@
1
- var h=new TextEncoder,b=new TextDecoder;function w(e){return typeof e=="string"?h.encode(e):e instanceof Uint8Array?e:new Uint8Array(e)}function s(e){return b.decode(e)}function d(e){let t=0;for(let i of e)t+=i.length;let a=new Uint8Array(t),n=0;for(let i of e)a.set(i,n),n+=i.length;return a}var m=Symbol.for("vfskit.VfsError"),f=class extends Error{code;path;[m]=!0;constructor(t,a,n){super(a),this.name="VfsError",this.code=t,this.path=n}};function u(e){return typeof e=="object"&&e!==null&&e[m]===!0}async function y(e,t,a){if(e.readStream)return e.readStream(t,a);let n=await e.read(t,a);return new ReadableStream({start(i){i.enqueue(n),i.close()}})}async function p(e,t,a){if(e.writeStream)return e.writeStream(t,a);let n=[];return new WritableStream({write(i){n.push(i)},async close(){await e.write(t,d(n),a)}})}async function l(e){let t=[],a=e.getReader();for(;;){let{done:n,value:i}=await a.read();if(n)break;i&&t.push(i)}return d(t)}function x(e){throw new Error("conformance: "+e)}function c(e,t="expected truthy"){e||x(t)}function r(e,t,a){e!==t&&x(a??`expected ${JSON.stringify(t)}, got ${JSON.stringify(e)}`)}async function o(e,t){let a;try{await e()}catch(n){a=n}c(u(a)&&a.code===t,`expected error ${t}, got ${u(a)?a.code:a}`)}async function S(e){let t;try{await e()}catch(a){t=a}c(t,"expected throw")}async function E(e,t=2e3){for(let n=0;n<t;n+=10){if(e())return;await new Promise(i=>setTimeout(i,10))}}var g=[{name:"writes and reads a file",async run(e){let t=e();await t.write("/a.txt","hello"),r(s(await t.read("/a.txt")),"hello")}},{name:"reports existence",async run(e){let t=e();r(await t.exists("/a.txt"),!1),await t.write("/a.txt","x"),r(await t.exists("/a.txt"),!0)}},{name:"stats a file",async run(e){let t=e();await t.write("/a.txt","hello");let a=await t.stat("/a.txt");r(a.type,"file"),c(a.size>0)}},{name:"throws NOT_FOUND for missing read",async run(e){let t=e();await o(()=>t.read("/nope"),"NOT_FOUND")}},{name:"lists directory children",async run(e){let t=e();await t.mkdir("/d"),await t.write("/d/a","1"),await t.write("/d/b","2"),r((await t.list("/d")).map(a=>a.name).sort().join(","),"a,b")}},{name:"lists recursively",async run(e){let t=e();await t.mkdir("/d"),await t.mkdir("/d/sub"),await t.write("/d/sub/a","1"),c((await t.list("/d",{recursive:!0})).some(a=>a.path==="/d/sub/a"))}},{name:"removes a file",async run(e){let t=e();await t.write("/a","1"),await t.remove("/a"),r(await t.exists("/a"),!1)}},{name:"requires recursive to remove a non-empty dir",async run(e){let t=e();await t.mkdir("/d"),await t.write("/d/a","1"),await S(()=>t.remove("/d")),await t.remove("/d",{recursive:!0}),r(await t.exists("/d"),!1)}},{name:"moves a file",async run(e){let t=e();await t.write("/a","1"),await t.move("/a","/b"),r(await t.exists("/a"),!1),r(s(await t.read("/b")),"1")}},{name:"copies a file",async run(e){let t=e();await t.write("/a","1"),await t.copy("/a","/b"),r(s(await t.read("/a")),"1"),r(s(await t.read("/b")),"1")}},{name:"stores and reads metadata",async run(e){let t=e();await t.write("/a","1",{meta:{tag:"x"}}),r((await t.getMeta("/a")).tag,"x"),await t.setMeta("/a",{tag:"y"}),r((await t.getMeta("/a")).tag,"y")}},{name:"emits watch events when supported",async run(e){let t=e();if(!t.capabilities().watch)return;let a=[],n=t.watch("/",i=>a.push(i.type+":"+i.path));await new Promise(i=>setTimeout(i,60)),await t.write("/a","1"),await E(()=>a.includes("create:/a")),n(),c(a.includes("create:/a"))}},{name:"moves a directory subtree",async run(e){let t=e();await t.mkdir("/d"),await t.mkdir("/d/sub"),await t.write("/d/sub/a","1"),await t.move("/d","/e"),r(await t.exists("/d"),!1),r(s(await t.read("/e/sub/a")),"1")}},{name:"copies a directory subtree deeply",async run(e){let t=e();await t.mkdir("/d"),await t.write("/d/a","1"),await t.copy("/d","/e"),await t.write("/e/a","2"),r(s(await t.read("/d/a")),"1"),r(s(await t.read("/e/a")),"2")}},{name:"isolates returned read buffers from the store",async run(e){let t=e();await t.write("/a","abc");let a=await t.read("/a");a[0]=0,r(s(await t.read("/a")),"abc")}},{name:"does not confuse sibling prefixes",async run(e){let t=e();await t.mkdir("/d"),await t.mkdir("/dx"),await t.write("/dx/a","1"),c(!(await t.list("/d")).some(a=>a.name==="a"))}},{name:"reports byte size matching written content",async run(e){let t=e();await t.write("/a","hello"),r((await t.stat("/a")).size,5)}},{name:"throws ALREADY_EXISTS creating an existing dir without recursive",async run(e){let t=e();await t.mkdir("/d"),await o(()=>t.mkdir("/d"),"ALREADY_EXISTS")}},{name:"preserves metadata when overwriting content without new meta",async run(e){let t=e();await t.write("/a","1",{meta:{tag:"x"}}),await t.write("/a","2"),r((await t.getMeta("/a")).tag,"x")}},{name:"throws NOT_A_DIRECTORY creating a directory under a file",async run(e){let t=e();await t.write("/f","1"),await o(()=>t.mkdir("/f/sub",{recursive:!0}),"NOT_A_DIRECTORY")}},{name:"throws ALREADY_EXISTS moving or copying onto an existing path",async run(e){let t=e();await t.write("/a","1"),await t.write("/b","2"),await o(()=>t.move("/a","/b"),"ALREADY_EXISTS"),await o(()=>t.copy("/a","/b"),"ALREADY_EXISTS")}},{name:"conditional write succeeds on matching version and CONFLICTs on stale (when capable)",async run(e){let t=e();if(!t.capabilities().conditionalWrite)return;await t.write("/a","1");let a=(await t.stat("/a")).version;c(typeof a=="string"&&a.length>0,"expected a version token"),await t.write("/a","2",{ifMatch:a}),r(s(await t.read("/a")),"2"),await o(()=>t.write("/a","3",{ifMatch:a}),"CONFLICT"),r(s(await t.read("/a")),"2")}},{name:"ifAbsent rejects overwriting an existing file (when capable)",async run(e){let t=e();t.capabilities().conditionalWrite&&(await t.write("/a","1"),await o(()=>t.write("/a","2",{ifAbsent:!0}),"ALREADY_EXISTS"),r(s(await t.read("/a")),"1"))}},{name:"streams content in and back out (helper, native or buffered)",async run(e){let t=e(),n=(await p(t,"/s.bin")).getWriter();await n.write(w("chunk-one;")),await n.write(w("chunk-two")),await n.close(),r(s(await l(await y(t,"/s.bin"))),"chunk-one;chunk-two"),r(s(await t.read("/s.bin")),"chunk-one;chunk-two")}}];function A(e){let t=globalThis;t.describe("vfs conformance",()=>{for(let a of g)t.it(a.name,()=>a.run(e))})}export{g as conformanceCases,A as runConformance};
1
+ var h=new TextEncoder,v=new TextDecoder;function w(a){return typeof a=="string"?h.encode(a):a instanceof Uint8Array?a:new Uint8Array(a)}function o(a){return v.decode(a)}function d(a){let t=0;for(let i of a)t+=i.length;let e=new Uint8Array(t),n=0;for(let i of a)e.set(i,n),n+=i.length;return e}var m=Symbol.for("vfskit.VfsError"),u=class extends Error{code;path;[m]=!0;constructor(t,e,n){super(e),this.name="VfsError",this.code=t,this.path=n}};function f(a){return typeof a=="object"&&a!==null&&a[m]===!0}async function y(a,t,e){if(a.readStream)return a.readStream(t,e);let n=await a.read(t,e);return new ReadableStream({start(i){i.enqueue(n),i.close()}})}async function p(a,t,e){if(a.writeStream)return a.writeStream(t,e);let n=[];return new WritableStream({write(i){n.push(i)},async close(){await a.write(t,d(n),e)}})}async function l(a){let t=[],e=a.getReader();for(;;){let{done:n,value:i}=await e.read();if(n)break;i&&t.push(i)}return d(t)}function x(a){throw new Error("conformance: "+a)}function c(a,t="expected truthy"){a||x(t)}function r(a,t,e){a!==t&&x(e??`expected ${JSON.stringify(t)}, got ${JSON.stringify(a)}`)}async function s(a,t){let e;try{await a()}catch(n){e=n}c(f(e)&&e.code===t,`expected error ${t}, got ${f(e)?e.code:e}`)}async function b(a){let t;try{await a()}catch(e){t=e}c(t,"expected throw")}async function S(a,t=2e3){for(let n=0;n<t;n+=10){if(a())return;await new Promise(i=>setTimeout(i,10))}}var g=[{name:"writes and reads a file",async run(a){let t=a();await t.write("/a.txt","hello"),r(o(await t.read("/a.txt")),"hello")}},{name:"reports existence",async run(a){let t=a();r(await t.exists("/a.txt"),!1),await t.write("/a.txt","x"),r(await t.exists("/a.txt"),!0)}},{name:"stats a file",async run(a){let t=a();await t.write("/a.txt","hello");let e=await t.stat("/a.txt");r(e.type,"file"),c(e.size>0)}},{name:"throws NOT_FOUND for missing read",async run(a){let t=a();await s(()=>t.read("/nope"),"NOT_FOUND")}},{name:"lists directory children",async run(a){let t=a();await t.mkdir("/d"),await t.write("/d/a","1"),await t.write("/d/b","2"),r((await t.list("/d")).map(e=>e.name).sort().join(","),"a,b")}},{name:"lists recursively",async run(a){let t=a();await t.mkdir("/d"),await t.mkdir("/d/sub"),await t.write("/d/sub/a","1"),c((await t.list("/d",{recursive:!0})).some(e=>e.path==="/d/sub/a"))}},{name:"removes a file",async run(a){let t=a();await t.write("/a","1"),await t.remove("/a"),r(await t.exists("/a"),!1)}},{name:"requires recursive to remove a non-empty dir",async run(a){let t=a();await t.mkdir("/d"),await t.write("/d/a","1"),await b(()=>t.remove("/d")),await t.remove("/d",{recursive:!0}),r(await t.exists("/d"),!1)}},{name:"moves a file",async run(a){let t=a();await t.write("/a","1"),await t.move("/a","/b"),r(await t.exists("/a"),!1),r(o(await t.read("/b")),"1")}},{name:"copies a file",async run(a){let t=a();await t.write("/a","1"),await t.copy("/a","/b"),r(o(await t.read("/a")),"1"),r(o(await t.read("/b")),"1")}},{name:"stores and reads metadata",async run(a){let t=a();await t.write("/a","1",{meta:{tag:"x"}}),r((await t.getMeta("/a")).tag,"x"),await t.setMeta("/a",{tag:"y"}),r((await t.getMeta("/a")).tag,"y")}},{name:"emits watch events when supported",async run(a){let t=a();if(!t.capabilities().watch)return;let e=[],n=t.watch("/",i=>e.push(i.type+":"+i.path));await new Promise(i=>setTimeout(i,60)),await t.write("/a","1"),await S(()=>e.includes("create:/a")),n(),c(e.includes("create:/a"))}},{name:"moves a directory subtree",async run(a){let t=a();await t.mkdir("/d"),await t.mkdir("/d/sub"),await t.write("/d/sub/a","1"),await t.move("/d","/e"),r(await t.exists("/d"),!1),r(o(await t.read("/e/sub/a")),"1")}},{name:"copies a directory subtree deeply",async run(a){let t=a();await t.mkdir("/d"),await t.write("/d/a","1"),await t.copy("/d","/e"),await t.write("/e/a","2"),r(o(await t.read("/d/a")),"1"),r(o(await t.read("/e/a")),"2")}},{name:"isolates returned read buffers from the store",async run(a){let t=a();await t.write("/a","abc");let e=await t.read("/a");e[0]=0,r(o(await t.read("/a")),"abc")}},{name:"does not confuse sibling prefixes",async run(a){let t=a();await t.mkdir("/d"),await t.mkdir("/dx"),await t.write("/dx/a","1"),c(!(await t.list("/d")).some(e=>e.name==="a"))}},{name:"reports byte size matching written content",async run(a){let t=a();await t.write("/a","hello"),r((await t.stat("/a")).size,5)}},{name:"throws ALREADY_EXISTS creating an existing dir without recursive",async run(a){let t=a();await t.mkdir("/d"),await s(()=>t.mkdir("/d"),"ALREADY_EXISTS")}},{name:"preserves metadata when overwriting content without new meta",async run(a){let t=a();await t.write("/a","1",{meta:{tag:"x"}}),await t.write("/a","2"),r((await t.getMeta("/a")).tag,"x")}},{name:"throws NOT_A_DIRECTORY creating a directory under a file",async run(a){let t=a();await t.write("/f","1"),await s(()=>t.mkdir("/f/sub",{recursive:!0}),"NOT_A_DIRECTORY")}},{name:"throws ALREADY_EXISTS moving or copying onto an existing path",async run(a){let t=a();await t.write("/a","1"),await t.write("/b","2"),await s(()=>t.move("/a","/b"),"ALREADY_EXISTS"),await s(()=>t.copy("/a","/b"),"ALREADY_EXISTS")}},{name:"conditional write succeeds on matching version and CONFLICTs on stale (when capable)",async run(a){let t=a();if(!t.capabilities().conditionalWrite)return;await t.write("/a","1");let e=(await t.stat("/a")).version;c(typeof e=="string"&&e.length>0,"expected a version token"),await t.write("/a","2",{ifMatch:e}),r(o(await t.read("/a")),"2"),await s(()=>t.write("/a","3",{ifMatch:e}),"CONFLICT"),r(o(await t.read("/a")),"2")}},{name:"ifAbsent rejects overwriting an existing file (when capable)",async run(a){let t=a();t.capabilities().conditionalWrite&&(await t.write("/a","1"),await s(()=>t.write("/a","2",{ifAbsent:!0}),"ALREADY_EXISTS"),r(o(await t.read("/a")),"1"))}},{name:"rejects removing the root",async run(a){let t=a();await s(()=>t.remove("/"),"IO"),await s(()=>t.remove("/",{recursive:!0}),"IO")}},{name:"mkdir over an existing file throws ALREADY_EXISTS",async run(a){let t=a();await t.write("/f","1"),await s(()=>t.mkdir("/f"),"ALREADY_EXISTS"),await s(()=>t.mkdir("/f",{recursive:!0}),"ALREADY_EXISTS")}},{name:"ifMatch on a missing file always conflicts (when capable)",async run(a){let t=a();t.capabilities().conditionalWrite&&(await s(()=>t.write("/a","x",{ifMatch:"0"}),"CONFLICT"),await s(()=>t.write("/a","x",{ifMatch:""}),"CONFLICT"))}},{name:"setMeta assigns a new version (when capable)",async run(a){let t=a();if(!t.capabilities().conditionalWrite)return;await t.write("/a","1");let e=(await t.stat("/a")).version;await t.setMeta("/a",{x:1});let n=(await t.stat("/a")).version;c(typeof n=="string"&&n.length>0&&n!==e,"expected a new version after setMeta")}},{name:"copy assigns a new version to the copy (when capable)",async run(a){let t=a();if(!t.capabilities().conditionalWrite)return;await t.write("/a","1");let e=(await t.stat("/a")).version;await t.copy("/a","/b");let n=(await t.stat("/b")).version;c(typeof n=="string"&&n.length>0&&n!==e,"expected a new version on copy")}},{name:"rejects copy or move into its own subtree",async run(a){let t=a();await t.mkdir("/d"),await t.write("/d/a","1"),await s(()=>t.copy("/d","/d/x"),"IO"),await s(()=>t.move("/d","/d/x"),"IO")}},{name:"throws NOT_A_DIRECTORY copying under a file",async run(a){let t=a();await t.write("/f","1"),await t.write("/src","2"),await s(()=>t.copy("/src","/f/x"),"NOT_A_DIRECTORY")}},{name:"changes the version token on move (when capable)",async run(a){let t=a();if(!t.capabilities().conditionalWrite)return;await t.write("/a","1");let e=(await t.stat("/a")).version;await t.move("/a","/b");let n=(await t.stat("/b")).version;c(typeof n=="string"&&n.length>0&&n!==e,"expected a new version after move")}},{name:"streams content in and back out (helper, native or buffered)",async run(a){let t=a(),n=(await p(t,"/s.bin")).getWriter();await n.write(w("chunk-one;")),await n.write(w("chunk-two")),await n.close(),r(o(await l(await y(t,"/s.bin"))),"chunk-one;chunk-two"),r(o(await t.read("/s.bin")),"chunk-one;chunk-two")}}];function E(a){let t=globalThis;t.describe("vfs conformance",()=>{for(let e of g)t.it(e.name,()=>e.run(a))})}export{g as conformanceCases,E as runConformance};
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 u(e){let a=[];for(let o of e.split("/"))if(!(o===""||o===".")){if(o===".."){a.pop();continue}a.push(o)}return"/"+a.join("/")}function gt(...e){return u(e.join("/"))}function b(e){let a=u(e),o=a.lastIndexOf("/");return o<=0?"/":a.slice(0,o)}function R(e){let a=u(e);return a.slice(a.lastIndexOf("/")+1)}function P(e){return u(e).split("/").filter(Boolean)}var K=Symbol.for("vfskit.VfsError"),x=class extends Error{code;path;[K]=!0;constructor(a,o,s){super(o),this.name="VfsError",this.code=a,this.path=s}},k=e=>new x("NOT_FOUND",`not found: ${e}`,e),U=e=>new x("ALREADY_EXISTS",`already exists: ${e}`,e),S=e=>new x("NOT_A_DIRECTORY",`not a directory: ${e}`,e),E=e=>new x("IS_A_DIRECTORY",`is a directory: ${e}`,e),bt=e=>new x("PERMISSION_DENIED",`permission denied: ${e}`,e),L=e=>new x("UNSUPPORTED",`unsupported: ${e}`),N=e=>new x("CONFLICT",`version conflict: ${e}`,e),v=(e,a)=>new x("IO",e,a);function kt(e){return typeof e=="object"&&e!==null&&e[K]===!0}var j=new TextEncoder,$=new TextDecoder;function A(e){return typeof e=="string"?j.encode(e):e instanceof Uint8Array?e:new Uint8Array(e)}function St(e){return $.decode(e)}function F(e){let a=0;for(let p of e)a+=p.length;let o=new Uint8Array(a),s=0;for(let p of e)o.set(p,s),s+=p.length;return o}async function Ut(e,a,o){if(e.readStream)return e.readStream(a,o);let s=await e.read(a,o);return new ReadableStream({start(p){p.enqueue(s),p.close()}})}async function Et(e,a,o){if(e.writeStream)return e.writeStream(a,o);let s=[];return new WritableStream({write(p){s.push(p)},async close(){await e.write(a,F(s),o)}})}async function Mt(e){let a=[],o=e.getReader();for(;;){let{done:s,value:p}=await o.read();if(s)break;p&&a.push(p)}return F(a)}var G={streaming:!1,watch:!0,atomicMove:!0,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0};function X(){let e=()=>Date.now(),a=0,o=()=>String(++a),s=new Map([["/",{type:"dir",meta:{},mtime:e(),ctime:e()}]]),p=new Set,f=(y,n)=>n===y||n.startsWith(y==="/"?"/":y+"/"),m=(y,n)=>{for(let t of p)f(t.base,n)&&t.cb({type:y,path:n})},w=y=>{let n=s.get(y);if(!n)throw k(y);return n},d=y=>{let n=b(y),t=s.get(n);if(!t)throw k(n);if(t.type!=="dir")throw S(n)};return{capabilities:()=>G,async read(y){let n=u(y),t=w(n);if(t.type==="dir")throw E(n);return t.data.slice()},async write(y,n,t){let r=u(y);d(r);let i=s.get(r);if(i&&i.type==="dir")throw E(r);if(t?.ifAbsent&&i)throw U(r);if(t?.ifMatch!==void 0&&i?.version!==t.ifMatch)throw N(r);let c=i?i.ctime:e();s.set(r,{type:"file",data:A(n).slice(),meta:t?.meta?{...t.meta}:i?.meta??{},ctime:c,mtime:e(),version:o()}),m(i?"update":"create",r)},async list(y,n){let t=u(y);if(w(t).type!=="dir")throw S(t);let i=[];for(let[c,l]of s)c===t||!f(t,c)||!n?.recursive&&b(c)!==t||i.push({name:c.slice(c.lastIndexOf("/")+1),path:c,type:l.type});return i},async stat(y){let n=u(y),t=w(n);return{type:t.type,size:t.type==="file"?t.data.length:0,mtime:t.mtime,ctime:t.ctime,meta:{...t.meta},version:t.type==="file"?t.version:void 0}},async exists(y){return s.has(u(y))},async mkdir(y,n){let t=u(y),r=s.get(t);if(r){if(n?.recursive&&r.type==="dir")return;throw U(t)}if(n?.recursive){let i="";for(let c of t.split("/").filter(Boolean)){i+="/"+c;let l=s.get(i);if(l){if(l.type!=="dir")throw S(i);continue}s.set(i,{type:"dir",meta:{},mtime:e(),ctime:e()}),m("create",i)}return}d(t),s.set(t,{type:"dir",meta:{},mtime:e(),ctime:e()}),m("create",t)},async remove(y,n){let t=u(y);if(t==="/")throw v("cannot remove root",t);w(t);let r=[...s.keys()].filter(i=>i!==t&&f(t,i));if(r.length&&!n?.recursive)throw v("directory not empty",t);for(let i of[...r,t])s.delete(i),m("remove",i)},async move(y,n){let t=u(y),r=u(n);if(w(t),d(r),s.has(r))throw U(r);if(f(t,r))throw v("cannot move into itself",r);for(let i of[...s.keys()].filter(c=>c===t||f(t,c))){let c=s.get(i);s.delete(i),c.type==="file"&&(c.version=o()),s.set(r+i.slice(t.length),c)}m("remove",t),m("create",r)},async copy(y,n){let t=u(y),r=u(n);if(w(t),d(r),s.has(r))throw U(r);if(f(t,r))throw v("cannot copy into itself",r);for(let i of[...s.keys()].filter(c=>c===t||f(t,c))){let c=s.get(i);s.set(r+i.slice(t.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(u(y)).meta}},async setMeta(y,n){let t=w(u(y));t.meta={...n},t.mtime=e(),t.type==="file"&&(t.version=o())},watch(y,n){let t={base:u(y),cb:n};return p.add(t),()=>{p.delete(t)}}}}var Q=e=>e.values(),Z={streaming:!0,watch:!1,atomicMove:!1,nativeMeta:!1,randomAccess:!1,conditionalWrite:!1};function D(e,a){let o=a.name;throw o==="NotFoundError"?k(e):o==="TypeMismatchError"?S(e):o==="InvalidModificationError"?v("not empty",e):v(String(a?.message??a),e)}function tt(e){let a=e?Promise.resolve(e):navigator.storage.getDirectory(),o=n=>n==="/.vfskit"||n.startsWith("/.vfskit/"),s=async(n,t)=>{let r=await a;for(let i of n)r=await r.getDirectoryHandle(i,{create:t});return r},p=n=>s(P(b(n)),!1),f=async n=>{if(u(n)==="/")return"dir";let t=await p(n).catch(()=>null);if(!t)return null;let r=R(n);try{return await t.getFileHandle(r),"file"}catch{}try{return await t.getDirectoryHandle(r),"dir"}catch{return null}},m=async()=>{try{let t=await(await(await s([".vfskit"],!1)).getFileHandle("meta.json")).getFile();return JSON.parse(await t.text())}catch{return{}}},w=async n=>{let i=(await(await(await s([".vfskit"],!0)).getFileHandle("meta.json",{create:!0})).createWritable()).getWriter();await i.write(A(JSON.stringify(n))),await i.close()},d=async n=>{let t=await m();n(t)&&await w(t)},y=async(n,t)=>(await p(n)).getFileHandle(R(n),{create:t});return{capabilities:()=>Z,async read(n){let t=u(n);if(t==="/")throw E(t);try{return new Uint8Array(await(await(await y(t,!1)).getFile()).arrayBuffer())}catch(r){if(await f(t)==="dir")throw E(t);D(t,r)}},async write(n,t,r){let i=u(n);if(await f(i)==="dir")throw E(i);try{let l=(await(await y(i,!0)).createWritable()).getWriter();await l.write(A(t)),await l.close()}catch(c){D(b(i),c)}r?.meta&&await d(c=>(c[i]=r.meta,!0))},async list(n,t){let r=u(n),i=[],c=async l=>{let h=await s(P(l),!1);for await(let g of Q(h)){let O=u(l+"/"+g.name);if(o(O))continue;let M=g.kind==="directory"?"dir":"file";i.push({name:g.name,path:O,type:M}),t?.recursive&&M==="dir"&&await c(O)}};try{await c(r)}catch(l){if(await f(r)==="file")throw S(r);D(r,l)}return i},async stat(n){let t=u(n),r=await f(t);if(r===null)throw k(t);let i=0;return r==="file"&&(i=(await(await y(t,!1)).getFile()).size),{type:r,size:i,mtime:0,ctime:0,meta:(await m())[t]??{}}},async exists(n){return await f(n)!==null},async mkdir(n,t){let r=u(n),i=await f(r);if(i!==null){if(t?.recursive&&i==="dir")return;throw U(r)}if(t?.recursive){let c="";for(let l of P(r)){c+="/"+l;let h=await f(c);if(h==="file")throw S(c);h===null&&await s(P(c),!0)}return}try{await(await p(r)).getDirectoryHandle(R(r),{create:!0})}catch(c){D(b(r),c)}},async remove(n,t){let r=u(n);if(r==="/")throw v("cannot remove root",r);if(await f(r)===null)throw k(r);let c=await p(r);try{await c.removeEntry(R(r),{recursive:!!t?.recursive})}catch(l){D(r,l)}await d(l=>{let h=!1;for(let g of Object.keys(l))(g===r||g.startsWith(r+"/"))&&(delete l[g],h=!0);return h})},async move(n,t){await this.copy(n,t),await this.remove(n,{recursive:!0})},async copy(n,t){let r=u(n),i=u(t);if(await f(r)===null)throw k(r);if(await f(i)!==null)throw U(i);if(i===r||i.startsWith(r+"/"))throw v("cannot copy into itself",i);let l=await f(b(i));if(l===null)throw k(b(i));if(l!=="dir")throw S(b(i));let h=await m(),g=!1,O=async(M,C)=>{if(await f(M)==="file")await this.write(C,await this.read(M)),h[M]&&(h[C]=h[M],g=!0);else{await this.mkdir(C),h[M]&&(h[C]=h[M],g=!0);for(let B of await this.list(M))await O(B.path,C+"/"+B.name)}};await O(r,i),g&&await w(h)},async getMeta(n){let t=u(n);if(await f(t)===null)throw k(t);return(await m())[t]??{}},async setMeta(n,t){let r=u(n);if(await f(r)===null)throw k(r);await d(i=>(i[r]=t,!0))},async readStream(n){let t=u(n);try{return(await(await y(t,!1)).getFile()).stream()}catch(r){if(await f(t)==="dir")throw E(t);D(t,r)}},async writeStream(n){let t=u(n);if(await f(t)==="dir")throw E(t);try{return await(await y(t,!0)).createWritable()}catch(r){D(b(t),r)}},watch(){return()=>{}}}}var et={streaming:!1,watch:!1,atomicMove:!1,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0};function rt(e){let a="";for(let o=0;o<e.length;o+=32768)a+=String.fromCharCode(...e.subarray(o,o+32768));return btoa(a)}function H(e){let a=atob(e),o=new Uint8Array(a.length);for(let s=0;s<a.length;s++)o[s]=a.charCodeAt(s);return o}function nt(){let e=new Map;return{async get(a){return e.get(a)},async set(a,o){e.set(a,o)},async delete(a){e.delete(a)},async list(a){return[...e.keys()].filter(o=>o.startsWith(a))}}}function at(e=globalThis.localStorage){return{async get(a){return e.getItem(a)},async set(a,o){e.setItem(a,o)},async delete(a){e.removeItem(a)},async list(a){let o=[];for(let s=0;s<e.length;s++){let p=e.key(s);p&&p.startsWith(a)&&o.push(p)}return o}}}function it(e){let a=e.store,o=(e.prefix?e.prefix+":":"")+"vfs:",s=r=>o+u(r),p=r=>r.slice(o.length),f=(r,i)=>i===r||i.startsWith(r==="/"?"/":r+"/"),m=async r=>{let i=await a.get(s(r));return i?JSON.parse(i):void 0},w=(r,i)=>a.set(s(r),JSON.stringify(i)),d=async r=>{let i=await m(r);if(!i)throw k(r);return i},y=async r=>(await a.list(s(r)===o+"/"?o+"/":s(r)+"/")).map(p).filter(i=>i!==r&&f(r,i)),n=async r=>{let i=b(r),c=await m(i);if(!c)throw k(i);if(c.t!=="dir")throw S(i)},t=(async()=>{await a.get(s("/"))||await w("/",{t:"dir",m:{},v:0})})();return{capabilities:()=>et,async read(r){await t;let i=u(r),c=await d(i);if(c.t==="dir")throw E(i);return H(c.d??"")},async write(r,i,c){await t;let l=u(r);await n(l);let h=await m(l);if(h?.t==="dir")throw E(l);if(c?.ifAbsent&&h)throw U(l);if(c?.ifMatch!==void 0&&(h?String(h.v):void 0)!==c.ifMatch)throw N(l);await w(l,{t:"file",d:rt(A(i)),m:c?.meta??h?.m??{},v:(h?.v??0)+1})},async list(r,i){await t;let c=u(r);if((await d(c)).t!=="dir")throw S(c);let h=[];for(let g of await y(c)){if(!i?.recursive&&b(g)!==c)continue;let O=await m(g);O&&h.push({name:g.slice(g.lastIndexOf("/")+1),path:g,type:O.t})}return h},async stat(r){await t;let i=u(r),c=await d(i);return{type:c.t,size:c.t==="file"?H(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 t,!!await m(u(r))},async mkdir(r,i){await t;let c=u(r),l=await m(c);if(l){if(i?.recursive&&l.t==="dir")return;throw U(c)}if(i?.recursive){let h="";for(let g of c.split("/").filter(Boolean)){h+="/"+g;let O=await m(h);if(O?.t==="file")throw S(h);O||await w(h,{t:"dir",m:{},v:0})}return}await n(c),await w(c,{t:"dir",m:{},v:0})},async remove(r,i){await t;let c=u(r);if(c==="/")throw v("cannot remove root",c);await d(c);let l=await y(c);if(l.length&&!i?.recursive)throw v("directory not empty",c);for(let h of[...l,c])await a.delete(s(h))},async move(r,i){await this.copy(r,i),await this.remove(r,{recursive:!0})},async copy(r,i){await t;let c=u(r),l=u(i);if(await d(c),await n(l),await m(l))throw U(l);if(f(c,l))throw v("cannot copy into itself",l);for(let h of[c,...await y(c)]){let g=await m(h);g&&await w(l+h.slice(c.length),{...g,v:(g.v??0)+1})}},async getMeta(r){return await t,{...(await d(u(r))).m}},async setMeta(r,i){await t;let c=u(r),l=await d(c);await w(c,{...l,m:{...i},v:l.t==="file"?(l.v??0)+1:l.v})},watch(){return()=>{}}}}var I=new Uint8Array([86,75,1]),_=47,T=globalThis.crypto.subtle;function z(e){let a=new Uint8Array(e);return globalThis.crypto.getRandomValues(a),a}async function st(e,a){let o=await T.importKey("raw",new TextEncoder().encode(e),"PBKDF2",!1,["deriveKey"]);return T.deriveKey({name:"PBKDF2",salt:a.slice(),iterations:21e4,hash:"SHA-256"},o,{name:"AES-GCM",length:256},!1,["encrypt","decrypt"])}function ot(e,a){if(!a.key&&!a.passphrase)throw new Error("encrypt: key or passphrase required");let o=a.key?T.importKey("raw",a.key.slice(),"AES-GCM",!1,["encrypt","decrypt"]):null,s=f=>o??st(a.passphrase,f),p={...e.capabilities(),streaming:!1,randomAccess:!1};return{...e,readStream:void 0,writeStream:void 0,capabilities:()=>p,async stat(f){let m=await e.stat(f);return m.type==="file"?{...m,size:Math.max(0,m.size-_)}:m},async write(f,m,w){let d=z(16),y=z(12),n=await s(d),t=new Uint8Array(await T.encrypt({name:"AES-GCM",iv:y},n,A(m).slice()));await e.write(f,F([I,d,y,t]),w)},async read(f,m){let w=await e.read(f,m);if(w.length<_||w[0]!==I[0]||w[1]!==I[1]||w[2]!==I[2])throw v("invalid ciphertext",f);let d=await s(w.slice(3,19)),y;try{y=await T.decrypt({name:"AES-GCM",iv:w.slice(19,31)},d,w.slice(31))}catch{throw v("decryption failed",f)}return new Uint8Array(y)}}}function ct(e,a={}){let o=a.store??new Map,s=a.ttlMs??0,p=()=>s?Date.now():0,f=()=>s?Date.now()+s:1/0,m=(d,y)=>y===d||y.startsWith(d==="/"?"/":d+"/"),w=d=>{for(let y of[...o.keys()])m(d,y)&&o.delete(y)};return{...e,readStream:void 0,writeStream:void 0,async read(d,y){let n=u(d),t=o.get(n);if(t&&t.exp>p())return t.data.slice();let r=await e.read(n,y);return o.set(n,{data:r.slice(),exp:f()}),r.slice()},async write(d,y,n){let t=u(d),r=A(y).slice();await e.write(t,r,n),o.set(t,{data:r,exp:f()})},async remove(d,y){let n=u(d);await e.remove(n,y),w(n)},async move(d,y){let n=u(d),t=u(y);await e.move(n,t),w(n),w(t)},async copy(d,y){let n=u(d),t=u(y);await e.copy(n,t),w(t)}}}var yt=new TextEncoder,pt=new TextDecoder,W=new Uint8Array(0);function ft(e,a=W){let o=yt.encode(JSON.stringify(e)),s=new Uint8Array(4+o.length+a.length);return new DataView(s.buffer).setUint32(0,o.length),s.set(o,4),s.set(a,4+o.length),s}function ut(e){let o=new DataView(e.buffer,e.byteOffset,e.byteLength).getUint32(0);return{header:JSON.parse(pt.decode(e.subarray(4,4+o))),data:e.subarray(4+o)}}function q(e,a,o=[],s){return ft({m:e,p:a,a:o},s)}function J(e){let{header:a,data:o}=ut(e);return a.ok?{ok:!0,value:a.v,data:o}:{ok:!1,data:W,code:a.c,message:a.e,path:a.p}}function V(e,a,o=W){let s=new Uint8Array(5+o.length);return s[0]=e,new DataView(s.buffer).setUint32(1,a),s.set(o,5),s}function Y(e){let a=new DataView(e.buffer,e.byteOffset,e.byteLength);return{type:e[0],id:a.getUint32(1),payload:e.subarray(5)}}function dt(e){let a=e.request?e:e.transport,o=e.capabilities??{streaming:!1,watch:!!a.watch,atomicMove:!1,nativeMeta:!0,randomAccess:!1,conditionalWrite:!0},s=async(p,f,m,w)=>{let d=m?[...m]:[];for(;d.length&&d[d.length-1]===void 0;)d.pop();let y=J(await a.request(q(p,f,d,w)));if(!y.ok)throw new x(y.code,y.message??"",y.path);return y};return{capabilities:()=>o,async read(p,f){return(await s("read",p,[f])).data},async write(p,f,m){await s("write",p,[m],A(f))},async list(p,f){return(await s("list",p,[f])).value},async stat(p){return(await s("stat",p)).value},async exists(p){return(await s("exists",p)).value},async mkdir(p,f){await s("mkdir",p,[f])},async remove(p,f){await s("remove",p,[f])},async move(p,f){await s("move",p,[f])},async copy(p,f){await s("copy",p,[f])},async getMeta(p){return(await s("getMeta",p)).value},async setMeta(p,f){await s("setMeta",p,[f])},watch(p,f){if(!a.watch)throw L("watch");return a.watch(p,f)}}}function lt(e,a){let o=a??((s,p)=>fetch(s,p));return{async request(s){let p=await o(e,{method:"POST",body:s});return new Uint8Array(await p.arrayBuffer())}}}var wt=new TextEncoder,mt=new TextDecoder;function ht(e,a){let o=a?a():new WebSocket(e);try{o.binaryType="arraybuffer"}catch{}let s=new Map,p=new Map,f=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:d,id:y,payload:n}=Y(new Uint8Array(w.data));d===1?(s.get(y)?.(n),s.delete(y)):d===4&&p.get(y)?.(JSON.parse(mt.decode(n)))},{async request(w){await m;let d=++f;return new Promise(y=>{s.set(d,y),o.send(V(0,d,w))})},watch(w,d){let y=++f;return p.set(y,d),m.then(()=>o.send(V(2,y,wt.encode(JSON.stringify({path:w}))))),()=>{p.delete(y),m.then(()=>o.send(V(3,y,W)))}}}}export{K as BRAND,x as VfsError,U as alreadyExists,R as basename,ct as cache,Mt as collect,F as concat,N as conflict,b as dirname,ot as encrypt,lt as httpTransport,v as io,E as isADirectory,kt as isVfsError,gt as join,it as kv,at as localStorageKv,nt as memKv,X as memory,u as normalize,S as notADirectory,k as notFound,tt as opfs,bt as permissionDenied,Ut as readStream,dt as remote,P as segments,A as toBytes,St as toText,L as unsupported,Et as writeStream,ht 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.1",
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"