///| JS host registry bridge for exported `src/lib` operations.
///|
priv struct LibJsHostFs {
host_id : Int
}
///|
fn LibJsHostFs::new(host_id : Int) -> LibJsHostFs {
{ host_id, }
}
///|
fn js_make_host_fs(host_id : Int) -> LibJsHostFs {
LibJsHostFs::new(host_id)
}
///|
fn js_bytes_to_array(bytes : Bytes) -> Array[Byte] {
let out : Array[Byte] = Array::new(capacity=bytes.length())
for i in 0.. String {
lib_js_host_last_error()
}
///|
fn js_unwrap_host_write(result : String?) -> Result[Unit, String] {
match result {
Some(error) => Err(error)
None => Ok(())
}
}
///|
extern "js" fn lib_js_host_last_error() -> String =
#| () => globalThis.__bitGitJsLastError ?? ""
///|
extern "js" fn lib_js_host_write_file(
host_id : Int,
path : String,
content : Array[Byte],
) -> String? =
#| (hostId, path, content) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| return `bit-git js host ${hostId} not found`;
#| }
#| if (typeof host.writeFile !== 'function') {
#| return 'bit-git js host.writeFile(path, bytes) is required';
#| }
#| try {
#| host.writeFile(path, Uint8Array.from(content ?? []));
#| return undefined;
#| } catch (error) {
#| return String(error?.message ?? error);
#| }
#| }
///|
extern "js" fn lib_js_host_write_string(
host_id : Int,
path : String,
content : String,
) -> String? =
#| (hostId, path, content) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| return `bit-git js host ${hostId} not found`;
#| }
#| if (typeof host.writeString !== 'function') {
#| return 'bit-git js host.writeString(path, content) is required';
#| }
#| try {
#| host.writeString(path, content);
#| return undefined;
#| } catch (error) {
#| return String(error?.message ?? error);
#| }
#| }
///|
extern "js" fn lib_js_host_mkdir_p(host_id : Int, path : String) -> String? =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| return `bit-git js host ${hostId} not found`;
#| }
#| if (typeof host.mkdirP !== 'function') {
#| return 'bit-git js host.mkdirP(path) is required';
#| }
#| try {
#| host.mkdirP(path);
#| return undefined;
#| } catch (error) {
#| return String(error?.message ?? error);
#| }
#| }
///|
extern "js" fn lib_js_host_remove_file(host_id : Int, path : String) -> String? =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| return `bit-git js host ${hostId} not found`;
#| }
#| if (typeof host.removeFile !== 'function') {
#| return 'bit-git js host.removeFile(path) is required';
#| }
#| try {
#| host.removeFile(path);
#| return undefined;
#| } catch (error) {
#| return String(error?.message ?? error);
#| }
#| }
///|
extern "js" fn lib_js_host_remove_dir(host_id : Int, path : String) -> String? =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| return `bit-git js host ${hostId} not found`;
#| }
#| if (typeof host.removeDir !== 'function') {
#| return 'bit-git js host.removeDir(path) is required';
#| }
#| try {
#| host.removeDir(path);
#| return undefined;
#| } catch (error) {
#| return String(error?.message ?? error);
#| }
#| }
///|
extern "js" fn lib_js_host_read_file(
host_id : Int,
path : String,
) -> Array[Byte]? =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| globalThis.__bitGitJsLastError = `bit-git js host ${hostId} not found`;
#| return { $tag: 0 };
#| }
#| if (typeof host.readFile !== 'function') {
#| globalThis.__bitGitJsLastError = 'bit-git js host.readFile(path) is required';
#| return { $tag: 0 };
#| }
#| try {
#| const value = host.readFile(path);
#| if (value instanceof Uint8Array) {
#| return { $tag: 1, _0: Array.from(value) };
#| }
#| if (value instanceof ArrayBuffer) {
#| return { $tag: 1, _0: Array.from(new Uint8Array(value)) };
#| }
#| if (Array.isArray(value)) {
#| return { $tag: 1, _0: value.map((item) => Number(item) & 0xff) };
#| }
#| if (typeof value === 'string') {
#| return { $tag: 1, _0: Array.from(new TextEncoder().encode(value)) };
#| }
#| globalThis.__bitGitJsLastError = 'bit-git js host.readFile must return Uint8Array, ArrayBuffer, Array, or string';
#| return { $tag: 0 };
#| } catch (error) {
#| globalThis.__bitGitJsLastError = String(error?.message ?? error);
#| return { $tag: 0 };
#| }
#| }
///|
extern "js" fn lib_js_host_readdir(
host_id : Int,
path : String,
) -> Array[String]? =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| globalThis.__bitGitJsLastError = `bit-git js host ${hostId} not found`;
#| return { $tag: 0 };
#| }
#| if (typeof host.readdir !== 'function') {
#| globalThis.__bitGitJsLastError = 'bit-git js host.readdir(path) is required';
#| return { $tag: 0 };
#| }
#| try {
#| return {
#| $tag: 1,
#| _0: Array.from(host.readdir(path) ?? [], (item) => String(item)),
#| };
#| } catch (error) {
#| globalThis.__bitGitJsLastError = String(error?.message ?? error);
#| return { $tag: 0 };
#| }
#| }
///|
extern "js" fn lib_js_host_is_dir(host_id : Int, path : String) -> Bool =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host || typeof host.isDir !== 'function') {
#| return false;
#| }
#| try {
#| return !!host.isDir(path);
#| } catch (_error) {
#| return false;
#| }
#| }
///|
extern "js" fn lib_js_host_is_file(host_id : Int, path : String) -> Bool =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host || typeof host.isFile !== 'function') {
#| return false;
#| }
#| try {
#| return !!host.isFile(path);
#| } catch (_error) {
#| return false;
#| }
#| }
///|
extern "js" fn lib_js_host_mtime(host_id : Int, path : String) -> Array[Int]? =
#| (hostId, path) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const host = state.hosts.get(hostId);
#| if (!host) {
#| globalThis.__bitGitJsLastError = `bit-git js host ${hostId} not found`;
#| return { $tag: 0 };
#| }
#| if (typeof host.mtime !== 'function') {
#| globalThis.__bitGitJsLastError = 'bit-git js host.mtime(path) is required';
#| return { $tag: 0 };
#| }
#| try {
#| const value = host.mtime(path);
#| if (!Array.isArray(value) || value.length !== 2) {
#| globalThis.__bitGitJsLastError = 'bit-git js host.mtime(path) must return [seconds, nanoseconds]';
#| return { $tag: 0 };
#| }
#| const seconds = Number(value[0]);
#| const nanoseconds = Number(value[1]);
#| if (!Number.isInteger(seconds) || !Number.isInteger(nanoseconds)) {
#| globalThis.__bitGitJsLastError = 'bit-git js host.mtime(path) must return integer seconds and nanoseconds';
#| return { $tag: 0 };
#| }
#| return { $tag: 1, _0: [seconds, nanoseconds] };
#| } catch (error) {
#| globalThis.__bitGitJsLastError = String(error?.message ?? error);
#| return { $tag: 0 };
#| }
#| }
///|
extern "js" fn lib_js_create_memory_host() -> Int =
#| () => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| const encoder = new TextEncoder();
#| const decoder = new TextDecoder();
#| const normalizePath = (path) => {
#| if (path === '' || path == null) return '/';
#| let normalized = String(path).replace(/\/+/g, '/');
#| if (!normalized.startsWith('/')) normalized = `/${normalized}`;
#| if (normalized.length > 1 && normalized.endsWith('/')) {
#| normalized = normalized.slice(0, -1);
#| }
#| return normalized;
#| };
#| const parentDir = (path) => {
#| const normalized = normalizePath(path);
#| if (normalized === '/') return '/';
#| const index = normalized.lastIndexOf('/');
#| return index <= 0 ? '/' : normalized.slice(0, index);
#| };
#| const baseName = (path) => {
#| const normalized = normalizePath(path);
#| if (normalized === '/') return '/';
#| const index = normalized.lastIndexOf('/');
#| return index < 0 ? normalized : normalized.slice(index + 1);
#| };
#| const ensureDirs = (dirs, path) => {
#| const normalized = normalizePath(path);
#| if (normalized === '/') {
#| dirs.add('/');
#| return;
#| }
#| const parts = normalized.split('/').filter(Boolean);
#| let current = '';
#| for (const part of parts) {
#| current += `/${part}`;
#| dirs.add(current);
#| }
#| };
#| const createHost = () => {
#| const files = new Map();
#| const mtimes = new Map();
#| const dirs = new Set(['/']);
#| return {
#| mkdirP(path) {
#| ensureDirs(dirs, path);
#| },
#| writeFile(path, content) {
#| const normalized = normalizePath(path);
#| ensureDirs(dirs, parentDir(normalized));
#| files.set(normalized, Uint8Array.from(content));
#| const now = Date.now();
#| mtimes.set(normalized, [
#| Math.floor(now / 1000),
#| (now % 1000) * 1_000_000,
#| ]);
#| },
#| writeString(path, content) {
#| this.writeFile(path, encoder.encode(String(content)));
#| },
#| removeFile(path) {
#| const normalized = normalizePath(path);
#| if (!files.delete(normalized)) {
#| throw new Error(`File not found: ${normalized}`);
#| }
#| mtimes.delete(normalized);
#| },
#| removeDir(path) {
#| const normalized = normalizePath(path);
#| if (!dirs.has(normalized)) {
#| throw new Error(`Directory not found: ${normalized}`);
#| }
#| for (const filePath of files.keys()) {
#| if (parentDir(filePath) === normalized) {
#| throw new Error(`Directory not empty: ${normalized}`);
#| }
#| }
#| for (const dirPath of dirs.values()) {
#| if (dirPath !== normalized && parentDir(dirPath) === normalized) {
#| throw new Error(`Directory not empty: ${normalized}`);
#| }
#| }
#| dirs.delete(normalized);
#| },
#| readFile(path) {
#| const normalized = normalizePath(path);
#| const value = files.get(normalized);
#| if (!value) {
#| throw new Error(`File not found: ${normalized}`);
#| }
#| return Uint8Array.from(value);
#| },
#| readdir(path) {
#| const normalized = normalizePath(path);
#| if (!dirs.has(normalized)) {
#| throw new Error(`Directory not found: ${normalized}`);
#| }
#| const entries = new Set();
#| for (const filePath of files.keys()) {
#| if (parentDir(filePath) === normalized) {
#| entries.add(baseName(filePath));
#| }
#| }
#| for (const dirPath of dirs.values()) {
#| if (dirPath !== normalized && parentDir(dirPath) === normalized) {
#| entries.add(baseName(dirPath));
#| }
#| }
#| return Array.from(entries).sort();
#| },
#| isDir(path) {
#| return dirs.has(normalizePath(path));
#| },
#| isFile(path) {
#| return files.has(normalizePath(path));
#| },
#| mtime(path) {
#| const normalized = normalizePath(path);
#| const value = mtimes.get(normalized);
#| if (!value) {
#| throw new Error(`File not found: ${normalized}`);
#| }
#| return value;
#| },
#| readString(path) {
#| return decoder.decode(this.readFile(path));
#| },
#| };
#| };
#| const hostId = state.nextHostId++;
#| state.hosts.set(hostId, createHost());
#| return hostId;
#| }
///|
extern "js" fn lib_js_destroy_host(host_id : Int) -> Unit =
#| (hostId) => {
#| const state = globalThis.__bitGitJsState ??= { nextHostId: 1, hosts: new Map() };
#| state.hosts.delete(hostId);
#| }
///|
pub fn js_create_memory_host() -> Int {
lib_js_create_memory_host()
}
///|
pub fn js_destroy_host(host_id : Int) -> Unit {
lib_js_destroy_host(host_id)
}
///|
pub fn js_host_write_string(
host_id : Int,
path : String,
content : String,
) -> Result[Unit, String] {
js_unwrap_host_write(lib_js_host_write_string(host_id, path, content))
}
///|
pub fn js_host_read_string(
host_id : Int,
path : String,
) -> Result[String, String] {
match lib_js_host_read_file(host_id, path) {
Some(bytes) => {
let content = Bytes::from_array(bytes)
Ok(@utf8.decode_lossy(content[:]))
}
None => Err(js_host_error_message())
}
}
///|
impl @bit.FileSystem for LibJsHostFs with fn mkdir_p(self, path) {
match lib_js_host_mkdir_p(self.host_id, path) {
Some(error) => raise @bit.GitError::IoError(error)
None => ()
}
}
///|
impl @bit.FileSystem for LibJsHostFs with fn write_file(self, path, content) {
match lib_js_host_write_file(self.host_id, path, js_bytes_to_array(content)) {
Some(error) => raise @bit.GitError::IoError(error)
None => ()
}
}
///|
impl @bit.FileSystem for LibJsHostFs with fn write_string(self, path, content) {
match lib_js_host_write_string(self.host_id, path, content) {
Some(error) => raise @bit.GitError::IoError(error)
None => ()
}
}
///|
impl @bit.FileSystem for LibJsHostFs with fn remove_file(self, path) {
match lib_js_host_remove_file(self.host_id, path) {
Some(error) => raise @bit.GitError::IoError(error)
None => ()
}
}
///|
impl @bit.FileSystem for LibJsHostFs with fn remove_dir(self, path) {
match lib_js_host_remove_dir(self.host_id, path) {
Some(error) => raise @bit.GitError::IoError(error)
None => ()
}
}
///|
impl @bit.RepoFileSystem for LibJsHostFs with fn read_file(self, path) {
match lib_js_host_read_file(self.host_id, path) {
Some(content) => Bytes::from_array(content)
None => raise @bit.GitError::IoError(js_host_error_message())
}
}
///|
impl @bit.RepoFileSystem for LibJsHostFs with fn readdir(self, path) {
match lib_js_host_readdir(self.host_id, path) {
Some(entries) => entries
None => raise @bit.GitError::IoError(js_host_error_message())
}
}
///|
impl @bit.RepoFileSystem for LibJsHostFs with fn is_dir(self, path) {
lib_js_host_is_dir(self.host_id, path)
}
///|
impl @bit.RepoFileSystem for LibJsHostFs with fn is_file(self, path) {
lib_js_host_is_file(self.host_id, path)
}
///|
impl @bit.RepoFileSystem for LibJsHostFs with fn mtime(self, path) {
match lib_js_host_mtime(self.host_id, path) {
Some(value) => (value[0], value[1])
None => raise @bit.GitError::IoError(js_host_error_message())
}
}