@elysiumjs/hermes 0.3.1 → 0.4.3

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 CHANGED
@@ -1,5 +1,73 @@
1
1
  # @elysiumjs/hermes
2
2
 
3
+ ## 0.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [`04773f2`](https://github.com/na2axl/elysiumjs/commit/04773f2acd4ab1c762a80ee903715d90f1c08e21) Thanks [@na2axl](https://github.com/na2axl)! - Ship the type declarations. The packages pointed `types` at `dist/index.d.ts` but their build never wrote it, so TypeScript projects got no types for them.
8
+
9
+ ## 0.4.2
10
+
11
+ ### Patch Changes
12
+
13
+ - [`68a0cdd`](https://github.com/na2axl/elysiumjs/commit/68a0cdd8b2e4a003d6e0af2d78985623cae40428) Thanks [@na2axl](https://github.com/na2axl)! - Route the framework's own diagnostics through the logger service.
14
+
15
+ Breaking changes:
16
+
17
+ - `@Event.listen()` with a mode other than `on` or `once` now throws an `ElysiumError` when the class is defined, instead of logging to the console and ignoring the listener.
18
+
19
+ Fixes and additions:
20
+
21
+ - `getLogger(context?)` and `LOGGER_SERVICE` (`'logger'`) are exported by `@elysiumjs/core`. `getLogger()` returns the `logger` service when one is registered (such as the one `HermesModule` provides), and a `ConsoleLogger` otherwise or when the service cannot be built or fails to create a child logger. It is resolved at call time from the global scope only, so a module's own `logger` service does not replace it, and returns a child logger bound to `context` when one is given.
22
+ - `Middleware.logger` (used by `RequestLoggerMiddleware`) falls back to a `ConsoleLogger` when no `logger` service is registered. It threw before.
23
+ - Core diagnostics go through the logger with structured context instead of `console.*`: the debug-mode request log (`debug`, with `method` and `url`), `onDestroy` failures on shutdown (`error`, with `target` and `error`), WAMP connection attempts (`warn` while retrying, `error` when giving up, with `connection`, `attempt`/`attempts`, `maxRetries` and `error`), and WAMP registrations and subscriptions (`info`, with `connection`, `topic` and the registration or subscription id).
24
+ - In debug mode, incoming requests are logged through the logger at `debug` level instead of printed with `console.log`. With `HermesModule`, they show only when its `level` is `debug` or `trace`.
25
+ - A failed WAMP registration or subscription is logged at `error` level and emitted as `elysium:error` with an error naming the topic and the connection, instead of becoming an unhandled rejection.
26
+ - The `elysium:error` emitted for a WAMP client error names the connection and its URL and includes the client's message (or the value it reported, when it is not an `Error`), with that value as `cause` when there is one.
27
+ - `HermesLogger` serialises an `error` field of the log context like pino's `err` (type, message and stack), so logged errors keep their stack trace. Elysium logs errors under `error`.
28
+ - `@Logged` resolves its fallback logger with `getLogger()`, and logs a thrown error whole under `error` (it logged only its message), so its stack trace is kept.
29
+ - The `elysium:error` emitted when a WAMP connection gives up states the number of attempts actually made (the first one plus the retries), as the logged `attempts` does. It stated `maxRetries` before.
30
+
31
+ - Updated dependencies [[`68a0cdd`](https://github.com/na2axl/elysiumjs/commit/68a0cdd8b2e4a003d6e0af2d78985623cae40428), [`2c49acd`](https://github.com/na2axl/elysiumjs/commit/2c49acd3b552aa43765d70339fa216961499e1b5), [`76ba0ff`](https://github.com/na2axl/elysiumjs/commit/76ba0ff18f3c196cebc2189426328f67cb699f46), [`4ebf029`](https://github.com/na2axl/elysiumjs/commit/4ebf029611e6d753a4f1bfdbe247a75d954d9ded), [`bacce71`](https://github.com/na2axl/elysiumjs/commit/bacce7146f667a1a46bf474dc0344fb0111ae12a)]:
32
+ - @elysiumjs/core@0.10.0
33
+
34
+ ## 0.4.1
35
+
36
+ ### Patch Changes
37
+
38
+ - Updated dependencies [[`5fef4a7`](https://github.com/na2axl/elysiumjs/commit/5fef4a7199f502fc4fe035299e7670739459a108)]:
39
+ - @elysiumjs/core@0.9.0
40
+
41
+ ## 0.4.0
42
+
43
+ ### Minor Changes
44
+
45
+ - [`07236b3`](https://github.com/na2axl/elysiumjs/commit/07236b386c2506590d74e8753ce847c1e184b3ee) Thanks [@na2axl](https://github.com/na2axl)! - Introduce the module system. Modules are now the only way to register anything in an Elysium.js app. This is a breaking change with no compatibility layer.
46
+
47
+ Breaking changes:
48
+
49
+ - `@Module.register()` takes `imports`, `services`, `exports`, `controllers`, `commands`, `jobs`, `prefix` and `env`. Services are private to their module unless exported (or global). The `beforeRegister` and `afterRegister` hooks are replaced by `onInit`, `onServe` and `onDestroy`.
50
+ - Configurable modules are bound with `with()`, for example `HermesModule.with({ level: 'debug' })`. `with()` accepts an object or a function.
51
+ - Decorators only record metadata. Importing a file no longer registers commands, services or event wiring, and the side-effect imports of the packages are removed. List commands, jobs and services in the module that owns them.
52
+ - `@Application.register()` no longer takes `database`, `redis`, `wamp` or `'elysium:hermes'`/`'elysium:cerberus'` keys. Use `DrizzleModule`, `RedisModule`, `WampModule`, `HermesModule` and `CerberusModule`. Package plugins are replaced by modules: add `SentryModule` instead of the `plugin()` function. `plugins` still accepts plain Elysia plugin factories.
53
+ - Constructing an `Application` no longer runs it. Use `await App.start()`; `Application.testing()` boots a module graph for tests. The `styx` binary loads `src/app.ts` (or `index.ts`) and starts the exported application, so a project entry no longer needs to import commands.
54
+ - The job system of core (`Job`, `Worker`, `WorkerPool`, `commandWork`) is removed. `@elysiumjs/heracles` is the only job system: extend its `Job`, list jobs in a module's `jobs`, declare queues with `HeraclesModule.with({ queues })`, and dispatch with `Heracles.dispatch()`.
55
+ - `@elysiumjs/styx` ships `StyxModule`, active in development only. The `migration:generate` and `migration:run` commands move to `DrizzleModule` and are available in production.
56
+ - `@elysiumjs/mnemosyne` ships `MnemosyneModule` for its caches (`redis: false` or `memory: false` opts out). `DrizzleModule` imports it.
57
+ - New modules: `HephaestusModule`, `ArgusModule` (`serviceName` is required), `SentryModule` (needs a `dsn` or `SENTRY_DSN`) and `CerberusModule` (`getSubject` is required).
58
+ - Module and service code throws typed errors (for example `InvalidModuleConfigError`) instead of exiting the process. `Application.run()` prints them and exits; command `run()` methods may still exit, and package plugin factories are unchanged.
59
+ - `Application.register` merges subclass props over the parent's (own props, then own metadata, then parent, then defaults), and concatenates `modules` parent first. Previously defaults and parent values won, so `openapi` could not be enabled.
60
+ - `Heracles.dispatch` starts a stopped queue on first use, so producers no longer start queues. `Queue.start()` is idempotent.
61
+ - `create-elysium` generates apps that use modules, and adds Sentry and Hephaestus to the `modules` list.
62
+ - `@Event.on`/`@Event.once` method listeners on classes in a loaded module's `services` or `controllers` are wired by the kernel and run in their module's context. Jobs, commands and middlewares are not wired, and an instance listener on a class that is not a service throws `EventListenerNotSupportedError`. Listeners on classes not listed in a loaded module no longer fire.
63
+ - New typed errors: `CircularDependencyError` (services that depend on each other, in sync and async resolution), `DuplicateModuleNameError` (two different module classes sharing a name; module scopes and config tokens are name based) and `EventListenerNotSupportedError`.
64
+ - Lazy boot contract: a CLI command initialises only its own module and that module's imports. Sync helpers such as `Database.getConnection()` work when the owning module's `onInit` is synchronous; otherwise they throw `ServiceNotReadyError` naming the module. Use `await Service.resolve()` or set `static boot = true` on the command.
65
+
66
+ ### Patch Changes
67
+
68
+ - Updated dependencies [[`8ac91ab`](https://github.com/na2axl/elysiumjs/commit/8ac91ab676b1119b4eecf0ddb3fd0a3e84017cac), [`3625883`](https://github.com/na2axl/elysiumjs/commit/3625883dba92f1596e7444ee52f9399d8e9b049f), [`07236b3`](https://github.com/na2axl/elysiumjs/commit/07236b386c2506590d74e8753ce847c1e184b3ee)]:
69
+ - @elysiumjs/core@0.8.0
70
+
3
71
  ## 0.3.0
4
72
 
5
73
  ### Minor Changes
package/README.md CHANGED
@@ -10,13 +10,31 @@ bun add @elysiumjs/hermes
10
10
 
11
11
  ## Usage
12
12
 
13
+ Add `HermesModule` to your application. It provides the logger as the global services `HermesLogger` and `logger`.
14
+
15
+ ```ts
16
+ import { Application } from '@elysiumjs/core';
17
+ import { HermesModule } from '@elysiumjs/hermes';
18
+
19
+ @Application.register({
20
+ modules: [HermesModule.with({ level: 'debug', format: 'pretty' })]
21
+ })
22
+ export class App extends Application {}
23
+ ```
24
+
25
+ Inject the logger in your services:
26
+
13
27
  ```ts
28
+ import { Service } from '@elysiumjs/core';
14
29
  import { HermesLogger } from '@elysiumjs/hermes';
15
30
 
16
- const logger = HermesLogger.make('my-logger');
31
+ export class UserService {
32
+ public constructor(@Service.inject() private readonly logger: HermesLogger) {}
17
33
 
18
- logger.info('Hello world!');
19
- logger.error('Something went wrong!');
34
+ public hello() {
35
+ this.logger.info('Hello world!');
36
+ }
37
+ }
20
38
  ```
21
39
 
22
40
  ## License
@@ -0,0 +1,16 @@
1
+ import type { LogLevel } from '../types';
2
+ export interface LoggedOptions {
3
+ level?: LogLevel;
4
+ includeArgs?: boolean;
5
+ includeResult?: boolean;
6
+ includeTiming?: boolean;
7
+ }
8
+ /**
9
+ * Decorator that logs method entry/exit with optional timing.
10
+ *
11
+ * Logger is resolved at CALL TIME (not decorator definition time)
12
+ * to avoid capturing a stale logger reference.
13
+ *
14
+ * @author Axel Nana <axel.nana@workbud.com>
15
+ */
16
+ export declare function Logged(options?: LoggedOptions): MethodDecorator;
@@ -0,0 +1,24 @@
1
+ import { type HermesConfig } from './types';
2
+ declare const HermesModule_base: (abstract new () => {
3
+ readonly config: HermesConfig;
4
+ onInit(): Promise<void> | void;
5
+ onDestroy(): Promise<void> | void;
6
+ onServe(_app: import("elysia").default<any>): Promise<void> | void;
7
+ }) & {
8
+ readonly config: string;
9
+ with(config: Partial<HermesConfig> | (() => Partial<HermesConfig> | Promise<Partial<HermesConfig>>)): import("@elysiumjs/core").ConfiguredModule<HermesConfig>;
10
+ resolveConfig(input?: Partial<HermesConfig> | undefined): HermesConfig;
11
+ register<TConfig_1 = any>(props?: import("@elysiumjs/core").ModuleProps<TConfig_1>): (target: import("@elysiumjs/core").ModuleClass) => void;
12
+ configurable<TConfig extends object>(options?: {
13
+ defaults?: Partial<TConfig>;
14
+ required?: (keyof TConfig)[];
15
+ }): any;
16
+ };
17
+ /**
18
+ * Provides the {@link HermesLogger} as the global services `HermesLogger` and `logger`
19
+ * (the same instance), built from the module config.
20
+ * @author Axel Nana <axel.nana@workbud.com>
21
+ */
22
+ export declare class HermesModule extends HermesModule_base {
23
+ }
24
+ export {};
@@ -0,0 +1,7 @@
1
+ export { HermesModule } from './hermes.module';
2
+ export { HermesLogger } from './services/logger.service';
3
+ export { HttpTransport } from './transports/http.transport';
4
+ export type { HttpTransportOptions } from './transports/http.transport';
5
+ export { Logged } from './decorators/logged';
6
+ export type { LoggedOptions } from './decorators/logged';
7
+ export type { HermesConfig, LogLevel, LogFormat, TransportConfig } from './types';
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
1
  // @bun
2
- var V=import.meta.require;import{Service as S}from"@elysiumjs/core";import{Service as G}from"@elysiumjs/core";import E from"pino";var Y={level:"info",format:"json"};class C{pino;config;static make(f){return G.get(C)?.child({name:f})??new C({name:f})}constructor(f={}){this.config={...Y,...f},this.pino=this.createPinoInstance()}createPinoInstance(){let f={level:this.config.level,name:this.config.name};if(this.config.format==="pretty")return E(f,E.transport({target:"pino-pretty"}));return E(f)}trace(f,O){this.pino.trace(O??{},f)}debug(f,O){this.pino.debug(O??{},f)}info(f,O){this.pino.info(O??{},f)}warn(f,O){this.pino.warn(O??{},f)}error(f,O){this.pino.error(O??{},f)}fatal(f,O){this.pino.fatal(O??{},f)}child(f){let O=Object.create(this);return O.pino=this.pino.child(f),O}}class b{options;buffer=[];flushTimer;maxBufferSize;constructor(f){this.options=f;this.options.batchSize??=100,this.options.flushInterval??=5000,this.maxBufferSize=this.options.maxBufferSize??1e4,this.flushTimer=setInterval(()=>this.flush(),this.options.flushInterval)}write(f){if(this.buffer.length>=this.maxBufferSize)this.buffer.shift();if(this.buffer.push(f),this.buffer.length>=this.options.batchSize)this.flush()}async flush(){if(this.buffer.length===0)return;let f=this.buffer.splice(0);try{await fetch(this.options.url,{method:"POST",headers:{"Content-Type":"application/json",...this.options.headers},body:JSON.stringify({logs:f})})}catch{let O=this.maxBufferSize-this.buffer.length;if(O>0)this.buffer.unshift(...f.slice(0,O))}}async close(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}}import{ConsoleLogger as q}from"@elysiumjs/core";function J(f={}){return function(O,D,F){let L=F.value,I=`${O.constructor.name}.${String(D)}`;return F.value=async function(...T){let W=this.logger??N(),U=Date.now();if(f.includeArgs)W.debug(`[${I}] Entry`,{args:T});try{let w=await L.apply(this,T);if(f.includeResult||f.includeTiming){let A=Date.now()-U;W[f.level??"debug"](`[${I}] Exit`,{...f.includeResult&&{result:w},...f.includeTiming&&{duration:`${A}ms`}})}return w}catch(w){let A=Date.now()-U;throw W.error(`[${I}] Error`,{error:w instanceof Error?w.message:w,duration:`${A}ms`}),w}},F}}function N(){try{let{Service:f}=V("@elysiumjs/core");return f.get("logger")??new q}catch{return new q}}var R=null,B=()=>{if(!R){let O=S.get("elysium.app")?.getConfig("elysium:hermes");R=new C(O??{})}return R};S.registerLazy(C.name,B);S.registerLazy("logger",B);export{J as Logged,b as HttpTransport,C as HermesLogger};
2
+ var __legacyDecorateClassTS=function(decorators,target,key,desc){var c=arguments.length,r=c<3?target:desc===null?desc=Object.getOwnPropertyDescriptor(target,key):desc,d;if(typeof Reflect==="object"&&typeof Reflect.decorate==="function")r=Reflect.decorate(decorators,target,key,desc);else for(var i=decorators.length-1;i>=0;i--)if(d=decorators[i])r=(c<3?d(r):c>3?d(target,key,r):d(target,key))||r;return c>3&&r&&Object.defineProperty(target,key,r),r};import{LOGGER_SERVICE,Module}from"@elysiumjs/core";import{Service}from"@elysiumjs/core";import pino from"pino";var DEFAULT_CONFIG={level:"info",format:"json"};class HermesLogger{pino;config;static make(name){return Service.get(HermesLogger)?.child({name})??new HermesLogger({name})}constructor(config={}){this.config={...DEFAULT_CONFIG,...config},this.pino=this.createPinoInstance()}createPinoInstance(){let options={level:this.config.level,name:this.config.name,serializers:{error:pino.stdSerializers.err}};if(this.config.format==="pretty")return pino(options,pino.transport({target:"pino-pretty"}));return pino(options)}trace(msg,ctx){this.pino.trace(ctx??{},msg)}debug(msg,ctx){this.pino.debug(ctx??{},msg)}info(msg,ctx){this.pino.info(ctx??{},msg)}warn(msg,ctx){this.pino.warn(ctx??{},msg)}error(msg,ctx){this.pino.error(ctx??{},msg)}fatal(msg,ctx){this.pino.fatal(ctx??{},msg)}flush(){this.pino.flush?.()}onDestroy(){this.flush()}child(ctx){let child=Object.create(this);return child.pino=this.pino.child(ctx),child}}class HermesModule extends Module.configurable({defaults:{...DEFAULT_CONFIG}}){}HermesModule=__legacyDecorateClassTS([Module.register({services:(config)=>[{name:HermesLogger.name,global:!0,factory:()=>new HermesLogger(config)},{name:LOGGER_SERVICE,global:!0,factory:(_config,get)=>get(HermesLogger.name)}]})],HermesModule);class HttpTransport{options;buffer=[];flushTimer;maxBufferSize;constructor(options){this.options=options;this.options.batchSize??=100,this.options.flushInterval??=5000,this.maxBufferSize=this.options.maxBufferSize??1e4,this.flushTimer=setInterval(()=>this.flush(),this.options.flushInterval)}write(formatted){if(this.buffer.length>=this.maxBufferSize)this.buffer.shift();if(this.buffer.push(formatted),this.buffer.length>=this.options.batchSize)this.flush()}async flush(){if(this.buffer.length===0)return;let batch=this.buffer.splice(0);try{await fetch(this.options.url,{method:"POST",headers:{"Content-Type":"application/json",...this.options.headers},body:JSON.stringify({logs:batch})})}catch{let space=this.maxBufferSize-this.buffer.length;if(space>0)this.buffer.unshift(...batch.slice(0,space))}}async close(){if(this.flushTimer)clearInterval(this.flushTimer);await this.flush()}}import{getLogger}from"@elysiumjs/core";function Logged(options={}){return function(target,propertyKey,descriptor){let original=descriptor.value,methodName=`${target.constructor.name}.${String(propertyKey)}`;return descriptor.value=async function(...args){let logger=this.logger??getLogger(),startTime=Date.now();if(options.includeArgs)logger.debug(`[${methodName}] Entry`,{args});try{let result=await original.apply(this,args);if(options.includeResult||options.includeTiming){let duration=Date.now()-startTime;logger[options.level??"debug"](`[${methodName}] Exit`,{...options.includeResult&&{result},...options.includeTiming&&{duration:`${duration}ms`}})}return result}catch(error){let duration=Date.now()-startTime;throw logger.error(`[${methodName}] Error`,{error,duration:`${duration}ms`}),error}},descriptor}}export{HermesLogger,HermesModule,HttpTransport,Logged};
@@ -0,0 +1,29 @@
1
+ import type { LoggerInterface } from '@elysiumjs/core';
2
+ import type { HermesConfig } from '../types';
3
+ /**
4
+ * Production-ready logger implementation wrapping Pino.
5
+ * Implements `LoggerInterface` from `@elysiumjs/core`.
6
+ *
7
+ * @author Axel Nana <axel.nana@workbud.com>
8
+ */
9
+ export declare class HermesLogger implements LoggerInterface {
10
+ private pino;
11
+ private config;
12
+ static make(name: string): HermesLogger;
13
+ constructor(config?: Partial<HermesConfig>);
14
+ private createPinoInstance;
15
+ trace(msg: string, ctx?: Record<string, unknown>): void;
16
+ debug(msg: string, ctx?: Record<string, unknown>): void;
17
+ info(msg: string, ctx?: Record<string, unknown>): void;
18
+ warn(msg: string, ctx?: Record<string, unknown>): void;
19
+ error(msg: string, ctx?: Record<string, unknown>): void;
20
+ fatal(msg: string, ctx?: Record<string, unknown>): void;
21
+ /**
22
+ * Flushes buffered log lines of the underlying transport, when it supports it.
23
+ * @author Axel Nana <axel.nana@workbud.com>
24
+ */
25
+ flush(): void;
26
+ /** Flushes on shutdown. Only called for a logger the container built. */
27
+ onDestroy(): void;
28
+ child(ctx: Record<string, unknown>): HermesLogger;
29
+ }
@@ -0,0 +1,22 @@
1
+ export interface HttpTransportOptions {
2
+ url: string;
3
+ headers?: Record<string, string>;
4
+ batchSize?: number;
5
+ flushInterval?: number;
6
+ maxBufferSize?: number;
7
+ }
8
+ /**
9
+ * HTTP transport for batching and sending logs to a remote endpoint.
10
+ *
11
+ * @author Axel Nana <axel.nana@workbud.com>
12
+ */
13
+ export declare class HttpTransport {
14
+ private options;
15
+ private buffer;
16
+ private flushTimer?;
17
+ private maxBufferSize;
18
+ constructor(options: HttpTransportOptions);
19
+ write(formatted: string): void;
20
+ flush(): Promise<void>;
21
+ close(): Promise<void>;
22
+ }
@@ -0,0 +1,13 @@
1
+ export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
2
+ export type LogFormat = 'json' | 'pretty' | 'logfmt';
3
+ export interface TransportConfig {
4
+ type: 'console' | 'file' | 'http';
5
+ options?: Record<string, unknown>;
6
+ }
7
+ export interface HermesConfig {
8
+ name?: string;
9
+ level: LogLevel;
10
+ format: LogFormat;
11
+ transports?: TransportConfig[];
12
+ }
13
+ export declare const DEFAULT_CONFIG: HermesConfig;
package/package.json CHANGED
@@ -1,16 +1,27 @@
1
1
  {
2
2
  "name": "@elysiumjs/hermes",
3
- "version": "0.3.1",
3
+ "version": "0.4.3",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/na2axl/elysiumjs.git",
7
+ "directory": "@elysiumjs/hermes"
8
+ },
9
+ "homepage": "https://github.com/na2axl/elysiumjs/tree/main/@elysiumjs/hermes#readme",
10
+ "bugs": {
11
+ "url": "https://github.com/na2axl/elysiumjs/issues"
12
+ },
4
13
  "type": "module",
5
14
  "module": "dist/index.js",
6
15
  "types": "dist/index.d.ts",
7
- "sideEffects": [
8
- "./src/index.ts"
16
+ "files": [
17
+ "dist",
18
+ "CHANGELOG.md"
9
19
  ],
20
+ "sideEffects": false,
10
21
  "scripts": {
11
22
  "format": "prettier --write \"./src/**/*.ts\"",
12
23
  "clean": "rimraf dist",
13
- "build:js": "bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify",
24
+ "build:js": "bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify-syntax --minify-whitespace",
14
25
  "build:ts": "tsc",
15
26
  "build": "bun run clean && bun run build:js && bun run build:ts",
16
27
  "test": "bun test",
@@ -19,13 +30,13 @@
19
30
  "prepublishOnly": "bun run build"
20
31
  },
21
32
  "dependencies": {
22
- "pino": "^10.0.0",
23
- "pino-pretty": "^13.0.0"
33
+ "pino": "^10.4.0",
34
+ "pino-pretty": "^13.2.0"
24
35
  },
25
36
  "devDependencies": {
26
- "@elysiumjs/core": "0.6.1"
37
+ "@elysiumjs/core": "0.10.0"
27
38
  },
28
39
  "peerDependencies": {
29
- "@elysiumjs/core": "^0.6.1"
40
+ "@elysiumjs/core": "^0.10.0"
30
41
  }
31
42
  }
package/index.ts DELETED
@@ -1,15 +0,0 @@
1
- // Copyright (c) 2025-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- export * from './src/index';
@@ -1,86 +0,0 @@
1
- // Copyright (c) 2026-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { LoggerInterface } from '@elysiumjs/core';
16
- import type { LogLevel } from '../types';
17
-
18
- import { ConsoleLogger } from '@elysiumjs/core';
19
-
20
- export interface LoggedOptions {
21
- level?: LogLevel;
22
- includeArgs?: boolean;
23
- includeResult?: boolean;
24
- includeTiming?: boolean;
25
- }
26
-
27
- /**
28
- * Decorator that logs method entry/exit with optional timing.
29
- *
30
- * Logger is resolved at CALL TIME (not decorator definition time)
31
- * to avoid capturing a stale logger reference.
32
- *
33
- * @author Axel Nana <axel.nana@workbud.com>
34
- */
35
- export function Logged(options: LoggedOptions = {}): MethodDecorator {
36
- return function (
37
- target: object,
38
- propertyKey: string | symbol,
39
- descriptor: TypedPropertyDescriptor<any>
40
- ) {
41
- const original = descriptor.value;
42
- const methodName = `${target.constructor.name}.${String(propertyKey)}`;
43
-
44
- descriptor.value = async function (...args: any[]) {
45
- const logger: LoggerInterface = (this as any).logger ?? getServiceLogger();
46
-
47
- const startTime = Date.now();
48
-
49
- if (options.includeArgs) {
50
- logger.debug(`[${methodName}] Entry`, { args });
51
- }
52
-
53
- try {
54
- const result = await original.apply(this, args);
55
-
56
- if (options.includeResult || options.includeTiming) {
57
- const duration = Date.now() - startTime;
58
- logger[options.level ?? 'debug'](`[${methodName}] Exit`, {
59
- ...(options.includeResult && { result }),
60
- ...(options.includeTiming && { duration: `${duration}ms` })
61
- });
62
- }
63
-
64
- return result;
65
- } catch (error) {
66
- const duration = Date.now() - startTime;
67
- logger.error(`[${methodName}] Error`, {
68
- error: error instanceof Error ? error.message : error,
69
- duration: `${duration}ms`
70
- });
71
- throw error;
72
- }
73
- };
74
-
75
- return descriptor;
76
- };
77
- }
78
-
79
- function getServiceLogger(): LoggerInterface {
80
- try {
81
- const { Service } = require('@elysiumjs/core');
82
- return Service.get('logger') ?? new ConsoleLogger();
83
- } catch {
84
- return new ConsoleLogger();
85
- }
86
- }
package/src/index.ts DELETED
@@ -1,40 +0,0 @@
1
- // Copyright (c) 2026-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { HermesConfig } from './types';
16
-
17
- import { Application, Service } from '@elysiumjs/core';
18
-
19
- import { HermesLogger } from './services/logger.service';
20
-
21
- // Lazy singleton resolver: reads config from Application on first access.
22
- let hermesInstance: HermesLogger | null = null;
23
- const hermesResolver = () => {
24
- if (!hermesInstance) {
25
- const app = Service.get<Application>('elysium.app');
26
- const config = app?.getConfig<Partial<HermesConfig>>('elysium:hermes');
27
- hermesInstance = new HermesLogger(config ?? {});
28
- }
29
- return hermesInstance;
30
- };
31
-
32
- Service.registerLazy(HermesLogger.name, hermesResolver);
33
- Service.registerLazy('logger', hermesResolver);
34
-
35
- export { HermesLogger } from './services/logger.service';
36
- export { HttpTransport } from './transports/http.transport';
37
- export type { HttpTransportOptions } from './transports/http.transport';
38
- export { Logged } from './decorators/logged';
39
- export type { LoggedOptions } from './decorators/logged';
40
- export type { HermesConfig, LogLevel, LogFormat, TransportConfig } from './types';
@@ -1,85 +0,0 @@
1
- // Copyright (c) 2026-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- import type { LoggerInterface } from '@elysiumjs/core';
16
- import type { Logger as PinoLogger } from 'pino';
17
- import type { HermesConfig } from '../types';
18
-
19
- import { Service } from '@elysiumjs/core';
20
- import pino from 'pino';
21
-
22
- import { DEFAULT_CONFIG } from '../types';
23
-
24
- /**
25
- * Production-ready logger implementation wrapping Pino.
26
- * Implements `LoggerInterface` from `@elysiumjs/core`.
27
- *
28
- * @author Axel Nana <axel.nana@workbud.com>
29
- */
30
- export class HermesLogger implements LoggerInterface {
31
- private pino: PinoLogger;
32
- private config: HermesConfig;
33
-
34
- public static make(name: string): HermesLogger {
35
- return Service.get(HermesLogger)?.child({ name }) ?? new HermesLogger({ name });
36
- }
37
-
38
- constructor(config: Partial<HermesConfig> = {}) {
39
- this.config = { ...DEFAULT_CONFIG, ...config };
40
- this.pino = this.createPinoInstance();
41
- }
42
-
43
- private createPinoInstance(): PinoLogger {
44
- const options: pino.LoggerOptions = {
45
- level: this.config.level,
46
- name: this.config.name
47
- };
48
-
49
- if (this.config.format === 'pretty') {
50
- return pino(options, pino.transport({ target: 'pino-pretty' }));
51
- }
52
-
53
- return pino(options);
54
- }
55
-
56
- public trace(msg: string, ctx?: Record<string, unknown>): void {
57
- this.pino.trace(ctx ?? {}, msg);
58
- }
59
-
60
- public debug(msg: string, ctx?: Record<string, unknown>): void {
61
- this.pino.debug(ctx ?? {}, msg);
62
- }
63
-
64
- public info(msg: string, ctx?: Record<string, unknown>): void {
65
- this.pino.info(ctx ?? {}, msg);
66
- }
67
-
68
- public warn(msg: string, ctx?: Record<string, unknown>): void {
69
- this.pino.warn(ctx ?? {}, msg);
70
- }
71
-
72
- public error(msg: string, ctx?: Record<string, unknown>): void {
73
- this.pino.error(ctx ?? {}, msg);
74
- }
75
-
76
- public fatal(msg: string, ctx?: Record<string, unknown>): void {
77
- this.pino.fatal(ctx ?? {}, msg);
78
- }
79
-
80
- public child(ctx: Record<string, unknown>): HermesLogger {
81
- const child = Object.create(this) as HermesLogger;
82
- child.pino = this.pino.child(ctx);
83
- return child;
84
- }
85
- }
@@ -1,79 +0,0 @@
1
- // Copyright (c) 2026-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- export interface HttpTransportOptions {
16
- url: string;
17
- headers?: Record<string, string>;
18
- batchSize?: number;
19
- flushInterval?: number;
20
- maxBufferSize?: number;
21
- }
22
-
23
- /**
24
- * HTTP transport for batching and sending logs to a remote endpoint.
25
- *
26
- * @author Axel Nana <axel.nana@workbud.com>
27
- */
28
- export class HttpTransport {
29
- private buffer: string[] = [];
30
- private flushTimer?: ReturnType<typeof setInterval>;
31
- private maxBufferSize: number;
32
-
33
- constructor(private options: HttpTransportOptions) {
34
- this.options.batchSize ??= 100;
35
- this.options.flushInterval ??= 5000;
36
- this.maxBufferSize = this.options.maxBufferSize ?? 10_000;
37
-
38
- this.flushTimer = setInterval(() => this.flush(), this.options.flushInterval);
39
- }
40
-
41
- public write(formatted: string): void {
42
- if (this.buffer.length >= this.maxBufferSize) {
43
- this.buffer.shift();
44
- }
45
-
46
- this.buffer.push(formatted);
47
-
48
- if (this.buffer.length >= this.options.batchSize!) {
49
- this.flush();
50
- }
51
- }
52
-
53
- public async flush(): Promise<void> {
54
- if (this.buffer.length === 0) return;
55
-
56
- const batch = this.buffer.splice(0);
57
-
58
- try {
59
- await fetch(this.options.url, {
60
- method: 'POST',
61
- headers: {
62
- 'Content-Type': 'application/json',
63
- ...this.options.headers
64
- },
65
- body: JSON.stringify({ logs: batch })
66
- });
67
- } catch {
68
- const space = this.maxBufferSize - this.buffer.length;
69
- if (space > 0) {
70
- this.buffer.unshift(...batch.slice(0, space));
71
- }
72
- }
73
- }
74
-
75
- public async close(): Promise<void> {
76
- if (this.flushTimer) clearInterval(this.flushTimer);
77
- await this.flush();
78
- }
79
- }
package/src/types.ts DELETED
@@ -1,34 +0,0 @@
1
- // Copyright (c) 2026-present Workbud Technologies Inc. All rights reserved.
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
-
15
- export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
16
-
17
- export type LogFormat = 'json' | 'pretty' | 'logfmt';
18
-
19
- export interface TransportConfig {
20
- type: 'console' | 'file' | 'http';
21
- options?: Record<string, unknown>;
22
- }
23
-
24
- export interface HermesConfig {
25
- name?: string;
26
- level: LogLevel;
27
- format: LogFormat;
28
- transports?: TransportConfig[];
29
- }
30
-
31
- export const DEFAULT_CONFIG: HermesConfig = {
32
- level: 'info',
33
- format: 'json'
34
- };