first commit

This commit is contained in:
2026-07-12 15:55:18 +05:30
commit ee98026cc5
404 changed files with 44522 additions and 0 deletions
+55
View File
@@ -0,0 +1,55 @@
/**
* Local-disk storage driver. Files live under a configured directory; keys map
* to relative paths inside it. Path traversal is rejected — a key can never
* escape the base dir.
*/
import { mkdir, readFile, unlink, writeFile } from "node:fs/promises";
import { dirname, isAbsolute, join, resolve, sep } from "node:path";
import type { LocalStoreConfig, PutMeta, StorageDriver } from "../driver.ts";
import { contentTypeOf } from "../mime.ts";
export function localDriver(config: LocalStoreConfig, appRoot: string): StorageDriver {
const base = resolve(isAbsolute(config.dir) ? config.dir : join(appRoot, config.dir));
/** Resolve a key to an absolute path that MUST stay inside `base`. */
function pathFor(key: string): string {
const abs = resolve(base, key);
if (abs !== base && !abs.startsWith(base + sep)) {
throw new Error(`unsafe storage key: ${key}`);
}
return abs;
}
return {
async put(key, data, _meta: PutMeta) {
const file = pathFor(key);
await mkdir(dirname(file), { recursive: true });
await writeFile(file, data);
},
async get(key) {
try {
const data = await readFile(pathFor(key));
return {
body: new Uint8Array(data),
contentType: contentTypeOf(key),
size: data.byteLength,
};
} catch (err) {
if ((err as NodeJS.ErrnoException).code === "ENOENT") return null;
throw err;
}
},
async delete(key) {
try {
await unlink(pathFor(key));
} catch (err) {
if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err;
}
},
publicUrl() {
// Local objects are served by the framework route (/__wrnexus/uploads/...).
return null;
},
};
}
+102
View File
@@ -0,0 +1,102 @@
/**
* S3 (and S3-compatible) storage driver — zero deps, SigV4-signed `fetch`.
* Works with AWS S3, Cloudflare R2, Backblaze B2, MinIO, DigitalOcean Spaces.
*
* Path-style vs virtual-hosted: AWS defaults to virtual-hosted
* (`bucket.s3.region.amazonaws.com`); custom endpoints (R2/MinIO) default to
* path-style (`endpoint/bucket/key`). Override with `forcePathStyle`.
*/
import type { S3StoreConfig, StorageDriver } from "../driver.ts";
import { contentTypeOf } from "../mime.ts";
import { encodeKey, sha256Hex, signS3 } from "../sigv4.ts";
export function s3Driver(config: S3StoreConfig): StorageDriver {
if (!config.bucket || /[\\/\0\r\n]/.test(config.bucket)) {
throw new TypeError("S3 bucket must be a non-empty name without path separators");
}
const pathStyle = config.forcePathStyle ?? !!config.endpoint;
const endpoint = config.endpoint ?? `https://s3.${config.region}.amazonaws.com`;
const parsed = new URL(endpoint);
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
throw new TypeError("S3 endpoint must use http or https");
}
if (parsed.username || parsed.password || parsed.search || parsed.hash) {
throw new TypeError("S3 endpoint cannot contain credentials, query parameters, or fragments");
}
const scheme = parsed.protocol.replace(":", "");
const endpointHost = parsed.host;
const endpointPath = parsed.pathname.replace(/\/+$/, "");
if (!pathStyle && endpointPath) {
throw new TypeError("Virtual-hosted S3 endpoints cannot contain a path prefix");
}
function target(key: string): { host: string; path: string; url: string } {
const ek = encodeKey(key);
if (pathStyle) {
const host = endpointHost;
const path = `${endpointPath}/${encodeURIComponent(config.bucket)}/${ek}`;
return { host, path, url: `${scheme}://${host}${path}` };
}
const host = `${config.bucket}.${endpointHost}`;
const path = `/${ek}`;
return { host, path, url: `${scheme}://${host}${path}` };
}
async function send(
method: string,
key: string,
body?: Uint8Array,
contentType?: string,
): Promise<Response> {
const { host, path, url } = target(key);
const headers = signS3({
method,
host,
path,
region: config.region,
accessKeyId: config.accessKeyId,
secretAccessKey: config.secretAccessKey,
payloadHash: sha256Hex(body ?? new Uint8Array()),
headers: contentType ? { "content-type": contentType } : {},
date: new Date(),
});
return fetch(url, { method, headers, body: body as BodyInit | undefined });
}
return {
async put(key, data, meta) {
const res = await send("PUT", key, data, meta.contentType);
if (!res.ok) throw new Error(`S3 put failed (${res.status}): ${await peek(res)}`);
},
async get(key) {
const res = await send("GET", key);
if (res.status === 404 || res.status === 403) return null;
if (!res.ok) throw new Error(`S3 get failed (${res.status})`);
const len = res.headers.get("content-length");
return {
body: res.body ?? new Uint8Array(),
contentType: res.headers.get("content-type") || contentTypeOf(key),
size: len ? Number(len) : undefined,
};
},
async delete(key) {
const res = await send("DELETE", key);
if (!res.ok && res.status !== 404) throw new Error(`S3 delete failed (${res.status})`);
},
publicUrl(key) {
if (config.publicBaseUrl) {
return `${config.publicBaseUrl.replace(/\/+$/, "")}/${encodeKey(key)}`;
}
return config.access === "public" ? target(key).url : null;
},
};
}
async function peek(res: Response): Promise<string> {
try {
return (await res.text()).slice(0, 200);
} catch {
return "";
}
}