Add a first cut at an express-based backend
This commit is contained in:
1
express/.gitignore
vendored
Normal file
1
express/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
out/
|
||||||
120
express/app.ts
Normal file
120
express/app.ts
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
import express, {
|
||||||
|
Request as ExpressRequest,
|
||||||
|
Response as ExpressResponse,
|
||||||
|
} from "express";
|
||||||
|
import { match } from "path-to-regexp";
|
||||||
|
import { contentTypes } from "./content-types";
|
||||||
|
import { httpCodes } from "./http-codes";
|
||||||
|
import { routes } from "./routes";
|
||||||
|
import { services } from "./services";
|
||||||
|
// import { URLPattern } from 'node:url';
|
||||||
|
import {
|
||||||
|
Call,
|
||||||
|
InternalHandler,
|
||||||
|
Method,
|
||||||
|
ProcessedRoute,
|
||||||
|
Result,
|
||||||
|
Route,
|
||||||
|
massageMethod,
|
||||||
|
methodParser,
|
||||||
|
} from "./types";
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
|
||||||
|
services.logging.log({ source: "logging", text: ["1"] });
|
||||||
|
const processedRoutes: { [K in Method]: ProcessedRoute[] } = {
|
||||||
|
GET: [],
|
||||||
|
POST: [],
|
||||||
|
PUT: [],
|
||||||
|
PATCH: [],
|
||||||
|
DELETE: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
function isPromise<T>(value: T | Promise<T>): value is Promise<T> {
|
||||||
|
return typeof (value as any)?.then === "function";
|
||||||
|
}
|
||||||
|
|
||||||
|
routes.forEach((route: Route, _idx: number, _allRoutes: Route[]) => {
|
||||||
|
// const pattern /*: URLPattern */ = new URLPattern({ pathname: route.path });
|
||||||
|
const matcher = match<Record<string, string>>(route.path);
|
||||||
|
const methodList = route.methods;
|
||||||
|
|
||||||
|
const handler: InternalHandler = async (
|
||||||
|
request: ExpressRequest,
|
||||||
|
): Promise<Result> => {
|
||||||
|
const method = massageMethod(request.method);
|
||||||
|
|
||||||
|
console.log("method", method);
|
||||||
|
|
||||||
|
if (!methodList.includes(method)) {
|
||||||
|
// XXX: Worth asserting this?
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("request.originalUrl", request.originalUrl);
|
||||||
|
console.log("beavis");
|
||||||
|
|
||||||
|
// const p = new URL(request.originalUrl);
|
||||||
|
// const path = p.pathname;
|
||||||
|
|
||||||
|
// console.log("p, path", p, path)
|
||||||
|
|
||||||
|
console.log("ok");
|
||||||
|
|
||||||
|
const req: Call = {
|
||||||
|
pattern: route.path,
|
||||||
|
// path,
|
||||||
|
path: request.originalUrl,
|
||||||
|
method,
|
||||||
|
parameters: { one: 1, two: 2 },
|
||||||
|
request,
|
||||||
|
};
|
||||||
|
|
||||||
|
const retval = await route.handler(req);
|
||||||
|
return retval;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const [_idx, method] of methodList.entries()) {
|
||||||
|
const pr: ProcessedRoute = { matcher, method, handler };
|
||||||
|
|
||||||
|
processedRoutes[method].push(pr);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function handler(
|
||||||
|
req: ExpressRequest,
|
||||||
|
_res: ExpressResponse,
|
||||||
|
): Promise<Result> {
|
||||||
|
const method = await methodParser.parseAsync(req.method);
|
||||||
|
|
||||||
|
const byMethod = processedRoutes[method];
|
||||||
|
for (const [_idx, pr] of byMethod.entries()) {
|
||||||
|
const match = pr.matcher(req.url);
|
||||||
|
if (match) {
|
||||||
|
console.log("match", match);
|
||||||
|
const resp = await pr.handler(req);
|
||||||
|
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const retval: Result = {
|
||||||
|
code: httpCodes.clientErrors.NotFound,
|
||||||
|
contentType: contentTypes.text.plain,
|
||||||
|
result: "not found",
|
||||||
|
};
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
|
|
||||||
|
app.use(async (req: ExpressRequest, res: ExpressResponse) => {
|
||||||
|
const result0 = await handler(req, res);
|
||||||
|
|
||||||
|
const code = result0.code.code;
|
||||||
|
const result = result0.result;
|
||||||
|
|
||||||
|
console.log(result);
|
||||||
|
|
||||||
|
res.status(code).send(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.listen(3000);
|
||||||
14
express/check.sh
Executable file
14
express/check.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
check_dir="$DIR"
|
||||||
|
|
||||||
|
out_dir="$check_dir/out"
|
||||||
|
|
||||||
|
source "$check_dir"/../framework/shims/common
|
||||||
|
source "$check_dir"/../framework/shims/node.common
|
||||||
|
|
||||||
|
$ROOT/cmd pnpm tsc --outDir "$out_dir"
|
||||||
11
express/config.ts
Normal file
11
express/config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const config = {
|
||||||
|
database: {
|
||||||
|
user: "abc123",
|
||||||
|
password: "abc123",
|
||||||
|
host: "localhost",
|
||||||
|
port: "5432",
|
||||||
|
database: "abc123",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { config };
|
||||||
40
express/content-types.ts
Normal file
40
express/content-types.ts
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import { Extensible } from "./interfaces";
|
||||||
|
|
||||||
|
export type ContentType = string;
|
||||||
|
|
||||||
|
// FIXME: Fill this out (get an AI to do it)
|
||||||
|
|
||||||
|
const contentTypes = {
|
||||||
|
text: {
|
||||||
|
plain: "text/plain",
|
||||||
|
html: "text/html",
|
||||||
|
css: "text/css",
|
||||||
|
javascript: "text/javascript",
|
||||||
|
xml: "text/xml",
|
||||||
|
},
|
||||||
|
image: {
|
||||||
|
jpeg: "image/jpeg",
|
||||||
|
png: "image/png",
|
||||||
|
gif: "image/gif",
|
||||||
|
svgPlusXml: "image/svg+xml",
|
||||||
|
webp: "image/webp",
|
||||||
|
},
|
||||||
|
audio: {
|
||||||
|
mpeg: "audio/mpeg",
|
||||||
|
wav: "audio/wav",
|
||||||
|
},
|
||||||
|
video: {
|
||||||
|
mp4: "video/mp4",
|
||||||
|
webm: "video/webm",
|
||||||
|
xMsvideo: "video/x-msvideo",
|
||||||
|
},
|
||||||
|
application: {
|
||||||
|
json: "application/json",
|
||||||
|
pdf: "application/pdf",
|
||||||
|
zip: "application/zip",
|
||||||
|
xWwwFormUrlencoded: "x-www-form-urlencoded",
|
||||||
|
octetStream: "octet-stream",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { contentTypes };
|
||||||
7
express/deps.ts
Normal file
7
express/deps.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
// Database
|
||||||
|
//export { Client as PostgresClient } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||||
|
//export type { ClientOptions as PostgresOptions } from "https://deno.land/x/postgres@v0.19.3/mod.ts";
|
||||||
|
|
||||||
|
// Redis
|
||||||
|
//export { connect as redisConnect } from "https://deno.land/x/redis@v0.37.1/mod.ts";
|
||||||
|
//export type { Redis } from "https://deno.land/x/redis@v0.37.1/mod.ts";
|
||||||
0
express/extensible.ts
Normal file
0
express/extensible.ts
Normal file
19
express/handlers.ts
Normal file
19
express/handlers.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { contentTypes } from "./content-types";
|
||||||
|
import { httpCodes } from "./http-codes";
|
||||||
|
import { services } from "./services";
|
||||||
|
import { Call, Handler, Result } from "./types";
|
||||||
|
|
||||||
|
const multiHandler: Handler = async (call: Call): Promise<Result> => {
|
||||||
|
const code = httpCodes.success.OK;
|
||||||
|
const rn = services.random.randomNumber();
|
||||||
|
|
||||||
|
const retval: Result = {
|
||||||
|
code,
|
||||||
|
result: `that was ${call.method} (${rn})`,
|
||||||
|
contentType: contentTypes.text.plain,
|
||||||
|
};
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { multiHandler };
|
||||||
43
express/http-codes.ts
Normal file
43
express/http-codes.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { Extensible } from "./interfaces";
|
||||||
|
|
||||||
|
export type HttpCode = {
|
||||||
|
code: number;
|
||||||
|
name: string;
|
||||||
|
description?: string;
|
||||||
|
};
|
||||||
|
type Group = "success" | "redirection" | "clientErrors" | "serverErrors";
|
||||||
|
type CodeDefinitions = {
|
||||||
|
[K in Group]: {
|
||||||
|
[K: string]: HttpCode;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
// FIXME: Figure out how to brand CodeDefinitions in a way that isn't
|
||||||
|
// tedious.
|
||||||
|
|
||||||
|
const httpCodes: CodeDefinitions = {
|
||||||
|
success: {
|
||||||
|
OK: { code: 200, name: "OK", description: "" },
|
||||||
|
Created: { code: 201, name: "Created" },
|
||||||
|
Accepted: { code: 202, name: "Accepted" },
|
||||||
|
NoContent: { code: 204, name: "No content" },
|
||||||
|
},
|
||||||
|
redirection: {
|
||||||
|
// later
|
||||||
|
},
|
||||||
|
clientErrors: {
|
||||||
|
BadRequest: { code: 400, name: "Bad Request" },
|
||||||
|
Unauthorized: { code: 401, name: "Unauthorized" },
|
||||||
|
Forbidden: { code: 403, name: "Forbidden" },
|
||||||
|
NotFound: { code: 404, name: "Not Found" },
|
||||||
|
MethodNotAllowed: { code: 405, name: "Method Not Allowed" },
|
||||||
|
NotAcceptable: { code: 406, name: "Not Acceptable" },
|
||||||
|
// More later
|
||||||
|
},
|
||||||
|
serverErrors: {
|
||||||
|
InternalServerError: { code: 500, name: "Internal Server Error" },
|
||||||
|
NotImplemented: { code: 500, name: "Not implemented" },
|
||||||
|
// more later
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { httpCodes };
|
||||||
3
express/interfaces.ts
Normal file
3
express/interfaces.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
type Brand<K, T> = K & { readonly __brand: T };
|
||||||
|
|
||||||
|
export type Extensible = Brand<"Extensible", {}>;
|
||||||
44
express/logging.ts
Normal file
44
express/logging.ts
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
// internal-logging.ts
|
||||||
|
|
||||||
|
// FIXME: Move this to somewhere more appropriate
|
||||||
|
type AtLeastOne<T> = [T, ...T[]];
|
||||||
|
|
||||||
|
type MessageSource = "logging" | "diagnostic" | "user";
|
||||||
|
|
||||||
|
type Message = {
|
||||||
|
// FIXME: number probably isn't what we want here
|
||||||
|
timestamp?: number;
|
||||||
|
source: MessageSource;
|
||||||
|
|
||||||
|
text: AtLeastOne<string>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const m1: Message = { timestamp: 123, source: "logging", text: ["foo"] };
|
||||||
|
const m2: Message = {
|
||||||
|
timestamp: 321,
|
||||||
|
source: "diagnostic",
|
||||||
|
text: ["ok", "whatever"],
|
||||||
|
};
|
||||||
|
|
||||||
|
type FilterArgument = {
|
||||||
|
limit?: number;
|
||||||
|
before?: number;
|
||||||
|
after?: number;
|
||||||
|
|
||||||
|
// FIXME: add offsets to use instead of or in addition to before/after
|
||||||
|
|
||||||
|
match?: (string | RegExp)[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const log = (_message: Message) => {
|
||||||
|
// WRITEME
|
||||||
|
};
|
||||||
|
|
||||||
|
const getLogs = (filter: FilterArgument) => {
|
||||||
|
// WRITEME
|
||||||
|
};
|
||||||
|
|
||||||
|
// FIXME: there's scope for more specialized functions although they
|
||||||
|
// probably should be defined in terms of the basic ones here.
|
||||||
|
|
||||||
|
export { getLogs, log };
|
||||||
47
express/package.json
Normal file
47
express/package.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"name": "express",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"prettier": "prettier",
|
||||||
|
"nodemon": "nodemon dist/index.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"packageManager": "pnpm@10.12.4",
|
||||||
|
"dependencies": {
|
||||||
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
||||||
|
"@types/node": "^24.10.1",
|
||||||
|
"@vercel/ncc": "^0.38.4",
|
||||||
|
"express": "^5.1.0",
|
||||||
|
"nodemon": "^3.1.11",
|
||||||
|
"path-to-regexp": "^8.3.0",
|
||||||
|
"prettier": "^3.6.2",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsx": "^4.20.6",
|
||||||
|
"typescript": "^5.9.3",
|
||||||
|
"zod": "^4.1.12"
|
||||||
|
},
|
||||||
|
"prettier": {
|
||||||
|
"arrowParens": "always",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"trailingComma": "all",
|
||||||
|
"tabWidth": 4,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false,
|
||||||
|
"importOrder": [
|
||||||
|
"<THIRD_PARTY_MODULES>",
|
||||||
|
"^[./]"
|
||||||
|
],
|
||||||
|
"importOrderCaseSensitive": true,
|
||||||
|
"plugins": [
|
||||||
|
"@ianvs/prettier-plugin-sort-imports"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/express": "^5.0.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
1510
express/pnpm-lock.yaml
generated
Normal file
1510
express/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
77
express/routes.ts
Normal file
77
express/routes.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/// <reference lib="dom" />
|
||||||
|
|
||||||
|
import { contentTypes } from "./content-types";
|
||||||
|
import { multiHandler } from "./handlers";
|
||||||
|
import { HttpCode, httpCodes } from "./http-codes";
|
||||||
|
import { services } from "./services";
|
||||||
|
import { Call, ProcessedRoute, Result, Route } from "./types";
|
||||||
|
|
||||||
|
// FIXME: Obviously put this somewhere else
|
||||||
|
const okText = (result: string): Result => {
|
||||||
|
const code = httpCodes.success.OK;
|
||||||
|
|
||||||
|
const retval: Result = {
|
||||||
|
code,
|
||||||
|
result,
|
||||||
|
contentType: contentTypes.text.plain,
|
||||||
|
};
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
};
|
||||||
|
|
||||||
|
const routes: Route[] = [
|
||||||
|
{
|
||||||
|
path: "/slow",
|
||||||
|
methods: ["GET"],
|
||||||
|
handler: async (_call: Call): Promise<Result> => {
|
||||||
|
console.log("starting slow request");
|
||||||
|
|
||||||
|
await services.misc.sleep(2);
|
||||||
|
|
||||||
|
console.log("finishing slow request");
|
||||||
|
const retval = okText("that was slow");
|
||||||
|
|
||||||
|
return retval;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/list",
|
||||||
|
methods: ["GET"],
|
||||||
|
handler: async (call: Call): Promise<Result> => {
|
||||||
|
const code = httpCodes.success.OK;
|
||||||
|
const lr = (rr: Route[]) => {
|
||||||
|
const ret = rr.map((r: Route) => {
|
||||||
|
return r.path;
|
||||||
|
});
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
};
|
||||||
|
|
||||||
|
const listing = lr(routes).join(", ");
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
result: listing + "\n",
|
||||||
|
contentType: contentTypes.text.plain,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/ok",
|
||||||
|
methods: ["GET", "POST", "PUT"],
|
||||||
|
handler: multiHandler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: "/alsook",
|
||||||
|
methods: ["GET"],
|
||||||
|
handler: async (_req): Promise<Result> => {
|
||||||
|
const code = httpCodes.success.OK;
|
||||||
|
return {
|
||||||
|
code,
|
||||||
|
result: "it is also ok",
|
||||||
|
contentType: contentTypes.text.plain,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export { routes };
|
||||||
32
express/run.sh
Executable file
32
express/run.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# XXX should we default to strict or non-strict here?
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
run_dir="$DIR"
|
||||||
|
|
||||||
|
source "$run_dir"/../framework/shims/common
|
||||||
|
source "$run_dir"/../framework/shims/node.common
|
||||||
|
|
||||||
|
strict_arg="${1:---no-strict}"
|
||||||
|
|
||||||
|
if [[ "$strict_arg" = "--strict" ]] ; then
|
||||||
|
strict="yes"
|
||||||
|
else
|
||||||
|
strict="no"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cmd="tsx"
|
||||||
|
if [[ "strict" = "yes" ]] ; then
|
||||||
|
cmd="ts-node"
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd "$run_dir"
|
||||||
|
|
||||||
|
"$run_dir"/check.sh
|
||||||
|
#echo checked
|
||||||
|
# $ROOT/cmd "$cmd" $run_dir/app.ts
|
||||||
|
../cmd node "$run_dir"/out/app.js
|
||||||
36
express/services.ts
Normal file
36
express/services.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
// services.ts
|
||||||
|
|
||||||
|
import { config } from "./config";
|
||||||
|
import { getLogs, log } from "./logging";
|
||||||
|
|
||||||
|
//const database = Client({
|
||||||
|
|
||||||
|
//})
|
||||||
|
|
||||||
|
const database = {};
|
||||||
|
|
||||||
|
const logging = {
|
||||||
|
log,
|
||||||
|
getLogs,
|
||||||
|
};
|
||||||
|
|
||||||
|
const random = {
|
||||||
|
randomNumber: () => {
|
||||||
|
return Math.random();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const misc = {
|
||||||
|
sleep: (ms: number) => {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const services = {
|
||||||
|
database,
|
||||||
|
logging,
|
||||||
|
misc,
|
||||||
|
random,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { services };
|
||||||
12
express/show-config.sh
Executable file
12
express/show-config.sh
Executable file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
check_dir="$DIR"
|
||||||
|
|
||||||
|
source "$check_dir"/../framework/shims/common
|
||||||
|
source "$check_dir"/../framework/shims/node.common
|
||||||
|
|
||||||
|
$ROOT/cmd pnpm tsc --showConfig
|
||||||
13
express/tsconfig.json
Normal file
13
express/tsconfig.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2023"],
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strict": true,
|
||||||
|
"types": ["node"],
|
||||||
|
"outDir": "out",
|
||||||
|
}
|
||||||
|
}
|
||||||
59
express/types.ts
Normal file
59
express/types.ts
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// types.ts
|
||||||
|
|
||||||
|
// FIXME: split this up into types used by app developers and types internal
|
||||||
|
// to the framework.
|
||||||
|
import {
|
||||||
|
Request as ExpressRequest,
|
||||||
|
Response as ExpressResponse,
|
||||||
|
} from "express";
|
||||||
|
import { MatchFunction } from "path-to-regexp";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { ContentType, contentTypes } from "./content-types";
|
||||||
|
import { HttpCode, httpCodes } from "./http-codes";
|
||||||
|
|
||||||
|
const methodParser = z.union([
|
||||||
|
z.literal("GET"),
|
||||||
|
z.literal("POST"),
|
||||||
|
z.literal("PUT"),
|
||||||
|
z.literal("PATCH"),
|
||||||
|
z.literal("DELETE"),
|
||||||
|
]);
|
||||||
|
|
||||||
|
export type Method = z.infer<typeof methodParser>;
|
||||||
|
const massageMethod = (input: string): Method => {
|
||||||
|
const r = methodParser.parse(input.toUpperCase());
|
||||||
|
|
||||||
|
return r;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Call = {
|
||||||
|
pattern: string;
|
||||||
|
path: string;
|
||||||
|
method: Method;
|
||||||
|
parameters: object;
|
||||||
|
request: ExpressRequest;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InternalHandler = (req: ExpressRequest) => Promise<Result>;
|
||||||
|
|
||||||
|
export type Handler = (call: Call) => Promise<Result>;
|
||||||
|
export type ProcessedRoute = {
|
||||||
|
matcher: MatchFunction<Record<string, string>>;
|
||||||
|
method: Method;
|
||||||
|
handler: InternalHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Result = {
|
||||||
|
code: HttpCode;
|
||||||
|
contentType: ContentType;
|
||||||
|
result: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Route = {
|
||||||
|
path: string;
|
||||||
|
methods: Method[];
|
||||||
|
handler: Handler;
|
||||||
|
interruptable?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { methodParser, massageMethod };
|
||||||
14
express/watch.sh
Executable file
14
express/watch.sh
Executable file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||||
|
|
||||||
|
check_dir="$DIR"
|
||||||
|
|
||||||
|
source "$check_dir"/../framework/shims/common
|
||||||
|
source "$check_dir"/../framework/shims/node.common
|
||||||
|
|
||||||
|
# $ROOT/cmd pnpm tsc --lib ES2023 --esModuleInterop -w $check_dir/app.ts
|
||||||
|
# $ROOT/cmd pnpm tsc -w $check_dir/app.ts
|
||||||
|
$ROOT/cmd pnpm tsc -w --project ./tsconfig.json
|
||||||
Reference in New Issue
Block a user