fix
This commit is contained in:
parent
a724792eb8
commit
b4d31bc897
|
|
@ -6,19 +6,6 @@ export const prasi_content_deploy: PrasiContent = {
|
|||
async prepare(site_id) {
|
||||
await ensureDeployExists(site_id);
|
||||
},
|
||||
async comps(comp_ids) {
|
||||
return [];
|
||||
},
|
||||
async file(url, options) {
|
||||
return { body: "", compression: "none" };
|
||||
},
|
||||
async layouts() {
|
||||
return [];
|
||||
},
|
||||
async page_urls() {
|
||||
return {};
|
||||
},
|
||||
async pages(page_ids) {
|
||||
return [];
|
||||
},
|
||||
async staticFile(ctx) {},
|
||||
async route(ctx) {},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,22 +1,18 @@
|
|||
import { g } from "utils/global";
|
||||
import type { PrasiContent } from "./types";
|
||||
|
||||
export const prasi_content_ipc: PrasiContent = {
|
||||
prepare(site_id) {
|
||||
console.log("mantap jiwa");
|
||||
},
|
||||
async comps(comp_ids) {
|
||||
return [];
|
||||
},
|
||||
async file(url, options) {
|
||||
return { body: "", compression: "none" };
|
||||
},
|
||||
async layouts() {
|
||||
return [];
|
||||
},
|
||||
async page_urls() {
|
||||
return {};
|
||||
},
|
||||
async pages(page_ids) {
|
||||
return [];
|
||||
async staticFile(ctx) {
|
||||
const asset = g.mode === "site" && g.ipc?.asset!;
|
||||
if (asset) {
|
||||
const response = asset.serve(ctx);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
}
|
||||
},
|
||||
async route(ctx) {},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { g } from "utils/global";
|
||||
|
||||
export const PrasiContent = () => {
|
||||
export const prasiContent = () => {
|
||||
return g.mode === "site" ? g.content : null;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +1,9 @@
|
|||
import type { ServerCtx } from "utils/server-ctx";
|
||||
|
||||
export type PrasiContent = {
|
||||
prepare: (site_id: string) => void | Promise<void>;
|
||||
page_urls: () => Promise<Record<string, string>>;
|
||||
pages: (page_ids: string[]) => Promise<IPage[]>;
|
||||
comps: (comp_ids: string[]) => Promise<IComp[]>;
|
||||
layouts: () => Promise<ILayout[]>;
|
||||
file: (
|
||||
url: string,
|
||||
options?: {
|
||||
accept: ("gzip" | "br" | "zstd")[];
|
||||
}
|
||||
) => Promise<{ body: any; compression: "none" | "gzip" | "br" | "zstd" }>;
|
||||
staticFile: (ctx: ServerCtx) => Promise<Response | void>;
|
||||
route: (ctx: ServerCtx) => Promise<Response | void>;
|
||||
};
|
||||
|
||||
export type ILayout = {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@ import { prasi_content_ipc } from "../content/content-ipc";
|
|||
import { prasi_content_deploy } from "../content/content-deploy";
|
||||
import { loadCurrentDeploy } from "../content/deploy/load";
|
||||
import { ipcSend } from "../content/ipc/send";
|
||||
import { staticFile } from "utils/static";
|
||||
import type { ServerCtx } from "utils/server-ctx";
|
||||
import { prasiContent } from "../content/content";
|
||||
|
||||
startup("site", async () => {
|
||||
await config.init("site:site.json");
|
||||
|
|
@ -12,28 +15,63 @@ startup("site", async () => {
|
|||
|
||||
if (g.ipc) {
|
||||
ipcSend({ type: "init" });
|
||||
process.on("message", (msg: { type: "start" }) => {
|
||||
if (g.mode === "site") {
|
||||
if (msg.type === "start") {
|
||||
g.server = Bun.serve({
|
||||
fetch(request, server) {},
|
||||
websocket: { message(ws, message) {} },
|
||||
port: 0,
|
||||
});
|
||||
ipcSend({ type: "ready", port: g.server.port });
|
||||
if (g.server) {
|
||||
console.log("restarting...");
|
||||
process.exit();
|
||||
} else {
|
||||
process.on(
|
||||
"message",
|
||||
async (msg: { type: "start"; path: { asset: string } }) => {
|
||||
if (g.mode === "site" && g.ipc) {
|
||||
if (msg.type === "start") {
|
||||
g.ipc.asset = await staticFile(msg.path.asset);
|
||||
startGlobalServer();
|
||||
ipcSend({ type: "ready", port: g.server.port });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
}
|
||||
} else {
|
||||
const ts = config.current?.deploy.current;
|
||||
if (ts) {
|
||||
await loadCurrentDeploy(ts);
|
||||
}
|
||||
|
||||
g.server = Bun.serve({
|
||||
fetch(request, server) {},
|
||||
websocket: { message(ws, message) {} },
|
||||
});
|
||||
startGlobalServer();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const startGlobalServer = async () => {
|
||||
if (g.mode === "site") {
|
||||
g.server = Bun.serve({
|
||||
async fetch(req, server) {
|
||||
const content = prasiContent();
|
||||
if (content) {
|
||||
const url = new URL(req.url);
|
||||
const pathname = req.url.split(url.host).pop() || "";
|
||||
const ctx: ServerCtx = {
|
||||
server,
|
||||
req: req,
|
||||
url: { pathname, raw: url },
|
||||
};
|
||||
|
||||
const response = await content.staticFile(ctx);
|
||||
if (response) {
|
||||
return response;
|
||||
}
|
||||
|
||||
const routed = await content.route(ctx);
|
||||
if (routed) {
|
||||
return routed;
|
||||
}
|
||||
|
||||
return new Response("Not Found", { status: 404 });
|
||||
}
|
||||
},
|
||||
websocket: { message(ws, message) {} },
|
||||
port: 0,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ startup("supervisor", async () => {
|
|||
await ensureDBReady();
|
||||
} else {
|
||||
g.mode = "site";
|
||||
if (g.mode === "site") g.ipc = true;
|
||||
if (g.mode === "site") g.ipc = {};
|
||||
}
|
||||
|
||||
await ensureServerReady(is_dev);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import { fs } from "./fs";
|
|||
import type { PrasiSpawn, spawn } from "./spawn";
|
||||
import type { prasi_content_ipc } from "../content/content-ipc";
|
||||
import type { prasi_content_deploy } from "../content/content-deploy";
|
||||
import type { StaticFile } from "./static";
|
||||
|
||||
if (!(globalThis as any).prasi) {
|
||||
(globalThis as any).prasi = {};
|
||||
|
|
@ -16,7 +17,10 @@ export const g = (globalThis as any).prasi as unknown as {
|
|||
| {
|
||||
mode: "site";
|
||||
server: Server;
|
||||
ipc: boolean;
|
||||
ipc?: {
|
||||
asset?: StaticFile;
|
||||
};
|
||||
static_cache: any;
|
||||
content: typeof prasi_content_ipc & typeof prasi_content_deploy;
|
||||
site?: {
|
||||
db?: SiteConfig["db"];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
import type { Serve } from "bun";
|
||||
|
||||
export type ServerCtx = {
|
||||
server: Serve;
|
||||
url: {
|
||||
pathname: string;
|
||||
raw: URL;
|
||||
};
|
||||
req: Request;
|
||||
};
|
||||
|
|
@ -0,0 +1,181 @@
|
|||
import * as zstd from "@bokuweb/zstd-wasm";
|
||||
import { Glob, gzipSync } from "bun";
|
||||
import { BunSqliteKeyValue } from "bun-sqlite-key-value";
|
||||
import { exists, existsAsync } from "fs-jetpack";
|
||||
import mime from "mime";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join } from "path";
|
||||
import { addRoute, createRouter, findRoute } from "rou3";
|
||||
import type { ServerCtx } from "./server-ctx";
|
||||
import { g } from "./global";
|
||||
import { waitUntil } from "./wait-until";
|
||||
|
||||
await zstd.init();
|
||||
|
||||
export type StaticFile = Awaited<ReturnType<typeof staticFile>>;
|
||||
export const staticFile = async (
|
||||
path: string,
|
||||
opt?: { index?: string; debug?: boolean }
|
||||
) => {
|
||||
if (g.mode !== "site") return;
|
||||
|
||||
if (!g.static_cache) {
|
||||
g.static_cache = {} as any;
|
||||
|
||||
if (!g.static_cache.gz) {
|
||||
g.static_cache.gz = new BunSqliteKeyValue(":memory:");
|
||||
}
|
||||
|
||||
if (!g.static_cache.zstd) {
|
||||
g.static_cache.zstd = new BunSqliteKeyValue(":memory:");
|
||||
}
|
||||
}
|
||||
|
||||
const store = g.static_cache;
|
||||
|
||||
const glob = new Glob("**");
|
||||
|
||||
const internal = {
|
||||
indexPath: "",
|
||||
rescan_timeout: null as any,
|
||||
router: createRouter<{
|
||||
mime: string | null;
|
||||
fullpath: string;
|
||||
path: string;
|
||||
}>(),
|
||||
};
|
||||
const static_file = {
|
||||
scanning: false,
|
||||
paths: new Set<string>(),
|
||||
// rescan will be overwritten below.
|
||||
async rescan(arg?: { immediatly?: boolean }) {},
|
||||
exists(rpath: string, arg?: { prefix?: string; debug?: boolean }) {
|
||||
let pathname = rpath;
|
||||
if (arg?.prefix && pathname) {
|
||||
pathname = pathname.substring(arg.prefix.length);
|
||||
}
|
||||
const found = findRoute(internal.router, undefined, path + pathname);
|
||||
return !!found;
|
||||
},
|
||||
serve: (ctx: ServerCtx, arg?: { prefix?: string; debug?: boolean }) => {
|
||||
let pathname = ctx.url.pathname || "";
|
||||
if (arg?.prefix && pathname) {
|
||||
pathname = pathname.substring(arg.prefix.length);
|
||||
}
|
||||
const found = findRoute(internal.router, undefined, pathname);
|
||||
|
||||
if (found) {
|
||||
const { fullpath, mime } = found.data;
|
||||
if (exists(fullpath)) {
|
||||
const { headers, content } = cachedResponse(
|
||||
ctx,
|
||||
fullpath,
|
||||
mime,
|
||||
store
|
||||
);
|
||||
headers["cache-control"] = "public, max-age=604800, immutable";
|
||||
return new Response(content, {
|
||||
headers,
|
||||
});
|
||||
} else {
|
||||
store.gz.delete(fullpath);
|
||||
store.zstd.delete(fullpath);
|
||||
}
|
||||
}
|
||||
|
||||
if (opt?.index) {
|
||||
const { headers, content } = cachedResponse(
|
||||
ctx,
|
||||
internal.indexPath,
|
||||
"text/html",
|
||||
store
|
||||
);
|
||||
return new Response(content, { headers });
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
const scan = async () => {
|
||||
if (static_file.scanning) {
|
||||
await waitUntil(() => !static_file.scanning);
|
||||
return;
|
||||
}
|
||||
static_file.scanning = true;
|
||||
if (await existsAsync(path)) {
|
||||
if (static_file.paths.size > 0) {
|
||||
store.gz.delete([...static_file.paths]);
|
||||
store.zstd.delete([...static_file.paths]);
|
||||
}
|
||||
|
||||
for await (const file of glob.scan(path)) {
|
||||
if (file === opt?.index) internal.indexPath = join(path, file);
|
||||
|
||||
static_file.paths.add(join(path, file));
|
||||
|
||||
let type = mime.getType(file);
|
||||
if (file.endsWith(".ts")) {
|
||||
type = "application/javascript";
|
||||
}
|
||||
|
||||
addRoute(internal.router, undefined, `/${file}`, {
|
||||
mime: type,
|
||||
path: file,
|
||||
fullpath: join(path, file),
|
||||
});
|
||||
}
|
||||
}
|
||||
static_file.scanning = false;
|
||||
};
|
||||
await scan();
|
||||
|
||||
static_file.rescan = (arg?: { immediatly?: boolean }) => {
|
||||
return new Promise<void>((resolve) => {
|
||||
clearTimeout(internal.rescan_timeout);
|
||||
internal.rescan_timeout = setTimeout(
|
||||
async () => {
|
||||
await scan();
|
||||
resolve();
|
||||
},
|
||||
arg?.immediatly ? 0 : 300
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return static_file;
|
||||
};
|
||||
|
||||
const cachedResponse = (
|
||||
ctx: ServerCtx,
|
||||
file_path: string,
|
||||
mime: string | null,
|
||||
store: any
|
||||
) => {
|
||||
const accept = ctx.req.headers.get("accept-encoding") || "";
|
||||
const headers: any = {
|
||||
"content-type": mime || "",
|
||||
};
|
||||
let content = null as any;
|
||||
|
||||
if (accept.includes("zstd")) {
|
||||
content = store.zstd.get(file_path);
|
||||
if (!content) {
|
||||
content = zstd.compress(
|
||||
new Uint8Array(readFileSync(file_path)) as any,
|
||||
10
|
||||
);
|
||||
store.zstd.set(file_path, content);
|
||||
}
|
||||
headers["content-encoding"] = "zstd";
|
||||
}
|
||||
|
||||
if (!content && accept.includes("gz")) {
|
||||
content = store.gz.get(file_path);
|
||||
if (!content) {
|
||||
content = gzipSync(new Uint8Array(readFileSync(file_path)));
|
||||
store.gz.set(file_path, content);
|
||||
}
|
||||
headers["content-encoding"] = "gzip";
|
||||
}
|
||||
|
||||
return { content, headers };
|
||||
};
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
export const waitUntil = (
|
||||
condition: number | (() => any),
|
||||
arg?: { timeout?: number; interval?: number }
|
||||
) => {
|
||||
return new Promise<void>(async (resolve) => {
|
||||
if (typeof condition === "function") {
|
||||
let tout = null as any;
|
||||
if (arg?.timeout) {
|
||||
tout = setTimeout(resolve, arg?.timeout);
|
||||
}
|
||||
if (await condition()) {
|
||||
clearTimeout(tout);
|
||||
resolve();
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
const c = setInterval(async () => {
|
||||
if (await condition()) {
|
||||
if (tout) clearTimeout(tout);
|
||||
clearInterval(c);
|
||||
resolve();
|
||||
}
|
||||
if (count > 100) {
|
||||
clearInterval(c);
|
||||
}
|
||||
}, arg?.interval || 10);
|
||||
} else if (typeof condition === "number") {
|
||||
setTimeout(() => {
|
||||
resolve();
|
||||
}, condition);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "bun run --watch internal/supervisor.ts --dev",
|
||||
"ipc": "bun run --watch internal/supervisor.ts --dev --ipc"
|
||||
"ipc": "bun run --hot internal/supervisor.ts --dev --ipc"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "latest"
|
||||
|
|
@ -13,12 +13,16 @@
|
|||
"typescript": "^5.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bokuweb/zstd-wasm": "^0.0.22",
|
||||
"@types/lodash.get": "^4.4.9",
|
||||
"@types/lodash.set": "^4.3.9",
|
||||
"bun-sqlite-key-value": "^1.13.1",
|
||||
"dayjs": "^1.11.13",
|
||||
"fs-jetpack": "^5.1.0",
|
||||
"lodash.get": "^4.4.2",
|
||||
"lodash.set": "^4.3.2",
|
||||
"msgpackr": "^1.11.2"
|
||||
"mime": "^4.0.6",
|
||||
"msgpackr": "^1.11.2",
|
||||
"rou3": "^0.5.1"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue