Skip to content

Commit

Permalink
Fix development server bug wherein onLogRequestBody would never be …
Browse files Browse the repository at this point in the history
…called (#104)
  • Loading branch information
mangs authored Oct 3, 2024
1 parent a94f5f7 commit ea6a26e
Show file tree
Hide file tree
Showing 16 changed files with 99 additions and 92 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Changelog

## 2.32.1

- Fix development server bug wherein `onLogRequestBody` would never be called
- Update dependency versions to latest

## 2.32.0

- Add a `getOrHead()` matcher to the router for matching `GET` or `HEAD` requests with a single configuration definition
Expand Down
Binary file modified bun.lockb
Binary file not shown.
3 changes: 3 additions & 0 deletions config/eslint/eslintConfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
{
"root": true,
"extends": ["@babbel/eslint-config/jsdoc-typescript", "@babbel/eslint-config/bun"],
"settings": {
"import/core-modules": ["bun"]
},
"overrides": [
{
"files": [
Expand Down
19 changes: 9 additions & 10 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mangs/bun-utils",
"version": "2.32.0",
"version": "2.32.1",
"author": "Eric L. Goldstein",
"description": "Useful utils for your Bun projects",
"engines": {
Expand Down Expand Up @@ -61,20 +61,19 @@
"reinstall:use-lock-file": "bun run --silent delete:node-modules && bun install --frozen-lockfile"
},
"dependencies": {
"type-fest": "4.26.0"
"type-fest": "4.26.1"
},
"devDependencies": {
"@babbel/eslint-config": "2.0.3",
"@types/bun": "1.1.8",
"eslint": "8.57.0",
"@babbel/eslint-config": "2.1.1",
"@types/bun": "1.1.10",
"eslint": "8.57.1",
"eslint-config-prettier": "9.1.0",
"eslint-plugin-jsdoc": "50.2.2",
"marked": "14.1.1",
"marked": "14.1.2",
"prettier": "3.3.3",
"typedoc": "0.26.6",
"typedoc": "0.26.7",
"typedoc-github-wiki-theme": "2.0.0",
"typedoc-plugin-markdown": "4.2.7",
"typescript": "5.5.4"
"typedoc-plugin-markdown": "4.2.9",
"typescript": "5.6.2"
},
"optionalDependencies": {
"sharp": "0.33.5"
Expand Down
4 changes: 2 additions & 2 deletions scripts/bun/checkEnvironmentSymlinks.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@
*/

// External Imports
import { lstat, realpath } from 'node:fs/promises';
import { nanoseconds } from 'bun';
import { lstat, realpath } from 'node:fs/promises';
import nodePath from 'node:path';

// Internal Imports
import { getPathsRecursive } from '../../src/filesystemUtils.mts';
import { getPerformanceLabel, printError, printSuccess } from '../../src/consoleUtils.mts';
import { getPathsRecursive } from '../../src/filesystemUtils.mts';

// Local Types
interface PathError {
Expand Down
6 changes: 3 additions & 3 deletions scripts/bun/checkEnvironmentVersions.mts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
*/

// External Imports
import { file, nanoseconds, semver, version as bunVersion } from 'bun';
import { version as bunVersion, file, nanoseconds, semver } from 'bun';
import { readdir } from 'node:fs/promises';

// Internal Imports
import { findMissingPaths } from '../../src/filesystemUtils.mts';
import { getPerformanceLabel, printError, printSuccess } from '../../src/consoleUtils.mts';
import { findMissingPaths } from '../../src/filesystemUtils.mts';

// Type Imports
import type { PackageJson } from 'type-fest';
Expand All @@ -24,7 +24,7 @@ const packageJson = JSON.parse(await file('./package.json').text()) as PackageJs

// Local Types
interface EnvironmentVersions {
[key: string]: string | Record<string, unknown>;
[key: string]: Record<string, unknown> | string;
bun: {
engineVersion: string;
githubVersion: string;
Expand Down
6 changes: 3 additions & 3 deletions scripts/bun/optimizeImages.mts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

// External Imports
import { argv, file, Glob, nanoseconds, write } from 'bun';
import { parseArgs } from 'node:util';
import nodePath from 'node:path';
import { parseArgs } from 'node:util';
import sharp from 'sharp'; // eslint-disable-line import/no-extraneous-dependencies -- used only for development

// Internal Imports
import { getHumanReadableFilesize } from '../../src/filesystemUtils.mts';
import { getPerformanceLabel } from '../../src/consoleUtils.mts';
import { getHumanReadableFilesize } from '../../src/filesystemUtils.mts';

// Type Imports
import type { AvifOptions, JpegOptions, PngOptions, WebpOptions } from 'sharp';
Expand Down Expand Up @@ -84,7 +84,7 @@ async function main() {
style: 'percent',
}).format(sizeAfter / sizeBefore);
const newFilePath = fullFilePath.replace(/\.\w+$/, `.${formatKeyed}`);
await write(newFilePath, fileArrayBuffer);
await write(newFilePath, fileArrayBuffer.buffer);

console.log(
`${newFilePath}: ${getHumanReadableFilesize(sizeBefore)} to ${getHumanReadableFilesize(sizeAfter)} (${percentage}) ${getPerformanceLabel(startTime)}`,
Expand Down
4 changes: 2 additions & 2 deletions scripts/bun/startDevelopmentServer.mts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

// External Imports
import { argv } from 'bun';
import { parseArgs } from 'node:util';
import nodePath from 'node:path';
import { parseArgs } from 'node:util';

// Internal Imports
import { startDevelopmentServer } from '../../src/networkUtils.mts';
Expand All @@ -16,7 +16,7 @@ import { startDevelopmentServer } from '../../src/networkUtils.mts';
import type { ServerConfiguration } from '../../src/networkUtils.mts';

// Local Types
type FetchFunction = (request: Request) => Response | Promise<Response>;
type FetchFunction = (request: Request) => Promise<Response> | Response;
type CodeModule = Record<string, { fetch: FetchFunction }>;

// Local Functions
Expand Down
2 changes: 1 addition & 1 deletion scripts/git/installGitHooks.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import { readdir, symlink } from 'node:fs/promises';

// Internal Imports
import { isDirectoryAccessible } from '../../src/filesystemUtils.mts';
import { printError, printInfo, printSuccess } from '../../src/consoleUtils.mts';
import { isDirectoryAccessible } from '../../src/filesystemUtils.mts';

// Type Imports
import type { PackageJson } from 'type-fest';
Expand Down
2 changes: 1 addition & 1 deletion scripts/git/removeGitHooks.mts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
import { readdir, unlink } from 'node:fs/promises';

// Internal Imports
import { isDirectoryAccessible } from '../../src/filesystemUtils.mts';
import { printInfo, printSuccess } from '../../src/consoleUtils.mts';
import { isDirectoryAccessible } from '../../src/filesystemUtils.mts';

// Local Variables
const gitHooksPath = '.git/hooks';
Expand Down
2 changes: 1 addition & 1 deletion src/buildUtils.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/

// External Imports
import nodePath from 'node:path';
import { build, file, nanoseconds, stringWidth } from 'bun';
import nodePath from 'node:path';

// Internal Imports
import {
Expand Down
2 changes: 1 addition & 1 deletion src/filesystemUtils.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/

// External Imports
import { access, constants, readdir, unlink } from 'node:fs/promises';
import { file } from 'bun';
import { access, constants, readdir, unlink } from 'node:fs/promises';
import nodePath from 'node:path';

// Type Imports
Expand Down
14 changes: 7 additions & 7 deletions src/networkUtils.mts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
*/

// External Imports
import { format } from 'node:util';
import { file, inspect, serve, stringWidth } from 'bun';
import { format } from 'node:util';

// Internal Imports
import { cyan, dim, green, printError, red, yellow } from './consoleUtils.mts';
Expand Down Expand Up @@ -38,14 +38,14 @@ interface FetchRetryOptions extends FetchRequestInit {
* @returns New delay value.
*/
onChangeRetryDelay?: (delay: number) => number;
/**
* Delay between retries; `onChangeRetryDelay` affects how it changes between retry iterations.
*/
retryDelay?: number;
/**
* Maximum number of retries before an error is thrown.
*/
retries?: number;
/**
* Delay between retries; `onChangeRetryDelay` affects how it changes between retry iterations.
*/
retryDelay?: number;
/**
* Time until the `fetch` request times out; can alternatively be overridden by passing an `AbortSignal` value to the `options.signal` parameter member.
*/
Expand Down Expand Up @@ -105,7 +105,7 @@ interface ServerConfiguration extends Pick<ServeOptions, 'error' | 'hostname' |
* @param options Options object that combines `fetch`'s 2nd parameter with custom options.
* @returns Data returned by `fetch`.
*/
async function fetchWithRetry(url: string | URL | Request, options: FetchRetryOptions = {}) {
async function fetchWithRetry(url: Request | string | URL, options: FetchRetryOptions = {}) {
const {
onBypassRetry = (statusCode) => statusCode < 500,
onChangeRetryDelay = (delay) => delay * 2,
Expand Down Expand Up @@ -234,7 +234,7 @@ async function startDevelopmentServer(
if (responseError) {
throw responseError;
}
if (request.body && onLogRequestBody(request, response)) {
if (requestClone.body && onLogRequestBody(requestClone, response)) {
const isJsonBody = requestClone.headers.get('content-type') === 'application/json';
const requestBody = isJsonBody
? ((await requestClone.json()) as unknown)
Expand Down
4 changes: 2 additions & 2 deletions src/performanceUtils.mts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { buildServerTimingHeader } from './timeUtils.mts';
* ```
*/
async function measureCpuUsage<TRunner>(
runner: () => TRunner | Promise<TRunner>,
runner: () => Promise<TRunner> | TRunner,
usesOneThread = false,
startTime = performance.now(),
) {
Expand Down Expand Up @@ -51,7 +51,7 @@ async function measureCpuUsage<TRunner>(
async function measurePerformanceMetrics<TRunner>(
metricName: string,
request: Request,
runner: () => TRunner | Promise<TRunner>,
runner: () => Promise<TRunner> | TRunner,
metricDescription?: string,
usesOneThread = false,
) {
Expand Down
112 changes: 56 additions & 56 deletions src/routerUtils.mts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { buildServerTimingHeader } from './timeUtils.mts';

// Local Types
type HttpRequestMethod = (typeof httpRequestMethods)[number]; // eslint-disable-line no-use-before-define -- defined nearby
type RouteHandlerFunction = (request: Request) => Response | Promise<Response>;
type RouteHandlerFunction = (request: Request) => Promise<Response> | Response;
type RouteHandlerLazyLoaded = Record<string, () => Promise<Record<string, RouteHandlerFunction>>>;
type RouteHandler = RouteHandlerFunction | RouteHandlerLazyLoaded;
type RouteEntry = Parameters<InstanceType<typeof Router>['get']>; // eslint-disable-line no-use-before-define -- only used for types
Expand Down Expand Up @@ -53,6 +53,16 @@ const httpRequestMethods = [
* ```
*/
class Router {
/**
* Array of route tuples.
*/
#routes: Routes;

/**
* Whether or not `Server-Timing` headers are appended to the `Request` object.
*/
#usesServerTiming: boolean;

/**
* Constructor that creates an empty array for route definitions.
* @param usesServerTiming Boolean indicating if `Server-Timing` headers are appended to the request object.
Expand All @@ -76,61 +86,6 @@ class Router {
// });
}

/**
* Array of route tuples.
*/
#routes: Routes;

/**
* Whether or not `Server-Timing` headers are appended to the `Request` object.
*/
#usesServerTiming: boolean;

/**
* Handles the incoming request after all route definitions have been made.
* @param request The `Request` object for the incoming request.
* @returns A `Response` object to build the response sent to the requester.
*/
async handleRequest(request: Request) {
const startTime = performance.now();
const { method } = request;
const { pathname: requestPath } = new URL(request.url);

for (const [routeMethod, [routePath, routeHandler]] of this.#routes) {
if (routeMethod !== 'ALL' && method !== routeMethod) {
continue; // eslint-disable-line no-continue -- ignore HTTP methods that don't match the request
}

// eslint-disable-next-line unicorn/prefer-regexp-test -- false positive: this is a glob.match() not string.match()
if (new Glob(routePath).match(requestPath)) {
if (typeof routeHandler === 'function') {
if (this.#usesServerTiming) {
request.headers.append(...buildServerTimingHeader('routeSync', startTime)[0]);
}
return routeHandler(request);
}

const [entry] = Object.entries(routeHandler);
if (!entry) {
throw new TypeError('No lazy-loaded configuration option provided');
}

const [moduleKey, modulePromiseFunction] = entry;
const routeModule = await modulePromiseFunction(); // eslint-disable-line no-await-in-loop -- this will only ever await once
const targetFunction = routeModule[moduleKey];
if (typeof targetFunction === 'function') {
if (this.#usesServerTiming) {
request.headers.append(...buildServerTimingHeader('routeAsync', startTime)[0]);
}
return targetFunction(request);
}
throw new TypeError(`Lazy-loaded route handler target "${moduleKey}" was not a function`);
}
}

throw new Error('No routes matched!');
}

/**
* Register a route handler for the specified HTTP request method.
* @param path A path-like string that will be used to match against the incoming request's path.
Expand Down Expand Up @@ -202,6 +157,51 @@ class Router {
return this.#handleMethod(path, routeHandler, 'GET').#handleMethod(path, routeHandler, 'HEAD');
}

/**
* Handles the incoming request after all route definitions have been made.
* @param request The `Request` object for the incoming request.
* @returns A `Response` object to build the response sent to the requester.
*/
async handleRequest(request: Request) {
const startTime = performance.now();
const { method } = request;
const { pathname: requestPath } = new URL(request.url);

for (const [routeMethod, [routePath, routeHandler]] of this.#routes) {
if (routeMethod !== 'ALL' && method !== routeMethod) {
continue; // eslint-disable-line no-continue -- ignore HTTP methods that don't match the request
}

// eslint-disable-next-line unicorn/prefer-regexp-test -- false positive: this is a glob.match() not string.match()
if (new Glob(routePath).match(requestPath)) {
if (typeof routeHandler === 'function') {
if (this.#usesServerTiming) {
request.headers.append(...buildServerTimingHeader('routeSync', startTime)[0]);
}
return routeHandler(request);
}

const [entry] = Object.entries(routeHandler);
if (!entry) {
throw new TypeError('No lazy-loaded configuration option provided');
}

const [moduleKey, modulePromiseFunction] = entry;
const routeModule = await modulePromiseFunction(); // eslint-disable-line no-await-in-loop -- this will only ever await once
const targetFunction = routeModule[moduleKey];
if (typeof targetFunction === 'function') {
if (this.#usesServerTiming) {
request.headers.append(...buildServerTimingHeader('routeAsync', startTime)[0]);
}
return targetFunction(request);
}
throw new TypeError(`Lazy-loaded route handler target "${moduleKey}" was not a function`);
}
}

throw new Error('No routes matched!');
}

/**
* Register a route handler that matches the `HEAD` HTTP request method.
* @param path A path-like string that will be used to match against the incoming request's path.
Expand Down
Loading

0 comments on commit ea6a26e

Please sign in to comment.