This commit is contained in:
Rizky 2024-01-23 08:29:51 +07:00
parent 38814dd5f1
commit 5f07237610
9 changed files with 538 additions and 451 deletions

2
app/web/.gitignore vendored
View File

@ -179,3 +179,5 @@ app/web/.parcel-cache
app/static app/static
.data .data
.data/* .data/*
timestamp.ts

View File

@ -10,36 +10,36 @@ import { sworkerAddCache, sworkerRegister } from "./sworker-boot";
import { w } from "./utils/types/general"; import { w } from "./utils/types/general";
const start = async () => { const start = async () => {
const base = `${location.protocol}//${location.host}`; const base = `${location.protocol}//${location.host}`;
let react = { let react = {
root: null as null | ReactRoot, root: null as null | ReactRoot,
}; };
w.mobile = registerMobile(); w.mobile = registerMobile();
const cur = new URL(location.href); const cur = new URL(location.href);
const base_url = `${cur.protocol}//${cur.host}`; const base_url = `${cur.protocol}//${cur.host}`;
w.db = dbProxy(base_url); w.db = dbProxy(base_url);
try { try {
await loadApiProxyDef(base_url, false); await loadApiProxyDef(base_url, false);
w.api = apiProxy(base_url); w.api = apiProxy(base_url);
} catch (e) { } catch (e) {
console.warn("Failed to load API:", base_url); console.warn("Failed to load API:", base_url);
} }
w.serverurl = base; w.serverurl = base;
sworkerRegister(react); sworkerRegister(react);
defineReact(); defineReact();
await defineWindow(false); await defineWindow(false);
sworkerAddCache(base); sworkerAddCache(base);
const el = document.getElementById("root"); const el = document.getElementById("root");
if (el) { if (el) {
react.root = createRoot(el); react.root = createRoot(el);
react.root.render(<Root />); react.root.render(<Root />);
} }
}; };
start(); start();

View File

@ -2,176 +2,235 @@ import { Root as ReactRoot } from "react-dom/client";
import { Root } from "./base/root"; import { Root } from "./base/root";
import { w } from "./utils/types/general"; import { w } from "./utils/types/general";
import { isLocalhost } from "./utils/ui/is-localhost"; import { isLocalhost } from "./utils/ui/is-localhost";
import { version } from "../timestamp";
const state = {
updating: false,
};
export const sworkerRegister = async (react: { root: null | ReactRoot }) => { export const sworkerRegister = async (react: { root: null | ReactRoot }) => {
if (navigator.serviceWorker) { if (navigator.serviceWorker) {
if (!isLocalhost()) { if (!isLocalhost()) {
const sw = await registerServiceWorker(); const sw = await registerServiceWorker();
const cacheCurrentPage = () => { const cacheCurrentPage = () => {
const swController = navigator.serviceWorker.controller; const swController = navigator.serviceWorker.controller;
if (swController) { if (swController) {
[location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach( [location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach(
(url) => { (url) => {
swController.postMessage({ swController.postMessage({
type: "add-cache", type: "add-cache",
url: url, url: url,
}); });
} },
); );
} }
}; };
cacheCurrentPage(); cacheCurrentPage();
navigator.serviceWorker.addEventListener("message", (e) => {
cacheCurrentPage();
if (react.root) {
if (e.data.type === "offline") {
w.offline = true;
const click = () => {
if (react.root) react.root.render(<Root />);
};
setTimeout(click, 5000);
react.root.render(
<>
<Root />
<div
className={cx(
css`
position: fixed;
bottom: 20px;
left: 0px;
right: 0px;
z-index: 999;
`,
"flex justify-center cursor-pointer"
)}
>
<div
className="bg-orange-500 text-white px-4 py-2 rounded-full text-sm"
onClick={click}
>
Network Failed
</div>
</div>
</>
);
}
if (e.data.type === "activated") { const curver = localStorage.getItem("prasi-version");
if (e.data.shouldRefresh && sw) { const swc = navigator.serviceWorker.controller;
react.root.render( if (version !== curver && curver && react.root && swc) {
<> react.root.render(
<Root /> <>
<div <Root />
className={cx( <div
css` className={cx(
position: fixed; css`
bottom: 20px; position: fixed;
left: 0px; bottom: 20px;
right: 0px; left: 0px;
z-index: 999; right: 0px;
`, z-index: 999;
"flex justify-center" `,
)} "flex justify-center cursor-pointer",
> )}
<div className="bg-blue-400 text-white px-4 py-2 rounded-full text-sm"> onClick={() => {
Updating App... swc.postMessage({
</div> type: "force-update",
</div> });
</> if (react.root)
); react.root.render(
<>
sw.unregister().then(() => { <Root />
window.location.reload(); <div
}); className={cx(
} else { css`
const localVersion = localStorage.getItem("prasi-version");
if (localVersion !== e.data.version) {
localStorage.setItem("prasi-version", e.data.version);
const click = () => {
if (react.root) react.root.render(<Root />);
};
setTimeout(click, 5000);
react.root.render(
<>
<Root />
<div
className={cx(
css`
position: fixed; position: fixed;
bottom: 20px; bottom: 20px;
left: 0px; left: 0px;
right: 0px; right: 0px;
z-index: 999; z-index: 999;
`, `,
"flex justify-center cursor-pointer" "flex justify-center",
)} )}
> >
<div <div className="bg-blue-400 text-white px-4 py-2 rounded-full text-sm">
className="bg-green-600 text-white px-4 py-2 rounded-full text-sm" Updating App...
onClick={click} </div>
> </div>
Prasi Updated{" "} </>,
<span className="opacity-50">{e.data.version}</span> );
</div> }}
</div> >
</> <div className="bg-orange-600 text-white px-4 py-2 rounded-full text-sm select-none">
); New Version Available. Click to Update
} </div>
} </div>
} </>,
} );
}); }
} else {
navigator.serviceWorker.getRegistrations().then(function (registrations) { navigator.serviceWorker.addEventListener("message", (e) => {
for (let registration of registrations) { cacheCurrentPage();
registration.unregister(); if (react.root) {
} if (e.data.type === "offline") {
}); w.offline = true;
} const click = () => {
} if (react.root) react.root.render(<Root />);
};
setTimeout(click, 5000);
react.root.render(
<>
<Root />
<div
className={cx(
css`
position: fixed;
bottom: 20px;
left: 0px;
right: 0px;
z-index: 999;
`,
"flex justify-center cursor-pointer",
)}
>
<div
className="bg-orange-500 text-white px-4 py-2 rounded-full text-sm"
onClick={click}
>
Network Failed
</div>
</div>
</>,
);
}
if (e.data.type === "activated") {
if (e.data.shouldRefresh && sw) {
react.root.render(
<>
<Root />
<div
className={cx(
css`
position: fixed;
bottom: 20px;
left: 0px;
right: 0px;
z-index: 999;
`,
"flex justify-center",
)}
>
<div className="bg-blue-400 text-white px-4 py-2 rounded-full text-sm">
Updating App...
</div>
</div>
</>,
);
sw.unregister().then(() => {
window.location.reload();
});
} else {
const localVersion = localStorage.getItem("prasi-version");
if (localVersion !== e.data.version) {
localStorage.setItem("prasi-version", e.data.version);
const click = () => {
if (react.root) react.root.render(<Root />);
};
setTimeout(click, 5000);
react.root.render(
<>
<Root />
<div
className={cx(
css`
position: fixed;
bottom: 20px;
left: 0px;
right: 0px;
z-index: 999;
`,
"flex justify-center cursor-pointer",
)}
>
<div
className="bg-green-600 text-white px-4 py-2 rounded-full text-sm"
onClick={click}
>
Prasi Updated{" "}
<span className="opacity-50">{e.data.version}</span>
</div>
</div>
</>,
);
}
}
}
}
});
} else {
navigator.serviceWorker.getRegistrations().then(function (registrations) {
for (let registration of registrations) {
registration.unregister();
}
});
}
}
}; };
const registerServiceWorker = async () => { const registerServiceWorker = async () => {
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
try { try {
return await navigator.serviceWorker.register( return await navigator.serviceWorker.register(
new URL("./sworker.ts", import.meta.url), new URL("./sworker.ts", import.meta.url),
{ {
type: "module", type: "module",
scope: "/", scope: "/",
} },
); );
} catch (error) { } catch (error) {
console.error(`Registration failed with ${error}`); console.error(`Registration failed with ${error}`);
} }
} }
}; };
export const sworkerAddCache = (base: string) => { export const sworkerAddCache = (base: string) => {
if (navigator.serviceWorker) { if (navigator.serviceWorker) {
if (!isLocalhost()) { if (!isLocalhost()) {
const swc = navigator.serviceWorker.controller; const swc = navigator.serviceWorker.controller;
if (swc) { if (swc) {
[location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach((url) => { [location.href, "", "/", "/ed", "/ed/_/_", "/login"].forEach((url) => {
swc.postMessage({ swc.postMessage({
type: "add-cache", type: "add-cache",
url: url, url: url,
}); });
}); });
if (w.prasiApi && w.prasiApi[base] && w.prasiApi[base].apiEntry) { if (w.prasiApi && w.prasiApi[base] && w.prasiApi[base].apiEntry) {
const routes = Object.entries(w.prasiApi[base].apiEntry).map( const routes = Object.entries(w.prasiApi[base].apiEntry).map(
([k, v]: any) => ({ ([k, v]: any) => ({
url: v.url, url: v.url,
name: k, name: k,
}) }),
); );
swc.postMessage({ swc.postMessage({
type: "define-route", type: "define-route",
routes, routes,
}); });
} }
} }
} }
} }
}; };

View File

@ -1,95 +1,110 @@
import { manifest, version } from "@parcel/service-worker"; import { manifest } from "@parcel/service-worker";
import { RadixRouter, createRouter } from "radix3"; import { RadixRouter, createRouter } from "radix3";
import { version } from "../timestamp";
const g = { const g = {
router: null as null | RadixRouter<any>, router: null as null | RadixRouter<any>,
offline: false, offline: false,
broadcast(msg: any) { broadcast(msg: any) {
// @ts-ignore // @ts-ignore
const c: Clients = self.clients; const c: Clients = self.clients;
c.matchAll({ includeUncontrolled: true }).then((clients) => { c.matchAll({ includeUncontrolled: true }).then((clients) => {
clients.forEach((client) => { clients.forEach((client) => {
client.postMessage(msg); client.postMessage(msg);
}); });
}); });
}, },
}; };
async function install() { async function install() {
const cache = await caches.open(version); const cache = await caches.open(version);
await cache.addAll(manifest); await cache.addAll(manifest);
g.broadcast({ type: "installed" }); g.broadcast({ type: "installed" });
} }
addEventListener("install", (e) => (e as ExtendableEvent).waitUntil(install())); addEventListener("install", (e) => (e as ExtendableEvent).waitUntil(install()));
async function activate() { async function activate() {
let shouldRefresh = false; let shouldRefresh = false;
if (!g.offline) { if (!g.offline) {
const keys = await caches.keys(); const keys = await caches.keys();
await Promise.all( await Promise.all(
keys.map(async (key) => { keys.map(async (key) => {
if (key !== version) { if (key !== version) {
await caches.delete(key); await caches.delete(key);
shouldRefresh = true; shouldRefresh = true;
} }
}) }),
); );
g.broadcast({ type: "activated", shouldRefresh, version }); g.broadcast({ type: "activated", shouldRefresh, version });
} }
} }
addEventListener("activate", (e) => addEventListener("activate", (e) =>
(e as ExtendableEvent).waitUntil(activate()) (e as ExtendableEvent).waitUntil(activate()),
); );
addEventListener("fetch", async (evt) => { addEventListener("fetch", async (evt) => {
const e = evt as FetchEvent; const e = evt as FetchEvent;
const url = new URL(e.request.url); const url = new URL(e.request.url);
if (g.router) { if (g.router) {
const found = g.router.lookup(url.pathname); const found = g.router.lookup(url.pathname);
if (found) { if (found) {
return; return;
} }
} }
e.respondWith( e.respondWith(
(async () => { (async () => {
const r = await caches.match(e.request); const r = await caches.match(e.request);
if (r) { if (r) {
return r; return r;
} }
try { try {
g.offline = false; g.offline = false;
return await fetch(e.request); return await fetch(e.request);
} catch (e) { } catch (e) {
g.offline = true; g.offline = true;
g.broadcast({ type: "offline" }); g.broadcast({ type: "offline" });
return new Response(); return new Response();
} }
})() })(),
); );
}); });
addEventListener("message", async (e) => { addEventListener("message", async (e) => {
const type = e.data.type; const type = e.data.type;
const cache = await caches.open(version); const cache = await caches.open(version);
switch (type) { switch (type) {
case "add-cache": case "add-cache":
{ {
const cached = await cache.match(e.data.url); const cached = await cache.match(e.data.url);
if (!cached) { if (!cached) {
await cache.add(e.data.url); await cache.add(e.data.url);
} }
} }
break; break;
case "define-route": case "define-route":
g.router = createRouter({ strictTrailingSlash: false }); g.router = createRouter({ strictTrailingSlash: false });
for (const route of e.data.routes) { for (const route of e.data.routes) {
g.router.insert(route.url, route); g.router.insert(route.url, route);
} }
await activate(); await activate();
break; break;
} case "force-update":
{
const keys = await caches.keys();
await Promise.all(
keys.map(async (key) => {
if (key !== version) {
await caches.delete(key);
}
}),
);
await install();
}
break;
}
}); });

View File

@ -3,7 +3,7 @@ export const isLocalhost = () => {
"localhost", "localhost",
"127.0.0.1", "127.0.0.1",
"192.168", "192.168",
"trycloudflare.com", // "trycloudflare.com",
"ngrok", "ngrok",
].find((e) => location.hostname.includes(e)); ].find((e) => location.hostname.includes(e));
}; };

View File

@ -1,103 +1,101 @@
import { createId } from "@paralleldrive/cuid2";
import brotliPromise from "brotli-wasm"; import brotliPromise from "brotli-wasm";
import { spawn } from "bun"; import { spawn } from "bun";
import { dir } from "dir"; import { dir } from "dir";
import { fdir } from "fdir"; import { fdir } from "fdir";
import { statSync } from "fs"; import { existsAsync, listAsync, removeAsync, writeAsync } from "fs-jetpack";
import {
copyAsync,
existsAsync,
listAsync,
removeAsync,
writeAsync,
} from "fs-jetpack";
const brotli = await brotliPromise; const brotli = await brotliPromise;
await removeAsync(dir.path("app/web/.parcel-cache")); await removeAsync(dir.path("app/web/.parcel-cache"));
await removeAsync(dir.path("app/static")); await removeAsync(dir.path("app/static"));
await writeAsync(
dir.path("app/web/timestamp.ts"),
`export const version = "${createId().substring(0, 7)}";`,
);
const args = [ const args = [
"node", "node",
dir.path("node_modules/.bin/parcel"), dir.path("node_modules/.bin/parcel"),
"build", "build",
"./src/index.tsx", "./src/index.tsx",
// "--no-optimize", // "--no-optimize",
"--no-scope-hoist", "--no-scope-hoist",
"--dist-dir", "--dist-dir",
dir.path(`app/static`), dir.path(`app/static`),
]; ];
const parcel = spawn({ const parcel = spawn({
cmd: args, cmd: args,
cwd: dir.path("app/web"), cwd: dir.path("app/web"),
stdio: ["ignore", "inherit", "inherit"], stdio: ["ignore", "inherit", "inherit"],
}); });
await parcel.exited; await parcel.exited;
const public_br = dir.path("app/web/public-br"); const public_br = dir.path("app/web/public-br");
if (!(await existsAsync(public_br))) { if (!(await existsAsync(public_br))) {
const api = new fdir().withRelativePaths().crawl(dir.path("app/web/public")); const api = new fdir().withRelativePaths().crawl(dir.path("app/web/public"));
const files = api.sync(); const files = api.sync();
if (files) { if (files) {
await Promise.all( await Promise.all(
files.map(async (file) => { files.map(async (file) => {
const br = brotli.compress( const br = brotli.compress(
new Uint8Array( new Uint8Array(
await Bun.file(dir.path(`app/web/public/${file}`)).arrayBuffer() await Bun.file(dir.path(`app/web/public/${file}`)).arrayBuffer(),
), ),
{ quality: 11 } { quality: 11 },
); );
if (br) { if (br) {
console.log(`Compressing [public] ${file}`); console.log(`Compressing [public] ${file}`);
await writeAsync( await writeAsync(
dir.path(`app/web/public-br/${file}`), dir.path(`app/web/public-br/${file}`),
Buffer.from(br) Buffer.from(br),
); );
} }
}) }),
); );
} }
} }
const static_br = dir.path("app/static-br"); const static_br = dir.path("app/static-br");
await removeAsync(static_br); await removeAsync(static_br);
const files = await listAsync(dir.path("app/static")); const files = await listAsync(dir.path("app/static"));
if (files) { if (files) {
await Promise.all( // await Promise.all(
files // files
.filter((file) => statSync(dir.path(`app/static/${file}`)).isFile()) // .filter((file) => statSync(dir.path(`app/static/${file}`)).isFile())
.map(async (file) => { // .map(async (file) => {
if (!(await Bun.file(dir.path(`app/static-br/${file}`)).exists())) { // if (!(await Bun.file(dir.path(`app/static-br/${file}`)).exists())) {
const br = brotli.compress( // const br = brotli.compress(
new Uint8Array( // new Uint8Array(
await Bun.file(dir.path(`app/static/${file}`)).arrayBuffer() // await Bun.file(dir.path(`app/static/${file}`)).arrayBuffer(),
), // ),
{ quality: 11 } // { quality: 11 },
); // );
if (br) { // if (br) {
console.log(`Compressing [static] ${file}`); // console.log(`Compressing [static] ${file}`);
await writeAsync( // await writeAsync(
dir.path(`app/static-br/${file}`), // dir.path(`app/static-br/${file}`),
Buffer.from(br) // Buffer.from(br),
); // );
} // }
} // }
}) // }),
); // );
// const pub = await listAsync(dir.path("app/web/public-br"));
const pub = await listAsync(dir.path("app/web/public-br")); // if (pub) {
if (pub) { // await Promise.all(
await Promise.all( // pub.map(async (file) => {
pub.map(async (file) => { // if (await existsAsync(`app/static-br/${file}`)) {
if (await existsAsync(`app/static-br/${file}`)) { // await removeAsync(`app/static-br/${file}`);
await removeAsync(`app/static-br/${file}`); // }
} // await copyAsync(
await copyAsync( // dir.path(`app/web/public-br/${file}`),
dir.path(`app/web/public-br/${file}`), // dir.path(`app/static-br/${file}`),
dir.path(`app/static-br/${file}`) // );
); // }),
}) // );
); // }
}
} }
await import("./build-site"); await import("./build-site");

View File

@ -9,58 +9,66 @@ import { syncActionDefinition } from "utils/sync-def";
import { user } from "../../app/srv/ws/sync/entity/user"; import { user } from "../../app/srv/ws/sync/entity/user";
import { snapshot } from "../../app/srv/ws/sync/entity/snapshot"; import { snapshot } from "../../app/srv/ws/sync/entity/snapshot";
import { initSrv } from "../../app/srv/init"; import { initSrv } from "../../app/srv/init";
import { createId } from "@paralleldrive/cuid2";
import { prepareApiRoutes } from "./server/api/api-scan"; import { prepareApiRoutes } from "./server/api/api-scan";
import { writeAsync } from "fs-jetpack";
import { dir } from "dir";
g.status = "init"; g.status = "init";
await writeAsync(
dir.path("app/web/timestamp.ts"),
`export const version = "${createId().substring(0, 7)}";`,
);
if (!g.Y) { if (!g.Y) {
g.Y = await import("yjs"); g.Y = await import("yjs");
g.syncronize = (await import("y-pojo")).syncronize; g.syncronize = (await import("y-pojo")).syncronize;
await createLogger(); await createLogger();
g.api = {}; g.api = {};
g.mode = process.argv.includes("dev") ? "dev" : "prod"; g.mode = process.argv.includes("dev") ? "dev" : "prod";
g.datadir = g.mode == "prod" ? "../data" : "data"; g.datadir = g.mode == "prod" ? "../data" : "data";
g.port = parseInt(process.env.PORT || "4550"); g.port = parseInt(process.env.PORT || "4550");
g.log.info(g.mode === "dev" ? "DEVELOPMENT" : "PRODUCTION"); g.log.info(g.mode === "dev" ? "DEVELOPMENT" : "PRODUCTION");
if (g.mode === "dev") { if (g.mode === "dev") {
await startDevWatcher(); await startDevWatcher();
} }
/** init lmdb */ /** init lmdb */
user.conf.init(); user.conf.init();
snapshot.init(); snapshot.init();
} }
const db = g.db; const db = g.db;
if (!db) { if (!db) {
await preparePrisma(); await preparePrisma();
await ensureNotRunning(); await ensureNotRunning();
const db = g.db; const db = g.db;
if (db) { if (db) {
db.$connect() db.$connect()
.catch((e: any) => { .catch((e: any) => {
g.log.error(`[DB ERROR]\n${e.message}`); g.log.error(`[DB ERROR]\n${e.message}`);
}) })
.then(() => { .then(() => {
g.log.info("Database connected"); g.log.info("Database connected");
}); });
} }
} }
if (!g.apiPrepared) { if (!g.apiPrepared) {
await initSrv(); await initSrv();
await syncActionDefinition(); await syncActionDefinition();
g.log.info("WS Action defined"); g.log.info("WS Action defined");
await prepareApiRoutes(); await prepareApiRoutes();
await prepareAPITypes(); await prepareAPITypes();
g.log.info("API Prepared"); g.log.info("API Prepared");
g.apiPrepared = true; g.apiPrepared = true;
} }
if (!g.parcel) { if (!g.parcel) {
await parcelBuild(); await parcelBuild();
} }
const { createServer } = await import("./server/create"); const { createServer } = await import("./server/create");

