@navios/commander 0.8.0 → 1.0.0-alpha.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@navios/commander",
3
- "version": "0.8.0",
3
+ "version": "1.0.0-alpha.2",
4
4
  "author": {
5
5
  "name": "Oleksandr Hanzha",
6
6
  "email": "alex@granted.name"
@@ -35,6 +35,6 @@
35
35
  "zod": "^4.2.1"
36
36
  },
37
37
  "dependencies": {
38
- "@navios/di": "^0.8.0"
38
+ "@navios/di": "^0.9.2"
39
39
  }
40
40
  }
@@ -1,4 +1,4 @@
1
- import type { ClassType } from '@navios/core'
1
+ import type { ClassType, Registry } from '@navios/core'
2
2
 
3
3
  import { Injectable, InjectableScope, InjectionToken } from '@navios/core'
4
4
 
@@ -20,6 +20,23 @@ export interface CliModuleOptions {
20
20
  * Imported modules' commands will be available in this module.
21
21
  */
22
22
  imports?: ClassType[] | Set<ClassType>
23
+ /**
24
+ * Service override classes to import for side effects.
25
+ * These classes are imported to ensure their @Injectable decorators execute,
26
+ * allowing them to register with the DI system. Overrides should use the same
27
+ * InjectionToken as the original service with a higher priority.
28
+ */
29
+ overrides?: ClassType[] | Set<ClassType>
30
+ /**
31
+ * Priority level for the module.
32
+ * Higher priority modules will be loaded first.
33
+ */
34
+ priority?: number
35
+ /**
36
+ * Registry to use for the module.
37
+ * Registry is used to store the module and its commands.
38
+ */
39
+ registry?: Registry
23
40
  }
24
41
 
25
42
  /**
@@ -45,9 +62,16 @@ export interface CliModuleOptions {
45
62
  * ```
46
63
  */
