keryx 0.7.0 → 0.8.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/api.ts +1 -1
- package/classes/Connection.ts +85 -26
- package/classes/Logger.ts +86 -45
- package/config/index.ts +2 -0
- package/config/logger.ts +2 -1
- package/config/observability.ts +7 -0
- package/config/server/web.ts +24 -24
- package/index.ts +1 -0
- package/initializers/actionts.ts +4 -0
- package/initializers/observability.ts +326 -0
- package/initializers/resque.ts +61 -3
- package/initializers/session.ts +7 -7
- package/package.json +3 -1
- package/servers/web.ts +83 -508
- package/util/webCompression.ts +145 -0
- package/util/webResponse.ts +110 -0
- package/util/webRouting.ts +143 -0
- package/util/webSocket.ts +160 -0
- package/util/webStaticFiles.ts +127 -0
package/api.ts
CHANGED
|
@@ -18,7 +18,7 @@ export {
|
|
|
18
18
|
} from "./classes/Channel";
|
|
19
19
|
export { Connection } from "./classes/Connection";
|
|
20
20
|
export { Initializer } from "./classes/Initializer";
|
|
21
|
-
export { Logger } from "./classes/Logger";
|
|
21
|
+
export { LogFormat, Logger } from "./classes/Logger";
|
|
22
22
|
export { Server } from "./classes/Server";
|
|
23
23
|
export type {
|
|
24
24
|
FanOutJob,
|
package/classes/Connection.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { SessionData } from "../initializers/session";
|
|
|
7
7
|
import type { RateLimitInfo } from "../middleware/rateLimit";
|
|
8
8
|
import { isSecret } from "../util/zodMixins";
|
|
9
9
|
import type { Action, ActionParams } from "./Action";
|
|
10
|
+
import { LogFormat } from "./Logger";
|
|
10
11
|
import { ErrorType, TypedError } from "./TypedError";
|
|
11
12
|
|
|
12
13
|
/**
|
|
@@ -21,6 +22,8 @@ export class Connection<T extends Record<string, any> = Record<string, any>> {
|
|
|
21
22
|
identifier: string;
|
|
22
23
|
/** Unique connection ID (UUID by default). Used as the key in `api.connections`. */
|
|
23
24
|
id: string;
|
|
25
|
+
/** Session ID used for Redis session lookup. Defaults to `id` but may differ for WebSocket connections where the session cookie differs from the connection map key. */
|
|
26
|
+
sessionId: string;
|
|
24
27
|
/** The connection's session data, lazily loaded on first action invocation. */
|
|
25
28
|
session?: SessionData<T>;
|
|
26
29
|
/** Set of channel names this connection is currently subscribed to. */
|
|
@@ -41,16 +44,19 @@ export class Connection<T extends Record<string, any> = Record<string, any>> {
|
|
|
41
44
|
* @param identifier - Human-readable identifier, typically the remote IP address.
|
|
42
45
|
* @param id - Unique connection ID. Defaults to a random UUID.
|
|
43
46
|
* @param rawConnection - The underlying transport handle (e.g., Bun `ServerWebSocket`).
|
|
47
|
+
* @param sessionId - Session ID for Redis session lookup. Defaults to `id`. Use a different value when the connection map key should differ from the session cookie (e.g., WebSocket connections).
|
|
44
48
|
*/
|
|
45
49
|
constructor(
|
|
46
50
|
type: string,
|
|
47
51
|
identifier: string,
|
|
48
52
|
id = randomUUID() as string,
|
|
49
53
|
rawConnection: any = undefined,
|
|
54
|
+
sessionId?: string,
|
|
50
55
|
) {
|
|
51
56
|
this.type = type;
|
|
52
57
|
this.identifier = identifier;
|
|
53
58
|
this.id = id;
|
|
59
|
+
this.sessionId = sessionId ?? id;
|
|
54
60
|
this.sessionLoaded = false;
|
|
55
61
|
this.subscriptions = new Set();
|
|
56
62
|
this.rawConnection = rawConnection;
|
|
@@ -152,35 +158,28 @@ export class Connection<T extends Record<string, any> = Record<string, any>> {
|
|
|
152
158
|
});
|
|
153
159
|
}
|
|
154
160
|
|
|
155
|
-
// Note: we want the params object to remain on the same line as the message, so we stringify
|
|
156
|
-
const sanitizedParams = sanitizeParams(params, action);
|
|
157
|
-
const loggingParams = config.logger.colorize
|
|
158
|
-
? colors.gray(JSON.stringify(sanitizedParams))
|
|
159
|
-
: JSON.stringify(sanitizedParams);
|
|
160
|
-
|
|
161
|
-
const statusMessage = `[ACTION:${this.type.toUpperCase()}:${loggerResponsePrefix}]`;
|
|
162
|
-
const messagePrefix = config.logger.colorize
|
|
163
|
-
? loggerResponsePrefix === "OK"
|
|
164
|
-
? colors.bgBlue(statusMessage)
|
|
165
|
-
: colors.bgMagenta(statusMessage)
|
|
166
|
-
: statusMessage;
|
|
167
|
-
|
|
168
161
|
const duration = new Date().getTime() - reqStartTime;
|
|
169
162
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
const correlationIdTag = this.correlationId
|
|
178
|
-
? ` [cor:${this.correlationId}]`
|
|
179
|
-
: "";
|
|
163
|
+
api.observability.action.executionsTotal.add(1, {
|
|
164
|
+
action: actionName ?? "unknown",
|
|
165
|
+
status: loggerResponsePrefix === "OK" ? "success" : "error",
|
|
166
|
+
});
|
|
167
|
+
api.observability.action.duration.record(duration, {
|
|
168
|
+
action: actionName ?? "unknown",
|
|
169
|
+
});
|
|
180
170
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
171
|
+
logAction({
|
|
172
|
+
actionName,
|
|
173
|
+
connectionType: this.type,
|
|
174
|
+
status: loggerResponsePrefix,
|
|
175
|
+
duration,
|
|
176
|
+
params: sanitizeParams(params, action),
|
|
177
|
+
method,
|
|
178
|
+
url,
|
|
179
|
+
identifier: this.identifier,
|
|
180
|
+
correlationId: this.correlationId,
|
|
181
|
+
error,
|
|
182
|
+
});
|
|
184
183
|
|
|
185
184
|
return { response, error };
|
|
186
185
|
}
|
|
@@ -334,6 +333,66 @@ export class Connection<T extends Record<string, any> = Record<string, any>> {
|
|
|
334
333
|
}
|
|
335
334
|
}
|
|
336
335
|
|
|
336
|
+
function logAction(opts: {
|
|
337
|
+
actionName: string | undefined;
|
|
338
|
+
connectionType: string;
|
|
339
|
+
status: "OK" | "ERROR";
|
|
340
|
+
duration: number;
|
|
341
|
+
params: Record<string, any>;
|
|
342
|
+
method: string;
|
|
343
|
+
url: string;
|
|
344
|
+
identifier: string;
|
|
345
|
+
correlationId: string | undefined;
|
|
346
|
+
error: TypedError | undefined;
|
|
347
|
+
}) {
|
|
348
|
+
if (config.logger.format === LogFormat.json) {
|
|
349
|
+
const data: Record<string, any> = {
|
|
350
|
+
action: opts.actionName,
|
|
351
|
+
connectionType: opts.connectionType,
|
|
352
|
+
status: opts.status,
|
|
353
|
+
duration: opts.duration,
|
|
354
|
+
params: opts.params,
|
|
355
|
+
};
|
|
356
|
+
if (opts.method) data.method = opts.method;
|
|
357
|
+
if (opts.url) data.url = opts.url;
|
|
358
|
+
if (opts.identifier) data.identifier = opts.identifier;
|
|
359
|
+
if (opts.correlationId) data.correlationId = opts.correlationId;
|
|
360
|
+
if (opts.error) {
|
|
361
|
+
data.error = opts.error.message;
|
|
362
|
+
data.errorType = opts.error.type;
|
|
363
|
+
if (opts.error.stack) data.errorStack = opts.error.stack;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
logger.info(`action: ${opts.actionName}`, data);
|
|
367
|
+
} else {
|
|
368
|
+
const loggingParams = config.logger.colorize
|
|
369
|
+
? colors.gray(JSON.stringify(opts.params))
|
|
370
|
+
: JSON.stringify(opts.params);
|
|
371
|
+
|
|
372
|
+
const statusMessage = `[ACTION:${opts.connectionType.toUpperCase()}:${opts.status}]`;
|
|
373
|
+
const messagePrefix = config.logger.colorize
|
|
374
|
+
? opts.status === "OK"
|
|
375
|
+
? colors.bgBlue(statusMessage)
|
|
376
|
+
: colors.bgMagenta(statusMessage)
|
|
377
|
+
: statusMessage;
|
|
378
|
+
|
|
379
|
+
const errorStack =
|
|
380
|
+
opts.error && opts.error.stack
|
|
381
|
+
? config.logger.colorize
|
|
382
|
+
? "\r\n" + colors.gray(opts.error.stack)
|
|
383
|
+
: "\r\n" + opts.error.stack
|
|
384
|
+
: "";
|
|
385
|
+
|
|
386
|
+
const correlationIdTag = opts.correlationId
|
|
387
|
+
? ` [cor:${opts.correlationId}]`
|
|
388
|
+
: "";
|
|
389
|
+
|
|
390
|
+
logger.info(
|
|
391
|
+
`${messagePrefix} ${opts.actionName} (${opts.duration}ms) ${opts.method.length > 0 ? `[${opts.method}]` : ""} ${opts.identifier}${opts.url.length > 0 ? `(${opts.url})` : ""}${correlationIdTag} ${opts.error ? opts.error : ""} ${loggingParams} ${errorStack}`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
337
396
|
const REDACTED = "[[secret]]" as const;
|
|
338
397
|
|
|
339
398
|
const sanitizeParams = (params: FormData, action: Action | undefined) => {
|
package/classes/Logger.ts
CHANGED
|
@@ -11,22 +11,30 @@ export enum LogLevel {
|
|
|
11
11
|
"fatal" = "fatal",
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
export enum LogFormat {
|
|
15
|
+
"text" = "text",
|
|
16
|
+
"json" = "json",
|
|
17
|
+
}
|
|
18
|
+
|
|
14
19
|
/**
|
|
15
|
-
* The Logger Class.
|
|
20
|
+
* The Logger Class. Writes to stdout/stderr in either human-readable text format
|
|
21
|
+
* (with optional ANSI colors) or structured NDJSON format for log aggregation systems.
|
|
16
22
|
*/
|
|
17
23
|
export class Logger {
|
|
18
24
|
/** Minimum log level to output. Messages below this level are silently dropped. */
|
|
19
25
|
level: LogLevel;
|
|
20
|
-
/** Whether to apply ANSI color codes to the output. */
|
|
26
|
+
/** Whether to apply ANSI color codes to the output (text format only). */
|
|
21
27
|
colorize: boolean;
|
|
22
28
|
/** Whether to prepend an ISO-8601 timestamp to each log line. */
|
|
23
29
|
includeTimestamps: boolean;
|
|
24
|
-
/** Indentation spaces used when JSON-stringifying the optional
|
|
30
|
+
/** Indentation spaces used when JSON-stringifying the optional data argument in text mode. */
|
|
25
31
|
jSONObjectParsePadding: number;
|
|
26
32
|
/** When `true`, all logging is suppressed (used by CLI mode). */
|
|
27
33
|
quiet: boolean;
|
|
28
34
|
/** The output function — defaults to `console.log`. Override for custom transports. */
|
|
29
35
|
outputStream: typeof console.log;
|
|
36
|
+
/** Output format: `"text"` for human-readable colored output, `"json"` for structured NDJSON. */
|
|
37
|
+
format: LogFormat;
|
|
30
38
|
|
|
31
39
|
constructor(config: typeof configLogger) {
|
|
32
40
|
this.level = config.level;
|
|
@@ -35,17 +43,23 @@ export class Logger {
|
|
|
35
43
|
this.jSONObjectParsePadding = 4;
|
|
36
44
|
this.quiet = false;
|
|
37
45
|
this.outputStream = console.log;
|
|
46
|
+
this.format = config.format;
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
/**
|
|
41
50
|
* Core logging method. Formats and writes a log line to `outputStream` if the given
|
|
42
|
-
* level meets the minimum threshold.
|
|
51
|
+
* level meets the minimum threshold.
|
|
52
|
+
*
|
|
53
|
+
* In text mode, outputs a human-readable string with optional timestamp, colors, and
|
|
54
|
+
* pretty-printed data object. In JSON mode, outputs a single NDJSON line with structured
|
|
55
|
+
* fields including `timestamp`, `level`, `message`, `pid`, and any fields from `data`.
|
|
43
56
|
*
|
|
44
57
|
* @param level - The severity level of this log entry.
|
|
45
58
|
* @param message - The log message string.
|
|
46
|
-
* @param
|
|
59
|
+
* @param data - Optional structured data to include. In text mode, JSON-stringified and
|
|
60
|
+
* appended to the log line. In JSON mode, merged into the output object.
|
|
47
61
|
*/
|
|
48
|
-
log(level: LogLevel, message: string,
|
|
62
|
+
log(level: LogLevel, message: string, data?: any) {
|
|
49
63
|
if (this.quiet) return;
|
|
50
64
|
|
|
51
65
|
if (
|
|
@@ -55,84 +69,111 @@ export class Logger {
|
|
|
55
69
|
return;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let formattedLevel = `[${level}]`;
|
|
64
|
-
if (this.colorize) {
|
|
65
|
-
formattedLevel = this.colorFromLopLevel(level)(formattedLevel);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
let prettyObject =
|
|
69
|
-
object !== undefined
|
|
70
|
-
? JSON.stringify(object, null, this.jSONObjectParsePadding)
|
|
71
|
-
: "";
|
|
72
|
-
if (this.colorize && prettyObject.length > 0) {
|
|
73
|
-
prettyObject = colors.cyan(prettyObject);
|
|
72
|
+
if (this.format === LogFormat.json) {
|
|
73
|
+
this.logJson(level, message, data);
|
|
74
|
+
} else {
|
|
75
|
+
this.logText(level, message, data);
|
|
74
76
|
}
|
|
75
|
-
|
|
76
|
-
this.outputStream(
|
|
77
|
-
`${timestamp} ${formattedLevel} ${message} ${prettyObject}`,
|
|
78
|
-
);
|
|
79
77
|
}
|
|
80
78
|
|
|
81
79
|
/**
|
|
82
80
|
* Log a trace message.
|
|
83
81
|
* @param message - The message to log.
|
|
84
|
-
* @param
|
|
82
|
+
* @param data - Optional structured data to include in the log entry.
|
|
85
83
|
*/
|
|
86
|
-
trace(message: string,
|
|
87
|
-
this.log(LogLevel.trace, message,
|
|
84
|
+
trace(message: string, data?: any) {
|
|
85
|
+
this.log(LogLevel.trace, message, data);
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
/**
|
|
91
89
|
* Log a debug message.
|
|
92
90
|
* @param message - The message to log.
|
|
93
|
-
* @param
|
|
91
|
+
* @param data - Optional structured data to include in the log entry.
|
|
94
92
|
*/
|
|
95
|
-
debug(message: string,
|
|
96
|
-
this.log(LogLevel.debug, message,
|
|
93
|
+
debug(message: string, data?: any) {
|
|
94
|
+
this.log(LogLevel.debug, message, data);
|
|
97
95
|
}
|
|
98
96
|
|
|
99
97
|
/**
|
|
100
98
|
* Log an info message.
|
|
101
99
|
* @param message - The message to log.
|
|
102
|
-
* @param
|
|
100
|
+
* @param data - Optional structured data to include in the log entry.
|
|
103
101
|
*/
|
|
104
|
-
info(message: string,
|
|
105
|
-
this.log(LogLevel.info, message,
|
|
102
|
+
info(message: string, data?: any) {
|
|
103
|
+
this.log(LogLevel.info, message, data);
|
|
106
104
|
}
|
|
107
105
|
|
|
108
106
|
/**
|
|
109
107
|
* Log a warning.
|
|
110
108
|
* @param message - The message to log.
|
|
111
|
-
* @param
|
|
109
|
+
* @param data - Optional structured data to include in the log entry.
|
|
112
110
|
*/
|
|
113
|
-
warn(message: string,
|
|
114
|
-
this.log(LogLevel.warn, message,
|
|
111
|
+
warn(message: string, data?: any) {
|
|
112
|
+
this.log(LogLevel.warn, message, data);
|
|
115
113
|
}
|
|
116
114
|
|
|
117
115
|
/**
|
|
118
116
|
* Log an error.
|
|
119
117
|
* @param message - The message to log.
|
|
120
|
-
* @param
|
|
118
|
+
* @param data - Optional structured data to include in the log entry.
|
|
121
119
|
*/
|
|
122
|
-
error(message: string,
|
|
123
|
-
this.log(LogLevel.error, message,
|
|
120
|
+
error(message: string, data?: any) {
|
|
121
|
+
this.log(LogLevel.error, message, data);
|
|
124
122
|
}
|
|
125
123
|
|
|
126
124
|
/**
|
|
127
125
|
* Log a fatal error.
|
|
128
126
|
* @param message - The message to log.
|
|
129
|
-
* @param
|
|
127
|
+
* @param data - Optional structured data to include in the log entry.
|
|
130
128
|
*/
|
|
131
|
-
fatal(message: string,
|
|
132
|
-
this.log(LogLevel.fatal, message,
|
|
129
|
+
fatal(message: string, data?: any) {
|
|
130
|
+
this.log(LogLevel.fatal, message, data);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private logText(level: LogLevel, message: string, data?: any) {
|
|
134
|
+
let timestamp = this.includeTimestamps ? `${new Date().toISOString()}` : "";
|
|
135
|
+
if (this.colorize && timestamp.length > 0) {
|
|
136
|
+
timestamp = colors.gray(timestamp);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
let formattedLevel = `[${level}]`;
|
|
140
|
+
if (this.colorize) {
|
|
141
|
+
formattedLevel = this.colorFromLogLevel(level)(formattedLevel);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
let prettyObject =
|
|
145
|
+
data !== undefined
|
|
146
|
+
? JSON.stringify(data, null, this.jSONObjectParsePadding)
|
|
147
|
+
: "";
|
|
148
|
+
if (this.colorize && prettyObject.length > 0) {
|
|
149
|
+
prettyObject = colors.cyan(prettyObject);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
this.outputStream(
|
|
153
|
+
`${timestamp} ${formattedLevel} ${message} ${prettyObject}`,
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
private logJson(level: LogLevel, message: string, data?: any) {
|
|
158
|
+
const entry: Record<string, any> = {
|
|
159
|
+
timestamp: new Date().toISOString(),
|
|
160
|
+
level,
|
|
161
|
+
message,
|
|
162
|
+
pid: process.pid,
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
if (data !== undefined && data !== null) {
|
|
166
|
+
if (typeof data === "object" && !Array.isArray(data)) {
|
|
167
|
+
Object.assign(entry, data);
|
|
168
|
+
} else {
|
|
169
|
+
entry.data = data;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
this.outputStream(JSON.stringify(entry));
|
|
133
174
|
}
|
|
134
175
|
|
|
135
|
-
private
|
|
176
|
+
private colorFromLogLevel(level: LogLevel) {
|
|
136
177
|
switch (level) {
|
|
137
178
|
case LogLevel.trace:
|
|
138
179
|
return colors.gray;
|
package/config/index.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { configActions } from "./actions";
|
|
|
2
2
|
import { configChannels } from "./channels";
|
|
3
3
|
import { configDatabase } from "./database";
|
|
4
4
|
import { configLogger } from "./logger";
|
|
5
|
+
import { configObservability } from "./observability";
|
|
5
6
|
import { configProcess } from "./process";
|
|
6
7
|
import { configRateLimit } from "./rateLimit";
|
|
7
8
|
import { configRedis } from "./redis";
|
|
@@ -17,6 +18,7 @@ export const config = {
|
|
|
17
18
|
process: configProcess,
|
|
18
19
|
logger: configLogger,
|
|
19
20
|
database: configDatabase,
|
|
21
|
+
observability: configObservability,
|
|
20
22
|
redis: configRedis,
|
|
21
23
|
rateLimit: configRateLimit,
|
|
22
24
|
session: configSession,
|
package/config/logger.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { LogLevel } from "../classes/Logger";
|
|
1
|
+
import { LogFormat, LogLevel } from "../classes/Logger";
|
|
2
2
|
import { loadFromEnvIfSet } from "../util/config";
|
|
3
3
|
|
|
4
4
|
export const configLogger = {
|
|
5
5
|
level: await loadFromEnvIfSet<LogLevel>("LOG_LEVEL", LogLevel.info),
|
|
6
6
|
includeTimestamps: await loadFromEnvIfSet("LOG_INCLUDE_TIMESTAMPS", true),
|
|
7
7
|
colorize: await loadFromEnvIfSet("LOG_COLORIZE", true),
|
|
8
|
+
format: await loadFromEnvIfSet<LogFormat>("LOG_FORMAT", LogFormat.text),
|
|
8
9
|
};
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { loadFromEnvIfSet } from "../util/config";
|
|
2
|
+
|
|
3
|
+
export const configObservability = {
|
|
4
|
+
enabled: await loadFromEnvIfSet("OTEL_METRICS_ENABLED", false),
|
|
5
|
+
metricsRoute: await loadFromEnvIfSet("OTEL_METRICS_ROUTE", "/metrics"),
|
|
6
|
+
serviceName: await loadFromEnvIfSet("OTEL_SERVICE_NAME", ""),
|
|
7
|
+
};
|
package/config/server/web.ts
CHANGED
|
@@ -21,30 +21,25 @@ export const configServerWeb = {
|
|
|
21
21
|
"WEB_SERVER_ALLOWED_HEADERS",
|
|
22
22
|
"Content-Type",
|
|
23
23
|
),
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"WEB_SERVER_STATIC_DIRECTORY",
|
|
27
|
-
"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
websocketMaxSubscriptions: await loadFromEnvIfSet(
|
|
44
|
-
"WS_MAX_SUBSCRIPTIONS",
|
|
45
|
-
100,
|
|
46
|
-
),
|
|
47
|
-
websocketDrainTimeout: await loadFromEnvIfSet("WS_DRAIN_TIMEOUT", 5000),
|
|
24
|
+
staticFiles: {
|
|
25
|
+
enabled: await loadFromEnvIfSet("WEB_SERVER_STATIC_ENABLED", true),
|
|
26
|
+
directory: await loadFromEnvIfSet("WEB_SERVER_STATIC_DIRECTORY", "assets"),
|
|
27
|
+
route: await loadFromEnvIfSet("WEB_SERVER_STATIC_ROUTE", "/"),
|
|
28
|
+
cacheControl: await loadFromEnvIfSet(
|
|
29
|
+
"WEB_SERVER_STATIC_CACHE_CONTROL",
|
|
30
|
+
"public, max-age=3600",
|
|
31
|
+
),
|
|
32
|
+
etag: await loadFromEnvIfSet("WEB_SERVER_STATIC_ETAG", true),
|
|
33
|
+
},
|
|
34
|
+
websocket: {
|
|
35
|
+
maxPayloadSize: await loadFromEnvIfSet("WS_MAX_PAYLOAD_SIZE", 65_536),
|
|
36
|
+
maxMessagesPerSecond: await loadFromEnvIfSet(
|
|
37
|
+
"WS_MAX_MESSAGES_PER_SECOND",
|
|
38
|
+
20,
|
|
39
|
+
),
|
|
40
|
+
maxSubscriptions: await loadFromEnvIfSet("WS_MAX_SUBSCRIPTIONS", 100),
|
|
41
|
+
drainTimeout: await loadFromEnvIfSet("WS_DRAIN_TIMEOUT", 5000),
|
|
42
|
+
},
|
|
48
43
|
includeStackInErrors: await loadFromEnvIfSet(
|
|
49
44
|
"WEB_SERVER_INCLUDE_STACK_IN_ERRORS",
|
|
50
45
|
(Bun.env.NODE_ENV ?? "development") !== "production",
|
|
@@ -71,6 +66,11 @@ export const configServerWeb = {
|
|
|
71
66
|
"strict-origin-when-cross-origin",
|
|
72
67
|
),
|
|
73
68
|
} as Record<string, string>,
|
|
69
|
+
compression: {
|
|
70
|
+
enabled: await loadFromEnvIfSet("WEB_COMPRESSION_ENABLED", true),
|
|
71
|
+
threshold: await loadFromEnvIfSet("WEB_COMPRESSION_THRESHOLD", 1024),
|
|
72
|
+
encodings: ["br", "gzip"] as ("br" | "gzip")[],
|
|
73
|
+
},
|
|
74
74
|
correlationId: {
|
|
75
75
|
header: await loadFromEnvIfSet("WEB_CORRELATION_ID_HEADER", "X-Request-Id"),
|
|
76
76
|
trustProxy: await loadFromEnvIfSet("WEB_CORRELATION_ID_TRUST_PROXY", false),
|
package/index.ts
CHANGED
|
@@ -7,6 +7,7 @@ import "./initializers/connections";
|
|
|
7
7
|
import "./initializers/db";
|
|
8
8
|
import "./initializers/mcp";
|
|
9
9
|
import "./initializers/oauth";
|
|
10
|
+
import "./initializers/observability";
|
|
10
11
|
import "./initializers/process";
|
|
11
12
|
import "./initializers/pubsub";
|
|
12
13
|
import "./initializers/redis";
|
package/initializers/actionts.ts
CHANGED
|
@@ -93,6 +93,10 @@ export class Actions extends Initializer {
|
|
|
93
93
|
});
|
|
94
94
|
}
|
|
95
95
|
queue = queue ?? action?.task?.queue ?? DEFAULT_QUEUE;
|
|
96
|
+
api.observability.task.enqueuedTotal.add(1, {
|
|
97
|
+
action: actionName,
|
|
98
|
+
queue,
|
|
99
|
+
});
|
|
96
100
|
return api.resque.queue.enqueue(queue, actionName, [inputs]);
|
|
97
101
|
};
|
|
98
102
|
|