@raiyyan/brisk 0.0.0-stage → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +535 -0
- package/LICENSE +21 -0
- package/README.md +1745 -2
- package/dist/bin/brisk.d.ts +3 -0
- package/dist/bin/brisk.d.ts.map +1 -0
- package/dist/bin/brisk.js +13 -0
- package/dist/bin/brisk.js.map +1 -0
- package/dist/src/cli/agents.d.ts +7 -0
- package/dist/src/cli/agents.d.ts.map +1 -0
- package/dist/src/cli/agents.js +55 -0
- package/dist/src/cli/agents.js.map +1 -0
- package/dist/src/cli/cli.d.ts +5 -0
- package/dist/src/cli/cli.d.ts.map +1 -0
- package/dist/src/cli/cli.js +113 -0
- package/dist/src/cli/cli.js.map +1 -0
- package/dist/src/cli/client.d.ts +12 -0
- package/dist/src/cli/client.d.ts.map +1 -0
- package/dist/src/cli/client.js +76 -0
- package/dist/src/cli/client.js.map +1 -0
- package/dist/src/cli/init.d.ts +16 -0
- package/dist/src/cli/init.d.ts.map +1 -0
- package/dist/src/cli/init.js +149 -0
- package/dist/src/cli/init.js.map +1 -0
- package/dist/src/cli/names.d.ts +14 -0
- package/dist/src/cli/names.d.ts.map +1 -0
- package/dist/src/cli/names.js +25 -0
- package/dist/src/cli/names.js.map +1 -0
- package/dist/src/cli/render.d.ts +30 -0
- package/dist/src/cli/render.d.ts.map +1 -0
- package/dist/src/cli/render.js +101 -0
- package/dist/src/cli/render.js.map +1 -0
- package/dist/src/cli/system.d.ts +35 -0
- package/dist/src/cli/system.d.ts.map +1 -0
- package/dist/src/cli/system.js +81 -0
- package/dist/src/cli/system.js.map +1 -0
- package/dist/src/client/client.d.ts +64 -0
- package/dist/src/client/client.d.ts.map +1 -0
- package/dist/src/client/client.js +170 -0
- package/dist/src/client/client.js.map +1 -0
- package/dist/src/client/index.d.ts +8 -0
- package/dist/src/client/index.d.ts.map +1 -0
- package/dist/src/client/index.js +7 -0
- package/dist/src/client/index.js.map +1 -0
- package/dist/src/config/env.d.ts +28 -0
- package/dist/src/config/env.d.ts.map +1 -0
- package/dist/src/config/env.js +35 -0
- package/dist/src/config/env.js.map +1 -0
- package/dist/src/config/logger.d.ts +32 -0
- package/dist/src/config/logger.d.ts.map +1 -0
- package/dist/src/config/logger.js +31 -0
- package/dist/src/config/logger.js.map +1 -0
- package/dist/src/di/container.d.ts +194 -0
- package/dist/src/di/container.d.ts.map +1 -0
- package/dist/src/di/container.js +225 -0
- package/dist/src/di/container.js.map +1 -0
- package/dist/src/di/index.d.ts +8 -0
- package/dist/src/di/index.d.ts.map +1 -0
- package/dist/src/di/index.js +8 -0
- package/dist/src/di/index.js.map +1 -0
- package/dist/src/errors/http-errors.d.ts +295 -0
- package/dist/src/errors/http-errors.d.ts.map +1 -0
- package/dist/src/errors/http-errors.js +311 -0
- package/dist/src/errors/http-errors.js.map +1 -0
- package/dist/src/errors/index.d.ts +8 -0
- package/dist/src/errors/index.d.ts.map +1 -0
- package/dist/src/errors/index.js +8 -0
- package/dist/src/errors/index.js.map +1 -0
- package/dist/src/http/app.d.ts +229 -0
- package/dist/src/http/app.d.ts.map +1 -0
- package/dist/src/http/app.js +45 -0
- package/dist/src/http/app.js.map +1 -0
- package/dist/src/http/context.d.ts +66 -0
- package/dist/src/http/context.d.ts.map +1 -0
- package/dist/src/http/context.js +96 -0
- package/dist/src/http/context.js.map +1 -0
- package/dist/src/http/controller.d.ts +195 -0
- package/dist/src/http/controller.d.ts.map +1 -0
- package/dist/src/http/controller.js +290 -0
- package/dist/src/http/controller.js.map +1 -0
- package/dist/src/http/error-handler.d.ts +86 -0
- package/dist/src/http/error-handler.d.ts.map +1 -0
- package/dist/src/http/error-handler.js +147 -0
- package/dist/src/http/error-handler.js.map +1 -0
- package/dist/src/http/instance.d.ts +14 -0
- package/dist/src/http/instance.d.ts.map +1 -0
- package/dist/src/http/instance.js +377 -0
- package/dist/src/http/instance.js.map +1 -0
- package/dist/src/http/middleware.d.ts +141 -0
- package/dist/src/http/middleware.d.ts.map +1 -0
- package/dist/src/http/middleware.js +160 -0
- package/dist/src/http/middleware.js.map +1 -0
- package/dist/src/http/openapi.d.ts +34 -0
- package/dist/src/http/openapi.d.ts.map +1 -0
- package/dist/src/http/openapi.js +151 -0
- package/dist/src/http/openapi.js.map +1 -0
- package/dist/src/http/path-params.d.ts +24 -0
- package/dist/src/http/path-params.d.ts.map +1 -0
- package/dist/src/http/path-params.js +2 -0
- package/dist/src/http/path-params.js.map +1 -0
- package/dist/src/http/routes.d.ts +141 -0
- package/dist/src/http/routes.d.ts.map +1 -0
- package/dist/src/http/routes.js +2 -0
- package/dist/src/http/routes.js.map +1 -0
- package/dist/src/http/server.d.ts +23 -0
- package/dist/src/http/server.d.ts.map +1 -0
- package/dist/src/http/server.js +22 -0
- package/dist/src/http/server.js.map +1 -0
- package/dist/src/http/validation.d.ts +16 -0
- package/dist/src/http/validation.d.ts.map +1 -0
- package/dist/src/http/validation.js +51 -0
- package/dist/src/http/validation.js.map +1 -0
- package/dist/src/index.d.ts +22 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +19 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/testing/index.d.ts +7 -0
- package/dist/src/testing/index.d.ts.map +1 -0
- package/dist/src/testing/index.js +7 -0
- package/dist/src/testing/index.js.map +1 -0
- package/dist/src/testing/test-client.d.ts +67 -0
- package/dist/src/testing/test-client.d.ts.map +1 -0
- package/dist/src/testing/test-client.js +78 -0
- package/dist/src/testing/test-client.js.map +1 -0
- package/package.json +89 -3
- package/starters/minimal/.env.example +3 -0
- package/starters/minimal/.oxfmtrc.json +5 -0
- package/starters/minimal/.oxlintrc.json +14 -0
- package/starters/minimal/AGENTS.md +23 -0
- package/starters/minimal/CLAUDE.md +2 -0
- package/starters/minimal/README.md +53 -0
- package/starters/minimal/_gitignore +3 -0
- package/starters/minimal/package.json +29 -0
- package/starters/minimal/src/app.ts +14 -0
- package/starters/minimal/src/env.ts +7 -0
- package/starters/minimal/src/features/index.ts +8 -0
- package/starters/minimal/src/features/notes/notes.controller.ts +38 -0
- package/starters/minimal/src/features/notes/notes.repository.ts +36 -0
- package/starters/minimal/src/features/notes/notes.schema.ts +24 -0
- package/starters/minimal/src/features/notes/notes.service.ts +33 -0
- package/starters/minimal/src/main.ts +4 -0
- package/starters/minimal/src/schemas.ts +26 -0
- package/starters/minimal/templates/feature/src/features/index.ts.controllers.insert +1 -0
- package/starters/minimal/templates/feature/src/features/index.ts.imports.insert +1 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +38 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +36 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
- package/starters/minimal/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
- package/starters/minimal/templates/feature/test/{{kebab}}.service.test.ts +20 -0
- package/starters/minimal/templates/feature/test/{{kebab}}.test.ts +35 -0
- package/starters/minimal/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
- package/starters/minimal/test/notes.service.test.ts +20 -0
- package/starters/minimal/test/notes.test.ts +35 -0
- package/starters/minimal/tsconfig.json +17 -0
- package/starters/minimal/vitest.config.ts +9 -0
- package/starters/sqlite-auth/.dockerignore +8 -0
- package/starters/sqlite-auth/.env.example +9 -0
- package/starters/sqlite-auth/.oxfmtrc.json +5 -0
- package/starters/sqlite-auth/.oxlintrc.json +14 -0
- package/starters/sqlite-auth/AGENTS.md +23 -0
- package/starters/sqlite-auth/CLAUDE.md +2 -0
- package/starters/sqlite-auth/Dockerfile +23 -0
- package/starters/sqlite-auth/README.md +93 -0
- package/starters/sqlite-auth/_gitignore +5 -0
- package/starters/sqlite-auth/compose.yaml +14 -0
- package/starters/sqlite-auth/package.json +35 -0
- package/starters/sqlite-auth/public/.gitkeep +0 -0
- package/starters/sqlite-auth/src/app.ts +39 -0
- package/starters/sqlite-auth/src/auth/tokens.ts +41 -0
- package/starters/sqlite-auth/src/database/clock.ts +4 -0
- package/starters/sqlite-auth/src/database/database.ts +29 -0
- package/starters/sqlite-auth/src/database/errors.ts +11 -0
- package/starters/sqlite-auth/src/database/migrations.ts +29 -0
- package/starters/sqlite-auth/src/env.ts +17 -0
- package/starters/sqlite-auth/src/features/auth/auth.controller.ts +28 -0
- package/starters/sqlite-auth/src/features/index.ts +12 -0
- package/starters/sqlite-auth/src/features/notes/notes.controller.ts +39 -0
- package/starters/sqlite-auth/src/features/notes/notes.repository.ts +47 -0
- package/starters/sqlite-auth/src/features/notes/notes.schema.ts +24 -0
- package/starters/sqlite-auth/src/features/notes/notes.service.ts +33 -0
- package/starters/sqlite-auth/src/features/users/admin-seed.ts +21 -0
- package/starters/sqlite-auth/src/features/users/users.controller.ts +42 -0
- package/starters/sqlite-auth/src/features/users/users.errors.ts +13 -0
- package/starters/sqlite-auth/src/features/users/users.repository.ts +77 -0
- package/starters/sqlite-auth/src/features/users/users.schema.ts +35 -0
- package/starters/sqlite-auth/src/features/users/users.service.ts +80 -0
- package/starters/sqlite-auth/src/main.ts +4 -0
- package/starters/sqlite-auth/src/middlewares/authenticate.ts +29 -0
- package/starters/sqlite-auth/src/schemas.ts +26 -0
- package/starters/sqlite-auth/templates/feature/src/database/migrations.ts.migrations.insert +5 -0
- package/starters/sqlite-auth/templates/feature/src/features/index.ts.controllers.insert +1 -0
- package/starters/sqlite-auth/templates/feature/src/features/index.ts.imports.insert +1 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.controller.ts +39 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.repository.ts +47 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.schema.ts +24 -0
- package/starters/sqlite-auth/templates/feature/src/features/{{kebab}}/{{kebab}}.service.ts +33 -0
- package/starters/sqlite-auth/templates/feature/test/{{kebab}}.service.test.ts +20 -0
- package/starters/sqlite-auth/templates/feature/test/{{kebab}}.test.ts +41 -0
- package/starters/sqlite-auth/templates/middleware/src/middlewares/{{kebab}}.ts +12 -0
- package/starters/sqlite-auth/test/auth.test.ts +18 -0
- package/starters/sqlite-auth/test/helpers.ts +13 -0
- package/starters/sqlite-auth/test/notes.service.test.ts +20 -0
- package/starters/sqlite-auth/test/notes.test.ts +41 -0
- package/starters/sqlite-auth/test/users.service.test.ts +25 -0
- package/starters/sqlite-auth/test/users.test.ts +164 -0
- package/starters/sqlite-auth/tsconfig.json +17 -0
- package/starters/sqlite-auth/vitest.config.ts +15 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { createServer } from "node:http";
|
|
3
|
+
import express, { Router } from "express";
|
|
4
|
+
import { createLogger, Logger } from "../config/logger.js";
|
|
5
|
+
import { Container, lifecycleFailure, provide } from "../di/container.js";
|
|
6
|
+
import { NotFoundError } from "../errors/http-errors.js";
|
|
7
|
+
import { createRequestContext, getContext } from "./context.js";
|
|
8
|
+
import { joinPath, methodGuard } from "./controller.js";
|
|
9
|
+
import { defaultErrorFormatter, ErrorTranslator, errorHandler, toHttpError } from "./error-handler.js";
|
|
10
|
+
import { ExpressMiddleware, resolveMiddleware, securityOf } from "./middleware.js";
|
|
11
|
+
import { buildOpenApi } from "./openapi.js";
|
|
12
|
+
import { HttpServer } from "./server.js";
|
|
13
|
+
const DEFAULT_SECURITY_HEADERS = {
|
|
14
|
+
"x-content-type-options": "nosniff",
|
|
15
|
+
"x-frame-options": "DENY",
|
|
16
|
+
"referrer-policy": "no-referrer",
|
|
17
|
+
"cross-origin-resource-policy": "same-origin",
|
|
18
|
+
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
|
|
19
|
+
};
|
|
20
|
+
/** Accepts a caller's `x-request-id` only when it is short and plain, so it cannot forge log lines. */
|
|
21
|
+
const SAFE_REQUEST_ID = /^[\w.:-]{1,128}$/;
|
|
22
|
+
/**
|
|
23
|
+
* Builds one instance of an app: security headers, request ids and logging, health check and OpenAPI routes,
|
|
24
|
+
* the app's `use`, controllers (each parsing its own JSON bodies), mounts, 404 and error handling, and the container that runs every
|
|
25
|
+
* lifecycle hook. `overrides` come after the app's own providers, so they win.
|
|
26
|
+
*/
|
|
27
|
+
export function createInstance(options, overrides = []) {
|
|
28
|
+
const logger = options.logger ?? createLogger();
|
|
29
|
+
const hooks = options.hooks ?? {};
|
|
30
|
+
const http = options.http ?? {};
|
|
31
|
+
const errors = options.errors ?? {};
|
|
32
|
+
const controllers = Object.entries(options.controllers ?? {});
|
|
33
|
+
const container = new Container([
|
|
34
|
+
provide(Logger, logger),
|
|
35
|
+
provide(ErrorTranslator, (error) => {
|
|
36
|
+
const translated = toHttpError(error, { mappers: errors.mappers ?? [], exposeInternalErrors: errors.exposeInternal ?? false }, logger);
|
|
37
|
+
const { status, code, message, details } = translated;
|
|
38
|
+
return { status, code, message, details };
|
|
39
|
+
}),
|
|
40
|
+
...controllers.flatMap(([, controller]) => controller.providers),
|
|
41
|
+
...(options.providers ?? []),
|
|
42
|
+
...overrides,
|
|
43
|
+
]);
|
|
44
|
+
const app = express();
|
|
45
|
+
const draining = { active: false, responses: new Map() };
|
|
46
|
+
app.disable("x-powered-by");
|
|
47
|
+
app.set("query parser", "simple");
|
|
48
|
+
for (const [name, value] of Object.entries(http.settings ?? {}))
|
|
49
|
+
app.set(name, value);
|
|
50
|
+
const allRoutes = [];
|
|
51
|
+
let document;
|
|
52
|
+
const openapi = () => (document ??= buildOpenApi(allRoutes, options.openapi ?? { title: "API", version: "0.0.0" }));
|
|
53
|
+
app.use(prelude({ logger, hooks, draining, container, headers: securityHeaders(http.securityHeaders) }));
|
|
54
|
+
// System routes come before the app's own middleware, so a rate limit or an auth check never blocks a probe.
|
|
55
|
+
if (http.healthPath !== false) {
|
|
56
|
+
app.get(http.healthPath ?? "/health", (_req, res) => {
|
|
57
|
+
if (draining.active)
|
|
58
|
+
res.status(503).json({ status: "shutting down" });
|
|
59
|
+
else
|
|
60
|
+
res.json({ status: "ok" });
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const openapiPath = options.openapi?.path ?? "/openapi.json";
|
|
64
|
+
if (options.openapi && openapiPath !== false) {
|
|
65
|
+
app.get(openapiPath, (_req, res) => {
|
|
66
|
+
res.json(openapi());
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
const use = options.use ?? [];
|
|
70
|
+
for (const middleware of use) {
|
|
71
|
+
app.use(middleware instanceof ExpressMiddleware ? middleware.handler : runEach(middleware));
|
|
72
|
+
}
|
|
73
|
+
const router = Router();
|
|
74
|
+
const parseBody = express.json({
|
|
75
|
+
limit: http.bodyLimit ?? "1mb",
|
|
76
|
+
type: ["application/json", "application/*+json"],
|
|
77
|
+
});
|
|
78
|
+
const appSecurity = securityOf(use);
|
|
79
|
+
const manifest = {};
|
|
80
|
+
for (const [name, controller] of controllers) {
|
|
81
|
+
const basePath = joinPath(options.prefix ?? "/", controller.prefix);
|
|
82
|
+
const mounted = controller.mount({ container, router, basePath, parseBody });
|
|
83
|
+
for (const endpoint of mounted.endpoints) {
|
|
84
|
+
const security = appSecurity ? { ...appSecurity, ...endpoint.security } : endpoint.security;
|
|
85
|
+
allRoutes.push({ ...endpoint, security, tag: name });
|
|
86
|
+
}
|
|
87
|
+
if (mounted.manifest)
|
|
88
|
+
manifest[name] = mounted.manifest;
|
|
89
|
+
}
|
|
90
|
+
assertUniqueRoutes(allRoutes);
|
|
91
|
+
const mounts = Object.entries(options.mount ?? {});
|
|
92
|
+
const routes = [
|
|
93
|
+
...allRoutes.map(({ method, path }) => `${method.toUpperCase()} ${path}`),
|
|
94
|
+
...mounts.map(([path]) => `MOUNT ${path}`),
|
|
95
|
+
];
|
|
96
|
+
const guard = methodGuard(allRoutes);
|
|
97
|
+
app.use((req, res, next) => {
|
|
98
|
+
if (req.method === "OPTIONS")
|
|
99
|
+
guard(req, res, next);
|
|
100
|
+
else
|
|
101
|
+
next();
|
|
102
|
+
});
|
|
103
|
+
app.use(router);
|
|
104
|
+
app.use(guard);
|
|
105
|
+
for (const [path, handlers] of mounts) {
|
|
106
|
+
if (!path.startsWith("/"))
|
|
107
|
+
throw new TypeError(`Mount path "${path}" must start with "/"`);
|
|
108
|
+
app.use(path, ...[handlers].flat());
|
|
109
|
+
}
|
|
110
|
+
app.use((req) => {
|
|
111
|
+
throw new NotFoundError(`Cannot ${req.method} ${req.path}`, { code: "ROUTE_NOT_FOUND" });
|
|
112
|
+
});
|
|
113
|
+
app.use(errorHandler({
|
|
114
|
+
mappers: errors.mappers ?? [],
|
|
115
|
+
format: errors.format ?? defaultErrorFormatter,
|
|
116
|
+
exposeInternalErrors: errors.exposeInternal ?? false,
|
|
117
|
+
onError: hooks.onError,
|
|
118
|
+
}));
|
|
119
|
+
let initializing;
|
|
120
|
+
let stopping;
|
|
121
|
+
let server;
|
|
122
|
+
const sockets = new Set();
|
|
123
|
+
const signals = ["SIGINT", "SIGTERM"];
|
|
124
|
+
const shutdownTimeoutMs = http.shutdownTimeoutMs ?? 10_000;
|
|
125
|
+
/** Shuts down and exits; a hook that never settles cannot keep the process alive past the deadline. */
|
|
126
|
+
const exitAfterShutdown = (code) => {
|
|
127
|
+
setTimeout(() => {
|
|
128
|
+
logger.fatal("shutdown did not finish in time, exiting");
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}, shutdownTimeoutMs + 10_000).unref();
|
|
131
|
+
void shutdown().then(() => process.exit(code));
|
|
132
|
+
};
|
|
133
|
+
let firstSignal;
|
|
134
|
+
/**
|
|
135
|
+
* The first signal starts a graceful shutdown. While it runs, the listeners stay installed: tools often send a
|
|
136
|
+
* second, different signal (`node --watch` and some init systems forward SIGTERM right after the terminal's
|
|
137
|
+
* SIGINT), and without a listener Node would kill the process mid-drain. Repeating the same signal (Ctrl+C
|
|
138
|
+
* twice) is a person asking to stop now, so it exits immediately.
|
|
139
|
+
*/
|
|
140
|
+
const onSignal = (signal) => {
|
|
141
|
+
if (firstSignal === undefined) {
|
|
142
|
+
firstSignal = signal;
|
|
143
|
+
logger.info({ signal }, "shutting down");
|
|
144
|
+
exitAfterShutdown(0);
|
|
145
|
+
}
|
|
146
|
+
else if (signal === firstSignal) {
|
|
147
|
+
logger.warn({ signal }, "second signal, exiting without waiting");
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
logger.info({ signal }, "already shutting down");
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const onCrash = (reason) => {
|
|
155
|
+
if (stopping) {
|
|
156
|
+
logger.error({ err: reason }, "unhandled error during shutdown");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
logger.fatal({ err: reason }, "unhandled error, shutting down");
|
|
160
|
+
exitAfterShutdown(1);
|
|
161
|
+
};
|
|
162
|
+
const reportHookError = (hook) => (error, provider) => {
|
|
163
|
+
logger.error({ err: error, provider }, `${hook} failed`);
|
|
164
|
+
};
|
|
165
|
+
const init = () => (initializing ??= container.init());
|
|
166
|
+
const becomeReady = async (manageProcess) => {
|
|
167
|
+
await container.ready();
|
|
168
|
+
if (manageProcess) {
|
|
169
|
+
for (const signal of signals)
|
|
170
|
+
process.on(signal, onSignal);
|
|
171
|
+
process.on("unhandledRejection", onCrash);
|
|
172
|
+
process.on("uncaughtException", onCrash);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Boots, and when booting fails logs why (naming the provider and hook when one threw), then shuts down
|
|
177
|
+
* whatever already started and rethrows.
|
|
178
|
+
*/
|
|
179
|
+
const boot = async (run) => {
|
|
180
|
+
try {
|
|
181
|
+
return await run();
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
logger.fatal({ err: error, ...lifecycleFailure(error) }, "startup failed");
|
|
185
|
+
await shutdown();
|
|
186
|
+
throw error;
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const start = ({ manageProcess = true }) => boot(async () => {
|
|
190
|
+
await init();
|
|
191
|
+
await becomeReady(manageProcess);
|
|
192
|
+
logger.info("started without an HTTP server");
|
|
193
|
+
});
|
|
194
|
+
const listen = (port, listenOptions) => boot(() => serve(port, listenOptions));
|
|
195
|
+
const serve = async (port, { host, server: custom, manageProcess = true }) => {
|
|
196
|
+
await init();
|
|
197
|
+
const target = custom ?? createServer();
|
|
198
|
+
target.on("request", app);
|
|
199
|
+
target.on("connection", (socket) => {
|
|
200
|
+
sockets.add(socket);
|
|
201
|
+
draining.responses.set(socket, undefined);
|
|
202
|
+
socket.once("close", () => {
|
|
203
|
+
sockets.delete(socket);
|
|
204
|
+
draining.responses.delete(socket);
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
await new Promise((resolve, reject) => {
|
|
208
|
+
target.once("error", reject);
|
|
209
|
+
const onListening = () => {
|
|
210
|
+
target.off("error", reject);
|
|
211
|
+
resolve();
|
|
212
|
+
};
|
|
213
|
+
if (host === undefined)
|
|
214
|
+
target.listen(port, onListening);
|
|
215
|
+
else
|
|
216
|
+
target.listen(port, host, onListening);
|
|
217
|
+
});
|
|
218
|
+
server = target;
|
|
219
|
+
container.set(HttpServer, target);
|
|
220
|
+
await becomeReady(manageProcess);
|
|
221
|
+
const address = target.address();
|
|
222
|
+
const boundPort = typeof address === "object" && address ? address.port : port;
|
|
223
|
+
logger.info({ port: boundPort, routes }, `listening on http://${host ?? "localhost"}:${boundPort}`);
|
|
224
|
+
};
|
|
225
|
+
const shutdown = () => (stopping ??= (async () => {
|
|
226
|
+
draining.active = true;
|
|
227
|
+
for (const res of draining.responses.values()) {
|
|
228
|
+
if (res && !res.headersSent)
|
|
229
|
+
res.setHeader("connection", "close");
|
|
230
|
+
}
|
|
231
|
+
const closed = server?.listening ? closeServer(server, sockets, shutdownTimeoutMs) : undefined;
|
|
232
|
+
await container.drain(reportHookError("onDrain"));
|
|
233
|
+
await closed;
|
|
234
|
+
await container.shutdown(reportHookError("onShutdown"));
|
|
235
|
+
for (const signal of signals)
|
|
236
|
+
process.off(signal, onSignal);
|
|
237
|
+
process.off("unhandledRejection", onCrash);
|
|
238
|
+
process.off("uncaughtException", onCrash);
|
|
239
|
+
logger.info("shutdown complete");
|
|
240
|
+
})());
|
|
241
|
+
return {
|
|
242
|
+
manifest,
|
|
243
|
+
openapi,
|
|
244
|
+
express: app,
|
|
245
|
+
logger,
|
|
246
|
+
container,
|
|
247
|
+
get server() {
|
|
248
|
+
return server;
|
|
249
|
+
},
|
|
250
|
+
init,
|
|
251
|
+
listen,
|
|
252
|
+
start,
|
|
253
|
+
shutdown,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/** The security headers to send, computed once; `undefined` when they are turned off. */
|
|
257
|
+
function securityHeaders(option) {
|
|
258
|
+
if (option === false)
|
|
259
|
+
return undefined;
|
|
260
|
+
const merged = { ...DEFAULT_SECURITY_HEADERS, ...(typeof option === "object" ? option : {}) };
|
|
261
|
+
const headers = new Map();
|
|
262
|
+
for (const [name, value] of Object.entries(merged))
|
|
263
|
+
if (value !== false)
|
|
264
|
+
headers.set(name, value);
|
|
265
|
+
return headers;
|
|
266
|
+
}
|
|
267
|
+
const proceed = (next) => next();
|
|
268
|
+
/**
|
|
269
|
+
* Everything brisk does before your Express code, in one layer: security headers, the request id and context
|
|
270
|
+
* (with its lazy logger), the response log and `onResponse`, shutdown's `Connection: close`, and the app's
|
|
271
|
+
* dependency scope, so `inject()` works for the rest of the request, the app's `use` and `mount` included.
|
|
272
|
+
*
|
|
273
|
+
* The response is logged on `close`, which also fires for requests the client abandoned (`finish` would not).
|
|
274
|
+
* The listener exists only when something consumes it: the `info` log level or an `onResponse` hook.
|
|
275
|
+
*/
|
|
276
|
+
function prelude({ logger, hooks, draining, container, headers }) {
|
|
277
|
+
return (req, res, next) => {
|
|
278
|
+
if (headers)
|
|
279
|
+
res.setHeaders(headers);
|
|
280
|
+
const incoming = req.get("x-request-id");
|
|
281
|
+
const requestId = incoming && SAFE_REQUEST_ID.test(incoming) ? incoming : randomUUID();
|
|
282
|
+
const ctx = createRequestContext(req, res, requestId, logger);
|
|
283
|
+
res.setHeader("x-request-id", requestId);
|
|
284
|
+
if (draining.active)
|
|
285
|
+
res.setHeader("connection", "close");
|
|
286
|
+
else if (draining.responses.has(req.socket))
|
|
287
|
+
draining.responses.set(req.socket, res);
|
|
288
|
+
const log = logger.isLevelEnabled("info");
|
|
289
|
+
if (log || hooks.onResponse) {
|
|
290
|
+
const started = performance.now();
|
|
291
|
+
res.once("close", () => {
|
|
292
|
+
const durationMs = Math.round(performance.now() - started);
|
|
293
|
+
const aborted = !res.writableFinished;
|
|
294
|
+
if (log) {
|
|
295
|
+
const line = {
|
|
296
|
+
method: req.method,
|
|
297
|
+
url: pathOf(req.originalUrl),
|
|
298
|
+
status: res.statusCode,
|
|
299
|
+
durationMs,
|
|
300
|
+
};
|
|
301
|
+
ctx.log.info(aborted ? { ...line, aborted } : line, "request");
|
|
302
|
+
}
|
|
303
|
+
hooks.onResponse?.(ctx, { status: res.statusCode, durationMs, aborted });
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
container.run(proceed, next);
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* The URL without its query string. Query strings carry secrets often enough (reset tokens, OAuth codes, API
|
|
311
|
+
* keys) that brisk never writes them to logs; log what you need from `ctx.req.query` in `onResponse`.
|
|
312
|
+
*/
|
|
313
|
+
function pathOf(url) {
|
|
314
|
+
const query = url.indexOf("?");
|
|
315
|
+
return query === -1 ? url : url.slice(0, query);
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* One app-level `defineMiddleware` as an Express layer. Its result is memoized for the request, so a route that
|
|
319
|
+
* lists it again, or reads it with `run(ctx)`, does not run it twice. Allocates only when it is async.
|
|
320
|
+
*/
|
|
321
|
+
function runEach(middleware) {
|
|
322
|
+
return (req, res, next) => {
|
|
323
|
+
const out = resolveMiddleware(middleware, getContext(req));
|
|
324
|
+
if (out instanceof Promise)
|
|
325
|
+
return continueAfter(out, res, next);
|
|
326
|
+
next();
|
|
327
|
+
return undefined;
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/** Express 5 sends a rejected promise to the error handler; a middleware that answered stops the request. */
|
|
331
|
+
async function continueAfter(out, res, next) {
|
|
332
|
+
await out;
|
|
333
|
+
if (!res.headersSent)
|
|
334
|
+
next();
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Stops accepting connections and resolves once every in-flight request has finished. Keep-alive sockets
|
|
338
|
+
* close as soon as they go idle; after `timeoutMs` every remaining socket is destroyed, including upgraded
|
|
339
|
+
* WebSocket connections, which Node's `closeAllConnections` does not track.
|
|
340
|
+
*/
|
|
341
|
+
function closeServer(server, sockets, timeoutMs) {
|
|
342
|
+
return new Promise((resolve) => {
|
|
343
|
+
const sweep = setInterval(() => server.closeIdleConnections(), 10);
|
|
344
|
+
const force = setTimeout(() => {
|
|
345
|
+
server.closeAllConnections();
|
|
346
|
+
for (const socket of sockets)
|
|
347
|
+
socket.destroy();
|
|
348
|
+
}, timeoutMs);
|
|
349
|
+
server.close(() => {
|
|
350
|
+
clearInterval(sweep);
|
|
351
|
+
clearTimeout(force);
|
|
352
|
+
resolve();
|
|
353
|
+
});
|
|
354
|
+
server.closeIdleConnections();
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Two routes that answer the same requests are a mistake: Express only ever runs the first, while the typed
|
|
359
|
+
* client and the OpenAPI document would both still list the second. Paths that differ only in parameter names
|
|
360
|
+
* (`:id` and `:userId`) match the same requests.
|
|
361
|
+
*/
|
|
362
|
+
function assertUniqueRoutes(endpoints) {
|
|
363
|
+
const seen = new Map();
|
|
364
|
+
for (const endpoint of endpoints) {
|
|
365
|
+
const key = `${endpoint.method} ${endpoint.path.replace(/([:*])[\w$]+/g, "$1")}`;
|
|
366
|
+
const first = seen.get(key);
|
|
367
|
+
if (!first) {
|
|
368
|
+
seen.set(key, endpoint);
|
|
369
|
+
continue;
|
|
370
|
+
}
|
|
371
|
+
throw new Error(`${describeRoute(endpoint)} can never run: ${describeRoute(first)} answers the same requests. Remove or change one.`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
function describeRoute({ method, path, tag, name }) {
|
|
375
|
+
return `${method.toUpperCase()} ${path} (${tag}.${name ?? "unnamed route"})`;
|
|
376
|
+
}
|
|
377
|
+
//# sourceMappingURL=instance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"instance.js","sourceRoot":"","sources":["../../../src/http/instance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { RequestHandler } from "express";
|
|
2
|
+
import type { z } from "zod";
|
|
3
|
+
import { type RequestContext } from "./context.ts";
|
|
4
|
+
/** An OpenAPI security scheme, in OpenAPI's own words: `{ type: "http", scheme: "bearer" }` and the like. */
|
|
5
|
+
export type SecurityScheme = {
|
|
6
|
+
type: "http";
|
|
7
|
+
scheme: string;
|
|
8
|
+
bearerFormat?: string;
|
|
9
|
+
description?: string;
|
|
10
|
+
} | {
|
|
11
|
+
type: "apiKey";
|
|
12
|
+
in: "header" | "query" | "cookie";
|
|
13
|
+
name: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
} | {
|
|
16
|
+
type: "oauth2";
|
|
17
|
+
flows: Readonly<Record<string, unknown>>;
|
|
18
|
+
description?: string;
|
|
19
|
+
} | {
|
|
20
|
+
type: "openIdConnect";
|
|
21
|
+
openIdConnectUrl: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
};
|
|
24
|
+
/** Security schemes by name, such as `{ bearer: { type: "http", scheme: "bearer" } }`. */
|
|
25
|
+
export type SecuritySchemes = Readonly<Record<string, SecurityScheme>>;
|
|
26
|
+
/**
|
|
27
|
+
* Per-request logic shared by many routes, as {@link defineMiddleware} makes it. `run(ctx)` resolves to its
|
|
28
|
+
* output, computing it at most once per request.
|
|
29
|
+
*/
|
|
30
|
+
export interface Middleware<Out extends object = object> {
|
|
31
|
+
/** The security schemes it enforces; the OpenAPI document lists them on every operation behind it. */
|
|
32
|
+
readonly security?: SecuritySchemes | undefined;
|
|
33
|
+
run(ctx: RequestContext): Promise<Out>;
|
|
34
|
+
}
|
|
35
|
+
/** Options for {@link defineMiddleware}. */
|
|
36
|
+
export interface MiddlewareOptions<P, Q> {
|
|
37
|
+
/** Path params the middleware reads, validated (400 when invalid) and typed before it runs. */
|
|
38
|
+
params?: P;
|
|
39
|
+
/** Query fields the middleware reads, validated and typed before it runs. */
|
|
40
|
+
query?: Q;
|
|
41
|
+
/** The security schemes it enforces, for the OpenAPI document. */
|
|
42
|
+
security?: SecuritySchemes;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Names a middleware may not return: the request basics and validated input share the handler's ctx, and a
|
|
46
|
+
* middleware field with one of these names would be silently replaced. `log` is allowed, to enrich the logger.
|
|
47
|
+
*/
|
|
48
|
+
type ReservedKey = "req" | "res" | "requestId" | "params" | "query" | "body" | "headers";
|
|
49
|
+
/** Rejects outputs that use a {@link ReservedKey}; the error names the offending key. */
|
|
50
|
+
type NoReservedKeys<Out> = {
|
|
51
|
+
[K in keyof Out]: K extends ReservedKey ? `"${K}" is reserved on ctx` : Out[K];
|
|
52
|
+
};
|
|
53
|
+
/** The middleware's output, or a type error naming any {@link ReservedKey} it uses. */
|
|
54
|
+
type Checked<Out> = Out extends object ? Out & NoReservedKeys<Out> : Out;
|
|
55
|
+
type Result<Out> = Out extends object ? Out : {};
|
|
56
|
+
/** The ctx of a middleware that declares input: the request basics plus the validated `params` and `query`. */
|
|
57
|
+
export type MiddlewareInput<P, Q> = RequestContext & (P extends z.ZodType ? {
|
|
58
|
+
params: z.output<P>;
|
|
59
|
+
} : {}) & (Q extends z.ZodType ? {
|
|
60
|
+
query: z.output<Q>;
|
|
61
|
+
} : {});
|
|
62
|
+
/**
|
|
63
|
+
* Declares per-request logic shared by many routes, as `(fn)` or `(options, fn)`. Return an object to add
|
|
64
|
+
* typed fields to the handler's ctx, return nothing for a pure check, or throw an error class to stop the
|
|
65
|
+
* request. Sync or async.
|
|
66
|
+
*
|
|
67
|
+
* Middlewares run before the route validates its input, so authentication rejects a request first. A
|
|
68
|
+
* middleware that needs input, such as the id its permission check depends on, declares `params` or `query`:
|
|
69
|
+
* they are validated (400 when invalid) and typed before it runs.
|
|
70
|
+
*
|
|
71
|
+
* Each runs at most once per request: another middleware, or a handler, can `await other.run(ctx)` to reuse
|
|
72
|
+
* its result. The names `req`, `res`, `requestId`, `params`, `query`, `body` and `headers` are reserved on ctx
|
|
73
|
+
* and rejected at compile time; returning `log` replaces the request logger.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* export const authenticate = defineMiddleware(
|
|
78
|
+
* { security: { bearer: { type: "http", scheme: "bearer" } } },
|
|
79
|
+
* async ({ req, log }) => {
|
|
80
|
+
* const user = await inject(Sessions).verify(req.get("authorization"));
|
|
81
|
+
* if (!user) throw new UnauthorizedError();
|
|
82
|
+
* return { user, log: log.child({ userId: user.id }) };
|
|
83
|
+
* },
|
|
84
|
+
* );
|
|
85
|
+
*
|
|
86
|
+
* export const requireProjectRole = (role: Role) =>
|
|
87
|
+
* defineMiddleware(
|
|
88
|
+
* { params: z.object({ projectId: z.uuid() }), security: authenticate.security },
|
|
89
|
+
* async (ctx) => {
|
|
90
|
+
* const { user } = await authenticate.run(ctx);
|
|
91
|
+
* const member = await inject(Members).find(ctx.params.projectId, user.id);
|
|
92
|
+
* if (!member) throw new NotFoundError("Project not found");
|
|
93
|
+
* if (!allows(member.role, role)) throw new ForbiddenError();
|
|
94
|
+
* return { member };
|
|
95
|
+
* },
|
|
96
|
+
* );
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
export declare function defineMiddleware<Out extends object | void>(fn: (ctx: RequestContext) => Checked<Out> | Promise<Checked<Out>>): Middleware<Result<Out>>;
|
|
100
|
+
export declare function defineMiddleware<Out extends object | void, P extends z.ZodType | undefined = undefined, Q extends z.ZodType | undefined = undefined>(options: MiddlewareOptions<P, Q>, fn: (ctx: MiddlewareInput<P, Q>) => Checked<Out> | Promise<Checked<Out>>): Middleware<Result<Out>>;
|
|
101
|
+
/**
|
|
102
|
+
* Runs an Express middleware as a brisk middleware, in order with the others in `use`: a body parser such as
|
|
103
|
+
* multer then runs after authentication, on the one route that needs it. An error it passes to `next` goes
|
|
104
|
+
* to the app's error handler; when it answers the request itself, the route stops there.
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* ```ts
|
|
108
|
+
* upload: route.post(
|
|
109
|
+
* "/:taskId/attachments",
|
|
110
|
+
* { use: [authenticate, fromExpress(multer({ limits: { fileSize: 5_000_000 } }).single("file"))] },
|
|
111
|
+
* ({ req, params }) => attachments.save(params.taskId, req.file),
|
|
112
|
+
* ),
|
|
113
|
+
* ```
|
|
114
|
+
*/
|
|
115
|
+
export declare function fromExpress(handler: RequestHandler): Middleware<{}>;
|
|
116
|
+
/**
|
|
117
|
+
* An Express middleware wrapped by {@link fromExpress}. In a route's `use` it runs in order with the others;
|
|
118
|
+
* in the app's `use` the app registers `handler` itself as a plain Express layer, at no extra cost.
|
|
119
|
+
*/
|
|
120
|
+
export declare class ExpressMiddleware implements Middleware<{}> {
|
|
121
|
+
readonly handler: RequestHandler;
|
|
122
|
+
constructor(handler: RequestHandler);
|
|
123
|
+
run({ req, res }: RequestContext): Promise<{}>;
|
|
124
|
+
}
|
|
125
|
+
type UnionToIntersection<U> = (U extends unknown ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
126
|
+
/** Merges the outputs of a middleware list into one type: `[auth, tenant]` gives `{ user } & { tenant }`. */
|
|
127
|
+
export type MiddlewareContext<T extends readonly Middleware[]> = [T[number]] extends [never] ? {} : UnionToIntersection<T[number] extends infer M ? (M extends Middleware<infer O> ? O : never) : never>;
|
|
128
|
+
/**
|
|
129
|
+
* Runs `list` in order against the request's context and writes each output onto `target`. Stays synchronous
|
|
130
|
+
* while the middlewares are, and returns a promise only from the first async one on.
|
|
131
|
+
*/
|
|
132
|
+
export declare function applyMiddlewares(base: RequestContext, target: object, list: readonly Middleware[], from?: number): void | Promise<void>;
|
|
133
|
+
/**
|
|
134
|
+
* Runs one middleware for the request, reusing its memoized result: a value when it stays synchronous, a
|
|
135
|
+
* promise otherwise.
|
|
136
|
+
*/
|
|
137
|
+
export declare function resolveMiddleware(middleware: Middleware, ctx: RequestContext): object | Promise<object>;
|
|
138
|
+
/** The security schemes a list of middlewares enforces, merged; `undefined` when none does. */
|
|
139
|
+
export declare function securityOf(middlewares: readonly Middleware[]): SecuritySchemes | undefined;
|
|
140
|
+
export {};
|
|
141
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../../src/http/middleware.ts"],"names":[],"mappings":"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"}
|