@plyaz/core 1.10.1 → 1.11.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/dist/index.js CHANGED
@@ -4708,18 +4708,18 @@ var require_logger_service = __commonJS({
4708
4708
  static getTimestamp() {
4709
4709
  return dateTimeFormatter.format(Date.now());
4710
4710
  }
4711
- static overrideLogger(logger7) {
4712
- if (Array.isArray(logger7)) {
4713
- Logger_1.logLevels = logger7;
4714
- return this.staticInstanceRef?.setLogLevels?.(logger7);
4711
+ static overrideLogger(logger9) {
4712
+ if (Array.isArray(logger9)) {
4713
+ Logger_1.logLevels = logger9;
4714
+ return this.staticInstanceRef?.setLogLevels?.(logger9);
4715
4715
  }
4716
- if ((0, shared_utils_1.isObject)(logger7)) {
4717
- if (logger7 instanceof Logger_1 && logger7.constructor !== Logger_1) {
4716
+ if ((0, shared_utils_1.isObject)(logger9)) {
4717
+ if (logger9 instanceof Logger_1 && logger9.constructor !== Logger_1) {
4718
4718
  const errorMessage = `Using the "extends Logger" instruction is not allowed in Nest v9. Please, use "extends ConsoleLogger" instead.`;
4719
4719
  this.staticInstanceRef?.error(errorMessage);
4720
4720
  throw new Error(errorMessage);
4721
4721
  }
4722
- this.staticInstanceRef = logger7;
4722
+ this.staticInstanceRef = logger9;
4723
4723
  } else {
4724
4724
  this.staticInstanceRef = void 0;
4725
4725
  }
@@ -5760,7 +5760,7 @@ var require_file_type_validator = __commonJS({
5760
5760
  var logger_service_1 = require_logger_service();
5761
5761
  var file_validator_interface_1 = require_file_validator_interface();
5762
5762
  var load_esm_1 = require_load_esm();
5763
- var logger7 = new logger_service_1.Logger("FileTypeValidator");
5763
+ var logger9 = new logger_service_1.Logger("FileTypeValidator");
5764
5764
  var FileTypeValidator = class extends file_validator_interface_1.FileValidator {
5765
5765
  static {
5766
5766
  __name(this, "FileTypeValidator");
@@ -5814,7 +5814,7 @@ var require_file_type_validator = __commonJS({
5814
5814
  } catch (error) {
5815
5815
  const errorMessage = error instanceof Error ? error.message : String(error);
5816
5816
  if (errorMessage.includes("ERR_VM_DYNAMIC_IMPORT_CALLBACK_MISSING") || errorMessage.includes("Cannot find module") || errorMessage.includes("ERR_MODULE_NOT_FOUND")) {
5817
- logger7.warn(`Failed to load the "file-type" package for magic number validation. If you are using Jest, run it with NODE_OPTIONS="--experimental-vm-modules". Error: ${errorMessage}`);
5817
+ logger9.warn(`Failed to load the "file-type" package for magic number validation. If you are using Jest, run it with NODE_OPTIONS="--experimental-vm-modules". Error: ${errorMessage}`);
5818
5818
  }
5819
5819
  if (this.validationOptions.fallbackToMimetype) {
5820
5820
  return !!file.mimetype.match(this.validationOptions.fileType);
@@ -6461,12 +6461,12 @@ var require_load_package_util = __commonJS({
6461
6461
  exports$1.loadPackage = loadPackage;
6462
6462
  var logger_service_1 = require_logger_service();
6463
6463
  var MISSING_REQUIRED_DEPENDENCY = /* @__PURE__ */ __name((name, reason) => `The "${name}" package is missing. Please, make sure to install it to take advantage of ${reason}.`, "MISSING_REQUIRED_DEPENDENCY");
6464
- var logger7 = new logger_service_1.Logger("PackageLoader");
6464
+ var logger9 = new logger_service_1.Logger("PackageLoader");
6465
6465
  function loadPackage(packageName, context, loaderFn) {
6466
6466
  try {
6467
6467
  return loaderFn ? loaderFn() : __require(packageName);
6468
6468
  } catch (e) {
6469
- logger7.error(MISSING_REQUIRED_DEPENDENCY(packageName, context));
6469
+ logger9.error(MISSING_REQUIRED_DEPENDENCY(packageName, context));
6470
6470
  logger_service_1.Logger.flush();
6471
6471
  process.exit(1);
6472
6472
  }
@@ -33950,7 +33950,7 @@ var LoggerAdapter = class extends BaseAdapter {
33950
33950
  let status = "unset";
33951
33951
  let statusMessage;
33952
33952
  const logSpans = this.logSpans;
33953
- const logger7 = this.observabilityLogger;
33953
+ const logger9 = this.observabilityLogger;
33954
33954
  return {
33955
33955
  context,
33956
33956
  setAttribute: /* @__PURE__ */ __name((key, value) => {
@@ -33962,7 +33962,7 @@ var LoggerAdapter = class extends BaseAdapter {
33962
33962
  addEvent: /* @__PURE__ */ __name((event) => {
33963
33963
  events.push(event);
33964
33964
  if (logSpans) {
33965
- logger7.debug(`[SPAN EVENT] ${options.name}: ${event.name}`, {
33965
+ logger9.debug(`[SPAN EVENT] ${options.name}: ${event.name}`, {
33966
33966
  traceId: context.traceId,
33967
33967
  spanId: context.spanId,
33968
33968
  eventAttributes: event.attributes
@@ -33977,7 +33977,7 @@ var LoggerAdapter = class extends BaseAdapter {
33977
33977
  const duration = (endTime ?? Date.now()) - startTime;
33978
33978
  if (logSpans) {
33979
33979
  const logMethod = status === "error" ? "warn" : "debug";
33980
- logger7[logMethod](`[SPAN END] ${options.name}`, {
33980
+ logger9[logMethod](`[SPAN END] ${options.name}`, {
33981
33981
  traceId: context.traceId,
33982
33982
  spanId: context.spanId,
33983
33983
  durationMs: duration,
@@ -33990,7 +33990,7 @@ var LoggerAdapter = class extends BaseAdapter {
33990
33990
  }, "end"),
33991
33991
  recordException: /* @__PURE__ */ __name((error) => {
33992
33992
  if (logSpans) {
33993
- logger7.error(`[SPAN ERROR] ${options.name}`, {
33993
+ logger9.error(`[SPAN ERROR] ${options.name}`, {
33994
33994
  traceId: context.traceId,
33995
33995
  spanId: context.spanId,
33996
33996
  error: error.message,
@@ -36517,26 +36517,76 @@ var Core = class _Core {
36517
36517
  errorStore,
36518
36518
  _Core.buildErrorHandlerConfig(_Core._errorConfig)
36519
36519
  );
36520
+ _Core.setupErrorEventSubscription(verbose);
36521
+ _Core.setupErrorStoreSubscription(verbose);
36522
+ _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
36523
+ if (_Core._errorConfig.httpHandler !== false) {
36524
+ await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
36525
+ }
36526
+ }
36527
+ /**
36528
+ * Log serialized errors with full details.
36529
+ */
36530
+ static logErrors(errors, prefix = "ErrorStore") {
36531
+ for (const err of errors) {
36532
+ _Core.logger.error(`[${prefix}] ${err.code}: ${err.message}`, {
36533
+ id: err.id,
36534
+ code: err.code,
36535
+ message: err.message,
36536
+ category: err.category,
36537
+ source: err.source,
36538
+ status: err.status,
36539
+ isRetryable: err.isRetryable,
36540
+ context: err.context,
36541
+ timestamp: err.timestamp
36542
+ });
36543
+ }
36544
+ }
36545
+ /**
36546
+ * Setup SYSTEM.ERROR event subscription for error store updates.
36547
+ * Backend: Also logs errors with full details.
36548
+ * Frontend: Only updates store (logging handled by store subscription).
36549
+ */
36550
+ static setupErrorEventSubscription(verbose) {
36551
+ const isBackend = types.BACKEND_RUNTIMES.includes(_Core._coreServices.runtime);
36520
36552
  const errorEventCleanup = CoreEventManager.on(
36521
36553
  core.CORE_EVENTS.SYSTEM.ERROR,
36522
36554
  (event) => {
36523
36555
  if (!_Core._rootStore) return;
36524
36556
  try {
36525
36557
  const { errors } = event.data;
36526
- if (errors && errors.length > 0) {
36527
- _Core._rootStore.getState().errors.addErrors(errors);
36528
- _Core.log(`Added ${errors.length} error(s) to store`, verbose);
36529
- }
36558
+ if (!errors || errors.length === 0) return;
36559
+ _Core._rootStore.getState().errors.addErrors(errors);
36560
+ if (isBackend) _Core.logErrors(errors);
36561
+ _Core.log(`Added ${errors.length} error(s) to store`, verbose);
36530
36562
  } catch (e) {
36531
36563
  _Core.logger.error("Failed to handle error event", { error: e });
36532
36564
  }
36533
36565
  }
36534
36566
  );
36535
36567
  _Core._eventCleanupFns.push(errorEventCleanup);
36536
- _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
36537
- if (_Core._errorConfig.httpHandler !== false) {
36538
- await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
36539
- }
36568
+ }
36569
+ /**
36570
+ * Setup error store subscription for frontend logging.
36571
+ * Logs new errors when they're added to the store.
36572
+ * Only active for non-backend runtimes (browser, nextjs, nuxt, edge).
36573
+ */
36574
+ static setupErrorStoreSubscription(verbose) {
36575
+ const isFrontend = !types.BACKEND_RUNTIMES.includes(_Core._coreServices.runtime);
36576
+ if (!isFrontend || !_Core._rootStore) return;
36577
+ let prevErrorCount = 0;
36578
+ const storeUnsubscribe = _Core._rootStore.subscribe((state) => {
36579
+ const currentCount = state.errors.errorCount;
36580
+ if (currentCount <= prevErrorCount) {
36581
+ prevErrorCount = currentCount;
36582
+ return;
36583
+ }
36584
+ const newErrors = state.errors.errors.slice(0, currentCount - prevErrorCount);
36585
+ _Core.logErrors(newErrors, "ErrorStore:FE");
36586
+ prevErrorCount = currentCount;
36587
+ });
36588
+ _Core._eventCleanupFns.push(storeUnsubscribe);
36589
+ _Core.log("Error store subscription initialized for frontend", verbose);
36540
36590
  }
36541
36591
  /**
36542
36592
  * Create HTTP error handler based on detected runtime.
@@ -38490,7 +38540,7 @@ var BaseFrontendDomainService = class _BaseFrontendDomainService extends BaseDom
38490
38540
  }
38491
38541
  };
38492
38542
  var logger2 = new logger$1.PackageLogger({ packageName: "core", service: "EventPersistence" });
38493
- var BackendEventPersistenceHandler = class {
38543
+ (class {
38494
38544
  static {
38495
38545
  __name(this, "BackendEventPersistenceHandler");
38496
38546
  }
@@ -38664,7 +38714,7 @@ var BackendEventPersistenceHandler = class {
38664
38714
  static getRegisteredDomains() {
38665
38715
  return Array.from(this.registeredDomains.keys());
38666
38716
  }
38667
- };
38717
+ });
38668
38718
  var logger3 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendEventPersistence" });
38669
38719
  var FrontendEventPersistenceHandler = class {
38670
38720
  static {
@@ -43341,6 +43391,7 @@ var ApiFeatureFlagProvider = class extends FeatureFlagProvider {
43341
43391
  return this.engine.getRules();
43342
43392
  }
43343
43393
  };
43394
+ var logger4 = new logger$1.PackageLogger({ packageName: "core", service: "DbService" });
43344
43395
  var DEFAULT_ENCRYPTION_FIELDS = {
43345
43396
  // User PII
43346
43397
  users: ["password_hash", "phone_number", "date_of_birth"],
@@ -43434,6 +43485,41 @@ var DbService = class _DbService {
43434
43485
  };
43435
43486
  CoreEventManager.emit(core.CORE_EVENTS.DATABASE.ERROR, payload);
43436
43487
  }
43488
+ /**
43489
+ * Creates merged event handlers that wrap user-provided handlers.
43490
+ * Adds Core-level logging and forwards to user handlers.
43491
+ *
43492
+ * Note: Unlike StorageService/NotificationService, DB events don't emit
43493
+ * to CoreEventManager by default (too verbose). User handlers can emit
43494
+ * if needed.
43495
+ *
43496
+ * @param userHandlers - User-provided event handlers from config
43497
+ * @returns Merged handlers with Core logging + user handlers
43498
+ */
43499
+ static createMergedEventHandlers(userHandlers) {
43500
+ return {
43501
+ ...userHandlers,
43502
+ onAfterWrite: /* @__PURE__ */ __name(async (event) => {
43503
+ logger4.debug("Database write completed", {
43504
+ operation: event.operation,
43505
+ table: event.table,
43506
+ duration: event.duration
43507
+ });
43508
+ if (userHandlers?.onAfterWrite) {
43509
+ await userHandlers.onAfterWrite(event);
43510
+ }
43511
+ }, "onAfterWrite"),
43512
+ onAfterRead: /* @__PURE__ */ __name(async (event) => {
43513
+ logger4.debug("Database read completed", {
43514
+ table: event.table,
43515
+ duration: event.duration
43516
+ });
43517
+ if (userHandlers?.onAfterRead) {
43518
+ await userHandlers.onAfterRead(event);
43519
+ }
43520
+ }, "onAfterRead")
43521
+ };
43522
+ }
43437
43523
  /**
43438
43524
  * Gets the singleton instance of DbService
43439
43525
  *
@@ -43638,6 +43724,8 @@ var DbService = class _DbService {
43638
43724
  if (cache) dbConfig.cache = cache;
43639
43725
  if (audit) dbConfig.audit = audit;
43640
43726
  if (encryption) dbConfig.encryption = encryption;
43727
+ const events = _DbService.createMergedEventHandlers(config.events);
43728
+ if (events) dbConfig.events = events;
43641
43729
  return dbConfig;
43642
43730
  }
43643
43731
  /**
@@ -46203,7 +46291,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46203
46291
  // Constructor
46204
46292
  // ─────────────────────────────────────────────────────────────────────────
46205
46293
  constructor(config = {}, options) {
46206
- const apiBasePath = config.apiBasePath || "/api/examples";
46294
+ const apiBasePath = config.apiBasePath || "";
46207
46295
  super({
46208
46296
  serviceName: "ExampleFrontendService",
46209
46297
  supportedRuntimes: ["frontend"],
@@ -46228,20 +46316,20 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46228
46316
  // Note: Use relative paths since apiClient.baseURL is already set to apiBasePath
46229
46317
  fetchers: {
46230
46318
  fetchAll: /* @__PURE__ */ __name(async (query) => {
46231
- return this.apiClient.get("", { params: query });
46319
+ return this.apiClient.get("/examples", { params: query });
46232
46320
  }, "fetchAll"),
46233
46321
  fetchById: /* @__PURE__ */ __name(async (id) => {
46234
- return this.apiClient.get(`/${id}`);
46322
+ return this.apiClient.get(`/examples/${id}`);
46235
46323
  }, "fetchById"),
46236
46324
  create: /* @__PURE__ */ __name(async (data) => {
46237
- return this.apiClient.post("", data);
46325
+ return this.apiClient.post("/examples", data);
46238
46326
  }, "create"),
46239
46327
  update: /* @__PURE__ */ __name(async (payload) => {
46240
46328
  const { id, data } = payload;
46241
- return this.apiClient.patch(`/${id}`, data);
46329
+ return this.apiClient.patch(`/examples/${id}`, data);
46242
46330
  }, "update"),
46243
46331
  delete: /* @__PURE__ */ __name(async (id) => {
46244
- return this.apiClient.delete(`/${id}`);
46332
+ return this.apiClient.delete(`/examples/${id}`);
46245
46333
  }, "delete")
46246
46334
  }
46247
46335
  // Store handlers - customize how data syncs to store
@@ -46309,7 +46397,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46309
46397
  // The ?? operator only falls through on null/undefined, not empty strings.
46310
46398
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
46311
46399
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
46312
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api/examples"
46400
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
46313
46401
  };
46314
46402
  const service = new _FrontendExampleDomainService(mergedConfig, options);
46315
46403
  if (mergedConfig.autoFetch) {
@@ -46690,337 +46778,710 @@ var CacheService = class _CacheService {
46690
46778
  }
46691
46779
  };
46692
46780
  var getCacheService = /* @__PURE__ */ __name(() => CacheService.getInstance(), "getCacheService");
46693
- var StorageService = class _StorageService {
46694
- constructor() {
46695
- this.storageService = null;
46696
- this.config = null;
46697
- this.initialized = false;
46698
- }
46781
+ var TABLE_NAME = "media";
46782
+ var DEFAULT_LIMIT2 = 100;
46783
+ var FilesRepository = class _FilesRepository extends db.BaseRepository {
46699
46784
  static {
46700
- __name(this, "StorageService");
46785
+ __name(this, "FilesRepository");
46701
46786
  }
46702
- static {
46703
- this.instance = null;
46787
+ constructor(db) {
46788
+ super(db, TABLE_NAME);
46704
46789
  }
46705
- // ─────────────────────────────────────────────────────────────────
46706
- // Error Handling
46707
- // ─────────────────────────────────────────────────────────────────
46790
+ // ─────────────────────────────────────────────────────────────────────────
46791
+ // Static Factory
46792
+ // ─────────────────────────────────────────────────────────────────────────
46708
46793
  /**
46709
- * Emits a storage error event via CoreEventManager.
46710
- * Called when storage operations fail to integrate with global error handling.
46794
+ * Create repository instance.
46795
+ * Uses DbService if initialized.
46711
46796
  */
46712
- emitStorageError(error, operation, options) {
46713
- const payload = {
46714
- error,
46715
- operation,
46716
- fileId: options?.fileId,
46717
- filename: options?.filename,
46718
- recoverable: options?.recoverable ?? false
46797
+ static create() {
46798
+ if (!DbService.isInitialized()) {
46799
+ throw new errors.DatabasePackageError(
46800
+ "DbService not initialized. Call DbService.initialize() first.",
46801
+ errors$1.DATABASE_ERROR_CODES.CONNECTION_FAILED
46802
+ );
46803
+ }
46804
+ const db = DbService.getInstance().getDatabase();
46805
+ return new _FilesRepository(db);
46806
+ }
46807
+ // ─────────────────────────────────────────────────────────────────────────
46808
+ // Overridden Methods (with domain-specific logic)
46809
+ // ─────────────────────────────────────────────────────────────────────────
46810
+ /**
46811
+ * Find multiple entities with default sorting
46812
+ */
46813
+ // eslint-disable-next-line complexity
46814
+ async findMany(options, config) {
46815
+ const mergedOptions = {
46816
+ sort: options?.sort ?? [{ field: "created_at", direction: "desc" }],
46817
+ pagination: {
46818
+ limit: options?.pagination?.limit ?? DEFAULT_LIMIT2,
46819
+ offset: options?.pagination?.offset ?? 0
46820
+ },
46821
+ filter: options?.filter
46719
46822
  };
46720
- CoreEventManager.emit(core.CORE_EVENTS.STORAGE.ERROR, payload);
46823
+ return super.findMany(mergedOptions, config);
46721
46824
  }
46722
- // ─────────────────────────────────────────────────────────────────
46723
- // Singleton Management
46724
- // ─────────────────────────────────────────────────────────────────
46725
46825
  /**
46726
- * Gets the singleton instance of StorageService
46826
+ * Create new file record with auto-generated ID and timestamps
46727
46827
  */
46728
- static getInstance() {
46729
- _StorageService.instance ??= new _StorageService();
46730
- return _StorageService.instance;
46828
+ // eslint-disable-next-line complexity
46829
+ async create(data, config) {
46830
+ const now = (/* @__PURE__ */ new Date()).toISOString();
46831
+ const id = data.id ?? generateId();
46832
+ const row = {
46833
+ id,
46834
+ user_id: data.user_id ?? "",
46835
+ type: data.type ?? "OTHER",
46836
+ filename: data.filename ?? "",
46837
+ original_filename: data.original_filename ?? data.filename ?? "",
46838
+ mime_type: data.mime_type ?? "application/octet-stream",
46839
+ file_size: data.file_size ?? 0,
46840
+ storage_path: data.storage_path ?? "",
46841
+ cdn_url: data.cdn_url ?? null,
46842
+ width: data.width ?? null,
46843
+ height: data.height ?? null,
46844
+ duration: data.duration ?? null,
46845
+ is_virus_scanned: data.is_virus_scanned ?? false,
46846
+ virus_scan_result: data.virus_scan_result ?? null,
46847
+ metadata: data.metadata ?? null,
46848
+ variants: data.variants ?? null,
46849
+ entity_type: data.entity_type ?? null,
46850
+ entity_id: data.entity_id ?? null,
46851
+ access_level: data.access_level ?? null,
46852
+ created_at: now,
46853
+ updated_at: now,
46854
+ deleted_at: null
46855
+ };
46856
+ return super.create(row, config);
46731
46857
  }
46732
46858
  /**
46733
- * Checks if the storage service has been initialized
46859
+ * Get the table name for this repository
46734
46860
  */
46735
- static isInitialized() {
46736
- return _StorageService.instance?.initialized ?? false;
46861
+ getTableName() {
46862
+ return TABLE_NAME;
46737
46863
  }
46864
+ // ─────────────────────────────────────────────────────────────────────────
46865
+ // Domain-Specific Methods
46866
+ // ─────────────────────────────────────────────────────────────────────────
46738
46867
  /**
46739
- * Resets the storage service by clearing the singleton instance
46868
+ * Find all files for a user
46740
46869
  */
46741
- static async reset() {
46742
- if (_StorageService.instance) {
46743
- _StorageService.instance.storageService = null;
46744
- _StorageService.instance.config = null;
46745
- _StorageService.instance.initialized = false;
46746
- _StorageService.instance = null;
46747
- }
46870
+ async findByUserId(userId, options) {
46871
+ return this.findMany({
46872
+ filter: { field: "user_id", operator: "eq", value: userId },
46873
+ pagination: {
46874
+ limit: options?.limit ?? DEFAULT_LIMIT2,
46875
+ offset: options?.offset ?? 0
46876
+ }
46877
+ });
46748
46878
  }
46749
46879
  /**
46750
- * Initializes the storage service
46751
- *
46752
- * @param config - Storage service configuration
46753
- * @returns The initialized StorageService instance
46880
+ * Find files for a specific entity (polymorphic association)
46754
46881
  */
46755
- static async initialize(config) {
46756
- const instance = _StorageService.getInstance();
46757
- if (instance.initialized) {
46758
- return instance;
46759
- }
46760
- instance.config = config;
46761
- instance.storageService = new storage$1.StorageService(config);
46762
- instance.initialized = true;
46763
- return instance;
46882
+ async findByEntityId(entityType, entityId, options) {
46883
+ return this.query().where("entity_type", "eq", entityType).andWhere("entity_id", "eq", entityId).whereNull("deleted_at").orderByDesc("created_at").paginate(
46884
+ Math.floor((options?.offset ?? 0) / (options?.limit ?? DEFAULT_LIMIT2)) + 1,
46885
+ options?.limit ?? DEFAULT_LIMIT2
46886
+ ).execute();
46764
46887
  }
46765
46888
  /**
46766
- * Gets the raw underlying storage service instance without error handling wrapper.
46767
- * Use this only if you need direct access to the underlying service.
46768
- *
46769
- * @returns The raw StorageService instance from @plyaz/storage
46770
- * @throws {StoragePackageError} When storage is not initialized
46889
+ * Find file by storage path (for deduplication checks)
46771
46890
  */
46772
- getRawStorage() {
46773
- if (!this.storageService) {
46774
- throw new errors.StoragePackageError(
46775
- "Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
46776
- errors$1.STORAGE_ERROR_CODES.INITIALIZATION_FAILED
46777
- );
46778
- }
46779
- return this.storageService;
46891
+ async findByStoragePath(storagePath) {
46892
+ return this.findOne({ field: "storage_path", operator: "eq", value: storagePath });
46780
46893
  }
46781
- // ─────────────────────────────────────────────────────────────────
46782
- // Service Access
46783
- // ─────────────────────────────────────────────────────────────────
46784
46894
  /**
46785
- * Gets the storage service with automatic error handling.
46786
- * All method calls are wrapped with try/catch and emit error events on failure.
46787
- * Any method added to @plyaz/storage will be automatically available.
46788
- *
46789
- * @example
46790
- * ```typescript
46791
- * const storage = StorageService.getInstance().getStorage();
46792
- * await storage.uploadFile({ file, filename: 'doc.pdf' });
46793
- * await storage.deleteFile({ fileId: '123' });
46794
- * ```
46795
- *
46796
- * @returns StorageServiceImpl with automatic error handling
46895
+ * Count files for a user
46797
46896
  */
46798
- getStorage() {
46799
- const self2 = this;
46800
- return new Proxy({}, {
46801
- get(_, prop) {
46802
- if (typeof prop === "symbol") {
46803
- return void 0;
46804
- }
46805
- const storage = self2.getRawStorage();
46806
- const value = storage[prop];
46807
- if (typeof value !== "function") {
46808
- return value;
46809
- }
46810
- return (...args) => {
46811
- try {
46812
- const result2 = value.apply(storage, args);
46813
- if (result2 instanceof Promise) {
46814
- return result2.catch((error) => {
46815
- self2.emitStorageError(error, prop, { recoverable: true });
46816
- throw error;
46817
- });
46818
- }
46819
- return result2;
46820
- } catch (error) {
46821
- self2.emitStorageError(error, prop, { recoverable: true });
46822
- throw error;
46823
- }
46824
- };
46897
+ async countByUserId(userId) {
46898
+ return this.count({ field: "user_id", operator: "eq", value: userId });
46899
+ }
46900
+ /**
46901
+ * Find files by MIME type
46902
+ */
46903
+ async findByMimeType(mimeType, options) {
46904
+ return this.findMany({
46905
+ filter: { field: "mime_type", operator: "eq", value: mimeType },
46906
+ pagination: {
46907
+ limit: options?.limit ?? DEFAULT_LIMIT2,
46908
+ offset: options?.offset ?? 0
46825
46909
  }
46826
46910
  });
46827
46911
  }
46828
- // ─────────────────────────────────────────────────────────────────
46829
- // Health Check (special handling for response transformation)
46830
- // ─────────────────────────────────────────────────────────────────
46831
46912
  /**
46832
- * Performs a health check on the storage service by checking all adapter health.
46833
- * This method has special handling to transform the response format.
46913
+ * Find files by type (IMAGE, VIDEO, DOCUMENT, AUDIO)
46834
46914
  */
46835
- async healthCheck() {
46836
- const startTime = Date.now();
46837
- try {
46838
- const storage = this.getRawStorage();
46839
- if (typeof storage.checkAllAdaptersHealth === "function") {
46840
- await storage.checkAllAdaptersHealth();
46915
+ async findByType(type, options) {
46916
+ return this.findMany({
46917
+ filter: { field: "type", operator: "eq", value: type },
46918
+ pagination: {
46919
+ limit: options?.limit ?? DEFAULT_LIMIT2,
46920
+ offset: options?.offset ?? 0
46841
46921
  }
46842
- const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
46843
- const responseTime = Date.now() - startTime;
46844
- return {
46845
- // Check if all adapters are healthy (healthy count equals total count)
46846
- isHealthy: summary ? summary.healthy === summary.total : true,
46847
- responseTime,
46848
- error: void 0
46849
- };
46850
- } catch (error) {
46851
- this.emitStorageError(error, "healthCheck", { recoverable: true });
46852
- return {
46853
- isHealthy: false,
46854
- responseTime: Date.now() - startTime,
46855
- error: error instanceof Error ? error.message : "Unknown error"
46856
- };
46857
- }
46922
+ });
46923
+ }
46924
+ };
46925
+
46926
+ // src/domain/files/mappers/FilesMapper.ts
46927
+ var FilesMapperClass = class _FilesMapperClass extends BaseMapper {
46928
+ static {
46929
+ __name(this, "FilesMapperClass");
46858
46930
  }
46859
- // ─────────────────────────────────────────────────────────────────
46860
- // Lifecycle
46861
- // ─────────────────────────────────────────────────────────────────
46862
46931
  /**
46863
- * Gets the current configuration
46932
+ * Type guard for UploadResult from event payloads
46933
+ * Validates that the value has the required structure for DB mapping
46864
46934
  */
46865
- getConfig() {
46866
- return this.config;
46935
+ static isUploadResult(value) {
46936
+ if (!isObject(value)) return false;
46937
+ const obj = value;
46938
+ if (!isObject(obj.metadata)) return false;
46939
+ const metadata = obj.metadata;
46940
+ return typeof metadata.fileId === "string" && typeof metadata.filename === "string" && typeof metadata.mimeType === "string" && typeof metadata.size === "number" && typeof metadata.path === "string";
46867
46941
  }
46868
46942
  /**
46869
- * Closes the storage service and cleans up resources
46943
+ * Infer file type from MIME type
46870
46944
  */
46871
- async close() {
46872
- if (this.storageService) {
46873
- await this.storageService.destroy();
46945
+ static inferFileType(mimeType) {
46946
+ if (mimeType.startsWith("image/")) return "IMAGE";
46947
+ if (mimeType.startsWith("video/")) return "VIDEO";
46948
+ if (mimeType.startsWith("audio/")) return "AUDIO";
46949
+ if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("text/")) {
46950
+ return "DOCUMENT";
46874
46951
  }
46875
- this.storageService = null;
46876
- this.initialized = false;
46877
- this.config = null;
46952
+ return "OTHER";
46878
46953
  }
46879
46954
  /**
46880
- * Creates a dedicated storage service instance (NOT the singleton)
46881
- *
46882
- * Use this when you need an isolated storage connection with its own configuration.
46883
- *
46884
- * @param config - Storage service configuration
46885
- * @returns Promise that resolves to a new dedicated StorageService instance
46955
+ * API Response Domain Entity
46886
46956
  */
46887
- static async createInstance(config) {
46888
- const dedicatedInstance = new _StorageService();
46889
- dedicatedInstance.config = config;
46890
- dedicatedInstance.storageService = new storage$1.StorageService(config);
46891
- dedicatedInstance.initialized = true;
46892
- return dedicatedInstance;
46893
- }
46894
- };
46895
- var NotificationService = class _NotificationService {
46896
- constructor() {
46897
- this.notificationService = null;
46898
- this.config = null;
46899
- this.initialized = false;
46900
- }
46901
- static {
46902
- __name(this, "NotificationService");
46903
- }
46904
- static {
46905
- this.instance = null;
46957
+ toDomain(dto) {
46958
+ const type = _FilesMapperClass.inferFileType(dto.mimeType);
46959
+ return {
46960
+ id: dto.id,
46961
+ key: dto.key,
46962
+ filename: dto.filename,
46963
+ mimeType: dto.mimeType,
46964
+ size: dto.size,
46965
+ checksum: dto.checksum,
46966
+ url: dto.url,
46967
+ publicUrl: dto.publicUrl,
46968
+ signedUrl: dto.signedUrl,
46969
+ bucket: dto.bucket,
46970
+ entityType: dto.entityType,
46971
+ entityId: dto.entityId,
46972
+ category: dto.category,
46973
+ uploadedAt: new Date(dto.uploadedAt),
46974
+ expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : void 0,
46975
+ variants: dto.variants,
46976
+ type,
46977
+ isImage: type === "IMAGE",
46978
+ isVideo: type === "VIDEO",
46979
+ isDocument: type === "DOCUMENT",
46980
+ isAudio: type === "AUDIO"
46981
+ };
46906
46982
  }
46907
- // ─────────────────────────────────────────────────────────────────
46908
- // Error Handling
46909
- // ─────────────────────────────────────────────────────────────────
46910
46983
  /**
46911
- * Emits a notification error event via CoreEventManager.
46912
- * Called when notification operations fail to integrate with global error handling.
46984
+ * Domain Entity Store State (serializable)
46913
46985
  */
46914
- emitNotificationError(error, operation, options) {
46915
- const payload = {
46916
- error,
46917
- operation,
46918
- recipientId: options?.recipientId,
46919
- channel: options?.channel,
46920
- recoverable: options?.recoverable ?? false
46986
+ toStoreState(entity) {
46987
+ return {
46988
+ id: entity.id,
46989
+ key: entity.key,
46990
+ filename: entity.filename,
46991
+ mimeType: entity.mimeType,
46992
+ size: entity.size,
46993
+ url: entity.url,
46994
+ publicUrl: entity.publicUrl,
46995
+ bucket: entity.bucket,
46996
+ entityType: entity.entityType,
46997
+ entityId: entity.entityId,
46998
+ category: entity.category,
46999
+ uploadedAt: entity.uploadedAt.toISOString(),
47000
+ type: entity.type,
47001
+ isImage: entity.isImage,
47002
+ isVideo: entity.isVideo,
47003
+ isDocument: entity.isDocument,
47004
+ isAudio: entity.isAudio
46921
47005
  };
46922
- CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.ERROR, payload);
46923
47006
  }
46924
- // ─────────────────────────────────────────────────────────────────
46925
- // Singleton Management
46926
- // ─────────────────────────────────────────────────────────────────
46927
47007
  /**
46928
- * Gets the singleton instance of NotificationService
47008
+ * Store State Domain Entity
46929
47009
  */
46930
- static getInstance() {
46931
- _NotificationService.instance ??= new _NotificationService();
46932
- return _NotificationService.instance;
47010
+ fromStoreState(state) {
47011
+ return {
47012
+ id: state.id,
47013
+ key: state.key,
47014
+ filename: state.filename,
47015
+ mimeType: state.mimeType,
47016
+ size: state.size,
47017
+ url: state.url,
47018
+ publicUrl: state.publicUrl,
47019
+ bucket: state.bucket,
47020
+ entityType: state.entityType,
47021
+ entityId: state.entityId,
47022
+ category: state.category,
47023
+ uploadedAt: new Date(state.uploadedAt),
47024
+ type: state.type,
47025
+ isImage: state.isImage,
47026
+ isVideo: state.isVideo,
47027
+ isDocument: state.isDocument,
47028
+ isAudio: state.isAudio
47029
+ };
46933
47030
  }
46934
47031
  /**
46935
- * Checks if the notification service has been initialized
47032
+ * Database Row API Response (for backend)
46936
47033
  */
46937
- static isInitialized() {
46938
- return _NotificationService.instance?.initialized ?? false;
47034
+ toResponseDTO(row) {
47035
+ return {
47036
+ id: row.id,
47037
+ key: row.storage_path,
47038
+ filename: row.filename,
47039
+ mimeType: row.mime_type,
47040
+ size: toNumber(row.file_size),
47041
+ checksum: void 0,
47042
+ url: row.cdn_url ?? void 0,
47043
+ publicUrl: row.cdn_url ?? void 0,
47044
+ signedUrl: void 0,
47045
+ bucket: "media",
47046
+ entityType: row.entity_type ?? void 0,
47047
+ entityId: row.entity_id ?? void 0,
47048
+ category: void 0,
47049
+ uploadedAt: row.created_at,
47050
+ expiresAt: void 0
47051
+ };
46939
47052
  }
46940
47053
  /**
46941
- * Resets the notification service by clearing the singleton instance
47054
+ * Create GeneratedFileItem from generation result
46942
47055
  */
46943
- static async reset() {
46944
- if (_NotificationService.instance) {
46945
- _NotificationService.instance.notificationService = null;
46946
- _NotificationService.instance.config = null;
46947
- _NotificationService.instance.initialized = false;
46948
- _NotificationService.instance = null;
46949
- }
47056
+ toGeneratedFileItem(templateId, buffer, size, outputFormat = "pdf") {
47057
+ return {
47058
+ id: crypto.randomUUID(),
47059
+ templateId,
47060
+ buffer,
47061
+ size,
47062
+ mimeType: `application/${outputFormat}`,
47063
+ filename: `${templateId}.${outputFormat}`,
47064
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
47065
+ };
46950
47066
  }
46951
47067
  /**
46952
- * Initializes the notification service
47068
+ * Convert upload result to database row format
47069
+ * Used by event handlers to persist upload results to DB
46953
47070
  *
46954
- * @param config - Notification service configuration
46955
- * @returns The initialized NotificationService instance
47071
+ * @param uploadResult - Result from storage upload
47072
+ * @param userId - User ID who uploaded the file (from auth context)
47073
+ * @returns Partial database row ready for repository.create()
46956
47074
  */
46957
- static async initialize(config) {
46958
- const instance = _NotificationService.getInstance();
46959
- if (instance.initialized) {
46960
- return instance;
46961
- }
46962
- instance.config = config;
46963
- instance.notificationService = new notifications.NotificationService(config);
46964
- instance.initialized = true;
46965
- return instance;
47075
+ // eslint-disable-next-line complexity
47076
+ toDbRow(uploadResult, userId) {
47077
+ const { metadata, variants } = uploadResult;
47078
+ return {
47079
+ id: metadata.fileId,
47080
+ user_id: userId ?? "system",
47081
+ type: _FilesMapperClass.inferFileType(metadata.mimeType),
47082
+ filename: metadata.filename,
47083
+ original_filename: metadata.filename,
47084
+ mime_type: metadata.mimeType,
47085
+ file_size: metadata.size,
47086
+ storage_path: metadata.path,
47087
+ cdn_url: metadata.url ?? null,
47088
+ width: metadata.extractedMetadata?.width ?? null,
47089
+ height: metadata.extractedMetadata?.height ?? null,
47090
+ duration: metadata.extractedMetadata?.duration ?? null,
47091
+ entity_type: metadata.entityType,
47092
+ entity_id: metadata.entityId,
47093
+ access_level: metadata.accessLevel,
47094
+ is_virus_scanned: false,
47095
+ virus_scan_result: null,
47096
+ metadata: metadata.customMetadata ?? null,
47097
+ variants: variants ?? null
47098
+ };
46966
47099
  }
46967
- /**
46968
- * Gets the raw underlying notification service instance without error handling wrapper.
47100
+ };
47101
+ var FilesMapper = new FilesMapperClass();
47102
+
47103
+ // src/domain/files/validators/FilesValidator.ts
47104
+ var FilesValidatorClass = class extends BaseValidator {
47105
+ static {
47106
+ __name(this, "FilesValidatorClass");
47107
+ }
47108
+ constructor() {
47109
+ super({});
47110
+ }
47111
+ /**
47112
+ * Check if user has permission to access file
47113
+ */
47114
+ validateUserAccess(userId, fileOwnerId, accessLevel, isAdmin = false) {
47115
+ if (isAdmin) return true;
47116
+ if (userId === fileOwnerId) return true;
47117
+ if (accessLevel === "public") return true;
47118
+ if (accessLevel === "protected" && userId) return true;
47119
+ return false;
47120
+ }
47121
+ };
47122
+ new FilesValidatorClass();
47123
+
47124
+ // src/domain/files/BackendFilesDomainService.ts
47125
+ var BackendFilesDomainService = class _BackendFilesDomainService extends BaseBackendDomainService {
47126
+ constructor(config = {}, injected) {
47127
+ super({
47128
+ serviceName: "BackendFilesDomainService",
47129
+ supportedRuntimes: ["backend"],
47130
+ serviceConfig: {
47131
+ enabled: true,
47132
+ throwOnValidationError: true,
47133
+ throwOnRepositoryError: true,
47134
+ emitEvents: true,
47135
+ ...config
47136
+ },
47137
+ mapperClass: FilesMapperClass,
47138
+ validatorClass: FilesValidatorClass,
47139
+ injected
47140
+ });
47141
+ this.eventPrefix = "files";
47142
+ this.cachePrefix = "files";
47143
+ }
47144
+ static {
47145
+ __name(this, "BackendFilesDomainService");
47146
+ }
47147
+ static {
47148
+ this.serviceKey = "files";
47149
+ }
47150
+ /**
47151
+ * Lazy repository getter - creates repository on first access.
47152
+ * Throws if DbService is not initialized.
47153
+ */
47154
+ get repository() {
47155
+ this._repository ??= FilesRepository.create();
47156
+ return this._repository;
47157
+ }
47158
+ /**
47159
+ * Create service instance
47160
+ */
47161
+ // eslint-disable-next-line complexity
47162
+ static async create(config = {}, options) {
47163
+ const injected = {
47164
+ cache: options?.cache?.instance,
47165
+ db: options?.db?.instance,
47166
+ api: options?.apiClient?.instance,
47167
+ storage: options?.storage?.instance,
47168
+ notifications: options?.notifications?.instance
47169
+ };
47170
+ return new _BackendFilesDomainService(config, injected);
47171
+ }
47172
+ isAvailable() {
47173
+ return this.isServiceEnabled;
47174
+ }
47175
+ dispose() {
47176
+ this.logInfo("Service disposed");
47177
+ }
47178
+ // ─────────────────────────────────────────────────────────────────────────
47179
+ // Storage operations (via @plyaz/storage)
47180
+ // ─────────────────────────────────────────────────────────────────────────
47181
+ /**
47182
+ * Download file content
47183
+ * GET /files/:id/download
47184
+ *
47185
+ * Follows same pattern as base class CRUD methods:
47186
+ * 1. assertReady
47187
+ * 2. Record start time
47188
+ * 3. Log debug info
47189
+ * 4. Emit starting event
47190
+ * 5. Execute operation
47191
+ * 6. Emit success event
47192
+ * 7. Record metrics
47193
+ *
47194
+ * @param params - Download parameters (fileId required)
47195
+ * @returns Download result with buffer, filename, mimeType
47196
+ */
47197
+ // eslint-disable-next-line complexity
47198
+ async downloadFile(params) {
47199
+ this.assertReady();
47200
+ const startTime = Date.now();
47201
+ this.logDebug("Downloading file", { fileId: params.fileId });
47202
+ this.emitEvent("download:downloading", { request: params });
47203
+ try {
47204
+ await this.beforeDownloadFile?.(params);
47205
+ if (!this.storageService) {
47206
+ throw new Error(
47207
+ "Storage service not available. Ensure storage is configured in Core.initialize()."
47208
+ );
47209
+ }
47210
+ const storage = this.storageService.getStorage();
47211
+ const result2 = await storage.downloadFile({
47212
+ fileId: params.fileId
47213
+ });
47214
+ const response = {
47215
+ buffer: result2?.file?.toString("base64") ?? "",
47216
+ filename: result2?.metadata?.filename ?? "download",
47217
+ mimeType: result2?.metadata?.mimeType ?? "application/octet-stream"
47218
+ };
47219
+ await this.afterDownloadFile?.(response);
47220
+ this.emitEvent("download:downloaded", { result: response });
47221
+ this.emitEvent("complete", { success: true, operation: "downloadFile" });
47222
+ await this.recordOperationMetrics("downloadFile", Date.now() - startTime, true);
47223
+ return response;
47224
+ } catch (error) {
47225
+ await this.recordOperationMetrics("downloadFile", Date.now() - startTime, false);
47226
+ this.emitEvent("download:error", { error });
47227
+ throw error;
47228
+ }
47229
+ }
47230
+ /**
47231
+ * Get signed URL for file
47232
+ * GET /files/:id/signed-url
47233
+ *
47234
+ * Follows same pattern as base class CRUD methods.
47235
+ *
47236
+ * @param params - Signed URL parameters (fileId required)
47237
+ * @returns Signed URL result with url and expiresAt
47238
+ */
47239
+ // eslint-disable-next-line complexity
47240
+ async getSignedUrl(params) {
47241
+ this.assertReady();
47242
+ const startTime = Date.now();
47243
+ this.logDebug("Getting signed URL", { fileId: params.fileId, expiresIn: params.expiresIn });
47244
+ this.emitEvent("signedUrl:requesting", { request: params });
47245
+ try {
47246
+ await this.beforeGetSignedUrl?.(params);
47247
+ if (!this.storageService) {
47248
+ throw new Error(
47249
+ "Storage service not available. Ensure storage is configured in Core.initialize()."
47250
+ );
47251
+ }
47252
+ const storage = this.storageService.getStorage();
47253
+ const result2 = await storage.getSignedUrl({
47254
+ fileId: params.fileId,
47255
+ expiresIn: params.expiresIn ?? config.DOWNLOAD_CONFIG.DEFAULT_SIGNED_URL_EXPIRY_SECONDS,
47256
+ operation: "get"
47257
+ });
47258
+ const response = {
47259
+ url: result2?.url ?? "",
47260
+ expiresAt: result2?.expiresAt?.toISOString() ?? new Date(Date.now() + config.TIME_CONSTANTS.HOUR).toISOString()
47261
+ };
47262
+ await this.afterGetSignedUrl?.(response);
47263
+ this.emitEvent("signedUrl:received", { result: response });
47264
+ this.emitEvent("complete", { success: true, operation: "getSignedUrl" });
47265
+ await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, true);
47266
+ return response;
47267
+ } catch (error) {
47268
+ await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, false);
47269
+ this.emitEvent("signedUrl:error", { error });
47270
+ throw error;
47271
+ }
47272
+ }
47273
+ // ─────────────────────────────────────────────────────────────────────────
47274
+ // DB Persistence (called by StorageService on upload complete)
47275
+ // ─────────────────────────────────────────────────────────────────────────
47276
+ /**
47277
+ * Persist uploaded file metadata to the media table.
47278
+ * Called by StorageService.onFileUploaded event handler.
47279
+ *
47280
+ * @param metadata - File metadata from storage upload
47281
+ * @param variants - Optional file variants (thumbnails, etc.)
47282
+ * @param userId - Optional user ID who uploaded the file
47283
+ */
47284
+ async persistUploadedFile(metadata, variants, userId) {
47285
+ const startTime = Date.now();
47286
+ this.logDebug("Persisting uploaded file to DB", {
47287
+ fileId: metadata.fileId,
47288
+ filename: metadata.filename
47289
+ });
47290
+ try {
47291
+ const dbRow = FilesMapper.toDbRow({ metadata, variants }, userId);
47292
+ await this.repository.create(dbRow);
47293
+ this.logDebug("File persisted to media table", {
47294
+ fileId: metadata.fileId,
47295
+ filename: metadata.filename
47296
+ });
47297
+ await this.recordOperationMetrics("persistUploadedFile", Date.now() - startTime, true);
47298
+ } catch (error) {
47299
+ await this.recordOperationMetrics("persistUploadedFile", Date.now() - startTime, false);
47300
+ throw error;
47301
+ }
47302
+ }
47303
+ // ─────────────────────────────────────────────────────────────────────────
47304
+ // NOTE: CRUD and Storage Methods Are Inherited from BaseBackendDomainService
47305
+ // ─────────────────────────────────────────────────────────────────────────
47306
+ //
47307
+ // CRUD methods:
47308
+ // - getById(id) - for GET /files/:id
47309
+ // - getAll(query) - for listing files
47310
+ // - delete(id) - for DELETE /files/:id
47311
+ // - exists(id) - check if file exists
47312
+ //
47313
+ // Storage methods (inherited):
47314
+ // - uploadFile(params) - for POST /files/upload
47315
+ // - generateFile(params) - for POST /files/generate-document
47316
+ // ─────────────────────────────────────────────────────────────────────────
47317
+ };
47318
+ var backendFilesDomainService = new BackendFilesDomainService();
47319
+ var _instance = null;
47320
+ async function getBackendFilesDomainService(config, options) {
47321
+ _instance ??= await BackendFilesDomainService.create(config, options);
47322
+ return _instance;
47323
+ }
47324
+ __name(getBackendFilesDomainService, "getBackendFilesDomainService");
47325
+
47326
+ // src/services/StorageService.ts
47327
+ var logger5 = new logger$1.PackageLogger({ packageName: "core", service: "StorageService" });
47328
+ var StorageService = class _StorageService {
47329
+ constructor() {
47330
+ this.storageService = null;
47331
+ this.config = null;
47332
+ this.initialized = false;
47333
+ }
47334
+ static {
47335
+ __name(this, "StorageService");
47336
+ }
47337
+ static {
47338
+ this.instance = null;
47339
+ }
47340
+ // ─────────────────────────────────────────────────────────────────
47341
+ // Error Handling
47342
+ // ─────────────────────────────────────────────────────────────────
47343
+ /**
47344
+ * Emits a storage error event via CoreEventManager.
47345
+ * Called when storage operations fail to integrate with global error handling.
47346
+ */
47347
+ emitStorageError(error, operation, options) {
47348
+ const payload = {
47349
+ error,
47350
+ operation,
47351
+ fileId: options?.fileId,
47352
+ filename: options?.filename,
47353
+ recoverable: options?.recoverable ?? false
47354
+ };
47355
+ CoreEventManager.emit(core.CORE_EVENTS.STORAGE.ERROR, payload);
47356
+ }
47357
+ // ─────────────────────────────────────────────────────────────────
47358
+ // Singleton Management
47359
+ // ─────────────────────────────────────────────────────────────────
47360
+ /**
47361
+ * Gets the singleton instance of StorageService
47362
+ */
47363
+ static getInstance() {
47364
+ _StorageService.instance ??= new _StorageService();
47365
+ return _StorageService.instance;
47366
+ }
47367
+ /**
47368
+ * Checks if the storage service has been initialized
47369
+ */
47370
+ static isInitialized() {
47371
+ return _StorageService.instance?.initialized ?? false;
47372
+ }
47373
+ /**
47374
+ * Resets the storage service by clearing the singleton instance
47375
+ */
47376
+ static async reset() {
47377
+ if (_StorageService.instance) {
47378
+ _StorageService.instance.storageService = null;
47379
+ _StorageService.instance.config = null;
47380
+ _StorageService.instance.initialized = false;
47381
+ _StorageService.instance = null;
47382
+ }
47383
+ }
47384
+ /**
47385
+ * Creates merged event handlers that persist uploads to the media table.
47386
+ * Merges Core's internal DB persistence handler with user-provided handlers.
47387
+ *
47388
+ * @param userHandlers - User-provided event handlers from config
47389
+ * @returns Merged handlers with DB persistence + user handlers
47390
+ */
47391
+ static createMergedEventHandlers(userHandlers) {
47392
+ return {
47393
+ ...userHandlers,
47394
+ // Persist uploaded files to media table, then call user handler
47395
+ onFileUploaded: /* @__PURE__ */ __name(async (payload) => {
47396
+ if (payload.metadata) {
47397
+ const filesService = await getBackendFilesDomainService();
47398
+ await filesService.persistUploadedFile(payload.metadata);
47399
+ }
47400
+ if (userHandlers?.onFileUploaded) {
47401
+ await userHandlers.onFileUploaded(payload);
47402
+ }
47403
+ }, "onFileUploaded")
47404
+ };
47405
+ }
47406
+ /**
47407
+ * Initializes the storage service
47408
+ *
47409
+ * @param config - Storage service configuration
47410
+ * @returns The initialized StorageService instance
47411
+ */
47412
+ static async initialize(config) {
47413
+ const instance = _StorageService.getInstance();
47414
+ if (instance.initialized) {
47415
+ return instance;
47416
+ }
47417
+ instance.config = config;
47418
+ const mergedHandlers = _StorageService.createMergedEventHandlers(config.handlers);
47419
+ const mergedConfig = {
47420
+ ...config,
47421
+ handlers: mergedHandlers
47422
+ };
47423
+ logger5.info("[StorageService] Initializing with merged event handlers for DB persistence");
47424
+ instance.storageService = new storage$1.StorageService(mergedConfig);
47425
+ instance.initialized = true;
47426
+ return instance;
47427
+ }
47428
+ /**
47429
+ * Gets the raw underlying storage service instance without error handling wrapper.
46969
47430
  * Use this only if you need direct access to the underlying service.
46970
47431
  *
46971
- * @returns The raw NotificationService instance from @plyaz/notifications
46972
- * @throws {NotificationsPackageError} When notifications is not initialized
47432
+ * @returns The raw StorageService instance from @plyaz/storage
47433
+ * @throws {StoragePackageError} When storage is not initialized
46973
47434
  */
46974
- getRawNotifications() {
46975
- if (!this.notificationService) {
46976
- throw new errors.NotificationPackageError(
46977
- "Notifications not initialized. Call NotificationService.initialize() first or use Core.initialize() with notifications config.",
46978
- errors$1.NOTIFICATION_ERROR_CODES.INITIALIZATION_FAILED
47435
+ getRawStorage() {
47436
+ if (!this.storageService) {
47437
+ throw new errors.StoragePackageError(
47438
+ "Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
47439
+ errors$1.STORAGE_ERROR_CODES.INITIALIZATION_FAILED
46979
47440
  );
46980
47441
  }
46981
- return this.notificationService;
47442
+ return this.storageService;
46982
47443
  }
46983
47444
  // ─────────────────────────────────────────────────────────────────
46984
47445
  // Service Access
46985
47446
  // ─────────────────────────────────────────────────────────────────
46986
47447
  /**
46987
- * Gets the notification service with automatic error handling.
47448
+ * Gets the storage service with automatic error handling.
46988
47449
  * All method calls are wrapped with try/catch and emit error events on failure.
46989
- * Any method added to @plyaz/notifications will be automatically available.
47450
+ * Any method added to @plyaz/storage will be automatically available.
46990
47451
  *
46991
47452
  * @example
46992
47453
  * ```typescript
46993
- * const notifications = NotificationService.getInstance().getNotifications();
46994
- * await notifications.sendEmail({ to: 'user@example.com', templateId: 'welcome' });
46995
- * await notifications.sendSMS({ to: '+1234567890', message: 'Hello!' });
47454
+ * const storage = StorageService.getInstance().getStorage();
47455
+ * await storage.uploadFile({ file, filename: 'doc.pdf' });
47456
+ * await storage.deleteFile({ fileId: '123' });
46996
47457
  * ```
46997
47458
  *
46998
- * @returns NotificationServiceImpl with automatic error handling
47459
+ * @returns StorageServiceImpl with automatic error handling
46999
47460
  */
47000
- getNotifications() {
47461
+ getStorage() {
47001
47462
  const self2 = this;
47002
47463
  return new Proxy({}, {
47003
47464
  get(_, prop) {
47004
47465
  if (typeof prop === "symbol") {
47005
47466
  return void 0;
47006
47467
  }
47007
- const notifications = self2.getRawNotifications();
47008
- const value = notifications[prop];
47468
+ const storage = self2.getRawStorage();
47469
+ const value = storage[prop];
47009
47470
  if (typeof value !== "function") {
47010
47471
  return value;
47011
47472
  }
47012
47473
  return (...args) => {
47013
47474
  try {
47014
- const result2 = value.apply(notifications, args);
47475
+ const result2 = value.apply(storage, args);
47015
47476
  if (result2 instanceof Promise) {
47016
47477
  return result2.catch((error) => {
47017
- self2.emitNotificationError(error, prop, { recoverable: true });
47478
+ self2.emitStorageError(error, prop, { recoverable: true });
47018
47479
  throw error;
47019
47480
  });
47020
47481
  }
47021
47482
  return result2;
47022
47483
  } catch (error) {
47023
- self2.emitNotificationError(error, prop, { recoverable: true });
47484
+ self2.emitStorageError(error, prop, { recoverable: true });
47024
47485
  throw error;
47025
47486
  }
47026
47487
  };
@@ -47031,23 +47492,29 @@ var NotificationService = class _NotificationService {
47031
47492
  // Health Check (special handling for response transformation)
47032
47493
  // ─────────────────────────────────────────────────────────────────
47033
47494
  /**
47034
- * Performs a health check on the notification service.
47495
+ * Performs a health check on the storage service by checking all adapter health.
47035
47496
  * This method has special handling to transform the response format.
47036
47497
  */
47037
47498
  async healthCheck() {
47499
+ const startTime = Date.now();
47038
47500
  try {
47039
- const notifications = this.getRawNotifications();
47040
- const result2 = await notifications.healthCheck();
47501
+ const storage = this.getRawStorage();
47502
+ if (typeof storage.checkAllAdaptersHealth === "function") {
47503
+ await storage.checkAllAdaptersHealth();
47504
+ }
47505
+ const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
47506
+ const responseTime = Date.now() - startTime;
47041
47507
  return {
47042
- isHealthy: result2.healthy,
47043
- providers: result2.providers,
47044
- // Use optional chaining since error may not exist on all health check results
47045
- error: "error" in result2 ? result2.error : void 0
47508
+ // Check if all adapters are healthy (healthy count equals total count)
47509
+ isHealthy: summary ? summary.healthy === summary.total : true,
47510
+ responseTime,
47511
+ error: void 0
47046
47512
  };
47047
47513
  } catch (error) {
47048
- this.emitNotificationError(error, "healthCheck", { recoverable: true });
47514
+ this.emitStorageError(error, "healthCheck", { recoverable: true });
47049
47515
  return {
47050
47516
  isHealthy: false,
47517
+ responseTime: Date.now() - startTime,
47051
47518
  error: error instanceof Error ? error.message : "Unknown error"
47052
47519
  };
47053
47520
  }
@@ -47062,844 +47529,539 @@ var NotificationService = class _NotificationService {
47062
47529
  return this.config;
47063
47530
  }
47064
47531
  /**
47065
- * Closes the notification service and cleans up resources
47532
+ * Closes the storage service and cleans up resources
47066
47533
  */
47067
47534
  async close() {
47068
- this.notificationService = null;
47535
+ if (this.storageService) {
47536
+ await this.storageService.destroy();
47537
+ }
47538
+ this.storageService = null;
47069
47539
  this.initialized = false;
47070
47540
  this.config = null;
47071
47541
  }
47072
47542
  /**
47073
- * Creates a dedicated notification service instance (NOT the singleton)
47543
+ * Creates a dedicated storage service instance (NOT the singleton)
47074
47544
  *
47075
- * Use this when you need an isolated notification service with its own configuration.
47545
+ * Use this when you need an isolated storage connection with its own configuration.
47076
47546
  *
47077
- * @param config - Notification service configuration
47078
- * @returns Promise that resolves to a new dedicated NotificationService instance
47547
+ * @param config - Storage service configuration
47548
+ * @returns Promise that resolves to a new dedicated StorageService instance
47079
47549
  */
47080
47550
  static async createInstance(config) {
47081
- const dedicatedInstance = new _NotificationService();
47551
+ const dedicatedInstance = new _StorageService();
47082
47552
  dedicatedInstance.config = config;
47083
- dedicatedInstance.notificationService = new notifications.NotificationService(config);
47553
+ dedicatedInstance.storageService = new storage$1.StorageService(config);
47084
47554
  dedicatedInstance.initialized = true;
47085
47555
  return dedicatedInstance;
47086
47556
  }
47087
47557
  };
47088
- var cachedVersion = "";
47089
- function getPackageVersion() {
47090
- if (cachedVersion) return cachedVersion;
47091
- try {
47092
- const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
47093
- const pkg = require2("../package.json");
47094
- cachedVersion = pkg.version ?? "0.0.0";
47095
- } catch {
47096
- try {
47097
- const currentFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
47098
- const currentDir = path.dirname(currentFile);
47099
- const pkgPath = path.join(currentDir, "..", "package.json");
47100
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
47101
- cachedVersion = pkg.version ?? "0.0.0";
47102
- } catch {
47103
- cachedVersion = "0.0.0";
47104
- }
47105
- }
47106
- return cachedVersion;
47107
- }
47108
- __name(getPackageVersion, "getPackageVersion");
47109
- var VERSION = getPackageVersion();
47110
- var PACKAGE_NAME = "@plyaz/core";
47111
-
47112
- // src/backend/index.ts
47113
- var backend_exports = {};
47114
- __export(backend_exports, {
47115
- BACKEND_EXAMPLE_DOMAIN_SERVICE: () => BACKEND_EXAMPLE_DOMAIN_SERVICE,
47116
- BACKEND_FILES_DOMAIN_SERVICE: () => BACKEND_FILES_DOMAIN_SERVICE,
47117
- BackendExampleDomainService: () => BackendExampleDomainService,
47118
- Caching: () => Caching,
47119
- ErrorHandlingInterceptor: () => ErrorHandlingInterceptor,
47120
- ExampleController: () => ExampleController,
47121
- ExampleModule: () => ExampleModule,
47122
- FeatureDisabled: () => FeatureDisabled,
47123
- FeatureEnabled: () => FeatureEnabled,
47124
- FeatureFlagConfigFactory: () => FeatureFlagConfigFactory,
47125
- FeatureFlagConfigValidator: () => FeatureFlagConfigValidator,
47126
- FeatureFlagController: () => FeatureFlagController,
47127
- FeatureFlagDatabaseRepository: () => FeatureFlagDatabaseRepository,
47128
- FeatureFlagDomainService: () => FeatureFlagDomainService,
47129
- FeatureFlagGuard: () => FeatureFlagGuard,
47130
- FeatureFlagLoggingInterceptor: () => FeatureFlagLoggingInterceptor,
47131
- FeatureFlagMiddleware: () => FeatureFlagMiddleware,
47132
- FeatureFlagModule: () => FeatureFlagModule,
47133
- FeatureFlagService: () => FeatureFlagService,
47134
- FeatureFlagServiceFactory: () => FeatureFlagServiceFactory,
47135
- FilesController: () => FilesController,
47136
- FilesModule: () => FilesModule
47137
- });
47138
-
47139
- // src/backend/example/example.module.ts
47140
- var import_common11 = __toESM(require_common(), 1);
47141
-
47142
- // src/backend/example/example.controller.ts
47143
- var import_common10 = __toESM(require_common(), 1);
47144
- var BACKEND_EXAMPLE_DOMAIN_SERVICE = "BACKEND_EXAMPLE_DOMAIN_SERVICE";
47145
- var ExampleController = class {
47146
- constructor(exampleService) {
47147
- this.exampleService = exampleService;
47148
- }
47149
- health() {
47150
- return errors.SuccessResponseStandard("Service is healthy", {
47151
- service: this.exampleService.isAvailable(),
47152
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
47153
- });
47154
- }
47155
- async getEntity(id) {
47156
- const entity = await this.exampleService.getById(id);
47157
- return errors.SuccessResponseStandard("Entity retrieved successfully", entity);
47158
- }
47159
- async createEntity(dto) {
47160
- const entity = await this.exampleService.create(dto);
47161
- return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47162
- }
47163
- async updateEntity(id, dto) {
47164
- const entity = await this.exampleService.patch(id, dto);
47165
- return errors.SuccessResponseStandard("Entity updated successfully", entity);
47166
- }
47167
- async deleteEntity(id) {
47168
- await this.exampleService.delete(id);
47169
- return errors.SuccessResponseStandard("Entity deleted successfully", null);
47170
- }
47171
- async createEntityWithValidation(dto) {
47172
- const entity = await this.exampleService.create(dto);
47173
- return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47174
- }
47175
- async demoSingleError() {
47176
- return await this.exampleService.demoSingleValidationError();
47177
- }
47178
- async demoArrayErrors() {
47179
- return await this.exampleService.demoMultipleValidationErrors();
47180
- }
47181
- async sendEmail(dto) {
47182
- const result2 = await this.exampleService.sendEmail(dto);
47183
- return errors.SuccessResponseStandard("Email sent successfully", result2);
47184
- }
47185
- };
47186
- __name(ExampleController, "ExampleController");
47187
- __decorateClass([
47188
- (0, import_common10.Get)("health")
47189
- ], ExampleController.prototype, "health", 1);
47190
- __decorateClass([
47191
- (0, import_common10.Get)("entities/:id"),
47192
- __decorateParam(0, (0, import_common10.Param)("id"))
47193
- ], ExampleController.prototype, "getEntity", 1);
47194
- __decorateClass([
47195
- (0, import_common10.Post)("entities"),
47196
- (0, import_common10.HttpCode)(import_common10.HttpStatus.CREATED),
47197
- __decorateParam(0, (0, import_common10.Body)())
47198
- ], ExampleController.prototype, "createEntity", 1);
47199
- __decorateClass([
47200
- (0, import_common10.Patch)("entities/:id"),
47201
- __decorateParam(0, (0, import_common10.Param)("id")),
47202
- __decorateParam(1, (0, import_common10.Body)())
47203
- ], ExampleController.prototype, "updateEntity", 1);
47204
- __decorateClass([
47205
- (0, import_common10.Delete)("entities/:id"),
47206
- (0, import_common10.HttpCode)(import_common10.HttpStatus.OK),
47207
- __decorateParam(0, (0, import_common10.Param)("id"))
47208
- ], ExampleController.prototype, "deleteEntity", 1);
47209
- __decorateClass([
47210
- (0, import_common10.Post)("entities/validated"),
47211
- (0, import_common10.HttpCode)(import_common10.HttpStatus.CREATED),
47212
- __decorateParam(0, (0, import_common10.Body)())
47213
- ], ExampleController.prototype, "createEntityWithValidation", 1);
47214
- __decorateClass([
47215
- (0, import_common10.Get)("errors/single")
47216
- ], ExampleController.prototype, "demoSingleError", 1);
47217
- __decorateClass([
47218
- (0, import_common10.Get)("errors/array")
47219
- ], ExampleController.prototype, "demoArrayErrors", 1);
47220
- __decorateClass([
47221
- (0, import_common10.Post)("email"),
47222
- (0, import_common10.HttpCode)(import_common10.HttpStatus.OK),
47223
- __decorateParam(0, (0, import_common10.Body)())
47224
- ], ExampleController.prototype, "sendEmail", 1);
47225
- ExampleController = __decorateClass([
47226
- (0, import_common10.Controller)("example"),
47227
- __decorateParam(0, (0, import_common10.Inject)(BACKEND_EXAMPLE_DOMAIN_SERVICE))
47228
- ], ExampleController);
47229
-
47230
- // src/backend/example/example.module.ts
47231
- var ExampleModule = class {
47232
- };
47233
- __name(ExampleModule, "ExampleModule");
47234
- ExampleModule = __decorateClass([
47235
- (0, import_common11.Module)({
47236
- controllers: [ExampleController],
47237
- providers: [
47238
- // Provide BackendExampleDomainService via factory using the singleton instance
47239
- // This ensures the service is shared across the application
47240
- {
47241
- provide: BACKEND_EXAMPLE_DOMAIN_SERVICE,
47242
- useFactory: /* @__PURE__ */ __name(() => backendExampleDomainService, "useFactory")
47243
- }
47244
- ],
47245
- exports: [BACKEND_EXAMPLE_DOMAIN_SERVICE]
47246
- })
47247
- ], ExampleModule);
47248
-
47249
- // src/backend/files/files.module.ts
47250
- var import_common17 = __toESM(require_common(), 1);
47251
-
47252
- // src/backend/files/files.controller.ts
47253
- var import_common12 = __toESM(require_common(), 1);
47254
- var BACKEND_FILES_DOMAIN_SERVICE = "BACKEND_FILES_DOMAIN_SERVICE";
47255
- var FilesController = class {
47256
- constructor(filesService) {
47257
- this.filesService = filesService;
47258
- }
47259
- health() {
47260
- return errors.SuccessResponseStandard("Files service is healthy", {
47261
- service: this.filesService.isAvailable(),
47262
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
47263
- });
47264
- }
47265
- async uploadFile(dto) {
47266
- const result2 = await this.filesService.uploadFile(dto);
47267
- return errors.SuccessResponseStandard("File uploaded successfully", result2);
47268
- }
47269
- async uploadFiles(dto) {
47270
- const results = await this.filesService.uploadFiles(dto.files);
47271
- return errors.SuccessResponseStandard("Files uploaded successfully", results);
47272
- }
47273
- async generateDocument(dto) {
47274
- const buffer = await this.filesService.generateFile(dto);
47275
- return errors.SuccessResponseStandard("Document generated successfully", {
47276
- buffer: buffer.toString("base64"),
47277
- size: buffer.length
47278
- });
47279
- }
47280
- async getFile(id) {
47281
- const entity = await this.filesService.getById(id);
47282
- return errors.SuccessResponseStandard("File retrieved", entity);
47283
- }
47284
- async downloadFile(id) {
47285
- const result2 = await this.filesService.downloadFile({ fileId: id });
47286
- return errors.SuccessResponseStandard("File downloaded", result2);
47287
- }
47288
- async getSignedUrl(id) {
47289
- const result2 = await this.filesService.getSignedUrl({ fileId: id });
47290
- return errors.SuccessResponseStandard("Signed URL generated", result2);
47291
- }
47292
- async deleteFile(id) {
47293
- await this.filesService.delete(id);
47294
- return errors.SuccessResponseStandard("File deleted", null);
47295
- }
47296
- };
47297
- __name(FilesController, "FilesController");
47298
- __decorateClass([
47299
- (0, import_common12.Get)("health")
47300
- ], FilesController.prototype, "health", 1);
47301
- __decorateClass([
47302
- (0, import_common12.Post)("upload"),
47303
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47304
- __decorateParam(0, (0, import_common12.Body)())
47305
- ], FilesController.prototype, "uploadFile", 1);
47306
- __decorateClass([
47307
- (0, import_common12.Post)("upload/bulk"),
47308
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47309
- __decorateParam(0, (0, import_common12.Body)())
47310
- ], FilesController.prototype, "uploadFiles", 1);
47311
- __decorateClass([
47312
- (0, import_common12.Post)("generate-document"),
47313
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47314
- __decorateParam(0, (0, import_common12.Body)())
47315
- ], FilesController.prototype, "generateDocument", 1);
47316
- __decorateClass([
47317
- (0, import_common12.Get)(":id"),
47318
- __decorateParam(0, (0, import_common12.Param)("id"))
47319
- ], FilesController.prototype, "getFile", 1);
47320
- __decorateClass([
47321
- (0, import_common12.Get)(":id/download"),
47322
- __decorateParam(0, (0, import_common12.Param)("id"))
47323
- ], FilesController.prototype, "downloadFile", 1);
47324
- __decorateClass([
47325
- (0, import_common12.Get)(":id/signed-url"),
47326
- __decorateParam(0, (0, import_common12.Param)("id"))
47327
- ], FilesController.prototype, "getSignedUrl", 1);
47328
- __decorateClass([
47329
- (0, import_common12.Delete)(":id"),
47330
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47331
- __decorateParam(0, (0, import_common12.Param)("id"))
47332
- ], FilesController.prototype, "deleteFile", 1);
47333
- FilesController = __decorateClass([
47334
- (0, import_common12.Controller)("files"),
47335
- __decorateParam(0, (0, import_common12.Inject)(BACKEND_FILES_DOMAIN_SERVICE))
47336
- ], FilesController);
47337
- var TABLE_NAME = "media";
47338
- var DEFAULT_LIMIT2 = 100;
47339
- var FilesRepository = class _FilesRepository extends db.BaseRepository {
47340
- static {
47341
- __name(this, "FilesRepository");
47342
- }
47343
- constructor(db) {
47344
- super(db, TABLE_NAME);
47345
- }
47346
- // ─────────────────────────────────────────────────────────────────────────
47347
- // Static Factory
47348
- // ─────────────────────────────────────────────────────────────────────────
47349
- /**
47350
- * Create repository instance.
47351
- * Uses DbService if initialized.
47352
- */
47353
- static create() {
47354
- if (!DbService.isInitialized()) {
47355
- throw new errors.DatabasePackageError(
47356
- "DbService not initialized. Call DbService.initialize() first.",
47357
- errors$1.DATABASE_ERROR_CODES.CONNECTION_FAILED
47358
- );
47359
- }
47360
- const db = DbService.getInstance().getDatabase();
47361
- return new _FilesRepository(db);
47362
- }
47363
- // ─────────────────────────────────────────────────────────────────────────
47364
- // Overridden Methods (with domain-specific logic)
47365
- // ─────────────────────────────────────────────────────────────────────────
47366
- /**
47367
- * Find multiple entities with default sorting
47368
- */
47369
- // eslint-disable-next-line complexity
47370
- async findMany(options, config) {
47371
- const mergedOptions = {
47372
- sort: options?.sort ?? [{ field: "created_at", direction: "desc" }],
47373
- pagination: {
47374
- limit: options?.pagination?.limit ?? DEFAULT_LIMIT2,
47375
- offset: options?.pagination?.offset ?? 0
47376
- },
47377
- filter: options?.filter
47378
- };
47379
- return super.findMany(mergedOptions, config);
47380
- }
47381
- /**
47382
- * Create new file record with auto-generated ID and timestamps
47383
- */
47384
- // eslint-disable-next-line complexity
47385
- async create(data, config) {
47386
- const now = (/* @__PURE__ */ new Date()).toISOString();
47387
- const id = data.id ?? generateId();
47388
- const row = {
47389
- id,
47390
- user_id: data.user_id ?? "",
47391
- type: data.type ?? "OTHER",
47392
- filename: data.filename ?? "",
47393
- original_filename: data.original_filename ?? data.filename ?? "",
47394
- mime_type: data.mime_type ?? "application/octet-stream",
47395
- file_size: data.file_size ?? 0,
47396
- storage_path: data.storage_path ?? "",
47397
- cdn_url: data.cdn_url ?? null,
47398
- width: data.width ?? null,
47399
- height: data.height ?? null,
47400
- duration: data.duration ?? null,
47401
- is_virus_scanned: data.is_virus_scanned ?? false,
47402
- virus_scan_result: data.virus_scan_result ?? null,
47403
- metadata: data.metadata ?? null,
47404
- variants: data.variants ?? null,
47405
- entity_type: data.entity_type ?? null,
47406
- entity_id: data.entity_id ?? null,
47407
- access_level: data.access_level ?? null,
47408
- created_at: now,
47409
- updated_at: now,
47410
- deleted_at: null
47411
- };
47412
- return super.create(row, config);
47413
- }
47414
- /**
47415
- * Get the table name for this repository
47416
- */
47417
- getTableName() {
47418
- return TABLE_NAME;
47419
- }
47420
- // ─────────────────────────────────────────────────────────────────────────
47421
- // Domain-Specific Methods
47422
- // ─────────────────────────────────────────────────────────────────────────
47423
- /**
47424
- * Find all files for a user
47425
- */
47426
- async findByUserId(userId, options) {
47427
- return this.findMany({
47428
- filter: { field: "user_id", operator: "eq", value: userId },
47429
- pagination: {
47430
- limit: options?.limit ?? DEFAULT_LIMIT2,
47431
- offset: options?.offset ?? 0
47432
- }
47433
- });
47434
- }
47435
- /**
47436
- * Find files for a specific entity (polymorphic association)
47437
- */
47438
- async findByEntityId(entityType, entityId, options) {
47439
- return this.query().where("entity_type", "eq", entityType).andWhere("entity_id", "eq", entityId).whereNull("deleted_at").orderByDesc("created_at").paginate(
47440
- Math.floor((options?.offset ?? 0) / (options?.limit ?? DEFAULT_LIMIT2)) + 1,
47441
- options?.limit ?? DEFAULT_LIMIT2
47442
- ).execute();
47443
- }
47444
- /**
47445
- * Find file by storage path (for deduplication checks)
47446
- */
47447
- async findByStoragePath(storagePath) {
47448
- return this.findOne({ field: "storage_path", operator: "eq", value: storagePath });
47449
- }
47450
- /**
47451
- * Count files for a user
47452
- */
47453
- async countByUserId(userId) {
47454
- return this.count({ field: "user_id", operator: "eq", value: userId });
47455
- }
47456
- /**
47457
- * Find files by MIME type
47458
- */
47459
- async findByMimeType(mimeType, options) {
47460
- return this.findMany({
47461
- filter: { field: "mime_type", operator: "eq", value: mimeType },
47462
- pagination: {
47463
- limit: options?.limit ?? DEFAULT_LIMIT2,
47464
- offset: options?.offset ?? 0
47465
- }
47466
- });
47467
- }
47468
- /**
47469
- * Find files by type (IMAGE, VIDEO, DOCUMENT, AUDIO)
47470
- */
47471
- async findByType(type, options) {
47472
- return this.findMany({
47473
- filter: { field: "type", operator: "eq", value: type },
47474
- pagination: {
47475
- limit: options?.limit ?? DEFAULT_LIMIT2,
47476
- offset: options?.offset ?? 0
47477
- }
47478
- });
47558
+ var MAX_FAIL_ATTEMPTS = 3;
47559
+ var NotificationService = class _NotificationService {
47560
+ constructor() {
47561
+ this.notificationService = null;
47562
+ this.config = null;
47563
+ this.initialized = false;
47479
47564
  }
47480
- };
47481
-
47482
- // src/domain/files/mappers/FilesMapper.ts
47483
- var FilesMapperClass = class _FilesMapperClass extends BaseMapper {
47484
47565
  static {
47485
- __name(this, "FilesMapperClass");
47486
- }
47487
- /**
47488
- * Type guard for UploadResult from event payloads
47489
- * Validates that the value has the required structure for DB mapping
47490
- */
47491
- static isUploadResult(value) {
47492
- if (!isObject(value)) return false;
47493
- const obj = value;
47494
- if (!isObject(obj.metadata)) return false;
47495
- const metadata = obj.metadata;
47496
- return typeof metadata.fileId === "string" && typeof metadata.filename === "string" && typeof metadata.mimeType === "string" && typeof metadata.size === "number" && typeof metadata.path === "string";
47497
- }
47498
- /**
47499
- * Infer file type from MIME type
47500
- */
47501
- static inferFileType(mimeType) {
47502
- if (mimeType.startsWith("image/")) return "IMAGE";
47503
- if (mimeType.startsWith("video/")) return "VIDEO";
47504
- if (mimeType.startsWith("audio/")) return "AUDIO";
47505
- if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("text/")) {
47506
- return "DOCUMENT";
47507
- }
47508
- return "OTHER";
47509
- }
47510
- /**
47511
- * API Response → Domain Entity
47512
- */
47513
- toDomain(dto) {
47514
- const type = _FilesMapperClass.inferFileType(dto.mimeType);
47515
- return {
47516
- id: dto.id,
47517
- key: dto.key,
47518
- filename: dto.filename,
47519
- mimeType: dto.mimeType,
47520
- size: dto.size,
47521
- checksum: dto.checksum,
47522
- url: dto.url,
47523
- publicUrl: dto.publicUrl,
47524
- signedUrl: dto.signedUrl,
47525
- bucket: dto.bucket,
47526
- entityType: dto.entityType,
47527
- entityId: dto.entityId,
47528
- category: dto.category,
47529
- uploadedAt: new Date(dto.uploadedAt),
47530
- expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : void 0,
47531
- variants: dto.variants,
47532
- type,
47533
- isImage: type === "IMAGE",
47534
- isVideo: type === "VIDEO",
47535
- isDocument: type === "DOCUMENT",
47536
- isAudio: type === "AUDIO"
47537
- };
47538
- }
47539
- /**
47540
- * Domain Entity → Store State (serializable)
47541
- */
47542
- toStoreState(entity) {
47543
- return {
47544
- id: entity.id,
47545
- key: entity.key,
47546
- filename: entity.filename,
47547
- mimeType: entity.mimeType,
47548
- size: entity.size,
47549
- url: entity.url,
47550
- publicUrl: entity.publicUrl,
47551
- bucket: entity.bucket,
47552
- entityType: entity.entityType,
47553
- entityId: entity.entityId,
47554
- category: entity.category,
47555
- uploadedAt: entity.uploadedAt.toISOString(),
47556
- type: entity.type,
47557
- isImage: entity.isImage,
47558
- isVideo: entity.isVideo,
47559
- isDocument: entity.isDocument,
47560
- isAudio: entity.isAudio
47561
- };
47562
- }
47563
- /**
47564
- * Store State → Domain Entity
47565
- */
47566
- fromStoreState(state) {
47567
- return {
47568
- id: state.id,
47569
- key: state.key,
47570
- filename: state.filename,
47571
- mimeType: state.mimeType,
47572
- size: state.size,
47573
- url: state.url,
47574
- publicUrl: state.publicUrl,
47575
- bucket: state.bucket,
47576
- entityType: state.entityType,
47577
- entityId: state.entityId,
47578
- category: state.category,
47579
- uploadedAt: new Date(state.uploadedAt),
47580
- type: state.type,
47581
- isImage: state.isImage,
47582
- isVideo: state.isVideo,
47583
- isDocument: state.isDocument,
47584
- isAudio: state.isAudio
47585
- };
47586
- }
47587
- /**
47588
- * Database Row → API Response (for backend)
47589
- */
47590
- toResponseDTO(row) {
47591
- return {
47592
- id: row.id,
47593
- key: row.storage_path,
47594
- filename: row.filename,
47595
- mimeType: row.mime_type,
47596
- size: toNumber(row.file_size),
47597
- checksum: void 0,
47598
- url: row.cdn_url ?? void 0,
47599
- publicUrl: row.cdn_url ?? void 0,
47600
- signedUrl: void 0,
47601
- bucket: "media",
47602
- entityType: row.entity_type ?? void 0,
47603
- entityId: row.entity_id ?? void 0,
47604
- category: void 0,
47605
- uploadedAt: row.created_at,
47606
- expiresAt: void 0
47607
- };
47608
- }
47609
- /**
47610
- * Create GeneratedFileItem from generation result
47611
- */
47612
- toGeneratedFileItem(templateId, buffer, size, outputFormat = "pdf") {
47613
- return {
47614
- id: crypto.randomUUID(),
47615
- templateId,
47616
- buffer,
47617
- size,
47618
- mimeType: `application/${outputFormat}`,
47619
- filename: `${templateId}.${outputFormat}`,
47620
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
47621
- };
47622
- }
47623
- /**
47624
- * Convert upload result to database row format
47625
- * Used by event handlers to persist upload results to DB
47626
- *
47627
- * @param uploadResult - Result from storage upload
47628
- * @param userId - User ID who uploaded the file (from auth context)
47629
- * @returns Partial database row ready for repository.create()
47630
- */
47631
- // eslint-disable-next-line complexity
47632
- toDbRow(uploadResult, userId) {
47633
- const { metadata, variants } = uploadResult;
47634
- return {
47635
- id: metadata.fileId,
47636
- user_id: userId ?? "system",
47637
- type: _FilesMapperClass.inferFileType(metadata.mimeType),
47638
- filename: metadata.filename,
47639
- original_filename: metadata.filename,
47640
- mime_type: metadata.mimeType,
47641
- file_size: metadata.size,
47642
- storage_path: metadata.path,
47643
- cdn_url: metadata.url ?? null,
47644
- width: metadata.extractedMetadata?.width ?? null,
47645
- height: metadata.extractedMetadata?.height ?? null,
47646
- duration: metadata.extractedMetadata?.duration ?? null,
47647
- entity_type: metadata.entityType,
47648
- entity_id: metadata.entityId,
47649
- access_level: metadata.accessLevel,
47650
- is_virus_scanned: false,
47651
- virus_scan_result: null,
47652
- metadata: metadata.customMetadata ?? null,
47653
- variants: variants ?? null
47654
- };
47566
+ __name(this, "NotificationService");
47655
47567
  }
47656
- };
47657
- var FilesMapper = new FilesMapperClass();
47658
-
47659
- // src/domain/files/validators/FilesValidator.ts
47660
- var FilesValidatorClass = class extends BaseValidator {
47661
47568
  static {
47662
- __name(this, "FilesValidatorClass");
47663
- }
47664
- constructor() {
47665
- super({});
47569
+ this.instance = null;
47666
47570
  }
47571
+ // ─────────────────────────────────────────────────────────────────
47572
+ // Error Handling
47573
+ // ─────────────────────────────────────────────────────────────────
47667
47574
  /**
47668
- * Check if user has permission to access file
47669
- */
47670
- validateUserAccess(userId, fileOwnerId, accessLevel, isAdmin = false) {
47671
- if (isAdmin) return true;
47672
- if (userId === fileOwnerId) return true;
47673
- if (accessLevel === "public") return true;
47674
- if (accessLevel === "protected" && userId) return true;
47675
- return false;
47676
- }
47677
- };
47678
- new FilesValidatorClass();
47679
-
47680
- // src/domain/files/BackendFilesDomainService.ts
47681
- var BackendFilesDomainService = class _BackendFilesDomainService extends BaseBackendDomainService {
47682
- constructor(config = {}, injected) {
47683
- super({
47684
- serviceName: "BackendFilesDomainService",
47685
- supportedRuntimes: ["backend"],
47686
- serviceConfig: {
47687
- enabled: true,
47688
- throwOnValidationError: true,
47689
- throwOnRepositoryError: true,
47690
- emitEvents: true,
47691
- ...config
47692
- },
47693
- mapperClass: FilesMapperClass,
47694
- validatorClass: FilesValidatorClass,
47695
- injected
47696
- });
47697
- this.eventPrefix = "files";
47698
- this.cachePrefix = "files";
47575
+ * Emits a notification error event via CoreEventManager.
47576
+ * Called when notification operations fail to integrate with global error handling.
47577
+ */
47578
+ emitNotificationError(error, operation, options) {
47579
+ const payload = {
47580
+ error,
47581
+ operation,
47582
+ recipientId: options?.recipientId,
47583
+ channel: options?.channel,
47584
+ recoverable: options?.recoverable ?? false
47585
+ };
47586
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.ERROR, payload);
47699
47587
  }
47700
- static {
47701
- __name(this, "BackendFilesDomainService");
47588
+ // ─────────────────────────────────────────────────────────────────
47589
+ // Merged Event Handlers
47590
+ // ─────────────────────────────────────────────────────────────────
47591
+ /**
47592
+ * Creates merged event handlers that emit to CoreEventManager.
47593
+ * Merges Core's internal handlers with user-provided handlers.
47594
+ *
47595
+ * @param userHandlers - User-provided event handlers from config
47596
+ * @returns Merged handlers with Core event emission + user handlers
47597
+ */
47598
+ static createMergedEventHandlers(userHandlers) {
47599
+ return {
47600
+ ...userHandlers,
47601
+ // Emit to CoreEventManager on sent, then call user handler
47602
+ onSent: /* @__PURE__ */ __name(async (payload) => {
47603
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.SENT, {
47604
+ notificationId: payload.notificationId,
47605
+ recipientId: payload.recipientId,
47606
+ channel: payload.channel,
47607
+ provider: payload.provider
47608
+ });
47609
+ if (userHandlers?.onSent) {
47610
+ await userHandlers.onSent(payload);
47611
+ }
47612
+ }, "onSent"),
47613
+ // Emit to CoreEventManager on failed, then call user handler
47614
+ onFailed: /* @__PURE__ */ __name(async (payload) => {
47615
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.FAILED, {
47616
+ notificationId: payload.notificationId,
47617
+ recipientId: payload.recipientId,
47618
+ channel: payload.channel,
47619
+ error: payload.error,
47620
+ retryable: payload.retryCount < MAX_FAIL_ATTEMPTS
47621
+ });
47622
+ if (userHandlers?.onFailed) {
47623
+ await userHandlers.onFailed(payload);
47624
+ }
47625
+ }, "onFailed"),
47626
+ // Emit to CoreEventManager on delivered, then call user handler
47627
+ onDelivered: /* @__PURE__ */ __name(async (payload) => {
47628
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.DELIVERED, {
47629
+ notificationId: payload.notificationId,
47630
+ recipientId: payload.recipientId,
47631
+ channel: payload.channel,
47632
+ deliveredAt: payload.deliveredAt
47633
+ });
47634
+ if (userHandlers?.onDelivered) {
47635
+ await userHandlers.onDelivered(payload);
47636
+ }
47637
+ }, "onDelivered")
47638
+ };
47702
47639
  }
47703
- static {
47704
- this.serviceKey = "files";
47640
+ // ─────────────────────────────────────────────────────────────────
47641
+ // Singleton Management
47642
+ // ─────────────────────────────────────────────────────────────────
47643
+ /**
47644
+ * Gets the singleton instance of NotificationService
47645
+ */
47646
+ static getInstance() {
47647
+ _NotificationService.instance ??= new _NotificationService();
47648
+ return _NotificationService.instance;
47705
47649
  }
47706
47650
  /**
47707
- * Lazy repository getter - creates repository on first access.
47708
- * Throws if DbService is not initialized.
47651
+ * Checks if the notification service has been initialized
47709
47652
  */
47710
- get repository() {
47711
- this._repository ??= FilesRepository.create();
47712
- return this._repository;
47653
+ static isInitialized() {
47654
+ return _NotificationService.instance?.initialized ?? false;
47713
47655
  }
47714
47656
  /**
47715
- * Create service instance
47657
+ * Resets the notification service by clearing the singleton instance
47716
47658
  */
47717
- // eslint-disable-next-line complexity
47718
- static async create(config = {}, options) {
47719
- this.registerEventHandlers();
47720
- const injected = {
47721
- cache: options?.cache?.instance,
47722
- db: options?.db?.instance,
47723
- api: options?.apiClient?.instance,
47724
- storage: options?.storage?.instance,
47725
- notifications: options?.notifications?.instance
47726
- };
47727
- return new _BackendFilesDomainService(config, injected);
47659
+ static async reset() {
47660
+ if (_NotificationService.instance) {
47661
+ _NotificationService.instance.notificationService = null;
47662
+ _NotificationService.instance.config = null;
47663
+ _NotificationService.instance.initialized = false;
47664
+ _NotificationService.instance = null;
47665
+ }
47728
47666
  }
47729
47667
  /**
47730
- * Register event handlers for file upload persistence.
47731
- * Called by CoreInitializer after storage and DB are initialized.
47732
- *
47733
- * Events handled:
47734
- * - files:upload:uploaded - Persist single file to DB
47735
- * - files:upload:bulk:uploaded - Persist multiple files to DB
47736
- *
47737
- * Deduplication: Uses unique constraint on storage_path.
47668
+ * Initializes the notification service
47738
47669
  *
47739
- * @param verbose - Enable verbose logging
47670
+ * @param config - Notification service configuration
47671
+ * @returns The initialized NotificationService instance
47740
47672
  */
47741
- static registerEventHandlers(verbose) {
47742
- if (BackendEventPersistenceHandler.isRegistered("files")) {
47743
- return;
47673
+ static async initialize(config) {
47674
+ const instance = _NotificationService.getInstance();
47675
+ if (instance.initialized) {
47676
+ return instance;
47744
47677
  }
47745
- BackendEventPersistenceHandler.register({
47746
- domain: "files",
47747
- events: {
47748
- "files:upload:uploaded": {
47749
- extractPayload: /* @__PURE__ */ __name((payload) => {
47750
- if (!isObject(payload)) return void 0;
47751
- const p = payload;
47752
- if (!FilesMapperClass.isUploadResult(p.result)) return void 0;
47753
- return p.result;
47754
- }, "extractPayload"),
47755
- isBulk: false,
47756
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
47757
- },
47758
- "files:upload:bulk:uploaded": {
47759
- extractPayload: /* @__PURE__ */ __name((payload) => {
47760
- if (!isObject(payload)) return [];
47761
- const p = payload;
47762
- const results = p.result?.results;
47763
- if (!Array.isArray(results)) return [];
47764
- return results.filter(FilesMapperClass.isUploadResult);
47765
- }, "extractPayload"),
47766
- isBulk: true,
47767
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
47768
- }
47769
- },
47770
- loadDependencies: /* @__PURE__ */ __name(async () => ({
47771
- repository: FilesRepository.create(),
47772
- mapper: FilesMapper
47773
- }), "loadDependencies"),
47774
- mapToDbRow: /* @__PURE__ */ __name((mapper, item) => mapper.toDbRow(item), "mapToDbRow"),
47775
- createRecord: /* @__PURE__ */ __name(async (repository, dbRow) => {
47776
- return repository.create(dbRow);
47777
- }, "createRecord"),
47778
- getUniqueKey: /* @__PURE__ */ __name((item) => item.metadata.path ?? item.metadata.fileId, "getUniqueKey"),
47779
- verbose
47678
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
47679
+ instance.config = config;
47680
+ instance.notificationService = new notifications.NotificationService({
47681
+ ...config,
47682
+ events: mergedEvents
47780
47683
  });
47684
+ instance.initialized = true;
47685
+ return instance;
47781
47686
  }
47782
- isAvailable() {
47783
- return this.isServiceEnabled;
47784
- }
47785
- dispose() {
47786
- this.logInfo("Service disposed");
47687
+ /**
47688
+ * Gets the raw underlying notification service instance without error handling wrapper.
47689
+ * Use this only if you need direct access to the underlying service.
47690
+ *
47691
+ * @returns The raw NotificationService instance from @plyaz/notifications
47692
+ * @throws {NotificationsPackageError} When notifications is not initialized
47693
+ */
47694
+ getRawNotifications() {
47695
+ if (!this.notificationService) {
47696
+ throw new errors.NotificationPackageError(
47697
+ "Notifications not initialized. Call NotificationService.initialize() first or use Core.initialize() with notifications config.",
47698
+ errors$1.NOTIFICATION_ERROR_CODES.INITIALIZATION_FAILED
47699
+ );
47700
+ }
47701
+ return this.notificationService;
47787
47702
  }
47788
- // ─────────────────────────────────────────────────────────────────────────
47789
- // Storage operations (via @plyaz/storage)
47790
- // ─────────────────────────────────────────────────────────────────────────
47703
+ // ─────────────────────────────────────────────────────────────────
47704
+ // Service Access
47705
+ // ─────────────────────────────────────────────────────────────────
47791
47706
  /**
47792
- * Download file content
47793
- * GET /files/:id/download
47707
+ * Gets the notification service with automatic error handling.
47708
+ * All method calls are wrapped with try/catch and emit error events on failure.
47709
+ * Any method added to @plyaz/notifications will be automatically available.
47794
47710
  *
47795
- * Follows same pattern as base class CRUD methods:
47796
- * 1. assertReady
47797
- * 2. Record start time
47798
- * 3. Log debug info
47799
- * 4. Emit starting event
47800
- * 5. Execute operation
47801
- * 6. Emit success event
47802
- * 7. Record metrics
47711
+ * @example
47712
+ * ```typescript
47713
+ * const notifications = NotificationService.getInstance().getNotifications();
47714
+ * await notifications.sendEmail({ to: 'user@example.com', templateId: 'welcome' });
47715
+ * await notifications.sendSMS({ to: '+1234567890', message: 'Hello!' });
47716
+ * ```
47803
47717
  *
47804
- * @param params - Download parameters (fileId required)
47805
- * @returns Download result with buffer, filename, mimeType
47718
+ * @returns NotificationServiceImpl with automatic error handling
47806
47719
  */
47807
- // eslint-disable-next-line complexity
47808
- async downloadFile(params) {
47809
- this.assertReady();
47810
- const startTime = Date.now();
47811
- this.logDebug("Downloading file", { fileId: params.fileId });
47812
- this.emitEvent("download:downloading", { request: params });
47813
- try {
47814
- await this.beforeDownloadFile?.(params);
47815
- if (!this.storageService) {
47816
- throw new Error(
47817
- "Storage service not available. Ensure storage is configured in Core.initialize()."
47818
- );
47720
+ getNotifications() {
47721
+ const self2 = this;
47722
+ return new Proxy({}, {
47723
+ get(_, prop) {
47724
+ if (typeof prop === "symbol") {
47725
+ return void 0;
47726
+ }
47727
+ const notifications = self2.getRawNotifications();
47728
+ const value = notifications[prop];
47729
+ if (typeof value !== "function") {
47730
+ return value;
47731
+ }
47732
+ return (...args) => {
47733
+ try {
47734
+ const result2 = value.apply(notifications, args);
47735
+ if (result2 instanceof Promise) {
47736
+ return result2.catch((error) => {
47737
+ self2.emitNotificationError(error, prop, { recoverable: true });
47738
+ throw error;
47739
+ });
47740
+ }
47741
+ return result2;
47742
+ } catch (error) {
47743
+ self2.emitNotificationError(error, prop, { recoverable: true });
47744
+ throw error;
47745
+ }
47746
+ };
47819
47747
  }
47820
- const storage = this.storageService.getStorage();
47821
- const result2 = await storage.downloadFile({
47822
- fileId: params.fileId
47823
- });
47824
- const response = {
47825
- buffer: result2?.file?.toString("base64") ?? "",
47826
- filename: result2?.metadata?.filename ?? "download",
47827
- mimeType: result2?.metadata?.mimeType ?? "application/octet-stream"
47748
+ });
47749
+ }
47750
+ // ─────────────────────────────────────────────────────────────────
47751
+ // Health Check (special handling for response transformation)
47752
+ // ─────────────────────────────────────────────────────────────────
47753
+ /**
47754
+ * Performs a health check on the notification service.
47755
+ * This method has special handling to transform the response format.
47756
+ */
47757
+ async healthCheck() {
47758
+ try {
47759
+ const notifications = this.getRawNotifications();
47760
+ const result2 = await notifications.healthCheck();
47761
+ return {
47762
+ isHealthy: result2.healthy,
47763
+ providers: result2.providers,
47764
+ // Use optional chaining since error may not exist on all health check results
47765
+ error: "error" in result2 ? result2.error : void 0
47828
47766
  };
47829
- await this.afterDownloadFile?.(response);
47830
- this.emitEvent("download:downloaded", { result: response });
47831
- this.emitEvent("complete", { success: true, operation: "downloadFile" });
47832
- await this.recordOperationMetrics("downloadFile", Date.now() - startTime, true);
47833
- return response;
47834
47767
  } catch (error) {
47835
- await this.recordOperationMetrics("downloadFile", Date.now() - startTime, false);
47836
- this.emitEvent("download:error", { error });
47837
- throw error;
47768
+ this.emitNotificationError(error, "healthCheck", { recoverable: true });
47769
+ return {
47770
+ isHealthy: false,
47771
+ error: error instanceof Error ? error.message : "Unknown error"
47772
+ };
47838
47773
  }
47839
47774
  }
47775
+ // ─────────────────────────────────────────────────────────────────
47776
+ // Lifecycle
47777
+ // ─────────────────────────────────────────────────────────────────
47778
+ /**
47779
+ * Gets the current configuration
47780
+ */
47781
+ getConfig() {
47782
+ return this.config;
47783
+ }
47840
47784
  /**
47841
- * Get signed URL for file
47842
- * GET /files/:id/signed-url
47785
+ * Closes the notification service and cleans up resources
47786
+ */
47787
+ async close() {
47788
+ this.notificationService = null;
47789
+ this.initialized = false;
47790
+ this.config = null;
47791
+ }
47792
+ /**
47793
+ * Creates a dedicated notification service instance (NOT the singleton)
47843
47794
  *
47844
- * Follows same pattern as base class CRUD methods.
47795
+ * Use this when you need an isolated notification service with its own configuration.
47845
47796
  *
47846
- * @param params - Signed URL parameters (fileId required)
47847
- * @returns Signed URL result with url and expiresAt
47797
+ * @param config - Notification service configuration
47798
+ * @returns Promise that resolves to a new dedicated NotificationService instance
47848
47799
  */
47849
- // eslint-disable-next-line complexity
47850
- async getSignedUrl(params) {
47851
- this.assertReady();
47852
- const startTime = Date.now();
47853
- this.logDebug("Getting signed URL", { fileId: params.fileId, expiresIn: params.expiresIn });
47854
- this.emitEvent("signedUrl:requesting", { request: params });
47800
+ static async createInstance(config) {
47801
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
47802
+ const dedicatedInstance = new _NotificationService();
47803
+ dedicatedInstance.config = config;
47804
+ dedicatedInstance.notificationService = new notifications.NotificationService({
47805
+ ...config,
47806
+ events: mergedEvents
47807
+ });
47808
+ dedicatedInstance.initialized = true;
47809
+ return dedicatedInstance;
47810
+ }
47811
+ };
47812
+ var cachedVersion = "";
47813
+ function getPackageVersion() {
47814
+ if (cachedVersion) return cachedVersion;
47815
+ try {
47816
+ const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
47817
+ const pkg = require2("../package.json");
47818
+ cachedVersion = pkg.version ?? "0.0.0";
47819
+ } catch {
47855
47820
  try {
47856
- await this.beforeGetSignedUrl?.(params);
47857
- if (!this.storageService) {
47858
- throw new Error(
47859
- "Storage service not available. Ensure storage is configured in Core.initialize()."
47860
- );
47861
- }
47862
- const storage = this.storageService.getStorage();
47863
- const result2 = await storage.getSignedUrl({
47864
- fileId: params.fileId,
47865
- expiresIn: params.expiresIn ?? config.DOWNLOAD_CONFIG.DEFAULT_SIGNED_URL_EXPIRY_SECONDS,
47866
- operation: "get"
47867
- });
47868
- const response = {
47869
- url: result2?.url ?? "",
47870
- expiresAt: result2?.expiresAt?.toISOString() ?? new Date(Date.now() + config.TIME_CONSTANTS.HOUR).toISOString()
47871
- };
47872
- await this.afterGetSignedUrl?.(response);
47873
- this.emitEvent("signedUrl:received", { result: response });
47874
- this.emitEvent("complete", { success: true, operation: "getSignedUrl" });
47875
- await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, true);
47876
- return response;
47877
- } catch (error) {
47878
- await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, false);
47879
- this.emitEvent("signedUrl:error", { error });
47880
- throw error;
47821
+ const currentFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
47822
+ const currentDir = path.dirname(currentFile);
47823
+ const pkgPath = path.join(currentDir, "..", "package.json");
47824
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
47825
+ cachedVersion = pkg.version ?? "0.0.0";
47826
+ } catch {
47827
+ cachedVersion = "0.0.0";
47881
47828
  }
47882
47829
  }
47883
- // ─────────────────────────────────────────────────────────────────────────
47884
- // NOTE: CRUD and Storage Methods Are Inherited from BaseBackendDomainService
47885
- // ─────────────────────────────────────────────────────────────────────────
47886
- //
47887
- // CRUD methods:
47888
- // - getById(id) - for GET /files/:id
47889
- // - getAll(query) - for listing files
47890
- // - delete(id) - for DELETE /files/:id
47891
- // - exists(id) - check if file exists
47892
- //
47893
- // Storage methods (inherited):
47894
- // - uploadFile(params) - for POST /files/upload
47895
- // - generateFile(params) - for POST /files/generate-document
47896
- // ─────────────────────────────────────────────────────────────────────────
47830
+ return cachedVersion;
47831
+ }
47832
+ __name(getPackageVersion, "getPackageVersion");
47833
+ var VERSION = getPackageVersion();
47834
+ var PACKAGE_NAME = "@plyaz/core";
47835
+
47836
+ // src/backend/index.ts
47837
+ var backend_exports = {};
47838
+ __export(backend_exports, {
47839
+ BACKEND_EXAMPLE_DOMAIN_SERVICE: () => BACKEND_EXAMPLE_DOMAIN_SERVICE,
47840
+ BACKEND_FILES_DOMAIN_SERVICE: () => BACKEND_FILES_DOMAIN_SERVICE,
47841
+ BackendExampleDomainService: () => BackendExampleDomainService,
47842
+ Caching: () => Caching,
47843
+ ErrorHandlingInterceptor: () => ErrorHandlingInterceptor,
47844
+ ExampleController: () => ExampleController,
47845
+ ExampleModule: () => ExampleModule,
47846
+ FeatureDisabled: () => FeatureDisabled,
47847
+ FeatureEnabled: () => FeatureEnabled,
47848
+ FeatureFlagConfigFactory: () => FeatureFlagConfigFactory,
47849
+ FeatureFlagConfigValidator: () => FeatureFlagConfigValidator,
47850
+ FeatureFlagController: () => FeatureFlagController,
47851
+ FeatureFlagDatabaseRepository: () => FeatureFlagDatabaseRepository,
47852
+ FeatureFlagDomainService: () => FeatureFlagDomainService,
47853
+ FeatureFlagGuard: () => FeatureFlagGuard,
47854
+ FeatureFlagLoggingInterceptor: () => FeatureFlagLoggingInterceptor,
47855
+ FeatureFlagMiddleware: () => FeatureFlagMiddleware,
47856
+ FeatureFlagModule: () => FeatureFlagModule,
47857
+ FeatureFlagService: () => FeatureFlagService,
47858
+ FeatureFlagServiceFactory: () => FeatureFlagServiceFactory,
47859
+ FilesController: () => FilesController,
47860
+ FilesModule: () => FilesModule
47861
+ });
47862
+
47863
+ // src/backend/example/example.module.ts
47864
+ var import_common14 = __toESM(require_common(), 1);
47865
+
47866
+ // src/backend/example/example.controller.ts
47867
+ var import_common13 = __toESM(require_common(), 1);
47868
+ var BACKEND_EXAMPLE_DOMAIN_SERVICE = "BACKEND_EXAMPLE_DOMAIN_SERVICE";
47869
+ var ExampleController = class {
47870
+ constructor(exampleService) {
47871
+ this.exampleService = exampleService;
47872
+ }
47873
+ health() {
47874
+ return errors.SuccessResponseStandard("Service is healthy", {
47875
+ service: this.exampleService.isAvailable(),
47876
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
47877
+ });
47878
+ }
47879
+ async getEntity(id) {
47880
+ const entity = await this.exampleService.getById(id);
47881
+ return errors.SuccessResponseStandard("Entity retrieved successfully", entity);
47882
+ }
47883
+ async createEntity(dto) {
47884
+ const entity = await this.exampleService.create(dto);
47885
+ return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47886
+ }
47887
+ async updateEntity(id, dto) {
47888
+ const entity = await this.exampleService.patch(id, dto);
47889
+ return errors.SuccessResponseStandard("Entity updated successfully", entity);
47890
+ }
47891
+ async deleteEntity(id) {
47892
+ await this.exampleService.delete(id);
47893
+ return errors.SuccessResponseStandard("Entity deleted successfully", null);
47894
+ }
47895
+ async createEntityWithValidation(dto) {
47896
+ const entity = await this.exampleService.create(dto);
47897
+ return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47898
+ }
47899
+ async demoSingleError() {
47900
+ return await this.exampleService.demoSingleValidationError();
47901
+ }
47902
+ async demoArrayErrors() {
47903
+ return await this.exampleService.demoMultipleValidationErrors();
47904
+ }
47905
+ async sendEmail(dto) {
47906
+ const result2 = await this.exampleService.sendEmail(dto);
47907
+ return errors.SuccessResponseStandard("Email sent successfully", result2);
47908
+ }
47897
47909
  };
47898
- var backendFilesDomainService = new BackendFilesDomainService();
47899
- var logger4 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendFilesDomainService" });
47910
+ __name(ExampleController, "ExampleController");
47911
+ __decorateClass([
47912
+ (0, import_common13.Get)("health")
47913
+ ], ExampleController.prototype, "health", 1);
47914
+ __decorateClass([
47915
+ (0, import_common13.Get)("entities/:id"),
47916
+ __decorateParam(0, (0, import_common13.Param)("id"))
47917
+ ], ExampleController.prototype, "getEntity", 1);
47918
+ __decorateClass([
47919
+ (0, import_common13.Post)("entities"),
47920
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.CREATED),
47921
+ __decorateParam(0, (0, import_common13.Body)())
47922
+ ], ExampleController.prototype, "createEntity", 1);
47923
+ __decorateClass([
47924
+ (0, import_common13.Patch)("entities/:id"),
47925
+ __decorateParam(0, (0, import_common13.Param)("id")),
47926
+ __decorateParam(1, (0, import_common13.Body)())
47927
+ ], ExampleController.prototype, "updateEntity", 1);
47928
+ __decorateClass([
47929
+ (0, import_common13.Delete)("entities/:id"),
47930
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.OK),
47931
+ __decorateParam(0, (0, import_common13.Param)("id"))
47932
+ ], ExampleController.prototype, "deleteEntity", 1);
47933
+ __decorateClass([
47934
+ (0, import_common13.Post)("entities/validated"),
47935
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.CREATED),
47936
+ __decorateParam(0, (0, import_common13.Body)())
47937
+ ], ExampleController.prototype, "createEntityWithValidation", 1);
47938
+ __decorateClass([
47939
+ (0, import_common13.Get)("errors/single")
47940
+ ], ExampleController.prototype, "demoSingleError", 1);
47941
+ __decorateClass([
47942
+ (0, import_common13.Get)("errors/array")
47943
+ ], ExampleController.prototype, "demoArrayErrors", 1);
47944
+ __decorateClass([
47945
+ (0, import_common13.Post)("email"),
47946
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.OK),
47947
+ __decorateParam(0, (0, import_common13.Body)())
47948
+ ], ExampleController.prototype, "sendEmail", 1);
47949
+ ExampleController = __decorateClass([
47950
+ (0, import_common13.Controller)("example"),
47951
+ __decorateParam(0, (0, import_common13.Inject)(BACKEND_EXAMPLE_DOMAIN_SERVICE))
47952
+ ], ExampleController);
47953
+
47954
+ // src/backend/example/example.module.ts
47955
+ var ExampleModule = class {
47956
+ };
47957
+ __name(ExampleModule, "ExampleModule");
47958
+ ExampleModule = __decorateClass([
47959
+ (0, import_common14.Module)({
47960
+ controllers: [ExampleController],
47961
+ providers: [
47962
+ // Provide BackendExampleDomainService via factory using the singleton instance
47963
+ // This ensures the service is shared across the application
47964
+ {
47965
+ provide: BACKEND_EXAMPLE_DOMAIN_SERVICE,
47966
+ useFactory: /* @__PURE__ */ __name(() => backendExampleDomainService, "useFactory")
47967
+ }
47968
+ ],
47969
+ exports: [BACKEND_EXAMPLE_DOMAIN_SERVICE]
47970
+ })
47971
+ ], ExampleModule);
47972
+
47973
+ // src/backend/files/files.module.ts
47974
+ var import_common16 = __toESM(require_common(), 1);
47975
+
47976
+ // src/backend/files/files.controller.ts
47977
+ var import_common15 = __toESM(require_common(), 1);
47978
+ var BACKEND_FILES_DOMAIN_SERVICE = "BACKEND_FILES_DOMAIN_SERVICE";
47979
+ var FilesController = class {
47980
+ constructor(filesService) {
47981
+ this.filesService = filesService;
47982
+ }
47983
+ health() {
47984
+ return errors.SuccessResponseStandard("Files service is healthy", {
47985
+ service: this.filesService.isAvailable(),
47986
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
47987
+ });
47988
+ }
47989
+ async uploadFile(dto) {
47990
+ const result2 = await this.filesService.uploadFile(dto);
47991
+ return errors.SuccessResponseStandard("File uploaded successfully", result2);
47992
+ }
47993
+ async uploadFiles(dto) {
47994
+ const results = await this.filesService.uploadFiles(dto.files);
47995
+ return errors.SuccessResponseStandard("Files uploaded successfully", results);
47996
+ }
47997
+ async generateDocument(dto) {
47998
+ const buffer = await this.filesService.generateFile(dto);
47999
+ return errors.SuccessResponseStandard("Document generated successfully", {
48000
+ buffer: buffer.toString("base64"),
48001
+ size: buffer.length
48002
+ });
48003
+ }
48004
+ async getFile(id) {
48005
+ const entity = await this.filesService.getById(id);
48006
+ return errors.SuccessResponseStandard("File retrieved", entity);
48007
+ }
48008
+ async downloadFile(id) {
48009
+ const result2 = await this.filesService.downloadFile({ fileId: id });
48010
+ return errors.SuccessResponseStandard("File downloaded", result2);
48011
+ }
48012
+ async getSignedUrl(id) {
48013
+ const result2 = await this.filesService.getSignedUrl({ fileId: id });
48014
+ return errors.SuccessResponseStandard("Signed URL generated", result2);
48015
+ }
48016
+ async deleteFile(id) {
48017
+ await this.filesService.delete(id);
48018
+ return errors.SuccessResponseStandard("File deleted", null);
48019
+ }
48020
+ };
48021
+ __name(FilesController, "FilesController");
48022
+ __decorateClass([
48023
+ (0, import_common15.Get)("health")
48024
+ ], FilesController.prototype, "health", 1);
48025
+ __decorateClass([
48026
+ (0, import_common15.Post)("upload"),
48027
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48028
+ __decorateParam(0, (0, import_common15.Body)())
48029
+ ], FilesController.prototype, "uploadFile", 1);
48030
+ __decorateClass([
48031
+ (0, import_common15.Post)("upload/bulk"),
48032
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48033
+ __decorateParam(0, (0, import_common15.Body)())
48034
+ ], FilesController.prototype, "uploadFiles", 1);
48035
+ __decorateClass([
48036
+ (0, import_common15.Post)("generate-document"),
48037
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48038
+ __decorateParam(0, (0, import_common15.Body)())
48039
+ ], FilesController.prototype, "generateDocument", 1);
48040
+ __decorateClass([
48041
+ (0, import_common15.Get)(":id"),
48042
+ __decorateParam(0, (0, import_common15.Param)("id"))
48043
+ ], FilesController.prototype, "getFile", 1);
48044
+ __decorateClass([
48045
+ (0, import_common15.Get)(":id/download"),
48046
+ __decorateParam(0, (0, import_common15.Param)("id"))
48047
+ ], FilesController.prototype, "downloadFile", 1);
48048
+ __decorateClass([
48049
+ (0, import_common15.Get)(":id/signed-url"),
48050
+ __decorateParam(0, (0, import_common15.Param)("id"))
48051
+ ], FilesController.prototype, "getSignedUrl", 1);
48052
+ __decorateClass([
48053
+ (0, import_common15.Delete)(":id"),
48054
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48055
+ __decorateParam(0, (0, import_common15.Param)("id"))
48056
+ ], FilesController.prototype, "deleteFile", 1);
48057
+ FilesController = __decorateClass([
48058
+ (0, import_common15.Controller)("files"),
48059
+ __decorateParam(0, (0, import_common15.Inject)(BACKEND_FILES_DOMAIN_SERVICE))
48060
+ ], FilesController);
48061
+ var logger6 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendFilesDomainService" });
47900
48062
  var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseFrontendDomainService {
47901
48063
  constructor(config = {}, options) {
47902
- const apiBasePath = config.apiBasePath || "/api";
48064
+ const apiBasePath = config.apiBasePath || "";
47903
48065
  super({
47904
48066
  serviceName: "FrontendFilesDomainService",
47905
48067
  supportedRuntimes: ["frontend"],
@@ -47962,7 +48124,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
47962
48124
  // The ?? operator only falls through on null/undefined, not empty strings.
47963
48125
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
47964
48126
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
47965
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api"
48127
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
47966
48128
  };
47967
48129
  return new _FrontendFilesDomainService(mergedConfig, options);
47968
48130
  }
@@ -47999,7 +48161,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
47999
48161
  */
48000
48162
  static registerEventHandlers(verbose) {
48001
48163
  if (this._eventHandlersRegistered) {
48002
- logger4.debug("Files frontend event handlers already registered");
48164
+ logger6.debug("Files frontend event handlers already registered");
48003
48165
  return () => {
48004
48166
  };
48005
48167
  }
@@ -48061,7 +48223,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
48061
48223
  verbose
48062
48224
  });
48063
48225
  this._eventHandlersRegistered = true;
48064
- logger4.info("Files frontend event handlers registered");
48226
+ logger6.info("Files frontend event handlers registered");
48065
48227
  return () => {
48066
48228
  FrontendEventPersistenceHandler.unregisterAll();
48067
48229
  this._eventHandlersRegistered = false;
@@ -48139,7 +48301,7 @@ var FilesModule = class {
48139
48301
  };
48140
48302
  __name(FilesModule, "FilesModule");
48141
48303
  FilesModule = __decorateClass([
48142
- (0, import_common17.Module)({
48304
+ (0, import_common16.Module)({
48143
48305
  controllers: [FilesController],
48144
48306
  providers: [
48145
48307
  // Provide BackendFilesDomainService via factory using the singleton instance
@@ -48430,10 +48592,10 @@ var FeatureFlagDomainService = class extends BaseDomainService {
48430
48592
  };
48431
48593
 
48432
48594
  // src/backend/featureFlags/feature-flag.module.ts
48433
- var import_common19 = __toESM(require_common(), 1);
48595
+ var import_common18 = __toESM(require_common(), 1);
48434
48596
 
48435
48597
  // src/backend/featureFlags/feature-flag.controller.ts
48436
- var import_common18 = __toESM(require_common(), 1);
48598
+ var import_common17 = __toESM(require_common(), 1);
48437
48599
  var FeatureFlagController = class {
48438
48600
  constructor(featureFlagService) {
48439
48601
  this.featureFlagService = featureFlagService;
@@ -48562,54 +48724,54 @@ var FeatureFlagController = class {
48562
48724
  };
48563
48725
  __name(FeatureFlagController, "FeatureFlagController");
48564
48726
  __decorateClass([
48565
- (0, import_common18.Post)(":key/evaluate"),
48566
- __decorateParam(0, (0, import_common18.Param)("key")),
48567
- __decorateParam(1, (0, import_common18.Body)())
48727
+ (0, import_common17.Post)(":key/evaluate"),
48728
+ __decorateParam(0, (0, import_common17.Param)("key")),
48729
+ __decorateParam(1, (0, import_common17.Body)())
48568
48730
  ], FeatureFlagController.prototype, "evaluateFlag", 1);
48569
48731
  __decorateClass([
48570
- (0, import_common18.Post)(":key/enabled"),
48571
- __decorateParam(0, (0, import_common18.Param)("key")),
48572
- __decorateParam(1, (0, import_common18.Body)())
48732
+ (0, import_common17.Post)(":key/enabled"),
48733
+ __decorateParam(0, (0, import_common17.Param)("key")),
48734
+ __decorateParam(1, (0, import_common17.Body)())
48573
48735
  ], FeatureFlagController.prototype, "isEnabled", 1);
48574
48736
  __decorateClass([
48575
- (0, import_common18.Post)("evaluate-all"),
48576
- __decorateParam(0, (0, import_common18.Body)())
48737
+ (0, import_common17.Post)("evaluate-all"),
48738
+ __decorateParam(0, (0, import_common17.Body)())
48577
48739
  ], FeatureFlagController.prototype, "evaluateAllFlags", 1);
48578
48740
  __decorateClass([
48579
- (0, import_common18.Post)(),
48580
- __decorateParam(0, (0, import_common18.Body)())
48741
+ (0, import_common17.Post)(),
48742
+ __decorateParam(0, (0, import_common17.Body)())
48581
48743
  ], FeatureFlagController.prototype, "createFlag", 1);
48582
48744
  __decorateClass([
48583
- (0, import_common18.Put)(":key"),
48584
- __decorateParam(0, (0, import_common18.Param)("key")),
48585
- __decorateParam(1, (0, import_common18.Body)())
48745
+ (0, import_common17.Put)(":key"),
48746
+ __decorateParam(0, (0, import_common17.Param)("key")),
48747
+ __decorateParam(1, (0, import_common17.Body)())
48586
48748
  ], FeatureFlagController.prototype, "updateFlag", 1);
48587
48749
  __decorateClass([
48588
- (0, import_common18.Delete)(":key"),
48589
- __decorateParam(0, (0, import_common18.Param)("key"))
48750
+ (0, import_common17.Delete)(":key"),
48751
+ __decorateParam(0, (0, import_common17.Param)("key"))
48590
48752
  ], FeatureFlagController.prototype, "deleteFlag", 1);
48591
48753
  __decorateClass([
48592
- (0, import_common18.Post)(":key/override"),
48593
- __decorateParam(0, (0, import_common18.Param)("key")),
48594
- __decorateParam(1, (0, import_common18.Body)("value"))
48754
+ (0, import_common17.Post)(":key/override"),
48755
+ __decorateParam(0, (0, import_common17.Param)("key")),
48756
+ __decorateParam(1, (0, import_common17.Body)("value"))
48595
48757
  ], FeatureFlagController.prototype, "setOverride", 1);
48596
48758
  __decorateClass([
48597
- (0, import_common18.Delete)(":key/override"),
48598
- __decorateParam(0, (0, import_common18.Param)("key"))
48759
+ (0, import_common17.Delete)(":key/override"),
48760
+ __decorateParam(0, (0, import_common17.Param)("key"))
48599
48761
  ], FeatureFlagController.prototype, "removeOverride", 1);
48600
48762
  __decorateClass([
48601
- (0, import_common18.Get)(":key/rules"),
48602
- __decorateParam(0, (0, import_common18.Param)("key"))
48763
+ (0, import_common17.Get)(":key/rules"),
48764
+ __decorateParam(0, (0, import_common17.Param)("key"))
48603
48765
  ], FeatureFlagController.prototype, "getFlagRules", 1);
48604
48766
  __decorateClass([
48605
- (0, import_common18.Post)("refresh")
48767
+ (0, import_common17.Post)("refresh")
48606
48768
  ], FeatureFlagController.prototype, "refreshCache", 1);
48607
48769
  __decorateClass([
48608
- (0, import_common18.Get)("health")
48770
+ (0, import_common17.Get)("health")
48609
48771
  ], FeatureFlagController.prototype, "getHealth", 1);
48610
48772
  FeatureFlagController = __decorateClass([
48611
- (0, import_common18.Controller)("feature-flags"),
48612
- __decorateParam(0, (0, import_common18.Inject)(FEATURE_FLAG_SERVICE))
48773
+ (0, import_common17.Controller)("feature-flags"),
48774
+ __decorateParam(0, (0, import_common17.Inject)(FEATURE_FLAG_SERVICE))
48613
48775
  ], FeatureFlagController);
48614
48776
 
48615
48777
  // src/backend/featureFlags/feature-flag.module.ts
@@ -48701,16 +48863,16 @@ var FeatureFlagModule = class {
48701
48863
  __name(FeatureFlagModule, "FeatureFlagModule");
48702
48864
  FeatureFlagModule.serviceInstance = null;
48703
48865
  FeatureFlagModule = __decorateClass([
48704
- (0, import_common19.Global)(),
48705
- (0, import_common19.Module)({
48866
+ (0, import_common18.Global)(),
48867
+ (0, import_common18.Module)({
48706
48868
  controllers: [FeatureFlagController]
48707
48869
  })
48708
48870
  ], FeatureFlagModule);
48709
48871
 
48710
48872
  // src/backend/featureFlags/decorators/feature-flag.decorator.ts
48711
- var import_common20 = __toESM(require_common(), 1);
48873
+ var import_common19 = __toESM(require_common(), 1);
48712
48874
  function FeatureFlag(key, expected = true) {
48713
- return (0, import_common20.SetMetadata)(types.FEATURE_FLAG_METADATA.FLAG_CHECK, { key, expected });
48875
+ return (0, import_common19.SetMetadata)(types.FEATURE_FLAG_METADATA.FLAG_CHECK, { key, expected });
48714
48876
  }
48715
48877
  __name(FeatureFlag, "FeatureFlag");
48716
48878
 
@@ -48727,7 +48889,7 @@ function FeatureEnabled(key) {
48727
48889
  __name(FeatureEnabled, "FeatureEnabled");
48728
48890
 
48729
48891
  // src/backend/featureFlags/guards/feature-flag.guard.ts
48730
- var import_common21 = __toESM(require_common(), 1);
48892
+ var import_common20 = __toESM(require_common(), 1);
48731
48893
  var FeatureFlagGuard = class {
48732
48894
  constructor(reflector, featureFlagService) {
48733
48895
  this.reflector = reflector;
@@ -48771,12 +48933,12 @@ var FeatureFlagGuard = class {
48771
48933
  };
48772
48934
  __name(FeatureFlagGuard, "FeatureFlagGuard");
48773
48935
  FeatureFlagGuard = __decorateClass([
48774
- (0, import_common21.Injectable)(),
48775
- __decorateParam(1, (0, import_common21.Inject)(FEATURE_FLAG_SERVICE))
48936
+ (0, import_common20.Injectable)(),
48937
+ __decorateParam(1, (0, import_common20.Inject)(FEATURE_FLAG_SERVICE))
48776
48938
  ], FeatureFlagGuard);
48777
48939
 
48778
48940
  // src/backend/featureFlags/middleware/feature-flag-middleware.ts
48779
- var import_common22 = __toESM(require_common(), 1);
48941
+ var import_common21 = __toESM(require_common(), 1);
48780
48942
  function isFeatureFlagKey(value) {
48781
48943
  return Object.keys(config.FEATURES).includes(value);
48782
48944
  }
@@ -48854,12 +49016,12 @@ var FeatureFlagMiddleware = class {
48854
49016
  };
48855
49017
  __name(FeatureFlagMiddleware, "FeatureFlagMiddleware");
48856
49018
  FeatureFlagMiddleware = __decorateClass([
48857
- (0, import_common22.Injectable)(),
48858
- __decorateParam(0, (0, import_common22.Inject)(FEATURE_FLAG_SERVICE))
49019
+ (0, import_common21.Injectable)(),
49020
+ __decorateParam(0, (0, import_common21.Inject)(FEATURE_FLAG_SERVICE))
48859
49021
  ], FeatureFlagMiddleware);
48860
49022
 
48861
49023
  // src/backend/featureFlags/interceptors/feature-flag-logging-interceptor.ts
48862
- var import_common23 = __toESM(require_common(), 1);
49024
+ var import_common22 = __toESM(require_common(), 1);
48863
49025
  var import_rxjs = __toESM(require_cjs(), 1);
48864
49026
  var FeatureFlagLoggingInterceptor = class {
48865
49027
  constructor() {
@@ -48892,11 +49054,11 @@ var FeatureFlagLoggingInterceptor = class {
48892
49054
  };
48893
49055
  __name(FeatureFlagLoggingInterceptor, "FeatureFlagLoggingInterceptor");
48894
49056
  FeatureFlagLoggingInterceptor = __decorateClass([
48895
- (0, import_common23.Injectable)()
49057
+ (0, import_common22.Injectable)()
48896
49058
  ], FeatureFlagLoggingInterceptor);
48897
49059
 
48898
49060
  // src/backend/featureFlags/interceptors/error-handling-interceptor.ts
48899
- var import_common24 = __toESM(require_common(), 1);
49061
+ var import_common23 = __toESM(require_common(), 1);
48900
49062
  var import_operators = __toESM(require_operators(), 1);
48901
49063
  var ErrorHandlingInterceptor = class {
48902
49064
  constructor() {
@@ -48925,9 +49087,9 @@ var ErrorHandlingInterceptor = class {
48925
49087
  };
48926
49088
  __name(ErrorHandlingInterceptor, "ErrorHandlingInterceptor");
48927
49089
  ErrorHandlingInterceptor = __decorateClass([
48928
- (0, import_common24.Injectable)()
49090
+ (0, import_common23.Injectable)()
48929
49091
  ], ErrorHandlingInterceptor);
48930
- var logger5 = new logger$1.PackageLogger({
49092
+ var logger7 = new logger$1.PackageLogger({
48931
49093
  packageName: "core",
48932
49094
  service: "FeatureFlagConfigValidator"
48933
49095
  });
@@ -49113,7 +49275,7 @@ var FeatureFlagConfigValidator = class {
49113
49275
  static getWarnings(config$1, environment) {
49114
49276
  const env = environment ?? (config$1.isLoggingEnabled ? types.NODE_ENVIRONMENTS.DEVELOPMENT : types.NODE_ENVIRONMENTS.PRODUCTION);
49115
49277
  if (config$1.cacheTtl > config.NUMERIC_CONSTANTS.ONE_HOUR_SECONDS) {
49116
- logger5.warn(
49278
+ logger7.warn(
49117
49279
  "Cache TTL is very high (>1 hour). Consider reducing for better responsiveness.",
49118
49280
  {
49119
49281
  field: "cacheTtl",
@@ -49123,13 +49285,13 @@ var FeatureFlagConfigValidator = class {
49123
49285
  );
49124
49286
  }
49125
49287
  if (env === types.NODE_ENVIRONMENTS.PRODUCTION && config$1.isLoggingEnabled) {
49126
- logger5.warn("Logging is enabled in production. Consider disabling for performance.", {
49288
+ logger7.warn("Logging is enabled in production. Consider disabling for performance.", {
49127
49289
  field: "isLoggingEnabled",
49128
49290
  code: "PRODUCTION_LOGGING_ENABLED"
49129
49291
  });
49130
49292
  }
49131
49293
  if (env === types.NODE_ENVIRONMENTS.DEVELOPMENT && !config$1.isCacheEnabled) {
49132
- logger5.warn("Cache is disabled in development. This may impact performance testing.", {
49294
+ logger7.warn("Cache is disabled in development. This may impact performance testing.", {
49133
49295
  field: "isCacheEnabled",
49134
49296
  code: "DEVELOPMENT_CACHE_DISABLED"
49135
49297
  });
@@ -49272,7 +49434,7 @@ var FeatureFlagConfigFactory = class {
49272
49434
  };
49273
49435
 
49274
49436
  // src/base/cache/feature/caching.ts
49275
- var import_common25 = __toESM(require_common(), 1);
49437
+ var import_common24 = __toESM(require_common(), 1);
49276
49438
  var import_rxjs2 = __toESM(require_cjs(), 1);
49277
49439
  var Caching = class {
49278
49440
  constructor() {
@@ -49295,7 +49457,7 @@ var Caching = class {
49295
49457
  };
49296
49458
  __name(Caching, "Caching");
49297
49459
  Caching = __decorateClass([
49298
- (0, import_common25.Injectable)()
49460
+ (0, import_common24.Injectable)()
49299
49461
  ], Caching);
49300
49462
 
49301
49463
  // src/frontend/index.ts
@@ -49422,7 +49584,7 @@ function ApiProvider({
49422
49584
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
49423
49585
  }
49424
49586
  __name(ApiProvider, "ApiProvider");
49425
- var logger6 = new logger$1.PackageLogger({ packageName: "core", service: "PlyazProvider" });
49587
+ var logger8 = new logger$1.PackageLogger({ packageName: "core", service: "PlyazProvider" });
49426
49588
  var PlyazContext = react.createContext(null);
49427
49589
  var FeatureFlagStore = class {
49428
49590
  constructor(config = {}) {
@@ -49517,7 +49679,7 @@ function createStoreRegistry() {
49517
49679
  getStore(key) {
49518
49680
  const state = store.useRootStore.getState();
49519
49681
  if (!state) {
49520
- logger6.warn(
49682
+ logger8.warn(
49521
49683
  "Store state is undefined - store may not be hydrated yet. This can cause side effects if called during SSR or before initialization."
49522
49684
  );
49523
49685
  return void 0;