@xrystal/core 3.20.7 → 3.21.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/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "author": "Yusuf Yasir KAYGUSUZ",
3
3
  "name": "@xrystal/core",
4
- "version": "3.20.7",
4
+ "version": "3.21.0",
5
5
  "description": "Project core for xrystal",
6
6
  "publishConfig": {
7
7
  "access": "public",
@@ -37,34 +37,27 @@
37
37
  "start": "bun --env-file=../infrastructer/x/environments/.global.env --env-file=../infrastructer/x/environments/.dev.env source/index.js"
38
38
  },
39
39
  "dependencies": {
40
- "@types/lodash": "^4.17.23",
41
- "@types/yaml": "^1.9.7",
40
+ "lodash": "^4.17.23",
42
41
  "awilix": "^12.0.5",
43
- "chalk": "^5.6.2",
44
- "commander": "^13.0.0",
45
- "ejs": "^3.1.9",
42
+ "yaml": "^2.5.0",
46
43
  "handlebars": "^4.7.8",
47
- "i": "^0.3.7",
44
+ "winston": "^3.19.0",
45
+ "winston-daily-rotate-file": "^5.0.0",
46
+ "winston-mongodb": "^7.0.1",
47
+ "kafkajs": "^2.2.4",
48
48
  "i18next": "^25.6.3",
49
49
  "i18next-fs-backend": "^2.6.1",
50
50
  "i18next-http-middleware": "^3.8.2",
51
- "kafkajs": "^2.2.4",
52
- "lodash": "^4.17.23",
53
- "moment-timezone": "^0.6.0",
54
- "npm": "^11.7.0",
55
51
  "ora": "^9.0.0",
56
- "picocolors": "^1.1.1",
57
52
  "qs": "^6.14.1",
58
53
  "soap": "^1.6.3",
59
- "winston": "^3.19.0",
60
- "winston-daily-rotate-file": "^5.0.0",
61
- "winston-mongodb": "^7.0.1",
62
- "yaml": "^2.5.0"
54
+ "chalk": "^5.6.2",
55
+ "commander": "^13.0.0",
56
+ "picocolors": "^1.1.1"
63
57
  },
64
58
  "devDependencies": {
65
- "@types/minimist": "^1.2.5",
66
- "@types/bun": "^1.3.5",
67
- "@types/node": "^25.0.6",
68
- "typescript": "^5.5.3"
59
+ "@types/lodash": "^4.17.23",
60
+ "@types/yaml": "^1.9.7",
61
+ "@types/bun": "^1.3.5"
69
62
  }
70
63
  }
@@ -26,26 +26,9 @@ export declare const getControllerCtx: () => {
26
26
  _isRaw?: boolean;
27
27
  };
28
28
  };
