Update API routes function. Add middleware. Update README.md

This commit is contained in:
2026-03-17 19:38:06 +01:00
parent ee1fb5897e
commit 35930857fd
20 changed files with 747 additions and 312 deletions
+28
View File
@@ -0,0 +1,28 @@
import { Command } from "commander";
import grabConfig from "../../functions/grab-config";
import init from "../../functions/init";
import type { BunextConfig } from "../../types";
import allPagesBundler from "../../functions/bundler/all-pages-bundler";
export default function () {
return new Command("build")
.description("Build Project")
.action(async () => {
console.log(`Building Project ...`);
process.env.NODE_ENV = "production";
await init();
const config: BunextConfig = (await grabConfig()) || {};
global.CONFIG = {
...config,
development: true,
};
allPagesBundler({
exit_after_first_build: true,
});
});
}
+24
View File
@@ -0,0 +1,24 @@
import { Command } from "commander";
import grabConfig from "../../functions/grab-config";
import startServer from "../../functions/server/start-server";
import init from "../../functions/init";
import type { BunextConfig } from "../../types";
export default function () {
return new Command("dev")
.description("Run development server")
.action(async () => {
console.log(`Running development server ...`);
await init();
const config: BunextConfig = (await grabConfig()) || {};
global.CONFIG = {
...config,
development: true,
};
await startServer({ dev: true });
});
}
+20
View File
@@ -0,0 +1,20 @@
import { Command } from "commander";
import grabConfig from "../../functions/grab-config";
import startServer from "../../functions/server/start-server";
import init from "../../functions/init";
export default function () {
return new Command("start")
.description("Start production server")
.action(async () => {
console.log(`Starting production server ...`);
await init();
const config = await grabConfig();
global.CONFIG = { ...config };
await startServer();
});
}
+1 -1
View File
@@ -41,7 +41,7 @@ type Params = {
export default async function allPagesBundler(params?: Params) {
const pages = grabAllPages({ exclude_api: true });
const { ClientRootElementIDName, ClientRootComponentWindowName } =
await grabConstants();
grabConstants();
const virtualEntries: Record<string, string> = {};
const dev = isDevelopment();
+31 -25
View File
@@ -1,9 +1,5 @@
import type { Server } from "bun";
import type {
APIResponseObject,
BunextServerRouteConfig,
BunxRouteParams,
} from "../../types";
import type { BunextServerRouteConfig, BunxRouteParams } from "../../types";
import grabRouteParams from "../../utils/grab-route-params";
import grabConstants from "../../utils/grab-constants";
import grabRouter from "../../utils/grab-router";
@@ -13,14 +9,10 @@ type Params = {
server: Server;
};
export default async function ({
req,
server,
}: Params): Promise<APIResponseObject | undefined> {
export default async function ({ req, server }: Params): Promise<Response> {
const url = new URL(req.url);
const { MBInBytes, ServerDefaultRequestBodyLimitBytes } =
await grabConstants();
const { MBInBytes, ServerDefaultRequestBodyLimitBytes } = grabConstants();
const router = grabRouter();
@@ -28,13 +20,19 @@ export default async function ({
if (!match?.filePath) {
const errMsg = `Route ${url.pathname} not found`;
// console.error(errMsg);
return {
success: false,
status: 401,
msg: errMsg,
};
return Response.json(
{
success: false,
msg: errMsg,
},
{
status: 401,
headers: {
"Content-Type": "application/json",
},
},
);
}
const routeParams: BunxRouteParams = await grabRouteParams({ req });
@@ -52,17 +50,25 @@ export default async function ({
size > config.maxRequestBodyMB * MBInBytes) ||
size > ServerDefaultRequestBodyLimitBytes
) {
return {
success: false,
status: 413,
msg: "Request Body Too Large!",
};
return Response.json(
{
success: false,
msg: "Request Body Too Large!",
},
{
status: 413,
headers: {
"Content-Type": "application/json",
},
},
);
}
}
const res: APIResponseObject = await module["default"](
routeParams as BunxRouteParams,
);
const res: Response = await module["default"]({
...routeParams,
server,
} as BunxRouteParams);
return res;
}
+16 -8
View File
@@ -5,6 +5,7 @@ import grabDirNames from "../../utils/grab-dir-names";
import handleWebPages from "./web-pages/handle-web-pages";
import handleRoutes from "./handle-routes";
import isDevelopment from "../../utils/is-development";
import grabConstants from "../../utils/grab-constants";
type Params = {
dev?: boolean;
@@ -19,6 +20,20 @@ export default async function (params?: Params): Promise<ServeOptions> {
try {
const url = new URL(req.url);
const { config } = grabConstants();
if (config?.middleware) {
const middleware_res = await config.middleware({
req,
url,
server,
});
if (typeof middleware_res == "object") {
return middleware_res;
}
}
if (url.pathname === "/__hmr" && isDevelopment()) {
const referer_url = new URL(
req.headers.get("referer") || "",
@@ -69,14 +84,7 @@ export default async function (params?: Params): Promise<ServeOptions> {
}
if (url.pathname.startsWith("/api/")) {
const res = await handleRoutes({ req, server });
return new Response(JSON.stringify(res), {
status: res?.status,
headers: {
"Content-Type": "application/json",
},
});
return await handleRoutes({ req, server });
}
if (url.pathname.startsWith("/public/")) {
-7
View File
@@ -5,8 +5,6 @@ import rebuildBundler from "./rebuild-bundler";
const { SRC_DIR } = grabDirNames();
const PAGE_FILE_RE = /\.(tsx?|jsx?|css)$/;
export default function watcher() {
watch(
SRC_DIR,
@@ -16,12 +14,7 @@ export default function watcher() {
},
async (event, filename) => {
if (!filename) return;
const file_path = path.join(SRC_DIR, filename);
// if (!PAGE_FILE_RE.test(filename)) return;
// "change" events (file content modified) are already handled by
// esbuild's internal ctx.watch(). Only "rename" (create or delete)
// requires a full rebuild because entry points have changed.
if (event !== "rename") return;
if (global.RECOMPILING) return;
@@ -16,7 +16,7 @@ export default async function genWebHTML({
routeParams,
}: LivePageDistGenParams) {
const { ClientRootElementIDName, ClientWindowPagePropsName } =
await grabContants();
grabContants();
const { renderToString } = await import(
path.join(process.cwd(), "node_modules", "react-dom", "server")
@@ -125,7 +125,7 @@ export default async function grabPageComponent({
: undefined;
const Component = module.default as FC<any>;
const Head = module.head as FC<any>;
const Head = module.Head as FC<any>;
const component = RootComponent ? (
<RootComponent {...serverRes}>
Executable
+82
View File
@@ -0,0 +1,82 @@
#!/usr/bin/env bun
import { program } from "commander";
import start from "./commands/start";
import dev from "./commands/dev";
import ora, { type Ora } from "ora";
import type {
BundlerCTXMap,
BunextConfig,
GlobalHMRControllerObject,
} from "./types";
import type { FileSystemRouter, Server } from "bun";
import init from "./functions/init";
import grabDirNames from "./utils/grab-dir-names";
import build from "./commands/build";
import type { BuildContext } from "esbuild";
/**
* # Declare Global Variables
*/
declare global {
var ORA_SPINNER: Ora;
var CONFIG: BunextConfig;
var SERVER: Server | undefined;
var RECOMPILING: boolean;
var WATCHER_TIMEOUT: any;
var ROUTER: FileSystemRouter;
var HMR_CONTROLLERS: GlobalHMRControllerObject[];
var LAST_BUILD_TIME: number;
var BUNDLER_CTX: BuildContext | undefined;
var BUNDLER_CTX_MAP: BundlerCTXMap[] | undefined;
var IS_FIRST_BUNDLE_READY: boolean;
var BUNDLER_REBUILDS: 0;
}
global.ORA_SPINNER = ora();
global.ORA_SPINNER.clear();
global.HMR_CONTROLLERS = [];
global.IS_FIRST_BUNDLE_READY = false;
global.BUNDLER_REBUILDS = 0;
await init();
const { PAGES_DIR } = grabDirNames();
const router = new Bun.FileSystemRouter({
style: "nextjs",
dir: PAGES_DIR,
});
global.ROUTER = router;
/**
* # Describe Program
*/
program
.name(`bunext`)
.description(`A React Next JS replacement built with bun JS`)
.version(`1.0.0`);
/**
* # Declare Commands
*/
program.addCommand(dev());
program.addCommand(start());
program.addCommand(build());
/**
* # Handle Unavailable Commands
*/
program.on("command:*", () => {
console.error(
"Invalid command: %s\nSee --help for a list of available commands.",
program.args.join(" "),
);
process.exit(1);
});
/**
* # Parse Arguments
*/
program.parse(Bun.argv);
+2
View File
@@ -0,0 +1,2 @@
const config = {};
export default config;
+11 -1
View File
@@ -48,6 +48,15 @@ export type BunextConfig = {
globalVars?: { [k: string]: any };
port?: number;
development?: boolean;
middleware?: (
params: BunextConfigMiddlewareParams,
) => Promise<Response | undefined> | Response | undefined;
};
export type BunextConfigMiddlewareParams = {
req: Request;
url: URL;
server: Server;
};
export type GetRouteReturn = {
@@ -69,6 +78,7 @@ export type BunxRouteParams = {
* Intercept and Transform the response object
*/
resTransform?: (res: Response) => Promise<Response> | Response;
server?: Server;
};
export interface PostInsertReturn {
@@ -146,7 +156,7 @@ export type BunextPageModule = {
default: FC<any>;
server?: BunextPageServerFn;
meta?: BunextPageModuleMeta | BunextPageModuleMetaFn;
head?: FC<BunextPageHeadFCProps>;
Head?: FC<BunextPageHeadFCProps>;
};
export type BunextPageModuleMetaFn = (params: {
+3 -5
View File
@@ -1,8 +1,5 @@
import path from "path";
import grabConfig from "../functions/grab-config";
export default async function grabConstants() {
const config = await grabConfig();
export default function grabConstants() {
const config = global.CONFIG;
const MB_IN_BYTES = 1024 * 1024;
const ClientWindowPagePropsName = "__PAGE_PROPS__";
@@ -20,5 +17,6 @@ export default async function grabConstants() {
ServerDefaultRequestBodyLimitBytes,
ClientRootComponentWindowName,
MaxBundlerRebuilds,
config,
} as const;
}
+1 -1
View File
@@ -1,4 +1,3 @@
import type { Server } from "bun";
import type { BunxRouteParams } from "../types";
import deserializeQuery from "./deserialize-query";
@@ -26,6 +25,7 @@ export default async function grabRouteParams({
url,
query,
body,
server: global.SERVER,
};
return routeParams;