View File

@ -1,5 +1,5 @@
import { dir } from "dir"; import { dir } from "dir";
import { inspectTreeAsync } from "fs-jetpack"; import { exists, inspectTreeAsync } from "fs-jetpack";
import { InspectTreeResult } from "fs-jetpack/types"; import { InspectTreeResult } from "fs-jetpack/types";
import { join } from "path"; import { join } from "path";
import { watch } from "fs"; import { watch } from "fs";
@ -8,83 +8,88 @@ import mime from "mime";
import { g } from "utils/global"; import { g } from "utils/global";
const web = { const web = {
get path() { brExists: null as null | boolean,
if (g.mode === "dev") return "static"; get path() {
return "static-br"; if (g.mode === "dev") return "static";
}, if (this.brExists === null) {
this.brExists = !!exists(dir.path("app/static-br"));
}
if (this.brExists) return "static-br";
else return "static";
},
}; };
const cache = { const cache = {
static: {} as Record< static: {} as Record<
string, string,
{ type: string; content: any; compression: "" | "br" } { type: string; content: any; compression: "" | "br" }
>, >,
}; };
export const serveStatic = { export const serveStatic = {
init: async () => { init: async () => {
const list = await inspectTreeAsync(dir.path(`app/${web.path}`)); const list = await inspectTreeAsync(dir.path(`app/${web.path}`));
const walk = async ( const walk = async (
list: InspectTreeResult, list: InspectTreeResult,
parent?: InspectTreeResult[] parent?: InspectTreeResult[],
) => { ) => {
if (list.type === "dir") { if (list.type === "dir") {
for (const item of list.children) { for (const item of list.children) {
await walk(item, [...(parent || []), list]); await walk(item, [...(parent || []), list]);
} }
} else { } else {
const path = join(...(parent || []).map((e) => e.name), list.name); const path = join(...(parent || []).map((e) => e.name), list.name);
const file = Bun.file(dir.path(`app/${path}`)); const file = Bun.file(dir.path(`app/${path}`));
if (await file.exists()) { if (await file.exists()) {
cache.static[path.substring(web.path.length)] = { cache.static[path.substring(web.path.length)] = {
type: mime.getType(path) || "application/octet-stream", type: mime.getType(path) || "application/octet-stream",
compression: g.mode === "prod" ? "br" : "", compression: g.mode === "prod" ? "br" : "",
content: await file.arrayBuffer(), content: await file.arrayBuffer(),
}; };
} }
} }
}; };
if (list) { if (list) {
await walk(list); await walk(list);
} }
if (g.mode === "dev") { if (g.mode === "dev") {
watch(dir.path(`app/static`), async (_, filename) => { watch(dir.path(`app/static`), async (_, filename) => {
if (filename) { if (filename) {
const path = join("static", filename); const path = join("static", filename);
const file = Bun.file(dir.path(`app/${path}`)); const file = Bun.file(dir.path(`app/${path}`));
if (await file.exists()) { if (await file.exists()) {
cache.static[`/${filename}`] = { cache.static[`/${filename}`] = {
type: mime.getType(path) || "application/octet-stream", type: mime.getType(path) || "application/octet-stream",
compression: g.mode === "prod" ? "br" : "", compression: g.mode === "prod" ? "br" : "",
content: await file.arrayBuffer(), content: await file.arrayBuffer(),
}; };
} }
} }
}); });
} }
}, },
exists: (url: URL) => { exists: (url: URL) => {
return !!cache.static[url.pathname]; return !!cache.static[url.pathname];
}, },
serve: (url: URL) => { serve: (url: URL) => {
let file = cache.static[url.pathname]; let file = cache.static[url.pathname];
if (file) { if (file) {
return new Response(file.content, { return new Response(file.content, {
headers: { headers: {
...{ "content-type": file.type }, ...{ "content-type": file.type },
...(file.compression ? { "content-encoding": file.compression } : {}), ...(file.compression ? { "content-encoding": file.compression } : {}),
}, },
}); });
} }
file = cache.static["/index.html"]; file = cache.static["/index.html"];
if (file) { if (file) {
return new Response(file.content, { return new Response(file.content, {
headers: { headers: {
...{ "content-type": file.type }, ...{ "content-type": file.type },
...(file.compression ? { "content-encoding": file.compression } : {}), ...(file.compression ? { "content-encoding": file.compression } : {}),
}, },
}); });
} }
}, },
}; };