29
- export declare const protocol: (callback: () => Promise<any>, protocol?: ProtocolEnum) => Promise<any>;
30
- export interface CustomRequest {
31
- accounts?: any;
32
- url: string;
33
- method: string;
34
- headers: Record<string, any>;
35
- body?: any;
36
- params: Record<string, any>;
37
- query: Record<string, any>;
38
- lang: string;
39
- t: (k: string, args?: any) => string;
40
- }
41
- export interface CustomResponse {
42
- status: (code: number) => CustomResponse;
43
- send: (data: any) => any;
44
- json: (data: any) => any;
45
- locals: Record<string, any>;
46
- }
47
- declare abstract class Controller {
29
+ export declare abstract class Controller {
48
30
  protected logger: LoggerService;
31
+ protected systemService: SystemService;
49
32
  protected supportedProtocols: ProtocolEnum[];
50
33
  protected get protocol(): ProtocolEnum;
51
34
  protected get currentStore(): {
@@ -64,9 +47,9 @@ declare abstract class Controller {
64
47
  protected get res(): CustomResponse;
65
48
  }
66
49
  export declare abstract class ControllerService extends Controller implements IService<any> {
67
- protected systemService: SystemService;
68
- constructor({ systemService }: {
50
+ constructor({ systemService, loggerService }: {
69
51
  systemService: SystemService;
52
+ loggerService: LoggerService;
70
53
  });
71
54
  load(): Promise<void>;
72
55
  schema({ checks, logic, response }: {
@@ -75,4 +58,20 @@ export declare abstract class ControllerService extends Controller implements IS
75
58
  response?: (args: any) => Promise<any>;
76
59
  }): Promise<any>;
77
60
  }
78
- export { Controller };
61
+ export interface CustomRequest {
62
+ accounts?: any;
63
+ url: string;
64
+ method: string;
65
+ headers: Record<string, any>;
66
+ body?: any;
67
+ params: Record<string, any>;
68
+ query: Record<string, any>;
69
+ lang: string;
70
+ t: (k: string, args?: any) => string;
71
+ }
72
+ export interface CustomResponse {
73
+ status: (code: number) => CustomResponse;
74
+ send: (data: any) => any;
75
+ json: (data: any) => any;
76
+ locals: Record<string, any>;
77
+ }
@@ -1,17 +1,10 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
- import { ProtocolEnum, responseMessageHelper, ResponseSchema, x } from '../../utils/index';
3
- import LoggerService from '../logger';
2
+ import { LoggerLayerEnum, ProtocolEnum, responseMessageHelper, ResponseSchema } from '../../utils/index';
4
3
  export const controllerContextStorage = new AsyncLocalStorage();
5
4
  export const getControllerCtx = () => controllerContextStorage.getStore();
6
- export const protocol = async (callback, protocol = ProtocolEnum.HTTP) => {
7
- const store = controllerContextStorage.getStore();
8
- if (store) {
9
- store.protocol = protocol;
10
- }
11
- return await callback();
12
- };
13
- class Controller {
14
- logger = x.get(LoggerService);
5
+ export class Controller {
6
+ logger;
7
+ systemService;
15
8
  supportedProtocols = [ProtocolEnum.HTTP, ProtocolEnum.WEBSOCKET];
16
9
  get protocol() {
17
10
  return this.currentStore?.protocol || ProtocolEnum.HTTP;
@@ -79,14 +72,14 @@ class Controller {
79
72
  }
80
73
  }
81
74
  export class ControllerService extends Controller {
82
- systemService;
83
- constructor({ systemService }) {
75
+ constructor({ systemService, loggerService }) {
84
76
  super();
85
77
  this.systemService = systemService;
78
+ this.logger = loggerService;
86
79
  }
87
80
  async load() {
88
- const protocols = this.systemService.tmp.configs.loaders.controller.protocols;
89
- this.supportedProtocols = Array.isArray(protocols) ? protocols : [protocols];
81
+ const protocols = this.systemService?.tmp?.configs?.loaders?.controller?.protocols;
82
+ this.supportedProtocols = Array.isArray(protocols) ? protocols : [protocols || ProtocolEnum.HTTP];
90
83
  }
91
84
  async schema({ checks, logic, response }) {
92
85
  try {
@@ -153,10 +146,7 @@ export class ControllerService extends Controller {
153
146
  }).getResponse);
154
147
  }
155
148
  catch (error) {
156
- this.logger.winston.log({
157
- level: 'error',
158
- message: `Controller Error: ${error.message}`
159
- });
149
+ this.logger?.log(LoggerLayerEnum.ERROR, `Controller Error: ${error.message}`);
160
150
  return this.res.status(500).send(new ResponseSchema({
161
151
  status: false,
162
152
  message: error.message,
@@ -165,4 +155,3 @@ export class ControllerService extends Controller {
165
155
  }
166
156
  }
167
157
  }
168
- export { Controller };
@@ -26,7 +26,7 @@ export default class LoggerService implements IService<any> {
26
26
  systemService: SystemService;
27
27
  configsService: ConfigsService;
28
28
  });
29
- load: ({}: {}) => Promise<void>;
29
+ load: () => Promise<void>;
30
30
  winstonLoader: ({ loadPath, loggerLevel }: {
31
31
  loadPath: string;
32
32
  loggerLevel: string;
@@ -43,18 +43,18 @@ export default class LoggerService {
43
43
  constructor({ systemService, configsService }) {
44
44
  this.#systemService = systemService;
45
45
  this.#configsService = configsService;
46
- this.kafkaLogsTopic = this.#configsService?.all.kafkaLogsTopic;
46
+ }
47
+ load = async () => {
48
+ this.serviceName = this.#systemService?.tmp?.configs?.service;
49
+ this.kafkaLogsTopic = this.#configsService?.all?.kafkaLogsTopic;
47
50
  winston.addColors(customColors);
48
51
  this.winston = winston.createLogger({
49
- level: this.#configsService.all.systemLoggerLayer || 'info',
52
+ level: this.#configsService?.all?.systemLoggerLayer || 'info',
50
53
  levels: customLevels,
51
54
  format: this.getConsoleFormat(),
52
55
  transports: [new winston.transports.Console()]
53
56
  });
54
- }
55
- load = async ({}) => {
56
- const loggersConfigs = this.#systemService.tmp.configs.loaders.loggers;
57
- this.serviceName = this.#systemService.tmp.configs.service;
57
+ const loggersConfigs = this.#systemService?.tmp?.configs?.loaders?.loggers;
58
58
  const { kafkaBrokers, isKafkaPassive } = this.#configsService.all;
59
59
  const brokers = kafkaBrokers ? String(kafkaBrokers).split(",").map((b) => b.trim()) : [];
60
60
  const isKafkaEnabled = isKafkaPassive === false && brokers.length > 0;
@@ -76,9 +76,11 @@ export default class LoggerService {
76
76
  this.isKafkaReady = false;
77
77
  }
78
78
  }
79
+ const logPath = loggersConfigs?.loadPath || "logs";
80
+ const loggerLevel = loggersConfigs?.loggerLevel || "info";
79
81
  this.winstonLoader({
80
- loadPath: path.join(this.#configsService.all.rootFolderPath, loggersConfigs?.loadPath),
81
- loggerLevel: loggersConfigs?.loggerLevel
82
+ loadPath: path.join(this.#configsService.all.rootFolderPath, logPath),
83
+ loggerLevel: loggerLevel
82
84
  });
83
85
  };
84
86
  winstonLoader = ({ loadPath, loggerLevel }) => {
@@ -7,8 +7,8 @@ export default class SystemService {
7
7
  load = async ({ core }) => {
8
8
  this._core = core;
9
9
  this._tmp = this._core._;
10
- await this.initializeKafkaInfrastructure();
11
10
  await this._systemLoader({});
11
+ await this.initializeKafkaInfrastructure();
12
12
  };
13
13
  initializeKafkaInfrastructure = async () => {
14
14
  const { kafkaBrokers, kafkaTopics, isKafkaPassive, serviceName } = {
@@ -16,7 +16,9 @@ const coreLoader = async ({}) => {
16
16
  await (await x.load([
17
17
  path.join(__dirname, '..', 'loader', '**/*.{ts,js}'),
18
18
  ], {
19
- exclude: [path.join(__dirname, '..', 'utils', '**/class.x.{ts,js}')]
19
+ exclude: [
20
+ path.join(__dirname, '..', 'utils', '**/class.x.{ts,js}'),
21
+ ]
20
22
  }))
21
23
  .initialize([
22
24
  {
@@ -1,19 +1,23 @@
1
1
  import { AwilixContainer, LifetimeType } from 'awilix';
2
+ type Constructor<T> = new (...args: any[]) => T;
3
+ type AbstractConstructor<T> = abstract new (...args: any[]) => T;
2
4
  declare abstract class XHelper {
3
5
  protected checkRegistration(container: AwilixContainer, name: string): boolean;
4
6
  }
5
7
  declare class X extends XHelper {
6
8
  private container;
7
9
  private initializedNames;
10
+ private loadedPaths;
11
+ private registeredClasses;
12
+ private isInitializing;
8
13
  constructor();
9
- private getSource;
14
+ private getName;
10
15
  load(patterns: string | string[], options?: {
11
16
  verbose?: boolean;
12
17
  exclude?: string | Function | (string | Function)[];
13
18
  lifetime?: LifetimeType;
14
19
  }): Promise<this>;
15
20
  register(Dependency: any, lifetime?: LifetimeType): this;
16
- registerAll(dependencies: any[], lifetime?: LifetimeType): this;
17
21
  registerInstance(name: string, instance: any): this;
18
22
  initialize<T = any>(input?: {
19
23
  service: any;
@@ -22,8 +26,8 @@ declare class X extends XHelper {
22
26
  service: any;
23
27
  props?: T;
24
28
  }[], verbose?: boolean): Promise<this>;
25
- shutdown(verbose?: boolean): Promise<void>;
26
- get<T>(target: (new (...args: any[]) => T) | string): T;
29
+ get<T>(target: Constructor<T> | AbstractConstructor<T> | string): T;
30
+ shutdown(): Promise<void>;
27
31
  get cradle(): any;
28
32
  }
29
33
  declare const _default: X;
@@ -3,12 +3,15 @@ import path from 'node:path';
3
3
  import { pathToFileURL } from 'node:url';
4
4
  class XHelper {
5
5
  checkRegistration(container, name) {
6
- return !!container.registrations[name] && container.registrations[name].resolve !== undefined;
6
+ return !!container.registrations[name];
7
7
  }
8
8
  }
9
9
  class X extends XHelper {
10
10
  container;
11
11
  initializedNames = new Set();
12
+ loadedPaths = new Set();
13
+ registeredClasses = new Set();
14
+ isInitializing = false;
12
15
  constructor() {
13
16
  super();
14
17
  this.container = createContainer({
@@ -16,18 +19,19 @@ class X extends XHelper {
16
19
  strict: true
17
20
  });
18
21
  }
19
- getSource(filePath) {
20
- const projectRoot = process.cwd().replace(/\\/g, '/');
21
- const normalizedPath = filePath.replace(/\\/g, '/');
22
- return normalizedPath.includes('node_modules') || !normalizedPath.startsWith(projectRoot) ? 'LIB' : 'APP';
22
+ getName(target) {
23
+ if (typeof target === 'string')
24
+ return target.charAt(0).toLowerCase() + target.slice(1);
25
+ if (typeof target === 'function' && target.name)
26
+ return target.name.charAt(0).toLowerCase() + target.name.slice(1);
27
+ return '';
23
28
  }
24
29
  async load(patterns, options = {}) {
25
30
  const { verbose = false, exclude = [], lifetime = Lifetime.SINGLETON } = options;
26
31
  const cwd = process.cwd();
27
- const excludeList = Array.isArray(exclude) ? exclude : [exclude];
28
32
  const resolvedPatterns = (Array.isArray(patterns) ? patterns : [patterns]).map(p => {
29
33
  const resolved = path.isAbsolute(p) ? p : path.resolve(cwd, p);
30
- return resolved.replace(/\\/g, '/');
34
+ return resolved.replace(/\\/g, '/').toLowerCase();
31
35
  });
32
36
  let modules = [];
33
37
  try {
@@ -37,18 +41,10 @@ class X extends XHelper {
37
41
  return this;
38
42
  }
39
43
  for (const m of modules) {
40
- const source = this.getSource(m.path);
41
- const normalizedMPath = m.path.replace(/\\/g, '/');
42
- if (normalizedMPath === __filename.replace(/\\/g, '/') || normalizedMPath.endsWith('.d.ts'))
44
+ const normalizedMPath = m.path.replace(/\\/g, '/').toLowerCase();
45
+ if (this.loadedPaths.has(normalizedMPath))
43
46
  continue;
44
- const isPathExcluded = excludeList.some(ex => {
45
- if (typeof ex === 'string') {
46
- const normalizedEx = ex.replace(/\\/g, '/');
47
- return normalizedMPath.includes(normalizedEx) || m.name === ex;
48
- }
49
- return false;
50
- });
51
- if (isPathExcluded)
47
+ if (normalizedMPath === __filename.replace(/\\/g, '/').toLowerCase() || normalizedMPath.endsWith('.d.ts'))
52
48
  continue;
53
49
  try {
54
50
  const fileUrl = pathToFileURL(m.path).href;
@@ -57,116 +53,109 @@ class X extends XHelper {
57
53
  if (!dependency) {
58
54
  dependency = Object.values(loaded).find(val => typeof val === 'function' && !!val.prototype && !!val.name);
59
55
  }
60
- const isClassExcluded = excludeList.some(ex => typeof ex === 'function' && dependency === ex);
61
- if (isClassExcluded)
62
- continue;
63
- const isClass = typeof dependency === 'function' && !!dependency.prototype && !!dependency.name;
64
- if (isClass) {
65
- const className = dependency.name;
66
- const name = className.charAt(0).toLowerCase() + className.slice(1);
67
- if (!this.checkRegistration(this.container, name)) {
56
+ if (typeof dependency === 'function' && !!dependency.prototype && !!dependency.name) {
57
+ if (this.registeredClasses.has(dependency))
58
+ continue;
59
+ const name = this.getName(dependency);
60
+ if (name && !this.checkRegistration(this.container, name)) {
68
61
  this.container.register({
69
62
  [name]: asClass(dependency).setLifetime(lifetime)
70
63
  });
64
+ this.loadedPaths.add(normalizedMPath);
65
+ this.registeredClasses.add(dependency);
71
66
  }
72
67
  }
73
68
  }
74
69
  catch (err) {
75
70
  if (verbose)
76
- console.error(`[DI][${source}] Load Error:`, err.message);
71
+ console.error(`[DI] Load Error:`, err.message);
77
72
  }
78
73
  }
79
74
  return this;
80
75
  }
81
76
  register(Dependency, lifetime = Lifetime.SINGLETON) {
82
- if (!Dependency?.name)
77
+ const name = this.getName(Dependency);
78
+ if (!name || this.registeredClasses.has(Dependency))
83
79
  return this;
84
- const name = Dependency.name.charAt(0).toLowerCase() + Dependency.name.slice(1);
85
80
  if (this.checkRegistration(this.container, name))
86
81
  return this;
87
82
  this.container.register({ [name]: asClass(Dependency).setLifetime(lifetime) });
88
- return this;
89
- }
90
- registerAll(dependencies, lifetime = Lifetime.SINGLETON) {
91
- if (Array.isArray(dependencies))
92
- dependencies.forEach(dep => this.register(dep, lifetime));
83
+ this.registeredClasses.add(Dependency);
93
84
  return this;
94
85
  }
95
86
  registerInstance(name, instance) {
96
87
  if (!name)
97
88
  return this;
98
- const formattedName = name.charAt(0).toLowerCase() + name.slice(1);
99
- if (this.checkRegistration(this.container, formattedName))
100
- return this;
89
+ const formattedName = this.getName(name);
101
90
  this.container.register({ [formattedName]: asValue(instance) });
102
91
  return this;
103
92
  }
104
93
  async initialize(input, verbose = false) {
105
- const cradle = this.container.cradle;
106
- const inputList = input ? (Array.isArray(input) ? input : [input]) : [];
107
- const propsMap = new Map();
108
- for (const item of inputList) {
109
- if (!item?.service)
110
- continue;
111
- const name = typeof item.service === 'function' ? item.service.name.charAt(0).toLowerCase() + item.service.name.slice(1) : item.service;
112
- if (name)
113
- propsMap.set(name, item.props);
114
- }
115
- const registrations = this.container.registrations;
116
- const allKeys = new Set([...propsMap.keys(), ...Object.keys(registrations)]);
117
- for (const key of allKeys) {
118
- if (this.initializedNames.has(key))
119
- continue;
120
- const reg = registrations[key];
121
- if (!reg || reg.lifetime !== Lifetime.SINGLETON)
122
- continue;
123
- const instance = cradle[key];
124
- if (instance && typeof instance.load === 'function') {
94
+ if (this.isInitializing)
95
+ return this;
96
+ this.isInitializing = true;
97
+ try {
98
+ const inputList = input ? (Array.isArray(input) ? input : [input]) : [];
99
+ // 1. ADIM: Senin verdiğin sıraya göre TEK TEK yükle (Senin için en önemli yer)
100
+ for (const item of inputList) {
101
+ const name = this.getName(item.service);
102
+ if (!name || this.initializedNames.has(name))
103
+ continue;
104
+ const reg = this.container.registrations[name];
105
+ if (!reg || reg.lifetime !== Lifetime.SINGLETON)
106
+ continue;
125
107
  try {
126
- const props = propsMap.get(key) || {};
127
- await instance.load(props);
128
- this.initializedNames.add(key);
108
+ // Awilix instance'ı oluşturur (constructor çalışır)
109
+ const instance = this.container.cradle[name];
110
+ if (instance && typeof instance.load === 'function') {
111
+ // Burada await ediyoruz, yani bu servis bitmeden döngü ilerlemez
112
+ await instance.load(item.props || {});
113
+ }
114
+ this.initializedNames.add(name);
129
115
  }
130
116
  catch (err) {
131
117
  if (verbose)
132
- console.error(`[DI] Initialization Failed: ${key} ->`, err.message);
118
+ console.error(`[DI] Priority Init Failed (${name}):`, err.message);
133
119
  }
134
120
  }
135
- }
136
- return this;
137
- }
138
- async shutdown(verbose = false) {
139
- const registrations = this.container.registrations;
140
- for (const key in registrations) {
141
- const instance = this.container.cradle[key];
142
- const shutdownMethod = instance?.dispose || instance?.destroy;
143
- if (instance && typeof shutdownMethod === 'function') {
121
+ // 2. ADIM: Listede olmayan ama register edilmiş diğer geri kalan singleton'ları yükle
122
+ const allKeys = Object.keys(this.container.registrations);
123
+ for (const key of allKeys) {
124
+ if (this.initializedNames.has(key))
125
+ continue;
126
+ const reg = this.container.registrations[key];
127
+ if (reg.lifetime !== Lifetime.SINGLETON)
128
+ continue;
144
129
  try {
145
- await shutdownMethod.call(instance);
146
- if (verbose)
147
- console.log(`[DI] Shutdown: ${key}`);
130
+ const instance = this.container.cradle[key];
131
+ if (instance && typeof instance.load === 'function') {
132
+ await instance.load({});
133
+ }
134
+ this.initializedNames.add(key);
148
135
  }
149
136
  catch (err) {
150
- console.error(`[DI] Shutdown Error (${key}):`, err.message);
137
+ if (verbose)
138
+ console.error(`[DI] Auto Init Failed (${key}):`, err.message);
151
139
  }
152
140
  }
153
141
  }
154
- await this.container.dispose();
142
+ finally {
143
+ this.isInitializing = false;
144
+ }
145
+ return this;
155
146
  }
156
147
  get(target) {
157
- try {
158
- const resolveName = typeof target === 'function'
159
- ? target.name.charAt(0).toLowerCase() + target.name.slice(1)
160
- : target;
161
- return this.container.resolve(resolveName);
162
- }
163
- catch (err) {
164
- if (err.message.includes('Cyclic dependencies')) {
165
- console.error(`\n❌ [DI][CRITICAL] Cyclic dependency detected!\n🔍 Path: ${err.resolutionStack}`);
166
- }
167
- throw err;
168
- }
148
+ const resolveName = this.getName(target);
149
+ return this.container.resolve(resolveName);
150
+ }
151
+ async shutdown() {
152
+ await this.container.dispose();
169
153
  }
170
154
  get cradle() { return this.container.cradle; }
171
155
  }
172
- export default new X();
156
+ const X_SYMBOL = Symbol.for('X');
157
+ const globalObj = global;
158
+ if (!globalObj[X_SYMBOL]) {
159
+ globalObj[X_SYMBOL] = new X();
160
+ }
161
+ export default globalObj[X_SYMBOL];