fix
This commit is contained in:
parent
a724792eb8
commit
b4d31bc897
|
|
@ -6,19 +6,6 @@ export const prasi_content_deploy: PrasiContent = {
|
||||||
async prepare(site_id) {
|
async prepare(site_id) {
|
||||||
await ensureDeployExists(site_id);
|
await ensureDeployExists(site_id);
|
||||||
},
|
},
|
||||||
async comps(comp_ids) {
|
async staticFile(ctx) {},
|
||||||
return [];
|
async route(ctx) {},
|
||||||
},
|
|
||||||
async file(url, options) {
|
|
||||||
return { body: "", compression: "none" };
|
|
||||||
},
|
|
||||||
async layouts() {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
async page_urls() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
async pages(page_ids) {
|
|
||||||
return [];
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
|
import { g } from "utils/global";
|
||||||
import type { PrasiContent } from "./types";
|
import type { PrasiContent } from "./types";
|
||||||
|
|
||||||
export const prasi_content_ipc: PrasiContent = {
|
export const prasi_content_ipc: PrasiContent = {
|
||||||
prepare(site_id) {
|
prepare(site_id) {
|
||||||
console.log("mantap jiwa");
|
console.log("mantap jiwa");
|
||||||
},
|
},
|
||||||
async comps(comp_ids) {
|
async staticFile(ctx) {
|
||||||
return [];
|
const asset = g.mode === "site" && g.ipc?.asset!;
|
||||||
},
|
if (asset) {
|
||||||
async file(url, options) {
|
const response = asset.serve(ctx);
|
||||||
return { body: "", compression: "none" };
|
if (response) {
|
||||||
},
|
return response;
|
||||||
async layouts() {
|
}
|
||||||
return [];
|
}
|
||||||
},
|
|
||||||
async page_urls() {
|
|
||||||
return {};
|
|
||||||
},
|
|
||||||
async pages(page_ids) {
|
|
||||||
return [];
|
|
||||||
},
|
},
|
||||||
|
async route(ctx) {},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { g } from "utils/global";
|
import { g } from "utils/global";
|
||||||
|
|
||||||
export const PrasiContent = () => {
|
export const prasiContent = () => {
|
||||||
return g.mode === "site" ? g.content : null;
|
return g.mode === "site" ? g.content : null;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,9 @@
|
||||||
|
import type { ServerCtx } from "utils/server-ctx";
|
||||||
|
|
||||||
export type PrasiContent = {
|
export type PrasiContent = {
|
||||||
prepare: (site_id: string) => void | Promise<void>;
|
prepare: (site_id: string) => void | Promise<void>;
|
||||||
page_urls: () => Promise<Record<string, string>>;
|
staticFile: (ctx: ServerCtx) => Promise<Response | void>;
|
||||||
pages: (page_ids: string[]) => Promise<IPage[]>;
|
route: (ctx: ServerCtx) => Promise<Response | void>;
|
||||||
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" }>;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ILayout = {
|
export type ILayout = {
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,9 @@ import { prasi_content_ipc } from "../content/content-ipc";
|
||||||
import { prasi_content_deploy } from "../content/content-deploy";
|
import { prasi_content_deploy } from "../content/content-deploy";
|
||||||
import { loadCurrentDeploy } from "../content/deploy/load";
|
import { loadCurrentDeploy } from "../content/deploy/load";
|
||||||
import { ipcSend } from "../content/ipc/send";
|
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 () => {
|
startup("site", async () => {
|
||||||
await config.init("site:site.json");
|
await config.init("site:site.json");
|
||||||
|
|
@ -12,28 +15,63 @@ startup("site", async () => {
|
||||||
|
|
||||||
if (g.ipc) {
|
if (g.ipc) {
|
||||||
ipcSend({ type: "init" });
|
ipcSend({ type: "init" });
|
||||||
process.on("message", (msg: { type: "start" }) => {
|
if (g.server) {
|
||||||
if (g.mode === "site") {
|
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") {
|
if (msg.type === "start") {
|
||||||
g.server = Bun.serve({
|
g.ipc.asset = await staticFile(msg.path.asset);
|
||||||
fetch(request, server) {},
|
startGlobalServer();
|
||||||
websocket: { message(ws, message) {} },
|
|
||||||
port: 0,
|
|
||||||
});
|
|
||||||
ipcSend({ type: "ready", port: g.server.port });
|
ipcSend({ type: "ready", port: g.server.port });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
const ts = config.current?.deploy.current;
|
const ts = config.current?.deploy.current;
|
||||||
if (ts) {
|
if (ts) {
|
||||||
await loadCurrentDeploy(ts);
|
await loadCurrentDeploy(ts);
|
||||||
}
|
}
|
||||||
|
|
||||||
g.server = Bun.serve({
|
startGlobalServer();
|
||||||
fetch(request, server) {},
|
|
||||||
websocket: { message(ws, message) {} },
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
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();
|
await ensureDBReady();
|
||||||
} else {
|
} else {
|
||||||
g.mode = "site";
|
g.mode = "site";
|
||||||
if (g.mode === "site") g.ipc = true;
|
if (g.mode === "site") g.ipc = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
await ensureServerReady(is_dev);
|
await ensureServerReady(is_dev);
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { fs } from "./fs";
|
||||||
import type { PrasiSpawn, spawn } from "./spawn";
|
import type { PrasiSpawn, spawn } from "./spawn";
|
||||||
import type { prasi_content_ipc } from "../content/content-ipc";
|
import type { prasi_content_ipc } from "../content/content-ipc";
|
||||||
import type { prasi_content_deploy } from "../content/content-deploy";
|
import type { prasi_content_deploy } from "../content/content-deploy";
|
||||||
|
import type { StaticFile } from "./static";
|
||||||
|
|
||||||
if (!(globalThis as any).prasi) {
|
if (!(globalThis as any).prasi) {
|
||||||
(globalThis as any).prasi = {};
|
(globalThis as any).prasi = {};
|
||||||
|
|
@ -16,7 +17,10 @@ export const g = (globalThis as any).prasi as unknown as {
|
||||||
| {
|
| {
|
||||||
mode: "site";
|
mode: "site";
|
||||||
server: Server;
|
server: Server;
|
||||||
ipc: boolean;
|
ipc?: {
|
||||||
|
asset?: StaticFile;
|
||||||
|
};
|
||||||
|
static_cache: any;
|
||||||
content: typeof prasi_content_ipc & typeof prasi_content_deploy;
|
content: typeof prasi_content_ipc & typeof prasi_content_deploy;
|
||||||
site?: {
|
site?: {
|
||||||
db?: SiteConfig["db"];
|
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",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run --watch internal/supervisor.ts --dev",
|
"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": {
|
"devDependencies": {
|
||||||
"@types/bun": "latest"
|
"@types/bun": "latest"
|
||||||
|
|
@ -13,12 +13,16 @@
|
||||||
"typescript": "^5.0.0"
|
"typescript": "^5.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@bokuweb/zstd-wasm": "^0.0.22",
|
||||||
"@types/lodash.get": "^4.4.9",
|
"@types/lodash.get": "^4.4.9",
|
||||||
"@types/lodash.set": "^4.3.9",
|
"@types/lodash.set": "^4.3.9",
|
||||||
|
"bun-sqlite-key-value": "^1.13.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"fs-jetpack": "^5.1.0",
|
"fs-jetpack": "^5.1.0",
|
||||||
"lodash.get": "^4.4.2",
|
"lodash.get": "^4.4.2",
|
||||||
"lodash.set": "^4.3.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