elysia-wide-event 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jay Choi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,92 @@
1
+ # elysia-wide-event
2
+
3
+ [![license](https://img.shields.io/github/license/choiexe1/elysia-wide-event)](./LICENSE)
4
+ [![Bun](https://img.shields.io/badge/Bun-%23000000.svg?logo=bun&logoColor=white)](https://bun.sh)
5
+ [![한국어](https://img.shields.io/badge/lang-한국어-blue.svg)](./docs/README.ko.md)
6
+
7
+ Wide event logging plugin for [Elysia](https://elysiajs.com). Aggregates all request context into a single structured log line for better observability.
8
+
9
+ Inspired by [Logging Sucks](https://loggingsucks.com/) - the wide event pattern that makes debugging actually enjoyable.
10
+
11
+ ![elysia-wide-event output](./images/image.png)
12
+
13
+ > **Bun + Elysia only.** This plugin is designed specifically for the Bun runtime and Elysia framework. Node.js is not supported.
14
+
15
+ ## Features
16
+
17
+ - **Context Accumulation**: Collect data throughout request lifecycle via `wideEvent.set()`
18
+ - **Flexible Output**: Pretty colored output or JSON - you choose
19
+ - **Request ID**: Auto-generates or extracts from `x-request-id` header
20
+ - **Performance Metrics**: Automatic request duration tracking
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ bun add elysia-wide-event
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```typescript
31
+ import { Elysia } from "elysia";
32
+ import { wideEvent } from "elysia-wide-event";
33
+
34
+ const app = new Elysia()
35
+ .use(wideEvent())
36
+ .post("/users", ({ wideEvent, body }) => {
37
+ wideEvent.set("user", { email: body.email });
38
+
39
+ const userId = "abc-123";
40
+ wideEvent.set("result", { userId });
41
+
42
+ return { success: true };
43
+ })
44
+ .listen(3000);
45
+ ```
46
+
47
+ ## Output
48
+
49
+ See the screenshot above for output examples. Pretty colored output by default, JSON with `json: true` option.
50
+
51
+ ## Options
52
+
53
+ ```typescript
54
+ wideEvent({
55
+ generateRequestId: () => crypto.randomUUID(),
56
+ requestIdHeader: "x-request-id",
57
+ json: false,
58
+ });
59
+ ```
60
+
61
+ | Option | Type | Default | Description |
62
+ | ------------------- | -------------- | ------------------- | --------------------------------------- |
63
+ | `generateRequestId` | `() => string` | `crypto.randomUUID` | Custom request ID generator |
64
+ | `requestIdHeader` | `string` | `"x-request-id"` | Header for incoming request ID |
65
+ | `json` | `boolean` | `false` | Output as JSON instead of pretty format |
66
+
67
+ ## API
68
+
69
+ ### `wideEvent.set(key, data)`
70
+
71
+ Add context to the current request log.
72
+
73
+ ```typescript
74
+ wideEvent.set("auth", { userId: "123", role: "admin" });
75
+ ```
76
+
77
+ ### `wideEvent.error({ type, message })`
78
+
79
+ Log error details.
80
+
81
+ ```typescript
82
+ wideEvent.error({ type: "ValidationError", message: "Invalid email" });
83
+ ```
84
+
85
+ ## Requirements
86
+
87
+ - Bun >= 1.0.0
88
+ - Elysia >= 1.0.0
89
+
90
+ ## License
91
+
92
+ MIT
@@ -0,0 +1,12 @@
1
+ import type { LogData } from "./types";
2
+ export declare const colors: {
3
+ readonly reset: "\u001B[0m";
4
+ readonly dim: "\u001B[2m";
5
+ readonly green: "\u001B[32m";
6
+ readonly red: "\u001B[31m";
7
+ readonly yellow: "\u001B[33m";
8
+ readonly blue: "\u001B[34m";
9
+ };
10
+ export declare function formatTime(): string;
11
+ export declare function formatData(data: LogData): string;
12
+ //# sourceMappingURL=formatter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"formatter.d.ts","sourceRoot":"","sources":["../src/formatter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAEvC,eAAO,MAAM,MAAM;;;;;;;CAOT,CAAC;AAEX,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAIhD"}
@@ -0,0 +1,39 @@
1
+ import { Elysia } from "elysia";
2
+ import type { WideEventOptions, FlushableLogger } from "./types";
3
+ export type { LogData, ErrorData, WideEventLogger, WideEventOptions, } from "./types";
4
+ export declare const wideEvent: (options?: WideEventOptions) => Elysia<"", {
5
+ decorator: {};
6
+ store: {};
7
+ derive: {
8
+ readonly wideEvent: FlushableLogger;
9
+ readonly requestId: string;
10
+ };
11
+ resolve: {};
12
+ }, {
13
+ typebox: {};
14
+ error: {};
15
+ }, {
16
+ schema: {};
17
+ standaloneSchema: {};
18
+ macro: {};
19
+ macroFn: {};
20
+ parser: {};
21
+ response: import("elysia").ExtractErrorFromHandle<{
22
+ readonly wideEvent: FlushableLogger;
23
+ readonly requestId: string;
24
+ }>;
25
+ }, {}, {
26
+ derive: {};
27
+ resolve: {};
28
+ schema: {};
29
+ standaloneSchema: {};
30
+ response: {};
31
+ }, {
32
+ derive: {};
33
+ resolve: {};
34
+ schema: {};
35
+ standaloneSchema: {};
36
+ response: {};
37
+ }>;
38
+ export default wideEvent;
39
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,KAAK,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAGjE,YAAY,EACV,OAAO,EACP,SAAS,EACT,eAAe,EACf,gBAAgB,GACjB,MAAM,SAAS,CAAC;AAEjB,eAAO,MAAM,SAAS,GAAI,UAAS,gBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA0BvD,CAAC;AAEF,eAAe,SAAS,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,113 @@
1
+ // @bun
2
+ // src/index.ts
3
+ import { Elysia } from "elysia";
4
+
5
+ // src/formatter.ts
6
+ var colors = {
7
+ reset: "\x1B[0m",
8
+ dim: "\x1B[2m",
9
+ green: "\x1B[32m",
10
+ red: "\x1B[31m",
11
+ yellow: "\x1B[33m",
12
+ blue: "\x1B[34m"
13
+ };
14
+ function formatTime() {
15
+ return new Date().toISOString().slice(11, 19);
16
+ }
17
+ function formatData(data) {
18
+ return Object.entries(data).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
19
+ }
20
+
21
+ // src/logger.ts
22
+ class RequestEventLogger {
23
+ requestId;
24
+ method;
25
+ path;
26
+ useJson;
27
+ startTime;
28
+ fields = new Map;
29
+ errorData;
30
+ headerTime;
31
+ constructor(requestId, method, path, useJson) {
32
+ this.requestId = requestId;
33
+ this.method = method;
34
+ this.path = path;
35
+ this.useJson = useJson;
36
+ this.startTime = Date.now();
37
+ this.headerTime = formatTime();
38
+ }
39
+ set(key, data) {
40
+ this.fields.set(key, data);
41
+ }
42
+ error(error) {
43
+ this.errorData = error;
44
+ }
45
+ flush(status) {
46
+ const durationMs = Date.now() - this.startTime;
47
+ if (this.useJson) {
48
+ this.flushJson(status, durationMs);
49
+ } else {
50
+ this.flushDev(status, durationMs);
51
+ }
52
+ }
53
+ flushDev(status, durationMs) {
54
+ const statusColor = status >= 400 ? colors.red : colors.green;
55
+ console.log(`
56
+ ${colors.dim}[${this.headerTime}]${colors.reset} ` + `${colors.yellow}${this.method} ${this.path}${colors.reset} ` + `${statusColor}${status}${colors.reset} ` + `${colors.dim}${durationMs}ms${colors.reset}`);
57
+ for (const [key, data] of this.fields) {
58
+ console.log(` ${colors.blue}${key}:${colors.reset} ${formatData(data)}`);
59
+ }
60
+ if (this.errorData) {
61
+ const errorMsg = this.errorData.message ? `${this.errorData.type}: ${this.errorData.message}` : this.errorData.type;
62
+ console.log(` ${colors.red}\u2717${colors.reset} ${errorMsg}`);
63
+ }
64
+ }
65
+ flushJson(status, durationMs) {
66
+ const record = {
67
+ timestamp: new Date().toISOString(),
68
+ request_id: this.requestId,
69
+ method: this.method,
70
+ path: this.path,
71
+ status,
72
+ duration_ms: durationMs
73
+ };
74
+ for (const [key, data] of this.fields) {
75
+ record[key] = data;
76
+ }
77
+ if (this.errorData) {
78
+ record.error = this.errorData;
79
+ console.error(JSON.stringify(record));
80
+ } else {
81
+ console.log(JSON.stringify(record));
82
+ }
83
+ }
84
+ }
85
+ function createLogger(requestId, method, path, useJson) {
86
+ return new RequestEventLogger(requestId, method, path, useJson);
87
+ }
88
+
89
+ // src/index.ts
90
+ var wideEvent = (options = {}) => {
91
+ const {
92
+ generateRequestId = () => crypto.randomUUID(),
93
+ requestIdHeader = "x-request-id",
94
+ json = false
95
+ } = options;
96
+ return new Elysia({ name: "elysia-wide-event" }).derive({ as: "global" }, (ctx) => {
97
+ const requestId = ctx.request.headers.get(requestIdHeader) ?? generateRequestId();
98
+ const method = ctx.request.method;
99
+ const path = new URL(ctx.request.url).pathname;
100
+ const wideEvent2 = createLogger(requestId, method, path, json);
101
+ return { wideEvent: wideEvent2, requestId };
102
+ }).onAfterResponse({ as: "global" }, ({ wideEvent: wideEvent2, set }) => {
103
+ if (!wideEvent2)
104
+ return;
105
+ const status = typeof set.status === "number" ? set.status : 200;
106
+ wideEvent2.flush(status);
107
+ });
108
+ };
109
+ var src_default = wideEvent;
110
+ export {
111
+ wideEvent,
112
+ src_default as default
113
+ };
@@ -0,0 +1,19 @@
1
+ import type { LogData, ErrorData, WideEventLogger, FlushableLogger } from "./types";
2
+ export declare class RequestEventLogger implements WideEventLogger {
3
+ private requestId;
4
+ private method;
5
+ private path;
6
+ private useJson;
7
+ private startTime;
8
+ private fields;
9
+ private errorData?;
10
+ private headerTime;
11
+ constructor(requestId: string, method: string, path: string, useJson: boolean);
12
+ set(key: string, data: LogData): void;
13
+ error(error: ErrorData): void;
14
+ flush(status: number): void;
15
+ private flushDev;
16
+ private flushJson;
17
+ }
18
+ export declare function createLogger(requestId: string, method: string, path: string, useJson: boolean): FlushableLogger;
19
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,OAAO,EACP,SAAS,EACT,eAAe,EAEf,eAAe,EAChB,MAAM,SAAS,CAAC;AAGjB,qBAAa,kBAAmB,YAAW,eAAe;IAOtD,OAAO,CAAC,SAAS;IACjB,OAAO,CAAC,MAAM;IACd,OAAO,CAAC,IAAI;IACZ,OAAO,CAAC,OAAO;IATjB,OAAO,CAAC,SAAS,CAAS;IAC1B,OAAO,CAAC,MAAM,CAAmC;IACjD,OAAO,CAAC,SAAS,CAAC,CAAY;IAC9B,OAAO,CAAC,UAAU,CAAS;gBAGjB,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO;IAM1B,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAIrC,KAAK,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI;IAI7B,KAAK,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAU3B,OAAO,CAAC,QAAQ;IAsBhB,OAAO,CAAC,SAAS;CAqBlB;AAED,wBAAgB,YAAY,CAC1B,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,OAAO,GACf,eAAe,CAEjB"}
@@ -0,0 +1,44 @@
1
+ export interface LogData {
2
+ [key: string]: unknown;
3
+ }
4
+ export interface ErrorData {
5
+ type: string;
6
+ code?: string;
7
+ message?: string;
8
+ [key: string]: unknown;
9
+ }
10
+ export interface WideEventLogger {
11
+ set: (key: string, data: LogData) => void;
12
+ error: (error: ErrorData) => void;
13
+ }
14
+ export interface WideEventOptions {
15
+ /**
16
+ * Custom request ID generator
17
+ * @default crypto.randomUUID()
18
+ */
19
+ generateRequestId?: () => string;
20
+ /**
21
+ * Request ID header name to use from incoming request
22
+ * @default "x-request-id"
23
+ */
24
+ requestIdHeader?: string;
25
+ /**
26
+ * Output format: true for JSON, false for pretty colored output
27
+ * @default false
28
+ */
29
+ json?: boolean;
30
+ }
31
+ export interface WideEventRecord {
32
+ timestamp: string;
33
+ request_id: string;
34
+ method: string;
35
+ path: string;
36
+ status?: number;
37
+ duration_ms?: number;
38
+ error?: ErrorData;
39
+ [key: string]: unknown;
40
+ }
41
+ export type FlushableLogger = WideEventLogger & {
42
+ flush: (status: number) => void;
43
+ };
44
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,OAAO;IACtB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1C,KAAK,EAAE,CAAC,KAAK,EAAE,SAAS,KAAK,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,iBAAiB,CAAC,EAAE,MAAM,MAAM,CAAC;IAEjC;;;OAGG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IAEzB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG;IAC9C,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACjC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "elysia-wide-event",
3
+ "version": "0.1.0",
4
+ "description": "Wide event logging plugin for Elysia - structured logging with request context",
5
+ "author": "Jay Choi",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/choiexe1/elysia-wide-event"
10
+ },
11
+ "keywords": [
12
+ "elysia",
13
+ "plugin",
14
+ "logging",
15
+ "wide-event",
16
+ "structured-logging",
17
+ "observability",
18
+ "bun"
19
+ ],
20
+ "main": "dist/index.js",
21
+ "module": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "exports": {
24
+ ".": {
25
+ "types": "./dist/index.d.ts",
26
+ "import": "./dist/index.js",
27
+ "default": "./dist/index.js"
28
+ }
29
+ },
30
+ "files": [
31
+ "dist"
32
+ ],
33
+ "scripts": {
34
+ "build": "bun build ./src/index.ts --outdir ./dist --target bun --external elysia && bun run build:types",
35
+ "build:types": "tsc -p tsconfig.build.json",
36
+ "prepublishOnly": "bun run build"
37
+ },
38
+ "engines": {
39
+ "bun": ">=1.0.0"
40
+ },
41
+ "peerDependencies": {
42
+ "elysia": ">=1.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "bun-types": "latest",
46
+ "elysia": "latest",
47
+ "typescript": "^5.0.0"
48
+ }
49
+ }