@ayonli/jsext
Version:
A JavaScript extension package for building strong and modern applications.
410 lines (352 loc) • 11.5 kB
text/typescript
import { Exception } from "../error.ts";
import { getMIME } from "../filetype.ts";
import type {
CopyOptions,
GetDirOptions,
GetFileOptions,
LinkOptions,
MkdirOptions,
ReadDirOptions,
ReadFileOptions,
RemoveOptions,
StatOptions,
WriteFileOptions,
} from "../fs.ts";
import type { FileSystemOptions, FileInfo, DirEntry, DirTree } from "../fs/types.ts";
import { makeTree } from "../fs/util.ts";
import { basename, extname, join } from "../path.ts";
import { readAsArray, readAsArrayBuffer } from "../reader.ts";
import { KVNamespace } from "./types.ts";
export type {
CopyOptions,
GetDirOptions,
GetFileOptions,
LinkOptions,
MkdirOptions,
ReadDirOptions,
ReadFileOptions,
RemoveOptions,
StatOptions,
WriteFileOptions,
};
export type { FileSystemOptions, FileInfo, DirEntry, DirTree };
export const EOL: "\n" | "\r\n" = "\n";
export async function getDirHandle(
path: string,
options: GetDirOptions = {}
): Promise<FileSystemDirectoryHandle> {
void path, options;
throw new Error("Unsupported runtime");
}
export async function getFileHandle(
path: string,
options: GetFileOptions = {}
): Promise<FileSystemFileHandle> {
void path, options;
throw new Error("Unsupported runtime");
}
function getKVStore(options: FileSystemOptions): KVNamespace {
// @ts-ignore
const kv = (options.root ?? globalThis["__STATIC_CONTENT"]) as KVNamespace | undefined;
if (!kv) {
throw new Error("Must set the `options.root` a KVNamespace object");
}
return kv as KVNamespace;
}
// @ts-ignore
const loadManifest: Promise<{ [filename: string]: string; }> = (async () => {
// @ts-ignore
if (globalThis["__STATIC_CONTENT_MANIFEST"]) {
// @ts-ignore
return globalThis["__STATIC_CONTENT_MANIFEST"];
}
// @ts-ignore
return import("__STATIC_CONTENT_MANIFEST")
.then((mod) => JSON.parse(mod.default))
.catch(() => ({}));
})();
function throwNotFoundError(filename: string, kind: "file" | "directory" = "file"): never {
throw new Exception(`${kind === "file" ? "File" : "Directory"} '${filename}' does not exist`, {
name: "NotFoundError",
code: 404,
});
}
export async function exists(path: string, options: FileSystemOptions = {}): Promise<boolean> {
void getKVStore(options);
path = join(path);
const manifest = await loadManifest;
const filenames = Object.keys(manifest);
if (filenames.includes(path)) {
return true;
} else {
const dirPath = path + "/";
return filenames.some(filename => filename.startsWith(dirPath));
}
}
export async function stat(
target: string | FileSystemFileHandle | FileSystemDirectoryHandle,
options: StatOptions = {}
): Promise<FileInfo> {
const filename = join(target as string);
const kv = getKVStore(options);
const manifest = await loadManifest;
const filenames = Object.keys(manifest);
if (filenames.includes(filename)) {
const buffer = await kv.get(filename, { type: "arrayBuffer" }) as ArrayBuffer;
return {
name: basename(filename),
kind: "file",
size: buffer.byteLength,
type: getMIME(extname(filename)) ?? "",
mtime: null,
atime: null,
birthtime: null,
mode: 0,
uid: 0,
gid: 0,
isBlockDevice: false,
isCharDevice: false,
isFIFO: false,
isSocket: false,
};
} else {
const dirPath = filename + "/";
if (filenames.some(filename => filename.startsWith(dirPath))) {
return {
name: basename(filename),
kind: "directory",
size: 0,
type: "",
mtime: null,
atime: null,
birthtime: null,
mode: 0,
uid: 0,
gid: 0,
isBlockDevice: false,
isCharDevice: false,
isFIFO: false,
isSocket: false,
};
} else {
throwNotFoundError(filename);
}
}
}
export async function mkdir(path: string, options: MkdirOptions = {}): Promise<void> {
void path, options;
throw new Error("Unsupported runtime");
}
export async function ensureDir(
path: string,
options: Omit<MkdirOptions, "recursive"> = {}
): Promise<void> {
void path, options;
throw new Error("Unsupported runtime");
}
export async function* readDir(
target: string | FileSystemDirectoryHandle,
options: ReadDirOptions = {}
): AsyncIterableIterator<DirEntry> {
void getKVStore(options);
const manifest = await loadManifest;
const StaticFilenames = Object.keys(manifest);
let dirPath = target as string;
if (dirPath === "." || dirPath.endsWith("/")) {
dirPath = dirPath.slice(0, -1);
}
let dirPaths = new Set<string>();
let hasFiles = false;
if (options.recursive) {
const prefix = dirPath ? dirPath + "/" : "";
const _filenames = prefix
? StaticFilenames.filter(filename => filename.startsWith(prefix))
: StaticFilenames;
if (!_filenames.length) {
throwNotFoundError(dirPath, "directory");
}
for (let relativePath of _filenames) {
relativePath = relativePath.slice(prefix.length);
const parts = relativePath.split("/");
if (parts.length >= 2) { // direct folder
const dirPath = parts.slice(0, -1).join("/");
if (!dirPaths.has(dirPath)) {
dirPaths.add(dirPath);
hasFiles = true;
yield {
name: parts.slice(-2, -1)[0]!,
kind: "directory",
relativePath: dirPath,
};
}
yield {
name: parts[0]!,
kind: "file",
relativePath,
};
} else if (parts.length === 1) { // direct file
hasFiles = true;
yield {
name: parts[0]!,
kind: "file",
relativePath,
};
}
}
if (!hasFiles) {
throwNotFoundError(dirPath, "directory");
}
} else {
const allEntries = await readAsArray(readDir(target, { ...options, recursive: true }));
for (const entry of allEntries) {
if (!entry.relativePath.includes("/")) {
yield entry;
}
}
}
}
export async function readTree(
target: string | FileSystemDirectoryHandle,
options: FileSystemOptions = {}
): Promise<DirTree> {
const entries = (await readAsArray(readDir(target, { ...options, recursive: true })));
return makeTree<DirEntry, DirTree>(target, entries, true);
}
export async function readFile(
target: string | FileSystemFileHandle,
options: ReadFileOptions = {}
): Promise<Uint8Array> {
const filename = target as string;
const kv = getKVStore(options);
const stream = await kv.get(filename, { type: "stream" });
if (!stream) {
throwNotFoundError(filename);
}
const ctrl = new AbortController();
ctrl.signal.addEventListener("abort", () => stream.cancel());
const buffer = await readAsArrayBuffer(stream);
return new Uint8Array(buffer);
}
export async function readFileAsText(
target: string | FileSystemFileHandle,
options: ReadFileOptions = {}
): Promise<string> {
const filename = target as string;
const kv = getKVStore(options);
const text = await kv.get(filename, { type: "text" });
if (text === null) {
throwNotFoundError(filename);
} else {
return text;
}
}
export async function readFileAsFile(
target: string | FileSystemFileHandle,
options: ReadFileOptions = {}
): Promise<File> {
const filename = target as string;
const kv = getKVStore(options);
const buffer = await kv.get(filename, { type: "arrayBuffer" });
if (!buffer) {
throwNotFoundError(filename);
}
const file = new File([buffer], filename, {
type: getMIME(extname(filename)) ?? "",
});
Object.defineProperty(file, "webkitRelativePath", {
configurable: true,
enumerable: true,
writable: false,
value: "",
});
return file;
}
export async function writeFile(
target: string | FileSystemFileHandle,
data: string | ArrayBuffer | ArrayBufferView | ReadableStream<Uint8Array> | Blob,
options: WriteFileOptions = {}
): Promise<void> {
void target, data, options;
throw new Error("Unsupported runtime");
}
export async function writeLines(
target: string | FileSystemFileHandle,
lines: string[],
options: WriteFileOptions = {}
): Promise<void> {
void target, lines, options;
throw new Error("Unsupported runtime");
}
export async function truncate(
target: string | FileSystemFileHandle,
size = 0,
options: FileSystemOptions = {}
): Promise<void> {
void target, size, options;
throw new Error("Unsupported runtime");
}
export async function remove(path: string, options: RemoveOptions = {}): Promise<void> {
void path, options;
throw new Error("Unsupported runtime");
}
export async function rename(
oldPath: string,
newPath: string,
options: FileSystemOptions = {}
): Promise<void> {
void oldPath, newPath, options;
throw new Error("Unsupported runtime");
}
export async function copy(src: string, dest: string, options?: CopyOptions): Promise<void>;
export async function copy(
src: FileSystemFileHandle,
dest: FileSystemFileHandle | FileSystemDirectoryHandle
): Promise<void>;
export async function copy(
src: FileSystemDirectoryHandle,
dest: FileSystemDirectoryHandle,
options?: Pick<CopyOptions, "recursive">
): Promise<void>;
export async function copy(
src: string | FileSystemFileHandle | FileSystemDirectoryHandle,
dest: string | FileSystemFileHandle | FileSystemDirectoryHandle,
options: CopyOptions = {}
): Promise<void> {
void src, dest, options;
throw new Error("Unsupported runtime");
}
export async function link(src: string, dest: string, options: LinkOptions = {}): Promise<void> {
void src, dest, options;
throw new Error("Unsupported runtime");
}
export async function readLink(path: string): Promise<string> {
void path;
throw new Error("Unsupported runtime");
}
export async function chmod(path: string, mode: number): Promise<void> {
void path, mode;
}
export async function chown(path: string, uid: number, gid: number): Promise<void> {
void path, uid, gid;
}
export async function utimes(
path: string,
atime: number | Date,
mtime: number | Date
): Promise<void> {
void path, atime, mtime;
}
export function createReadableStream(
target: string | FileSystemFileHandle,
options: FileSystemOptions = {}
): ReadableStream<Uint8Array> {
void target, options;
throw new Error("Unsupported runtime");
}
export function createWritableStream(
target: string | FileSystemFileHandle,
options: Omit<WriteFileOptions, "signal"> = {}
): WritableStream<Uint8Array> {
void target, options;
throw new Error("Unsupported runtime");
}