char/avril
(wip) why'd you have to go and make things so complicaaaaated?
git clone https://git.t4t.associates/char/avril
791f952
main
Filesystem I/O
Filesystem APIs come in asynchronous and synchronous modules:
| Async | Sync | |
|---|---|---|
| Primitives | avril:std | avril:std/sync |
| Convenience operations | avril:fs | avril:fs/sync |
The sync modules use the same function names, without a Sync suffix. They
return values directly and throw errors synchronously. No CLI flag is needed.
import { readTextFile } from "avril:fs/sync" ; const config = JSON . parse ( readTextFile ( "config.json" ));
When mixing modules, alias imports as needed:
import { readTextFile } from "avril:fs" ; import { readTextFile as readTextFileSync } from "avril:fs/sync" ;
For lexical path manipulation without filesystem access (joining,
normalization, parsing, file: URL conversion), see avril:path.
Synchronous primitives
avril:std/sync exports:
open,create,stat,lstat,readDirmkdir,remove,rename,copyFilesymlink,readLink,realPath,linkchmod,chown,lchown,utime,lutime,truncate- The same
FileHandle,SeekMode, anderrorsobjects asavril:std.
Options, paths (strings or file URLs), metadata, errors, and platform
limitations match the async APIs. readDir returns a synchronous iterable;
use for ... of. mkdir creates one directory; use mkdirs for parents.
Watchers remain in avril:std and are asynchronous.
Handles
Both versions of open and create return the same FileHandle, with async
methods and synchronous counterparts:
readSync,writeSync,seekSync,statSync,truncateSyncsyncSync,syncDataSyncfchmodSync,fchownSync,futimeSync,flockSync,funlockSync
close() and [Symbol.dispose]() are already synchronous. A handle retains
its readable/writable Web Streams and can be lent to subprocesses regardless
of how it was opened.
import { open , SeekMode } from "avril:std/sync" ; const file = open ( "data.bin" , { read :true , write :true }); try { const header = new Uint8Array ( 16 ); const count = file . readSync ( header ); file . seekSync ( 0 , SeekMode . Start ); await file . write ( new Uint8Array ([ 1 , 2 , 3 ])); } finally { file . close (); }
Direct reads and writes may be partial. Reads return null at EOF, or 0
for an empty buffer. Use the convenience functions for whole-file transfers.
Opening non-regular files is not supported.
writev (and writevSync) writes an array of views as one ordered operation:
the byte sequence equals the concatenation of its views, empty views are
skipped, and unlike write the call completes the entire vector before
resolving with the total bytes written. No other operation on the handle can
interleave between its batches, and an error may leave a prefix written
without rolling it back.
The whole vector is validated and snapshotted at invocation, so mutating the array or its views afterwards cannot affect any submitted bytes. An empty or all-empty vector returns 0, still performing the usual closed and authority checks. Snapshots retain the entire payload until completion; await writes rather than queueing unbounded batches.
Sync and async handle operations share the native queue and file cursor. A sync operation waits behind already-submitted native operations, but does not run JavaScript callbacks or settle their JS promises while waiting. Web Stream writes can defer submission until a later JS turn; await stream writes before mixing them with direct handle operations.
Advisory lock waits release the handle queue, as with async flock. Locks are
exclusive by default. A synchronous lock wait blocks its isolate: a timer or
promise callback in that same isolate cannot release the conflicting lock.
Another isolate or process must do so. Terminating a worker interrupts its
synchronous lock wait.
Convenience operations
avril:fs/sync exports readFile, readTextFile, writeFile,
writeTextFile, mkdirs, makeTempDir, makeTempFile, copy, move,
and exists.
These follow the async module's behavior:
- Whole-file operations handle partial reads and writes and close their handles.
- Text is UTF-8. Writes support
append,create,createNew, andmode. mkdirscreates missing parents and accepts existing directories.copyhandles trees and preserves symlinks by default;{ dereference: true }follows them and rejects ancestor cycles. Ordinary mode bits are preserved.movetries rename, then copies and removes on cross-device moves. That fallback is not atomic.existsreturns false only for missing paths; permission errors still throw.makeTempDirandmakeTempFilecreate a uniquely-named empty directory (0o700) or file (0o600) from{ dir, prefix, suffix }, defaulting to the OS temp directory and empty affixes. Names areprefixplus random hex plussuffix; collisions retry. Missing parents are an error, and the caller removes the result. Prefixes and suffixes reject control characters and<>:"/\\|?*.
Authority and scope
Both modes use the same capability checks, including live pledge()
restrictions on already-open handles and symlink-target checks. The sync
modules do not grant additional authority.
These are avril filesystem APIs, not a Deno facade. Blocking standard-stream
interfaces are also available from avril:std/sync; see standard I/O.
There are no synchronous socket APIs or globalThis.Deno.
1# Filesystem I/O 2 3Filesystem APIs come in asynchronous and synchronous modules: 4 5| | Async | Sync | 6|---|---|---| 7| Primitives | `avril:std` | `avril:std/sync` | 8| Convenience operations | `avril:fs` | `avril:fs/sync` | 9 10The sync modules use the same function names, without a `Sync` suffix. They 11return values directly and throw errors synchronously. No CLI flag is needed. 12 13``` ts 14import { readTextFile } from "avril:fs/sync"; 15 16const config = JSON.parse(readTextFile("config.json")); 17``` 18 19When mixing modules, alias imports as needed: 20 21``` ts 22import { readTextFile } from "avril:fs"; 23import { readTextFile as readTextFileSync } from "avril:fs/sync"; 24``` 25 26For lexical path manipulation without filesystem access (joining, 27normalization, parsing, `file:` URL conversion), see [`avril:path`](path.md). 28 29## Synchronous primitives 30 31`avril:std/sync` exports: 32 33- `open`, `create`, `stat`, `lstat`, `readDir` 34- `mkdir`, `remove`, `rename`, `copyFile` 35- `symlink`, `readLink`, `realPath`, `link` 36- `chmod`, `chown`, `lchown`, `utime`, `lutime`, `truncate` 37- The same `FileHandle`, `SeekMode`, and `errors` objects as `avril:std`. 38 39Options, paths (strings or file URLs), metadata, errors, and platform 40limitations match the async APIs. `readDir` returns a synchronous iterable; 41use `for ... of`. `mkdir` creates one directory; use `mkdirs` for parents. 42Watchers remain in `avril:std` and are asynchronous. 43 44## Handles 45 46Both versions of `open` and `create` return the same `FileHandle`, with async 47methods and synchronous counterparts: 48 49- `readSync`, `writeSync`, `seekSync`, `statSync`, `truncateSync` 50- `syncSync`, `syncDataSync` 51- `fchmodSync`, `fchownSync`, `futimeSync`, `flockSync`, `funlockSync` 52 53`close()` and `[Symbol.dispose]()` are already synchronous. A handle retains 54its readable/writable Web Streams and can be lent to subprocesses regardless 55of how it was opened. 56 57``` ts 58import { open, SeekMode } from "avril:std/sync"; 59 60const file = open("data.bin", { read: true, write: true }); 61try { 62const header = new Uint8Array(16); 63const count = file.readSync(header); 64file.seekSync(0, SeekMode.Start); 65await file.write(new Uint8Array([1, 2, 3])); 66} finally { 67file.close(); 68} 69``` 70 71Direct reads and writes may be partial. Reads return `null` at EOF, or `0` 72for an empty buffer. Use the convenience functions for whole-file transfers. 73Opening non-regular files is not supported. 74 75`writev` (and `writevSync`) writes an array of views as one ordered operation: 76the byte sequence equals the concatenation of its views, empty views are 77skipped, and unlike `write` the call completes the entire vector before 78resolving with the total bytes written. No other operation on the handle can 79interleave between its batches, and an error may leave a prefix written 80without rolling it back. 81 82The whole vector is validated and snapshotted at invocation, so mutating the 83array or its views afterwards cannot affect any submitted bytes. An empty or 84all-empty vector returns 0, still performing the usual closed and authority 85checks. Snapshots retain the entire payload until completion; await writes 86rather than queueing unbounded batches. 87 88Sync and async handle operations share the native queue and file cursor. A 89sync operation waits behind already-submitted native operations, but does not 90run JavaScript callbacks or settle their JS promises while waiting. Web Stream 91writes can defer submission until a later JS turn; await stream writes before 92mixing them with direct handle operations. 93 94Advisory lock waits release the handle queue, as with async `flock`. Locks are 95exclusive by default. A synchronous lock wait blocks its isolate: a timer or 96promise callback in that same isolate cannot release the conflicting lock. 97Another isolate or process must do so. Terminating a worker interrupts its 98synchronous lock wait. 99 100## Convenience operations 101 102`avril:fs/sync` exports `readFile`, `readTextFile`, `writeFile`, 103`writeTextFile`, `mkdirs`, `makeTempDir`, `makeTempFile`, `copy`, `move`, 104and `exists`. 105 106These follow the async module's behavior: 107 108- Whole-file operations handle partial reads and writes and close their handles. 109- Text is UTF-8. Writes support `append`, `create`, `createNew`, and `mode`. 110- `mkdirs` creates missing parents and accepts existing directories. 111- `copy` handles trees and preserves symlinks by default; `{ dereference: true }` 112follows them and rejects ancestor cycles. Ordinary mode bits are preserved. 113- `move` tries rename, then copies and removes on cross-device moves. That 114fallback is not atomic. 115- `exists` returns false only for missing paths; permission errors still throw. 116- `makeTempDir` and `makeTempFile` create a uniquely-named empty directory 117(`0o700`) or file (`0o600`) from `{ dir, prefix, suffix }`, defaulting to 118the OS temp directory and empty affixes. Names are `prefix` plus random hex 119plus `suffix`; collisions retry. Missing parents are an error, and the 120caller removes the result. Prefixes and suffixes reject control characters 121and `<>:"/\\|?*`. 122 123## Authority and scope 124 125Both modes use the same capability checks, including live `pledge()` 126restrictions on already-open handles and symlink-target checks. The sync 127modules do not grant additional authority. 128 129These are avril filesystem APIs, not a Deno facade. Blocking standard-stream 130interfaces are also available from `avril:std/sync`; see [standard I/O](stdio.md). 131There are no synchronous socket APIs or `globalThis.Deno`.