@elysiumjs/hermes 0.3.1 → 0.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @elysiumjs/hermes
2
2
 
3
+ ## 0.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`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.
8
+
9
+ Breaking changes:
10
+
11
+ - `@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`.
12
+ - Configurable modules are bound with `with()`, for example `HermesModule.with({ level: 'debug' })`. `with()` accepts an object or a function.
13
+ - 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.
14
+ - `@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.
15
+ - 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.
16
+ - 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()`.
17
+ - `@elysiumjs/styx` ships `StyxModule`, active in development only. The `migration:generate` and `migration:run` commands move to `DrizzleModule` and are available in production.
18
+ - `@elysiumjs/mnemosyne` ships `MnemosyneModule` for its caches (`redis: false` or `memory: false` opts out). `DrizzleModule` imports it.
19
+ - New modules: `HephaestusModule`, `ArgusModule` (`serviceName` is required), `SentryModule` (needs a `dsn` or `SENTRY_DSN`) and `CerberusModule` (`getSubject` is required).
20
+ - 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.
21
+ - `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.
22
+ - `Heracles.dispatch` starts a stopped queue on first use, so producers no longer start queues. `Queue.start()` is idempotent.
23
+ - `create-elysium` generates apps that use modules, and adds Sentry and Hephaestus to the `modules` list.
24
+ - `@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.
25
+ - 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`.
26
+ - 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.
27
+
28
+ ### Patch Changes
29
+
30
+ - 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)]:
31
+ - @elysiumjs/core@0.8.0
32
+
3
33
  ## 0.3.0
4
34
 
5
35
  ### 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
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};var __require=import.meta.require;import{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};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",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{ConsoleLogger}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??getServiceLogger(),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:error instanceof Error?error.message:error,duration:`${duration}ms`}),error}},descriptor}}function getServiceLogger(){try{let{Service}=__require("@elysiumjs/core");return Service.get("logger")??new ConsoleLogger}catch{return new ConsoleLogger}}export{HermesLogger,HermesModule,HttpTransport,Logged};
package/package.json CHANGED
@@ -1,16 +1,14 @@
1
1
  {
2
2
  "name": "@elysiumjs/hermes",
3
- "version": "0.3.1",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
- "sideEffects": [
8
- "./src/index.ts"
9
- ],
7
+ "sideEffects": false,
10
8
  "scripts": {
11
9
  "format": "prettier --write \"./src/**/*.ts\"",
12
10
  "clean": "rimraf dist",
13
- "build:js": "bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify",
11
+ "build:js": "bun build ./src/index.ts --outdir ./dist --target bun --packages external --root ./src --minify-syntax --minify-whitespace",
14
12
  "build:ts": "tsc",
15
13
  "build": "bun run clean && bun run build:js && bun run build:ts",
16
14
  "test": "bun test",
@@ -19,13 +17,13 @@
19
17
  "prepublishOnly": "bun run build"
20
18
  },
21
19
  "dependencies": {
22
- "pino": "^10.0.0",
23
- "pino-pretty": "^13.0.0"
20
+ "pino": "^10.4.0",
21
+ "pino-pretty": "^13.2.0"
24
22
  },
25
23
  "devDependencies": {
26
- "@elysiumjs/core": "0.6.1"
24
+ "@elysiumjs/core": "0.8.0"
27
25
  },
28
26
  "peerDependencies": {
29
- "@elysiumjs/core": "^0.6.1"
27
+ "@elysiumjs/core": "^0.8.0"
30
28
  }
31
29
  }
@@ -0,0 +1,37 @@
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 { Module } from '@elysiumjs/core';
16
+
17
+ import { HermesLogger } from './services/logger.service';
18
+ import { DEFAULT_CONFIG, type HermesConfig } from './types';
19
+
20
+ /**
21
+ * Provides the {@link HermesLogger} as the global services `HermesLogger` and `logger`
22
+ * (the same instance), built from the module config.
23
+ * @author Axel Nana <axel.nana@workbud.com>
24
+ */
25
+ @Module.register<HermesConfig>({
26
+ services: (config) => [
27
+ { name: HermesLogger.name, global: true, factory: () => new HermesLogger(config) },
28
+ {
29
+ name: 'logger',
30
+ global: true,
31
+ factory: (_config: unknown, get: <T>(name: string) => T) => get<HermesLogger>(HermesLogger.name)
32
+ }
33
+ ]
34
+ })
35
+ export class HermesModule extends Module.configurable<HermesConfig>({
36
+ defaults: { ...DEFAULT_CONFIG }
37
+ }) {}
package/src/index.ts CHANGED
@@ -12,26 +12,7 @@
12
12
  // See the License for the specific language governing permissions and
13
13
  // limitations under the License.
14
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
-
15
+ export { HermesModule } from './hermes.module';
35
16
  export { HermesLogger } from './services/logger.service';
36
17
  export { HttpTransport } from './transports/http.transport';
37
18
  export type { HttpTransportOptions } from './transports/http.transport';
@@ -77,6 +77,19 @@ export class HermesLogger implements LoggerInterface {
77
77
  this.pino.fatal(ctx ?? {}, msg);
78
78
  }
79
79
 
80
+ /**
81
+ * Flushes buffered log lines of the underlying transport, when it supports it.
82
+ * @author Axel Nana <axel.nana@workbud.com>
83
+ */
84
+ public flush(): void {
85
+ this.pino.flush?.();
86
+ }
87
+
88
+ /** Flushes on shutdown. Only called for a logger the container built. */
89
+ public onDestroy(): void {
90
+ this.flush();
91
+ }
92
+
80
93
  public child(ctx: Record<string, unknown>): HermesLogger {
81
94
  const child = Object.create(this) as HermesLogger;
82
95
  child.pino = this.pino.child(ctx);