@beignet/core 0.0.1
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/CHANGELOG.md +5 -0
- package/README.md +288 -0
- package/dist/application/index.d.ts +260 -0
- package/dist/application/index.d.ts.map +1 -0
- package/dist/application/index.js +324 -0
- package/dist/application/index.js.map +1 -0
- package/dist/client/client.d.ts +241 -0
- package/dist/client/client.d.ts.map +1 -0
- package/dist/client/client.js +531 -0
- package/dist/client/client.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +8 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/types.d.ts +139 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/config/index.d.ts +122 -0
- package/dist/config/index.d.ts.map +1 -0
- package/dist/config/index.js +216 -0
- package/dist/config/index.js.map +1 -0
- package/dist/contracts/contract-builder.d.ts +121 -0
- package/dist/contracts/contract-builder.d.ts.map +1 -0
- package/dist/contracts/contract-builder.js +346 -0
- package/dist/contracts/contract-builder.js.map +1 -0
- package/dist/contracts/contract-group.d.ts +106 -0
- package/dist/contracts/contract-group.d.ts.map +1 -0
- package/dist/contracts/contract-group.js +240 -0
- package/dist/contracts/contract-group.js.map +1 -0
- package/dist/contracts/contract-like.d.ts +21 -0
- package/dist/contracts/contract-like.d.ts.map +1 -0
- package/dist/contracts/contract-like.js +9 -0
- package/dist/contracts/contract-like.js.map +1 -0
- package/dist/contracts/index.d.ts +15 -0
- package/dist/contracts/index.d.ts.map +1 -0
- package/dist/contracts/index.js +11 -0
- package/dist/contracts/index.js.map +1 -0
- package/dist/contracts/openapi-meta.d.ts +23 -0
- package/dist/contracts/openapi-meta.d.ts.map +1 -0
- package/dist/contracts/openapi-meta.js +2 -0
- package/dist/contracts/openapi-meta.js.map +1 -0
- package/dist/contracts/path-template.d.ts +17 -0
- package/dist/contracts/path-template.d.ts.map +1 -0
- package/dist/contracts/path-template.js +50 -0
- package/dist/contracts/path-template.js.map +1 -0
- package/dist/contracts/rate-limit.d.ts +50 -0
- package/dist/contracts/rate-limit.d.ts.map +1 -0
- package/dist/contracts/rate-limit.js +2 -0
- package/dist/contracts/rate-limit.js.map +1 -0
- package/dist/contracts/types.d.ts +97 -0
- package/dist/contracts/types.d.ts.map +1 -0
- package/dist/contracts/types.js +54 -0
- package/dist/contracts/types.js.map +1 -0
- package/dist/contracts/utils.d.ts +3 -0
- package/dist/contracts/utils.d.ts.map +1 -0
- package/dist/contracts/utils.js +44 -0
- package/dist/contracts/utils.js.map +1 -0
- package/dist/domain/entity.d.ts +87 -0
- package/dist/domain/entity.d.ts.map +1 -0
- package/dist/domain/entity.js +155 -0
- package/dist/domain/entity.js.map +1 -0
- package/dist/domain/events.d.ts +41 -0
- package/dist/domain/events.d.ts.map +1 -0
- package/dist/domain/events.js +21 -0
- package/dist/domain/events.js.map +1 -0
- package/dist/domain/index.d.ts +14 -0
- package/dist/domain/index.d.ts.map +1 -0
- package/dist/domain/index.js +14 -0
- package/dist/domain/index.js.map +1 -0
- package/dist/domain/value-object.d.ts +60 -0
- package/dist/domain/value-object.d.ts.map +1 -0
- package/dist/domain/value-object.js +87 -0
- package/dist/domain/value-object.js.map +1 -0
- package/dist/errors/catalog.d.ts +71 -0
- package/dist/errors/catalog.d.ts.map +1 -0
- package/dist/errors/catalog.js +71 -0
- package/dist/errors/catalog.js.map +1 -0
- package/dist/errors/http.d.ts +77 -0
- package/dist/errors/http.d.ts.map +1 -0
- package/dist/errors/http.js +74 -0
- package/dist/errors/http.js.map +1 -0
- package/dist/errors/index.d.ts +10 -0
- package/dist/errors/index.d.ts.map +1 -0
- package/dist/errors/index.js +14 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/response.d.ts +26 -0
- package/dist/errors/response.d.ts.map +1 -0
- package/dist/errors/response.js +34 -0
- package/dist/errors/response.js.map +1 -0
- package/dist/errors/validation.d.ts +18 -0
- package/dist/errors/validation.d.ts.map +1 -0
- package/dist/errors/validation.js +21 -0
- package/dist/errors/validation.js.map +1 -0
- package/dist/events/index.d.ts +58 -0
- package/dist/events/index.d.ts.map +1 -0
- package/dist/events/index.js +102 -0
- package/dist/events/index.js.map +1 -0
- package/dist/jobs/index.d.ts +56 -0
- package/dist/jobs/index.d.ts.map +1 -0
- package/dist/jobs/index.js +89 -0
- package/dist/jobs/index.js.map +1 -0
- package/dist/mail/index.d.ts +75 -0
- package/dist/mail/index.d.ts.map +1 -0
- package/dist/mail/index.js +84 -0
- package/dist/mail/index.js.map +1 -0
- package/dist/openapi/index.d.ts +207 -0
- package/dist/openapi/index.d.ts.map +1 -0
- package/dist/openapi/index.js +449 -0
- package/dist/openapi/index.js.map +1 -0
- package/dist/openapi/schema-introspector.d.ts +38 -0
- package/dist/openapi/schema-introspector.d.ts.map +1 -0
- package/dist/openapi/schema-introspector.js +67 -0
- package/dist/openapi/schema-introspector.js.map +1 -0
- package/dist/ports/audit.d.ts +58 -0
- package/dist/ports/audit.d.ts.map +1 -0
- package/dist/ports/audit.js +74 -0
- package/dist/ports/audit.js.map +1 -0
- package/dist/ports/auth.d.ts +23 -0
- package/dist/ports/auth.d.ts.map +1 -0
- package/dist/ports/auth.js +31 -0
- package/dist/ports/auth.js.map +1 -0
- package/dist/ports/builder.d.ts +61 -0
- package/dist/ports/builder.d.ts.map +1 -0
- package/dist/ports/builder.js +48 -0
- package/dist/ports/builder.js.map +1 -0
- package/dist/ports/cache.d.ts +15 -0
- package/dist/ports/cache.d.ts.map +1 -0
- package/dist/ports/cache.js +57 -0
- package/dist/ports/cache.js.map +1 -0
- package/dist/ports/clock.d.ts +10 -0
- package/dist/ports/clock.d.ts.map +1 -0
- package/dist/ports/clock.js +21 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/events.d.ts +71 -0
- package/dist/ports/events.d.ts.map +1 -0
- package/dist/ports/events.js +2 -0
- package/dist/ports/events.js.map +1 -0
- package/dist/ports/id-generator.d.ts +12 -0
- package/dist/ports/id-generator.d.ts.map +1 -0
- package/dist/ports/id-generator.js +22 -0
- package/dist/ports/id-generator.js.map +1 -0
- package/dist/ports/index.d.ts +98 -0
- package/dist/ports/index.d.ts.map +1 -0
- package/dist/ports/index.js +67 -0
- package/dist/ports/index.js.map +1 -0
- package/dist/ports/logger.d.ts +22 -0
- package/dist/ports/logger.d.ts.map +1 -0
- package/dist/ports/logger.js +34 -0
- package/dist/ports/logger.js.map +1 -0
- package/dist/ports/mailer.d.ts +6 -0
- package/dist/ports/mailer.d.ts.map +1 -0
- package/dist/ports/mailer.js +2 -0
- package/dist/ports/mailer.js.map +1 -0
- package/dist/ports/policy.d.ts +53 -0
- package/dist/ports/policy.d.ts.map +1 -0
- package/dist/ports/policy.js +81 -0
- package/dist/ports/policy.js.map +1 -0
- package/dist/ports/rate-limit.d.ts +41 -0
- package/dist/ports/rate-limit.d.ts.map +1 -0
- package/dist/ports/rate-limit.js +37 -0
- package/dist/ports/rate-limit.js.map +1 -0
- package/dist/ports/redaction.d.ts +26 -0
- package/dist/ports/redaction.d.ts.map +1 -0
- package/dist/ports/redaction.js +126 -0
- package/dist/ports/redaction.js.map +1 -0
- package/dist/ports/schedules.d.ts +9 -0
- package/dist/ports/schedules.d.ts.map +1 -0
- package/dist/ports/schedules.js +2 -0
- package/dist/ports/schedules.js.map +1 -0
- package/dist/ports/storage.d.ts +47 -0
- package/dist/ports/storage.d.ts.map +1 -0
- package/dist/ports/storage.js +185 -0
- package/dist/ports/storage.js.map +1 -0
- package/dist/ports/testing.d.ts +73 -0
- package/dist/ports/testing.d.ts.map +1 -0
- package/dist/ports/testing.js +105 -0
- package/dist/ports/testing.js.map +1 -0
- package/dist/ports/unit-of-work.d.ts +56 -0
- package/dist/ports/unit-of-work.d.ts.map +1 -0
- package/dist/ports/unit-of-work.js +64 -0
- package/dist/ports/unit-of-work.js.map +1 -0
- package/dist/providers/index.d.ts +8 -0
- package/dist/providers/index.d.ts.map +1 -0
- package/dist/providers/index.js +8 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/instrumentation.d.ts +91 -0
- package/dist/providers/instrumentation.d.ts.map +1 -0
- package/dist/providers/instrumentation.js +93 -0
- package/dist/providers/instrumentation.js.map +1 -0
- package/dist/providers/provider.d.ts +146 -0
- package/dist/providers/provider.d.ts.map +1 -0
- package/dist/providers/provider.js +31 -0
- package/dist/providers/provider.js.map +1 -0
- package/dist/schedules/index.d.ts +105 -0
- package/dist/schedules/index.d.ts.map +1 -0
- package/dist/schedules/index.js +178 -0
- package/dist/schedules/index.js.map +1 -0
- package/dist/server/contract-like.d.ts +5 -0
- package/dist/server/contract-like.d.ts.map +1 -0
- package/dist/server/contract-like.js +5 -0
- package/dist/server/contract-like.js.map +1 -0
- package/dist/server/health.d.ts +41 -0
- package/dist/server/health.d.ts.map +1 -0
- package/dist/server/health.js +46 -0
- package/dist/server/health.js.map +1 -0
- package/dist/server/hooks/auth.d.ts +42 -0
- package/dist/server/hooks/auth.d.ts.map +1 -0
- package/dist/server/hooks/auth.js +61 -0
- package/dist/server/hooks/auth.js.map +1 -0
- package/dist/server/hooks/cors.d.ts +13 -0
- package/dist/server/hooks/cors.d.ts.map +1 -0
- package/dist/server/hooks/cors.js +70 -0
- package/dist/server/hooks/cors.js.map +1 -0
- package/dist/server/hooks/errors.d.ts +66 -0
- package/dist/server/hooks/errors.d.ts.map +1 -0
- package/dist/server/hooks/errors.js +83 -0
- package/dist/server/hooks/errors.js.map +1 -0
- package/dist/server/hooks/index.d.ts +12 -0
- package/dist/server/hooks/index.d.ts.map +1 -0
- package/dist/server/hooks/index.js +12 -0
- package/dist/server/hooks/index.js.map +1 -0
- package/dist/server/hooks/logging.d.ts +33 -0
- package/dist/server/hooks/logging.d.ts.map +1 -0
- package/dist/server/hooks/logging.js +90 -0
- package/dist/server/hooks/logging.js.map +1 -0
- package/dist/server/hooks/rate-limit.d.ts +29 -0
- package/dist/server/hooks/rate-limit.d.ts.map +1 -0
- package/dist/server/hooks/rate-limit.js +93 -0
- package/dist/server/hooks/rate-limit.js.map +1 -0
- package/dist/server/hooks/utils.d.ts +9 -0
- package/dist/server/hooks/utils.d.ts.map +1 -0
- package/dist/server/hooks/utils.js +16 -0
- package/dist/server/hooks/utils.js.map +1 -0
- package/dist/server/hooks.d.ts +2 -0
- package/dist/server/hooks.d.ts.map +1 -0
- package/dist/server/hooks.js +2 -0
- package/dist/server/hooks.js.map +1 -0
- package/dist/server/http.d.ts +124 -0
- package/dist/server/http.d.ts.map +1 -0
- package/dist/server/http.js +2 -0
- package/dist/server/http.js.map +1 -0
- package/dist/server/index.d.ts +19 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +15 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/openapi.d.ts +32 -0
- package/dist/server/openapi.d.ts.map +1 -0
- package/dist/server/openapi.js +43 -0
- package/dist/server/openapi.js.map +1 -0
- package/dist/server/providers/index.d.ts +4 -0
- package/dist/server/providers/index.d.ts.map +1 -0
- package/dist/server/providers/index.js +4 -0
- package/dist/server/providers/index.js.map +1 -0
- package/dist/server/providers/loadProviderConfig.d.ts +7 -0
- package/dist/server/providers/loadProviderConfig.d.ts.map +1 -0
- package/dist/server/providers/loadProviderConfig.js +42 -0
- package/dist/server/providers/loadProviderConfig.js.map +1 -0
- package/dist/server/server.d.ts +86 -0
- package/dist/server/server.d.ts.map +1 -0
- package/dist/server/server.js +1031 -0
- package/dist/server/server.js.map +1 -0
- package/dist/server/types.d.ts +3 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/types.js +3 -0
- package/dist/server/types.js.map +1 -0
- package/package.json +129 -0
- package/src/application/index.ts +747 -0
- package/src/client/client.ts +1105 -0
- package/src/client/index.ts +45 -0
- package/src/client/types.ts +305 -0
- package/src/config/index.ts +497 -0
- package/src/contracts/contract-builder.ts +583 -0
- package/src/contracts/contract-group.ts +502 -0
- package/src/contracts/contract-like.ts +29 -0
- package/src/contracts/index.ts +53 -0
- package/src/contracts/openapi-meta.ts +22 -0
- package/src/contracts/path-template.ts +91 -0
- package/src/contracts/rate-limit.ts +50 -0
- package/src/contracts/types.ts +207 -0
- package/src/contracts/utils.ts +56 -0
- package/src/domain/entity.ts +256 -0
- package/src/domain/events.ts +52 -0
- package/src/domain/index.ts +18 -0
- package/src/domain/value-object.ts +135 -0
- package/src/errors/catalog.ts +149 -0
- package/src/errors/http.ts +80 -0
- package/src/errors/index.ts +28 -0
- package/src/errors/response.ts +54 -0
- package/src/errors/validation.ts +35 -0
- package/src/events/index.ts +246 -0
- package/src/jobs/index.ts +211 -0
- package/src/mail/index.ts +177 -0
- package/src/openapi/index.ts +865 -0
- package/src/openapi/schema-introspector.ts +107 -0
- package/src/ports/audit.ts +176 -0
- package/src/ports/auth.ts +76 -0
- package/src/ports/builder.ts +97 -0
- package/src/ports/cache.ts +94 -0
- package/src/ports/clock.ts +34 -0
- package/src/ports/events.ts +100 -0
- package/src/ports/id-generator.ts +36 -0
- package/src/ports/index.ts +221 -0
- package/src/ports/logger.ts +67 -0
- package/src/ports/policy.ts +242 -0
- package/src/ports/rate-limit.ts +91 -0
- package/src/ports/redaction.ts +199 -0
- package/src/ports/storage.ts +282 -0
- package/src/ports/testing.ts +234 -0
- package/src/ports/unit-of-work.ts +134 -0
- package/src/providers/index.ts +40 -0
- package/src/providers/instrumentation.ts +248 -0
- package/src/providers/provider.ts +191 -0
- package/src/schedules/index.ts +442 -0
- package/src/server/contract-like.ts +8 -0
- package/src/server/health.ts +82 -0
- package/src/server/hooks/auth.ts +147 -0
- package/src/server/hooks/cors.ts +87 -0
- package/src/server/hooks/errors.ts +126 -0
- package/src/server/hooks/index.ts +43 -0
- package/src/server/hooks/logging.ts +121 -0
- package/src/server/hooks/rate-limit.ts +171 -0
- package/src/server/hooks/utils.ts +16 -0
- package/src/server/hooks.ts +1 -0
- package/src/server/http.ts +189 -0
- package/src/server/index.ts +35 -0
- package/src/server/openapi.ts +72 -0
- package/src/server/providers/index.ts +3 -0
- package/src/server/providers/loadProviderConfig.ts +72 -0
- package/src/server/server.ts +1521 -0
- package/src/server/types.ts +2 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CORS hook utilities for @beignet/core/server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { HttpRequestLike, ServerHook } from "../types";
|
|
6
|
+
|
|
7
|
+
export interface CorsConfig {
|
|
8
|
+
origins?: string[] | "*";
|
|
9
|
+
methods?: string[];
|
|
10
|
+
headers?: string[];
|
|
11
|
+
credentials?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const DEFAULT_CORS: Required<CorsConfig> = {
|
|
15
|
+
origins: "*",
|
|
16
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
17
|
+
headers: ["Content-Type", "Authorization"],
|
|
18
|
+
credentials: false,
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
function appendVaryOrigin(headers: Record<string, string>): void {
|
|
22
|
+
const varyKey =
|
|
23
|
+
Object.keys(headers).find((key) => key.toLowerCase() === "vary") ?? "Vary";
|
|
24
|
+
const current = headers[varyKey];
|
|
25
|
+
const values = current?.split(",").map((value) => value.trim().toLowerCase());
|
|
26
|
+
if (values?.includes("origin")) return;
|
|
27
|
+
|
|
28
|
+
headers[varyKey] = current ? `${current}, Origin` : "Origin";
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function applyCorsHeaders(
|
|
32
|
+
headers: Record<string, string>,
|
|
33
|
+
req: HttpRequestLike,
|
|
34
|
+
corsConfig: CorsConfig,
|
|
35
|
+
): void {
|
|
36
|
+
const origins = corsConfig.origins ?? DEFAULT_CORS.origins;
|
|
37
|
+
const methods = corsConfig.methods ?? DEFAULT_CORS.methods;
|
|
38
|
+
const allowedHeaders = corsConfig.headers ?? DEFAULT_CORS.headers;
|
|
39
|
+
const credentials = corsConfig.credentials ?? DEFAULT_CORS.credentials;
|
|
40
|
+
|
|
41
|
+
if (credentials && origins === "*") {
|
|
42
|
+
const requestOrigin = req.headers.get("Origin");
|
|
43
|
+
if (requestOrigin) {
|
|
44
|
+
headers["Access-Control-Allow-Origin"] = requestOrigin;
|
|
45
|
+
appendVaryOrigin(headers);
|
|
46
|
+
}
|
|
47
|
+
} else if (origins === "*") {
|
|
48
|
+
headers["Access-Control-Allow-Origin"] = "*";
|
|
49
|
+
} else if (Array.isArray(origins)) {
|
|
50
|
+
const requestOrigin = req.headers.get("Origin");
|
|
51
|
+
if (requestOrigin && origins.includes(requestOrigin)) {
|
|
52
|
+
headers["Access-Control-Allow-Origin"] = requestOrigin;
|
|
53
|
+
appendVaryOrigin(headers);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
headers["Access-Control-Allow-Methods"] = methods.join(", ");
|
|
58
|
+
headers["Access-Control-Allow-Headers"] = allowedHeaders.join(", ");
|
|
59
|
+
|
|
60
|
+
if (credentials) {
|
|
61
|
+
headers["Access-Control-Allow-Credentials"] = "true";
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function createCorsHooks<Ctx>(config: CorsConfig): ServerHook<Ctx> {
|
|
66
|
+
return {
|
|
67
|
+
name: "cors",
|
|
68
|
+
onRequest: ({ req }) => {
|
|
69
|
+
if (req.method !== "OPTIONS") return undefined;
|
|
70
|
+
const headers: Record<string, string> = {};
|
|
71
|
+
applyCorsHeaders(headers, req, config);
|
|
72
|
+
return {
|
|
73
|
+
status: 204,
|
|
74
|
+
headers,
|
|
75
|
+
body: null,
|
|
76
|
+
};
|
|
77
|
+
},
|
|
78
|
+
beforeSend: ({ req, response }) => {
|
|
79
|
+
const headers = { ...(response.headers ?? {}) };
|
|
80
|
+
applyCorsHeaders(headers, req, config);
|
|
81
|
+
return {
|
|
82
|
+
...response,
|
|
83
|
+
headers,
|
|
84
|
+
};
|
|
85
|
+
},
|
|
86
|
+
};
|
|
87
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic error mapping utilities for @beignet/core/server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { createErrorResponseBody, type ErrorResponseBody } from "../../errors";
|
|
6
|
+
import type { AppEnvironment } from "../health";
|
|
7
|
+
import { getRequestIdFromContext } from "./utils";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Re-export AppEnvironment for convenience
|
|
11
|
+
*/
|
|
12
|
+
export type { AppEnvironment } from "../health";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Error mapping result
|
|
16
|
+
*/
|
|
17
|
+
export interface ErrorMappingResult {
|
|
18
|
+
status: number;
|
|
19
|
+
body: unknown;
|
|
20
|
+
headers?: Record<string, string>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Error mapping configuration
|
|
25
|
+
*/
|
|
26
|
+
export interface ErrorMappingConfig<Ctx> {
|
|
27
|
+
/** Custom error mapper function */
|
|
28
|
+
mapErrorToResponse?: (err: unknown, ctx: Ctx) => ErrorMappingResult;
|
|
29
|
+
|
|
30
|
+
/** Include stack traces in error responses (default: true in dev/test, false in production) */
|
|
31
|
+
includeStackInResponse?: boolean;
|
|
32
|
+
|
|
33
|
+
/** Application environment */
|
|
34
|
+
env?: AppEnvironment;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Create default error response body
|
|
39
|
+
*/
|
|
40
|
+
function createDefaultErrorBody(
|
|
41
|
+
err: unknown,
|
|
42
|
+
includeStack: boolean,
|
|
43
|
+
requestId?: string,
|
|
44
|
+
): ErrorResponseBody {
|
|
45
|
+
return createErrorResponseBody({
|
|
46
|
+
code: "INTERNAL_SERVER_ERROR",
|
|
47
|
+
message: "Internal server error",
|
|
48
|
+
requestId,
|
|
49
|
+
details:
|
|
50
|
+
includeStack && err instanceof Error
|
|
51
|
+
? {
|
|
52
|
+
error: {
|
|
53
|
+
message: err.message,
|
|
54
|
+
stack: err.stack,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
: undefined,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Default error mapping function that handles unknown errors and converts them
|
|
63
|
+
* to a standard error response format.
|
|
64
|
+
*
|
|
65
|
+
* **Important:** This function does NOT handle AppError instances from @beignet/core/errors.
|
|
66
|
+
* AppError is handled separately in the router's error handling flow before reaching
|
|
67
|
+
* this function. This function is only called for truly unexpected errors that bypass normal
|
|
68
|
+
* error handling (e.g., unhandled exceptions, infrastructure errors).
|
|
69
|
+
*
|
|
70
|
+
* This function:
|
|
71
|
+
* 1. First tries the custom mapErrorToResponse if provided
|
|
72
|
+
* 2. Falls back to a default 500 error response
|
|
73
|
+
* 3. Optionally includes stack traces in development/test environments
|
|
74
|
+
*
|
|
75
|
+
* @param err - The error that was thrown (excluding AppError instances)
|
|
76
|
+
* @param ctx - The request context
|
|
77
|
+
* @param config - Error mapping configuration
|
|
78
|
+
* @returns An error mapping result with status, body, and optional headers
|
|
79
|
+
*
|
|
80
|
+
* @example
|
|
81
|
+
* ```ts
|
|
82
|
+
* const errorConfig = {
|
|
83
|
+
* mapErrorToResponse: (err, ctx) => ({
|
|
84
|
+
* status: 500,
|
|
85
|
+
* body: {
|
|
86
|
+
* code: "INTERNAL_SERVER_ERROR",
|
|
87
|
+
* message: "Custom error",
|
|
88
|
+
* requestId: ctx.requestId,
|
|
89
|
+
* },
|
|
90
|
+
* }),
|
|
91
|
+
* includeStackInResponse: true,
|
|
92
|
+
* env: "development",
|
|
93
|
+
* };
|
|
94
|
+
*
|
|
95
|
+
* const result = defaultMapErrorToResponse(error, ctx, errorConfig);
|
|
96
|
+
* ```
|
|
97
|
+
*/
|
|
98
|
+
export function defaultMapErrorToResponse<Ctx>(
|
|
99
|
+
err: unknown,
|
|
100
|
+
ctx: Ctx,
|
|
101
|
+
config: ErrorMappingConfig<Ctx>,
|
|
102
|
+
): ErrorMappingResult {
|
|
103
|
+
// First, try the user's custom error handler
|
|
104
|
+
if (config.mapErrorToResponse) {
|
|
105
|
+
try {
|
|
106
|
+
return config.mapErrorToResponse(err, ctx);
|
|
107
|
+
} catch {
|
|
108
|
+
// Fall through to default error response below
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Determine if stack traces should be included
|
|
113
|
+
const includeStack =
|
|
114
|
+
config.includeStackInResponse ??
|
|
115
|
+
(config.env === "development" || config.env === "test");
|
|
116
|
+
|
|
117
|
+
// Default error response
|
|
118
|
+
const requestId = getRequestIdFromContext(ctx);
|
|
119
|
+
const body = createDefaultErrorBody(err, includeStack, requestId);
|
|
120
|
+
|
|
121
|
+
return {
|
|
122
|
+
status: 500,
|
|
123
|
+
body,
|
|
124
|
+
headers: { "Content-Type": "application/json" },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook utilities for @beignet/core/server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { AnyPorts } from "../../ports";
|
|
6
|
+
import type { ServerHook } from "../http";
|
|
7
|
+
|
|
8
|
+
export {
|
|
9
|
+
type AuthHookArgs,
|
|
10
|
+
type AuthHookAssignArgs,
|
|
11
|
+
type AuthHookMode,
|
|
12
|
+
type AuthHookModeInput,
|
|
13
|
+
type AuthHooksOptions,
|
|
14
|
+
type AuthHookUnauthorizedArgs,
|
|
15
|
+
type CtxWithAuthPort,
|
|
16
|
+
createAuthHooks,
|
|
17
|
+
} from "./auth";
|
|
18
|
+
export {
|
|
19
|
+
applyCorsHeaders,
|
|
20
|
+
type CorsConfig,
|
|
21
|
+
createCorsHooks,
|
|
22
|
+
} from "./cors";
|
|
23
|
+
export {
|
|
24
|
+
defaultMapErrorToResponse,
|
|
25
|
+
type ErrorMappingConfig,
|
|
26
|
+
type ErrorMappingResult,
|
|
27
|
+
} from "./errors";
|
|
28
|
+
export {
|
|
29
|
+
createLoggingHooks,
|
|
30
|
+
type Logger,
|
|
31
|
+
type LoggingConfig,
|
|
32
|
+
} from "./logging";
|
|
33
|
+
export {
|
|
34
|
+
type CtxWithRateLimit,
|
|
35
|
+
createRateLimitHooks,
|
|
36
|
+
type RateLimitOptions,
|
|
37
|
+
} from "./rate-limit";
|
|
38
|
+
|
|
39
|
+
export function composeHooks<Ctx, Ports extends AnyPorts = AnyPorts>(
|
|
40
|
+
...hooks: (ServerHook<Ctx, Ports> | readonly ServerHook<Ctx, Ports>[])[]
|
|
41
|
+
): ServerHook<Ctx, Ports>[] {
|
|
42
|
+
return hooks.flatMap((hook) => (Array.isArray(hook) ? hook : [hook]));
|
|
43
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logging hook utilities for @beignet/core/server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { HttpContractConfig } from "../../contracts";
|
|
6
|
+
import type { HttpRequestLike, ServerHook } from "../types";
|
|
7
|
+
import { getRequestIdFromContext } from "./utils";
|
|
8
|
+
|
|
9
|
+
export interface Logger {
|
|
10
|
+
info: (...args: unknown[]) => void;
|
|
11
|
+
error: (...args: unknown[]) => void;
|
|
12
|
+
warn?: (...args: unknown[]) => void;
|
|
13
|
+
debug?: (...args: unknown[]) => void;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface LoggingConfig<Ctx> {
|
|
17
|
+
logger?: Logger;
|
|
18
|
+
requestIdHeader?: string;
|
|
19
|
+
onRequestStart?: (args: {
|
|
20
|
+
ctx?: Ctx;
|
|
21
|
+
req: HttpRequestLike;
|
|
22
|
+
contract?: HttpContractConfig;
|
|
23
|
+
}) => void;
|
|
24
|
+
onRequestEnd?: (args: {
|
|
25
|
+
ctx?: Ctx;
|
|
26
|
+
req: HttpRequestLike;
|
|
27
|
+
res: { status: number; headers: Record<string, string> };
|
|
28
|
+
durationMs: number;
|
|
29
|
+
contract?: HttpContractConfig;
|
|
30
|
+
error?: unknown;
|
|
31
|
+
}) => void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createLoggingHooks<Ctx>(
|
|
35
|
+
config: LoggingConfig<Ctx>,
|
|
36
|
+
): ServerHook<Ctx> {
|
|
37
|
+
const requestIdHeader = config.requestIdHeader;
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
name: "logging",
|
|
41
|
+
onRequest: ({ req, contract }) => {
|
|
42
|
+
if (config.onRequestStart) {
|
|
43
|
+
try {
|
|
44
|
+
config.onRequestStart({ req, contract, ctx: undefined });
|
|
45
|
+
} catch {
|
|
46
|
+
// Ignore logging errors
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!config.logger) return undefined;
|
|
52
|
+
try {
|
|
53
|
+
const url = new URL(req.url);
|
|
54
|
+
config.logger.info(
|
|
55
|
+
{ method: req.method, url: url.pathname },
|
|
56
|
+
"Request start",
|
|
57
|
+
);
|
|
58
|
+
} catch {
|
|
59
|
+
// Ignore logging errors
|
|
60
|
+
}
|
|
61
|
+
return undefined;
|
|
62
|
+
},
|
|
63
|
+
...(requestIdHeader
|
|
64
|
+
? {
|
|
65
|
+
beforeSend: ({ ctx, response }) => {
|
|
66
|
+
const requestId = getRequestIdFromContext(ctx);
|
|
67
|
+
if (!requestId) {
|
|
68
|
+
return response;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
...response,
|
|
73
|
+
headers: {
|
|
74
|
+
...(response.headers ?? {}),
|
|
75
|
+
[requestIdHeader]: String(requestId),
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
: {}),
|
|
81
|
+
afterSend: ({ ctx, req, response, durationMs, contract, error }) => {
|
|
82
|
+
const headers = response.headers ?? {};
|
|
83
|
+
if (config.onRequestEnd) {
|
|
84
|
+
try {
|
|
85
|
+
config.onRequestEnd({
|
|
86
|
+
ctx,
|
|
87
|
+
req,
|
|
88
|
+
res: { status: response.status, headers },
|
|
89
|
+
durationMs,
|
|
90
|
+
contract,
|
|
91
|
+
error,
|
|
92
|
+
});
|
|
93
|
+
} catch {
|
|
94
|
+
// Ignore logging errors
|
|
95
|
+
}
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (!config.logger) return;
|
|
100
|
+
try {
|
|
101
|
+
const url = new URL(req.url);
|
|
102
|
+
const requestId = getRequestIdFromContext(ctx);
|
|
103
|
+
const payload = {
|
|
104
|
+
method: req.method,
|
|
105
|
+
url: url.pathname,
|
|
106
|
+
status: response.status,
|
|
107
|
+
durationMs: Math.round(durationMs),
|
|
108
|
+
...(requestId !== undefined ? { requestId } : {}),
|
|
109
|
+
...(error !== undefined ? { error } : {}),
|
|
110
|
+
};
|
|
111
|
+
if (error !== undefined) {
|
|
112
|
+
config.logger.error(payload, "Request complete with error");
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
config.logger.info(payload, "Request complete");
|
|
116
|
+
} catch {
|
|
117
|
+
// Ignore logging errors
|
|
118
|
+
}
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate limit hooks for @beignet/core/server
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { RateLimitScope } from "../../contracts";
|
|
6
|
+
import { AppError, httpErrors } from "../../errors";
|
|
7
|
+
import type { ActivityActor, RateLimitPort } from "../../ports";
|
|
8
|
+
import type { HttpRequestLike, ServerHook } from "../types";
|
|
9
|
+
|
|
10
|
+
export type RateLimitPorts = {
|
|
11
|
+
rateLimit: RateLimitPort;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export type CtxWithRateLimit = {
|
|
15
|
+
ports: RateLimitPorts;
|
|
16
|
+
actor?: ActivityActor;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
type EarlyRateLimitScope = Exclude<RateLimitScope, "user">;
|
|
20
|
+
|
|
21
|
+
export interface RateLimitOptions<Ctx> {
|
|
22
|
+
key?: (args: {
|
|
23
|
+
ctx: Ctx;
|
|
24
|
+
req: HttpRequestLike;
|
|
25
|
+
scope: RateLimitScope;
|
|
26
|
+
}) => string;
|
|
27
|
+
earlyKey?: (args: {
|
|
28
|
+
req: HttpRequestLike;
|
|
29
|
+
scope: EarlyRateLimitScope;
|
|
30
|
+
}) => string;
|
|
31
|
+
getClientIp?: (req: HttpRequestLike) => string | undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function defaultGetClientIp(req: HttpRequestLike): string | undefined {
|
|
35
|
+
const xfwd = req.headers.get("x-forwarded-for") ?? "";
|
|
36
|
+
return xfwd.split(",")[0].trim() || undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function emitUserKey(userId: string): string {
|
|
40
|
+
return `user:${userId}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function emitIpKey(ip: string): string {
|
|
44
|
+
return `ip:${ip}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function defaultRateLimitKey<Ctx extends CtxWithRateLimit>(
|
|
48
|
+
args: {
|
|
49
|
+
ctx: Ctx;
|
|
50
|
+
req: HttpRequestLike;
|
|
51
|
+
scope: RateLimitScope;
|
|
52
|
+
},
|
|
53
|
+
getClientIp: (req: HttpRequestLike) => string | undefined,
|
|
54
|
+
): string {
|
|
55
|
+
const { ctx, req, scope } = args;
|
|
56
|
+
|
|
57
|
+
if (scope === "user" && ctx.actor?.type === "user" && ctx.actor.id) {
|
|
58
|
+
return emitUserKey(ctx.actor.id);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (scope === "ip") {
|
|
62
|
+
const ip = getClientIp(req) || "unknown";
|
|
63
|
+
return emitIpKey(ip);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return "global";
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function defaultEarlyRateLimitKey(
|
|
70
|
+
args: {
|
|
71
|
+
req: HttpRequestLike;
|
|
72
|
+
scope: EarlyRateLimitScope;
|
|
73
|
+
},
|
|
74
|
+
getClientIp: (req: HttpRequestLike) => string | undefined,
|
|
75
|
+
): string {
|
|
76
|
+
if (args.scope === "ip") {
|
|
77
|
+
const ip = getClientIp(args.req) || "unknown";
|
|
78
|
+
return emitIpKey(ip);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return "global";
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function enforceRateLimit(
|
|
85
|
+
rateLimit: RateLimitPort,
|
|
86
|
+
args: {
|
|
87
|
+
key: string;
|
|
88
|
+
limit: number;
|
|
89
|
+
windowSec: number;
|
|
90
|
+
scope: RateLimitScope;
|
|
91
|
+
},
|
|
92
|
+
): Promise<void> {
|
|
93
|
+
const result = await rateLimit.hit({
|
|
94
|
+
key: args.key,
|
|
95
|
+
limit: args.limit,
|
|
96
|
+
windowSec: args.windowSec,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
if (result.allowed) {
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
throw new AppError(
|
|
104
|
+
httpErrors.TooManyRequests,
|
|
105
|
+
{
|
|
106
|
+
key: args.key,
|
|
107
|
+
scope: args.scope,
|
|
108
|
+
retryAfterSeconds: result.retryAfterSeconds,
|
|
109
|
+
resetAt: result.resetAt?.toISOString() ?? null,
|
|
110
|
+
},
|
|
111
|
+
"Rate limit exceeded",
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function createRateLimitHooks<Ctx extends CtxWithRateLimit>(
|
|
116
|
+
options: RateLimitOptions<Ctx> = {},
|
|
117
|
+
): ServerHook<Ctx, RateLimitPorts> {
|
|
118
|
+
const getClientIp = options.getClientIp ?? defaultGetClientIp;
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
name: "rate-limit",
|
|
122
|
+
onRequest: async ({ contract, ports, req }) => {
|
|
123
|
+
const rlMeta = contract.metadata?.rateLimit;
|
|
124
|
+
if (!rlMeta) {
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const scope: RateLimitScope = rlMeta.scope ?? "global";
|
|
129
|
+
if (scope === "user") {
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const key =
|
|
134
|
+
options.earlyKey?.({ req, scope }) ??
|
|
135
|
+
defaultEarlyRateLimitKey({ req, scope }, getClientIp);
|
|
136
|
+
|
|
137
|
+
await enforceRateLimit(ports.rateLimit, {
|
|
138
|
+
key,
|
|
139
|
+
limit: rlMeta.max,
|
|
140
|
+
windowSec: rlMeta.windowSec,
|
|
141
|
+
scope,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return undefined;
|
|
145
|
+
},
|
|
146
|
+
beforeHandle: async ({ ctx, contract, req }) => {
|
|
147
|
+
const rlMeta = contract.metadata?.rateLimit;
|
|
148
|
+
if (!rlMeta) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const scope: RateLimitScope = rlMeta.scope ?? "global";
|
|
153
|
+
if (scope !== "user") {
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const key =
|
|
158
|
+
options.key?.({ ctx, req, scope }) ??
|
|
159
|
+
defaultRateLimitKey({ ctx, req, scope }, getClientIp);
|
|
160
|
+
|
|
161
|
+
await enforceRateLimit(ctx.ports.rateLimit, {
|
|
162
|
+
key,
|
|
163
|
+
limit: rlMeta.max,
|
|
164
|
+
windowSec: rlMeta.windowSec,
|
|
165
|
+
scope,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
return undefined;
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared utilities for server hooks.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extract requestId from context object if it exists.
|
|
7
|
+
* This helper reduces type casting duplication throughout the codebase.
|
|
8
|
+
*/
|
|
9
|
+
export function getRequestIdFromContext(ctx: unknown): string | undefined {
|
|
10
|
+
if (ctx !== null && ctx !== undefined && typeof ctx === "object") {
|
|
11
|
+
const ctxRecord = ctx as Record<string, unknown>;
|
|
12
|
+
const requestId = ctxRecord.requestId;
|
|
13
|
+
return typeof requestId === "string" ? requestId : undefined;
|
|
14
|
+
}
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./hooks/index";
|