47
64
  export function CliModule(
48
- { commands = [], imports = [] }: CliModuleOptions = {
65
+ {
66
+ commands = [],
67
+ imports = [],
68
+ overrides = [],
69
+ priority,
70
+ registry,
71
+ }: CliModuleOptions = {
49
72
  commands: [],
50
73
  imports: [],
74
+ overrides: [],
51
75
  },
52
76
  ) {
53
77
  return (target: ClassType, context: ClassDecoratorContext) => {
@@ -65,10 +89,15 @@ export function CliModule(
65
89
  for (const importedModule of imports) {
66
90
  moduleMetadata.imports.add(importedModule)
67
91
  }
92
+ for (const override of overrides) {
93
+ moduleMetadata.overrides.add(override)
94
+ }
68
95
 
69
96
  return Injectable({
70
97
  token,
71
98
  scope: InjectableScope.Singleton,
99
+ priority,
100
+ registry,
72
101
  })(target, context)
73
102
  }
74
103
  }
@@ -1,4 +1,4 @@
1
- import type { ClassType } from '@navios/core'
1
+ import type { ClassType, Registry } from '@navios/core'
2
2
  import type { ZodObject } from 'zod'
3
3
 
4
4
  import { Injectable, InjectableScope, InjectionToken } from '@navios/core'
@@ -21,6 +21,16 @@ export interface CommandOptions {
21
21
  * If provided, options will be validated and parsed according to this schema.
22
22
  */
23
23
  optionsSchema?: ZodObject
24
+ /**
25
+ * Priority level for the command.
26
+ * Higher priority commands will be loaded first.
27
+ */
28
+ priority?: number
29
+ /**
30
+ * Registry to use for the command.
31
+ * Registry is used to store the command and its options schema.
32
+ */
33
+ registry?: Registry
24
34
  }
25
35
 
26
36
  /**
@@ -53,7 +63,12 @@ export interface CommandOptions {
53
63
  * }
54
64
  * ```
55
65
  */
56
- export function Command({ path, optionsSchema }: CommandOptions) {
66
+ export function Command({
67
+ path,
68
+ optionsSchema,
69
+ priority,
70
+ registry,
71
+ }: CommandOptions) {
57
72
  return function (target: ClassType, context: ClassDecoratorContext) {
58
73
  if (context.kind !== 'class') {
59
74
  throw new Error(
@@ -67,6 +82,8 @@ export function Command({ path, optionsSchema }: CommandOptions) {
67
82
  return Injectable({
68
83
  token,
69
84
  scope: InjectableScope.Singleton,
85
+ priority,
86
+ registry,
70
87
  })(target, context)
71
88
  }
72
89
  }
@@ -20,6 +20,10 @@ export interface CliModuleMetadata {
20
20
  * Set of other modules imported by this module.
21
21
  */
22
22
  imports: Set<ClassType>
23
+ /**
24
+ * Set of service override classes imported for side effects.
25
+ */
26
+ overrides: Set<ClassType>
23
27
  /**
24
28
  * Map of custom attributes that can be attached to the module.
25
29
  */
@@ -48,6 +52,7 @@ export function getCliModuleMetadata(
48
52
  const newMetadata: CliModuleMetadata = {
49
53
  commands: new Set<ClassType>(),
50
54
  imports: new Set<ClassType>(),
55
+ overrides: new Set<ClassType>(),
51
56
  customAttributes: new Map<string | symbol, any>(),
52
57
  }
53
58
  context.metadata[CliModuleMetadataKey] = newMetadata
@@ -1,6 +1,12 @@
1
1
  import type { ClassTypeWithInstance, NaviosModule } from '@navios/core'
2
2
 
3
- import { Container, inject, Injectable } from '@navios/core'
3
+ import {
4
+ Container,
5
+ getInjectableToken,
6
+ inject,
7
+ Injectable,
8
+ Logger,
9
+ } from '@navios/core'
4
10
 
5
11
  import type { CommandHandler } from '../interfaces/index.mjs'
6
12
  import type { CliModuleMetadata, CommandMetadata } from '../metadata/index.mjs'
@@ -36,6 +42,9 @@ export interface CommandWithMetadata {
36
42
  */
37
43
  @Injectable()
38
44
  export class CliModuleLoaderService {
45
+ private logger = inject(Logger, {
46
+ context: CliModuleLoaderService.name,
47
+ })
39
48
  protected container = inject(Container)
40
49
  private modulesMetadata: Map<string, CliModuleMetadata> = new Map()
41
50
  private loadedModules: Map<string, any> = new Map()
@@ -65,7 +74,8 @@ export class CliModuleLoaderService {
65
74
  if (parentMetadata) {
66
75
  this.mergeMetadata(metadata, parentMetadata)
67
76
  }
68
- const moduleName = module.name
77
+ const moduleToken = getInjectableToken(module)
78
+ const moduleName = moduleToken.id
69
79
  if (this.modulesMetadata.has(moduleName)) {
70
80
  return
71
81
  }
@@ -85,6 +95,7 @@ export class CliModuleLoaderService {
85
95
  this.traverseModules(importedModule, metadata),
86
96
  )
87
97
  await Promise.all(loadingPromises)
98
+ this.validateOverrides(metadata, moduleName)
88
99
  const instance = await this.container.get(module)
89
100
  if (instance.onModuleInit) {
90
101
  await instance.onModuleInit()
@@ -92,6 +103,67 @@ export class CliModuleLoaderService {
92
103
  this.loadedModules.set(moduleName, instance)
93
104
  }
94
105
 
106
+ private validateOverrides(
107
+ metadata: CliModuleMetadata,
108
+ moduleName: string,
109
+ ): void {
110
+ if (!metadata.overrides || metadata.overrides.size === 0) {
111
+ return
112
+ }
113
+
114
+ const registry = this.container.getRegistry()
115
+
116
+ for (const overrideClass of metadata.overrides) {
117
+ try {
118
+ // Get the token for the override class
119
+ const overrideToken = getInjectableToken(overrideClass)
120
+
121
+ // Get all registrations for this token (sorted by priority, highest first)
122
+ const allRegistrations = registry.getAll(overrideToken)
123
+
124
+ if (allRegistrations.length === 0) {
125
+ this.logger.warn(
126
+ `[Navios Commander] Override ${overrideClass.name} in module ${moduleName} is not registered. ` +
127
+ `Make sure it has @Injectable decorator.`,
128
+ )
129
+ continue
130
+ }
131
+
132
+ // Check if the override class has the highest priority
133
+ const highestPriorityRegistration = allRegistrations[0]
134
+ if (highestPriorityRegistration.target !== overrideClass) {
135
+ const overrideRegistration = allRegistrations.find(
136
+ (r) => r.target === overrideClass,
137
+ )
138
+
139
+ if (!overrideRegistration) {
140
+ this.logger.warn(
141
+ `[Navios Commander] Override ${overrideClass.name} in module ${moduleName} is registered ` +
142
+ `but not found in registry for token ${overrideToken.toString()}.`,
143
+ )
144
+ } else {
145
+ this.logger.warn(
146
+ `[Navios Commander] Override ${overrideClass.name} in module ${moduleName} is not active. ` +
147
+ `Current active service: ${highestPriorityRegistration.target.name} ` +
148
+ `(priority: ${highestPriorityRegistration.priority}). ` +
149
+ `Override priority: ${overrideRegistration.priority}. ` +
150
+ `Override needs higher priority to take effect.`,
151
+ )
152
+ }
153
+ } else {
154
+ this.logger.debug(
155
+ `[Navios Commander] Override ${overrideClass.name} in module ${moduleName} is active ` +
156
+ `(priority: ${highestPriorityRegistration.priority})`,
157
+ )
158
+ }
159
+ } catch (error) {
160
+ this.logger.warn(
161
+ `[Navios Commander] Failed to validate override ${overrideClass.name} in module ${moduleName}: ${error}`,
162
+ )
163
+ }
164
+ }
165
+ }
166
+
95
167
  private mergeMetadata(
96
168
  metadata: CliModuleMetadata,
97
169
  parentMetadata: CliModuleMetadata,