View File

@ -1,22 +1,22 @@
import { $ } from "execa"; import { $ } from "execa";
import { existsAsync } from "fs-jetpack"; import { exists, existsAsync } from "fs-jetpack";
import { dir } from "./dir"; import { dir } from "./dir";
import { g } from "./global"; import { g } from "./global";
export const preparePrisma = async () => { export const preparePrisma = async () => {
if ( if (
(await existsAsync(dir.path("app/db/.env"))) || (await existsAsync(dir.path("app/db/.env"))) ||
process.env.DATABASE_URL process.env.DATABASE_URL
) { ) {
if (g.mode === "prod") { if (g.mode === "prod" && exists(dir.path("app/static-br"))) {
g.log.info("Prisma: db pull & generate"); g.log.info("Prisma: db pull & generate");
await $({ cwd: dir.path(`app/db`) })`bun prisma db pull`; await $({ cwd: dir.path(`app/db`) })`bun prisma db pull`;
await $({ cwd: dir.path(`app/db`) })`bun prisma generate`; await $({ cwd: dir.path(`app/db`) })`bun prisma generate`;
} }
const { PrismaClient } = await import("../../../app/db/db");
g.db = new PrismaClient();
}
g.dburl = process.env.DATABASE_URL || ""; const { PrismaClient } = await import("../../../app/db/db");
g.db = new PrismaClient();
}
g.dburl = process.env.DATABASE_URL || "";
}; };