@plyaz/core 1.10.0 → 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,
@@ -35703,6 +35703,11 @@ var Core = class _Core {
35703
35703
  environment: globalEnvironment,
35704
35704
  runtime,
35705
35705
  apiClient: apiConfig ? { baseURL: apiConfig.baseURL, ...apiConfig } : void 0,
35706
+ db: options.db,
35707
+ cache: options.cache,
35708
+ storage: options.storage,
35709
+ notifications: options.notifications,
35710
+ observability: options.observability,
35706
35711
  services: mergedServices,
35707
35712
  stores: {
35708
35713
  // Returns specific slice from namespaced root store (type-safe)
@@ -36512,26 +36517,76 @@ var Core = class _Core {
36512
36517
  errorStore,
36513
36518
  _Core.buildErrorHandlerConfig(_Core._errorConfig)
36514
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);
36515
36552
  const errorEventCleanup = CoreEventManager.on(
36516
36553
  core.CORE_EVENTS.SYSTEM.ERROR,
36517
36554
  (event) => {
36518
36555
  if (!_Core._rootStore) return;
36519
36556
  try {
36520
36557
  const { errors } = event.data;
36521
- if (errors && errors.length > 0) {
36522
- _Core._rootStore.getState().errors.addErrors(errors);
36523
- _Core.log(`Added ${errors.length} error(s) to store`, verbose);
36524
- }
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);
36525
36562
  } catch (e) {
36526
36563
  _Core.logger.error("Failed to handle error event", { error: e });
36527
36564
  }
36528
36565
  }
36529
36566
  );
36530
36567
  _Core._eventCleanupFns.push(errorEventCleanup);
36531
- _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
36532
- if (_Core._errorConfig.httpHandler !== false) {
36533
- await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
36534
- }
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);
36535
36590
  }
36536
36591
  /**
36537
36592
  * Create HTTP error handler based on detected runtime.
@@ -38485,7 +38540,7 @@ var BaseFrontendDomainService = class _BaseFrontendDomainService extends BaseDom
38485
38540
  }
38486
38541
  };
38487
38542
  var logger2 = new logger$1.PackageLogger({ packageName: "core", service: "EventPersistence" });
38488
- var BackendEventPersistenceHandler = class {
38543
+ (class {
38489
38544
  static {
38490
38545
  __name(this, "BackendEventPersistenceHandler");
38491
38546
  }
@@ -38659,7 +38714,7 @@ var BackendEventPersistenceHandler = class {
38659
38714
  static getRegisteredDomains() {
38660
38715
  return Array.from(this.registeredDomains.keys());
38661
38716
  }
38662
- };
38717
+ });
38663
38718
  var logger3 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendEventPersistence" });
38664
38719
  var FrontendEventPersistenceHandler = class {
38665
38720
  static {
@@ -43336,6 +43391,7 @@ var ApiFeatureFlagProvider = class extends FeatureFlagProvider {
43336
43391
  return this.engine.getRules();
43337
43392
  }
43338
43393
  };
43394
+ var logger4 = new logger$1.PackageLogger({ packageName: "core", service: "DbService" });
43339
43395
  var DEFAULT_ENCRYPTION_FIELDS = {
43340
43396
  // User PII
43341
43397
  users: ["password_hash", "phone_number", "date_of_birth"],
@@ -43429,6 +43485,41 @@ var DbService = class _DbService {
43429
43485
  };
43430
43486
  CoreEventManager.emit(core.CORE_EVENTS.DATABASE.ERROR, payload);
43431
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
+ }
43432
43523
  /**
43433
43524
  * Gets the singleton instance of DbService
43434
43525
  *
@@ -43633,6 +43724,8 @@ var DbService = class _DbService {
43633
43724
  if (cache) dbConfig.cache = cache;
43634
43725
  if (audit) dbConfig.audit = audit;
43635
43726
  if (encryption) dbConfig.encryption = encryption;
43727
+ const events = _DbService.createMergedEventHandlers(config.events);
43728
+ if (events) dbConfig.events = events;
43636
43729
  return dbConfig;
43637
43730
  }
43638
43731
  /**
@@ -46198,7 +46291,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46198
46291
  // Constructor
46199
46292
  // ─────────────────────────────────────────────────────────────────────────
46200
46293
  constructor(config = {}, options) {
46201
- const apiBasePath = config.apiBasePath || "/api/examples";
46294
+ const apiBasePath = config.apiBasePath || "";
46202
46295
  super({
46203
46296
  serviceName: "ExampleFrontendService",
46204
46297
  supportedRuntimes: ["frontend"],
@@ -46223,20 +46316,20 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46223
46316
  // Note: Use relative paths since apiClient.baseURL is already set to apiBasePath
46224
46317
  fetchers: {
46225
46318
  fetchAll: /* @__PURE__ */ __name(async (query) => {
46226
- return this.apiClient.get("", { params: query });
46319
+ return this.apiClient.get("/examples", { params: query });
46227
46320
  }, "fetchAll"),
46228
46321
  fetchById: /* @__PURE__ */ __name(async (id) => {
46229
- return this.apiClient.get(`/${id}`);
46322
+ return this.apiClient.get(`/examples/${id}`);
46230
46323
  }, "fetchById"),
46231
46324
  create: /* @__PURE__ */ __name(async (data) => {
46232
- return this.apiClient.post("", data);
46325
+ return this.apiClient.post("/examples", data);
46233
46326
  }, "create"),
46234
46327
  update: /* @__PURE__ */ __name(async (payload) => {
46235
46328
  const { id, data } = payload;
46236
- return this.apiClient.patch(`/${id}`, data);
46329
+ return this.apiClient.patch(`/examples/${id}`, data);
46237
46330
  }, "update"),
46238
46331
  delete: /* @__PURE__ */ __name(async (id) => {
46239
- return this.apiClient.delete(`/${id}`);
46332
+ return this.apiClient.delete(`/examples/${id}`);
46240
46333
  }, "delete")
46241
46334
  }
46242
46335
  // Store handlers - customize how data syncs to store
@@ -46304,7 +46397,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46304
46397
  // The ?? operator only falls through on null/undefined, not empty strings.
46305
46398
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
46306
46399
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
46307
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api/examples"
46400
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
46308
46401
  };
46309
46402
  const service = new _FrontendExampleDomainService(mergedConfig, options);
46310
46403
  if (mergedConfig.autoFetch) {
@@ -46685,337 +46778,710 @@ var CacheService = class _CacheService {
46685
46778
  }
46686
46779
  };
46687
46780
  var getCacheService = /* @__PURE__ */ __name(() => CacheService.getInstance(), "getCacheService");
46688
- var StorageService = class _StorageService {
46689
- constructor() {
46690
- this.storageService = null;
46691
- this.config = null;
46692
- this.initialized = false;
46693
- }
46781
+ var TABLE_NAME = "media";
46782
+ var DEFAULT_LIMIT2 = 100;
46783
+ var FilesRepository = class _FilesRepository extends db.BaseRepository {
46694
46784
  static {
46695
- __name(this, "StorageService");
46785
+ __name(this, "FilesRepository");
46696
46786
  }
46697
- static {
46698
- this.instance = null;
46787
+ constructor(db) {
46788
+ super(db, TABLE_NAME);
46699
46789
  }
46700
- // ─────────────────────────────────────────────────────────────────
46701
- // Error Handling
46702
- // ─────────────────────────────────────────────────────────────────
46790
+ // ─────────────────────────────────────────────────────────────────────────
46791
+ // Static Factory
46792
+ // ─────────────────────────────────────────────────────────────────────────
46703
46793
  /**
46704
- * Emits a storage error event via CoreEventManager.
46705
- * Called when storage operations fail to integrate with global error handling.
46794
+ * Create repository instance.
46795
+ * Uses DbService if initialized.
46706
46796
  */
46707
- emitStorageError(error, operation, options) {
46708
- const payload = {
46709
- error,
46710
- operation,
46711
- fileId: options?.fileId,
46712
- filename: options?.filename,
46713
- 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
46714
46822
  };
46715
- CoreEventManager.emit(core.CORE_EVENTS.STORAGE.ERROR, payload);
46823
+ return super.findMany(mergedOptions, config);
46716
46824
  }
46717
- // ─────────────────────────────────────────────────────────────────
46718
- // Singleton Management
46719
- // ─────────────────────────────────────────────────────────────────
46720
46825
  /**
46721
- * Gets the singleton instance of StorageService
46826
+ * Create new file record with auto-generated ID and timestamps
46722
46827
  */
46723
- static getInstance() {
46724
- _StorageService.instance ??= new _StorageService();
46725
- 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);
46726
46857
  }
46727
46858
  /**
46728
- * Checks if the storage service has been initialized
46859
+ * Get the table name for this repository
46729
46860
  */
46730
- static isInitialized() {
46731
- return _StorageService.instance?.initialized ?? false;
46861
+ getTableName() {
46862
+ return TABLE_NAME;
46732
46863
  }
46864
+ // ─────────────────────────────────────────────────────────────────────────
46865
+ // Domain-Specific Methods
46866
+ // ─────────────────────────────────────────────────────────────────────────
46733
46867
  /**
46734
- * Resets the storage service by clearing the singleton instance
46868
+ * Find all files for a user
46735
46869
  */
46736
- static async reset() {
46737
- if (_StorageService.instance) {
46738
- _StorageService.instance.storageService = null;
46739
- _StorageService.instance.config = null;
46740
- _StorageService.instance.initialized = false;
46741
- _StorageService.instance = null;
46742
- }
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
+ });
46743
46878
  }
46744
46879
  /**
46745
- * Initializes the storage service
46746
- *
46747
- * @param config - Storage service configuration
46748
- * @returns The initialized StorageService instance
46880
+ * Find files for a specific entity (polymorphic association)
46749
46881
  */
46750
- static async initialize(config) {
46751
- const instance = _StorageService.getInstance();
46752
- if (instance.initialized) {
46753
- return instance;
46754
- }
46755
- instance.config = config;
46756
- instance.storageService = new storage$1.StorageService(config);
46757
- instance.initialized = true;
46758
- 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();
46759
46887
  }
46760
46888
  /**
46761
- * Gets the raw underlying storage service instance without error handling wrapper.
46762
- * Use this only if you need direct access to the underlying service.
46763
- *
46764
- * @returns The raw StorageService instance from @plyaz/storage
46765
- * @throws {StoragePackageError} When storage is not initialized
46889
+ * Find file by storage path (for deduplication checks)
46766
46890
  */
46767
- getRawStorage() {
46768
- if (!this.storageService) {
46769
- throw new errors.StoragePackageError(
46770
- "Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
46771
- errors$1.STORAGE_ERROR_CODES.INITIALIZATION_FAILED
46772
- );
46773
- }
46774
- return this.storageService;
46891
+ async findByStoragePath(storagePath) {
46892
+ return this.findOne({ field: "storage_path", operator: "eq", value: storagePath });
46775
46893
  }
46776
- // ─────────────────────────────────────────────────────────────────
46777
- // Service Access
46778
- // ─────────────────────────────────────────────────────────────────
46779
46894
  /**
46780
- * Gets the storage service with automatic error handling.
46781
- * All method calls are wrapped with try/catch and emit error events on failure.
46782
- * Any method added to @plyaz/storage will be automatically available.
46783
- *
46784
- * @example
46785
- * ```typescript
46786
- * const storage = StorageService.getInstance().getStorage();
46787
- * await storage.uploadFile({ file, filename: 'doc.pdf' });
46788
- * await storage.deleteFile({ fileId: '123' });
46789
- * ```
46790
- *
46791
- * @returns StorageServiceImpl with automatic error handling
46895
+ * Count files for a user
46792
46896
  */
46793
- getStorage() {
46794
- const self2 = this;
46795
- return new Proxy({}, {
46796
- get(_, prop) {
46797
- if (typeof prop === "symbol") {
46798
- return void 0;
46799
- }
46800
- const storage = self2.getRawStorage();
46801
- const value = storage[prop];
46802
- if (typeof value !== "function") {
46803
- return value;
46804
- }
46805
- return (...args) => {
46806
- try {
46807
- const result2 = value.apply(storage, args);
46808
- if (result2 instanceof Promise) {
46809
- return result2.catch((error) => {
46810
- self2.emitStorageError(error, prop, { recoverable: true });
46811
- throw error;
46812
- });
46813
- }
46814
- return result2;
46815
- } catch (error) {
46816
- self2.emitStorageError(error, prop, { recoverable: true });
46817
- throw error;
46818
- }
46819
- };
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
46820
46909
  }
46821
46910
  });
46822
46911
  }
46823
- // ─────────────────────────────────────────────────────────────────
46824
- // Health Check (special handling for response transformation)
46825
- // ─────────────────────────────────────────────────────────────────
46826
46912
  /**
46827
- * Performs a health check on the storage service by checking all adapter health.
46828
- * This method has special handling to transform the response format.
46913
+ * Find files by type (IMAGE, VIDEO, DOCUMENT, AUDIO)
46829
46914
  */
46830
- async healthCheck() {
46831
- const startTime = Date.now();
46832
- try {
46833
- const storage = this.getRawStorage();
46834
- if (typeof storage.checkAllAdaptersHealth === "function") {
46835
- 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
46836
46921
  }
46837
- const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
46838
- const responseTime = Date.now() - startTime;
46839
- return {
46840
- // Check if all adapters are healthy (healthy count equals total count)
46841
- isHealthy: summary ? summary.healthy === summary.total : true,
46842
- responseTime,
46843
- error: void 0
46844
- };
46845
- } catch (error) {
46846
- this.emitStorageError(error, "healthCheck", { recoverable: true });
46847
- return {
46848
- isHealthy: false,
46849
- responseTime: Date.now() - startTime,
46850
- error: error instanceof Error ? error.message : "Unknown error"
46851
- };
46852
- }
46922
+ });
46923
+ }
46924
+ };
46925
+
46926
+ // src/domain/files/mappers/FilesMapper.ts
46927
+ var FilesMapperClass = class _FilesMapperClass extends BaseMapper {
46928
+ static {
46929
+ __name(this, "FilesMapperClass");
46853
46930
  }
46854
- // ─────────────────────────────────────────────────────────────────
46855
- // Lifecycle
46856
- // ─────────────────────────────────────────────────────────────────
46857
46931
  /**
46858
- * Gets the current configuration
46932
+ * Type guard for UploadResult from event payloads
46933
+ * Validates that the value has the required structure for DB mapping
46859
46934
  */
46860
- getConfig() {
46861
- 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";
46862
46941
  }
46863
46942
  /**
46864
- * Closes the storage service and cleans up resources
46943
+ * Infer file type from MIME type
46865
46944
  */
46866
- async close() {
46867
- if (this.storageService) {
46868
- 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";
46869
46951
  }
46870
- this.storageService = null;
46871
- this.initialized = false;
46872
- this.config = null;
46952
+ return "OTHER";
46873
46953
  }
46874
46954
  /**
46875
- * Creates a dedicated storage service instance (NOT the singleton)
46876
- *
46877
- * Use this when you need an isolated storage connection with its own configuration.
46878
- *
46879
- * @param config - Storage service configuration
46880
- * @returns Promise that resolves to a new dedicated StorageService instance
46955
+ * API Response Domain Entity
46881
46956
  */
46882
- static async createInstance(config) {
46883
- const dedicatedInstance = new _StorageService();
46884
- dedicatedInstance.config = config;
46885
- dedicatedInstance.storageService = new storage$1.StorageService(config);
46886
- dedicatedInstance.initialized = true;
46887
- return dedicatedInstance;
46888
- }
46889
- };
46890
- var NotificationService = class _NotificationService {
46891
- constructor() {
46892
- this.notificationService = null;
46893
- this.config = null;
46894
- this.initialized = false;
46895
- }
46896
- static {
46897
- __name(this, "NotificationService");
46898
- }
46899
- static {
46900
- 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
+ };
46901
46982
  }
46902
- // ─────────────────────────────────────────────────────────────────
46903
- // Error Handling
46904
- // ─────────────────────────────────────────────────────────────────
46905
46983
  /**
46906
- * Emits a notification error event via CoreEventManager.
46907
- * Called when notification operations fail to integrate with global error handling.
46984
+ * Domain Entity Store State (serializable)
46908
46985
  */
46909
- emitNotificationError(error, operation, options) {
46910
- const payload = {
46911
- error,
46912
- operation,
46913
- recipientId: options?.recipientId,
46914
- channel: options?.channel,
46915
- 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
46916
47005
  };
46917
- CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.ERROR, payload);
46918
47006
  }
46919
- // ─────────────────────────────────────────────────────────────────
46920
- // Singleton Management
46921
- // ─────────────────────────────────────────────────────────────────
46922
47007
  /**
46923
- * Gets the singleton instance of NotificationService
47008
+ * Store State Domain Entity
46924
47009
  */
46925
- static getInstance() {
46926
- _NotificationService.instance ??= new _NotificationService();
46927
- 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
+ };
46928
47030
  }
46929
47031
  /**
46930
- * Checks if the notification service has been initialized
47032
+ * Database Row API Response (for backend)
46931
47033
  */
46932
- static isInitialized() {
46933
- 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
+ };
46934
47052
  }
46935
47053
  /**
46936
- * Resets the notification service by clearing the singleton instance
47054
+ * Create GeneratedFileItem from generation result
46937
47055
  */
46938
- static async reset() {
46939
- if (_NotificationService.instance) {
46940
- _NotificationService.instance.notificationService = null;
46941
- _NotificationService.instance.config = null;
46942
- _NotificationService.instance.initialized = false;
46943
- _NotificationService.instance = null;
46944
- }
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
+ };
46945
47066
  }
46946
47067
  /**
46947
- * Initializes the notification service
47068
+ * Convert upload result to database row format
47069
+ * Used by event handlers to persist upload results to DB
46948
47070
  *
46949
- * @param config - Notification service configuration
46950
- * @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()
46951
47074
  */
46952
- static async initialize(config) {
46953
- const instance = _NotificationService.getInstance();
46954
- if (instance.initialized) {
46955
- return instance;
46956
- }
46957
- instance.config = config;
46958
- instance.notificationService = new notifications.NotificationService(config);
46959
- instance.initialized = true;
46960
- 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
+ };
46961
47099
  }
46962
- /**
46963
- * 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.
46964
47430
  * Use this only if you need direct access to the underlying service.
46965
47431
  *
46966
- * @returns The raw NotificationService instance from @plyaz/notifications
46967
- * @throws {NotificationsPackageError} When notifications is not initialized
47432
+ * @returns The raw StorageService instance from @plyaz/storage
47433
+ * @throws {StoragePackageError} When storage is not initialized
46968
47434
  */
46969
- getRawNotifications() {
46970
- if (!this.notificationService) {
46971
- throw new errors.NotificationPackageError(
46972
- "Notifications not initialized. Call NotificationService.initialize() first or use Core.initialize() with notifications config.",
46973
- 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
46974
47440
  );
46975
47441
  }
46976
- return this.notificationService;
47442
+ return this.storageService;
46977
47443
  }
46978
47444
  // ─────────────────────────────────────────────────────────────────
46979
47445
  // Service Access
46980
47446
  // ─────────────────────────────────────────────────────────────────
46981
47447
  /**
46982
- * Gets the notification service with automatic error handling.
47448
+ * Gets the storage service with automatic error handling.
46983
47449
  * All method calls are wrapped with try/catch and emit error events on failure.
46984
- * Any method added to @plyaz/notifications will be automatically available.
47450
+ * Any method added to @plyaz/storage will be automatically available.
46985
47451
  *
46986
47452
  * @example
46987
47453
  * ```typescript
46988
- * const notifications = NotificationService.getInstance().getNotifications();
46989
- * await notifications.sendEmail({ to: 'user@example.com', templateId: 'welcome' });
46990
- * 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' });
46991
47457
  * ```
46992
47458
  *
46993
- * @returns NotificationServiceImpl with automatic error handling
47459
+ * @returns StorageServiceImpl with automatic error handling
46994
47460
  */
46995
- getNotifications() {
47461
+ getStorage() {
46996
47462
  const self2 = this;
46997
47463
  return new Proxy({}, {
46998
47464
  get(_, prop) {
46999
47465
  if (typeof prop === "symbol") {
47000
47466
  return void 0;
47001
47467
  }
47002
- const notifications = self2.getRawNotifications();
47003
- const value = notifications[prop];
47468
+ const storage = self2.getRawStorage();
47469
+ const value = storage[prop];
47004
47470
  if (typeof value !== "function") {
47005
47471
  return value;
47006
47472
  }
47007
47473
  return (...args) => {
47008
47474
  try {
47009
- const result2 = value.apply(notifications, args);
47475
+ const result2 = value.apply(storage, args);
47010
47476
  if (result2 instanceof Promise) {
47011
47477
  return result2.catch((error) => {
47012
- self2.emitNotificationError(error, prop, { recoverable: true });
47478
+ self2.emitStorageError(error, prop, { recoverable: true });
47013
47479
  throw error;
47014
47480
  });
47015
47481
  }
47016
47482
  return result2;
47017
47483
  } catch (error) {
47018
- self2.emitNotificationError(error, prop, { recoverable: true });
47484
+ self2.emitStorageError(error, prop, { recoverable: true });
47019
47485
  throw error;
47020
47486
  }
47021
47487
  };
@@ -47026,23 +47492,29 @@ var NotificationService = class _NotificationService {
47026
47492
  // Health Check (special handling for response transformation)
47027
47493
  // ─────────────────────────────────────────────────────────────────
47028
47494
  /**
47029
- * Performs a health check on the notification service.
47495
+ * Performs a health check on the storage service by checking all adapter health.
47030
47496
  * This method has special handling to transform the response format.
47031
47497
  */
47032
47498
  async healthCheck() {
47499
+ const startTime = Date.now();
47033
47500
  try {
47034
- const notifications = this.getRawNotifications();
47035
- 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;
47036
47507
  return {
47037
- isHealthy: result2.healthy,
47038
- providers: result2.providers,
47039
- // Use optional chaining since error may not exist on all health check results
47040
- 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
47041
47512
  };
47042
47513
  } catch (error) {
47043
- this.emitNotificationError(error, "healthCheck", { recoverable: true });
47514
+ this.emitStorageError(error, "healthCheck", { recoverable: true });
47044
47515
  return {
47045
47516
  isHealthy: false,
47517
+ responseTime: Date.now() - startTime,
47046
47518
  error: error instanceof Error ? error.message : "Unknown error"
47047
47519
  };
47048
47520
  }
@@ -47057,844 +47529,539 @@ var NotificationService = class _NotificationService {
47057
47529
  return this.config;
47058
47530
  }
47059
47531
  /**
47060
- * Closes the notification service and cleans up resources
47532
+ * Closes the storage service and cleans up resources
47061
47533
  */
47062
47534
  async close() {
47063
- this.notificationService = null;
47535
+ if (this.storageService) {
47536
+ await this.storageService.destroy();
47537
+ }
47538
+ this.storageService = null;
47064
47539
  this.initialized = false;
47065
47540
  this.config = null;
47066
47541
  }
47067
47542
  /**
47068
- * Creates a dedicated notification service instance (NOT the singleton)
47543
+ * Creates a dedicated storage service instance (NOT the singleton)
47069
47544
  *
47070
- * 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.
47071
47546
  *
47072
- * @param config - Notification service configuration
47073
- * @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
47074
47549
  */
47075
47550
  static async createInstance(config) {
47076
- const dedicatedInstance = new _NotificationService();
47551
+ const dedicatedInstance = new _StorageService();
47077
47552
  dedicatedInstance.config = config;
47078
- dedicatedInstance.notificationService = new notifications.NotificationService(config);
47553
+ dedicatedInstance.storageService = new storage$1.StorageService(config);
47079
47554
  dedicatedInstance.initialized = true;
47080
47555
  return dedicatedInstance;
47081
47556
  }
47082
47557
  };
47083
- var cachedVersion = "";
47084
- function getPackageVersion() {
47085
- if (cachedVersion) return cachedVersion;
47086
- try {
47087
- 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)));
47088
- const pkg = require2("../package.json");
47089
- cachedVersion = pkg.version ?? "0.0.0";
47090
- } catch {
47091
- try {
47092
- 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)));
47093
- const currentDir = path.dirname(currentFile);
47094
- const pkgPath = path.join(currentDir, "..", "package.json");
47095
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
47096
- cachedVersion = pkg.version ?? "0.0.0";
47097
- } catch {
47098
- cachedVersion = "0.0.0";
47099
- }
47100
- }
47101
- return cachedVersion;
47102
- }
47103
- __name(getPackageVersion, "getPackageVersion");
47104
- var VERSION = getPackageVersion();
47105
- var PACKAGE_NAME = "@plyaz/core";
47106
-
47107
- // src/backend/index.ts
47108
- var backend_exports = {};
47109
- __export(backend_exports, {
47110
- BACKEND_EXAMPLE_DOMAIN_SERVICE: () => BACKEND_EXAMPLE_DOMAIN_SERVICE,
47111
- BACKEND_FILES_DOMAIN_SERVICE: () => BACKEND_FILES_DOMAIN_SERVICE,
47112
- BackendExampleDomainService: () => BackendExampleDomainService,
47113
- Caching: () => Caching,
47114
- ErrorHandlingInterceptor: () => ErrorHandlingInterceptor,
47115
- ExampleController: () => ExampleController,
47116
- ExampleModule: () => ExampleModule,
47117
- FeatureDisabled: () => FeatureDisabled,
47118
- FeatureEnabled: () => FeatureEnabled,
47119
- FeatureFlagConfigFactory: () => FeatureFlagConfigFactory,
47120
- FeatureFlagConfigValidator: () => FeatureFlagConfigValidator,
47121
- FeatureFlagController: () => FeatureFlagController,
47122
- FeatureFlagDatabaseRepository: () => FeatureFlagDatabaseRepository,
47123
- FeatureFlagDomainService: () => FeatureFlagDomainService,
47124
- FeatureFlagGuard: () => FeatureFlagGuard,
47125
- FeatureFlagLoggingInterceptor: () => FeatureFlagLoggingInterceptor,
47126
- FeatureFlagMiddleware: () => FeatureFlagMiddleware,
47127
- FeatureFlagModule: () => FeatureFlagModule,
47128
- FeatureFlagService: () => FeatureFlagService,
47129
- FeatureFlagServiceFactory: () => FeatureFlagServiceFactory,
47130
- FilesController: () => FilesController,
47131
- FilesModule: () => FilesModule
47132
- });
47133
-
47134
- // src/backend/example/example.module.ts
47135
- var import_common11 = __toESM(require_common(), 1);
47136
-
47137
- // src/backend/example/example.controller.ts
47138
- var import_common10 = __toESM(require_common(), 1);
47139
- var BACKEND_EXAMPLE_DOMAIN_SERVICE = "BACKEND_EXAMPLE_DOMAIN_SERVICE";
47140
- var ExampleController = class {
47141
- constructor(exampleService) {
47142
- this.exampleService = exampleService;
47143
- }
47144
- health() {
47145
- return errors.SuccessResponseStandard("Service is healthy", {
47146
- service: this.exampleService.isAvailable(),
47147
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
47148
- });
47149
- }
47150
- async getEntity(id) {
47151
- const entity = await this.exampleService.getById(id);
47152
- return errors.SuccessResponseStandard("Entity retrieved successfully", entity);
47153
- }
47154
- async createEntity(dto) {
47155
- const entity = await this.exampleService.create(dto);
47156
- return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47157
- }
47158
- async updateEntity(id, dto) {
47159
- const entity = await this.exampleService.patch(id, dto);
47160
- return errors.SuccessResponseStandard("Entity updated successfully", entity);
47161
- }
47162
- async deleteEntity(id) {
47163
- await this.exampleService.delete(id);
47164
- return errors.SuccessResponseStandard("Entity deleted successfully", null);
47165
- }
47166
- async createEntityWithValidation(dto) {
47167
- const entity = await this.exampleService.create(dto);
47168
- return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47169
- }
47170
- async demoSingleError() {
47171
- return await this.exampleService.demoSingleValidationError();
47172
- }
47173
- async demoArrayErrors() {
47174
- return await this.exampleService.demoMultipleValidationErrors();
47175
- }
47176
- async sendEmail(dto) {
47177
- const result2 = await this.exampleService.sendEmail(dto);
47178
- return errors.SuccessResponseStandard("Email sent successfully", result2);
47179
- }
47180
- };
47181
- __name(ExampleController, "ExampleController");
47182
- __decorateClass([
47183
- (0, import_common10.Get)("health")
47184
- ], ExampleController.prototype, "health", 1);
47185
- __decorateClass([
47186
- (0, import_common10.Get)("entities/:id"),
47187
- __decorateParam(0, (0, import_common10.Param)("id"))
47188
- ], ExampleController.prototype, "getEntity", 1);
47189
- __decorateClass([
47190
- (0, import_common10.Post)("entities"),
47191
- (0, import_common10.HttpCode)(import_common10.HttpStatus.CREATED),
47192
- __decorateParam(0, (0, import_common10.Body)())
47193
- ], ExampleController.prototype, "createEntity", 1);
47194
- __decorateClass([
47195
- (0, import_common10.Patch)("entities/:id"),
47196
- __decorateParam(0, (0, import_common10.Param)("id")),
47197
- __decorateParam(1, (0, import_common10.Body)())
47198
- ], ExampleController.prototype, "updateEntity", 1);
47199
- __decorateClass([
47200
- (0, import_common10.Delete)("entities/:id"),
47201
- (0, import_common10.HttpCode)(import_common10.HttpStatus.OK),
47202
- __decorateParam(0, (0, import_common10.Param)("id"))
47203
- ], ExampleController.prototype, "deleteEntity", 1);
47204
- __decorateClass([
47205
- (0, import_common10.Post)("entities/validated"),
47206
- (0, import_common10.HttpCode)(import_common10.HttpStatus.CREATED),
47207
- __decorateParam(0, (0, import_common10.Body)())
47208
- ], ExampleController.prototype, "createEntityWithValidation", 1);
47209
- __decorateClass([
47210
- (0, import_common10.Get)("errors/single")
47211
- ], ExampleController.prototype, "demoSingleError", 1);
47212
- __decorateClass([
47213
- (0, import_common10.Get)("errors/array")
47214
- ], ExampleController.prototype, "demoArrayErrors", 1);
47215
- __decorateClass([
47216
- (0, import_common10.Post)("email"),
47217
- (0, import_common10.HttpCode)(import_common10.HttpStatus.OK),
47218
- __decorateParam(0, (0, import_common10.Body)())
47219
- ], ExampleController.prototype, "sendEmail", 1);
47220
- ExampleController = __decorateClass([
47221
- (0, import_common10.Controller)("example"),
47222
- __decorateParam(0, (0, import_common10.Inject)(BACKEND_EXAMPLE_DOMAIN_SERVICE))
47223
- ], ExampleController);
47224
-
47225
- // src/backend/example/example.module.ts
47226
- var ExampleModule = class {
47227
- };
47228
- __name(ExampleModule, "ExampleModule");
47229
- ExampleModule = __decorateClass([
47230
- (0, import_common11.Module)({
47231
- controllers: [ExampleController],
47232
- providers: [
47233
- // Provide BackendExampleDomainService via factory using the singleton instance
47234
- // This ensures the service is shared across the application
47235
- {
47236
- provide: BACKEND_EXAMPLE_DOMAIN_SERVICE,
47237
- useFactory: /* @__PURE__ */ __name(() => backendExampleDomainService, "useFactory")
47238
- }
47239
- ],
47240
- exports: [BACKEND_EXAMPLE_DOMAIN_SERVICE]
47241
- })
47242
- ], ExampleModule);
47243
-
47244
- // src/backend/files/files.module.ts
47245
- var import_common17 = __toESM(require_common(), 1);
47246
-
47247
- // src/backend/files/files.controller.ts
47248
- var import_common12 = __toESM(require_common(), 1);
47249
- var BACKEND_FILES_DOMAIN_SERVICE = "BACKEND_FILES_DOMAIN_SERVICE";
47250
- var FilesController = class {
47251
- constructor(filesService) {
47252
- this.filesService = filesService;
47253
- }
47254
- health() {
47255
- return errors.SuccessResponseStandard("Files service is healthy", {
47256
- service: this.filesService.isAvailable(),
47257
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
47258
- });
47259
- }
47260
- async uploadFile(dto) {
47261
- const result2 = await this.filesService.uploadFile(dto);
47262
- return errors.SuccessResponseStandard("File uploaded successfully", result2);
47263
- }
47264
- async uploadFiles(dto) {
47265
- const results = await this.filesService.uploadFiles(dto.files);
47266
- return errors.SuccessResponseStandard("Files uploaded successfully", results);
47267
- }
47268
- async generateDocument(dto) {
47269
- const buffer = await this.filesService.generateFile(dto);
47270
- return errors.SuccessResponseStandard("Document generated successfully", {
47271
- buffer: buffer.toString("base64"),
47272
- size: buffer.length
47273
- });
47274
- }
47275
- async getFile(id) {
47276
- const entity = await this.filesService.getById(id);
47277
- return errors.SuccessResponseStandard("File retrieved", entity);
47278
- }
47279
- async downloadFile(id) {
47280
- const result2 = await this.filesService.downloadFile({ fileId: id });
47281
- return errors.SuccessResponseStandard("File downloaded", result2);
47282
- }
47283
- async getSignedUrl(id) {
47284
- const result2 = await this.filesService.getSignedUrl({ fileId: id });
47285
- return errors.SuccessResponseStandard("Signed URL generated", result2);
47286
- }
47287
- async deleteFile(id) {
47288
- await this.filesService.delete(id);
47289
- return errors.SuccessResponseStandard("File deleted", null);
47290
- }
47291
- };
47292
- __name(FilesController, "FilesController");
47293
- __decorateClass([
47294
- (0, import_common12.Get)("health")
47295
- ], FilesController.prototype, "health", 1);
47296
- __decorateClass([
47297
- (0, import_common12.Post)("upload"),
47298
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47299
- __decorateParam(0, (0, import_common12.Body)())
47300
- ], FilesController.prototype, "uploadFile", 1);
47301
- __decorateClass([
47302
- (0, import_common12.Post)("upload/bulk"),
47303
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47304
- __decorateParam(0, (0, import_common12.Body)())
47305
- ], FilesController.prototype, "uploadFiles", 1);
47306
- __decorateClass([
47307
- (0, import_common12.Post)("generate-document"),
47308
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47309
- __decorateParam(0, (0, import_common12.Body)())
47310
- ], FilesController.prototype, "generateDocument", 1);
47311
- __decorateClass([
47312
- (0, import_common12.Get)(":id"),
47313
- __decorateParam(0, (0, import_common12.Param)("id"))
47314
- ], FilesController.prototype, "getFile", 1);
47315
- __decorateClass([
47316
- (0, import_common12.Get)(":id/download"),
47317
- __decorateParam(0, (0, import_common12.Param)("id"))
47318
- ], FilesController.prototype, "downloadFile", 1);
47319
- __decorateClass([
47320
- (0, import_common12.Get)(":id/signed-url"),
47321
- __decorateParam(0, (0, import_common12.Param)("id"))
47322
- ], FilesController.prototype, "getSignedUrl", 1);
47323
- __decorateClass([
47324
- (0, import_common12.Delete)(":id"),
47325
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47326
- __decorateParam(0, (0, import_common12.Param)("id"))
47327
- ], FilesController.prototype, "deleteFile", 1);
47328
- FilesController = __decorateClass([
47329
- (0, import_common12.Controller)("files"),
47330
- __decorateParam(0, (0, import_common12.Inject)(BACKEND_FILES_DOMAIN_SERVICE))
47331
- ], FilesController);
47332
- var TABLE_NAME = "media";
47333
- var DEFAULT_LIMIT2 = 100;
47334
- var FilesRepository = class _FilesRepository extends db.BaseRepository {
47335
- static {
47336
- __name(this, "FilesRepository");
47337
- }
47338
- constructor(db) {
47339
- super(db, TABLE_NAME);
47340
- }
47341
- // ─────────────────────────────────────────────────────────────────────────
47342
- // Static Factory
47343
- // ─────────────────────────────────────────────────────────────────────────
47344
- /**
47345
- * Create repository instance.
47346
- * Uses DbService if initialized.
47347
- */
47348
- static create() {
47349
- if (!DbService.isInitialized()) {
47350
- throw new errors.DatabasePackageError(
47351
- "DbService not initialized. Call DbService.initialize() first.",
47352
- errors$1.DATABASE_ERROR_CODES.CONNECTION_FAILED
47353
- );
47354
- }
47355
- const db = DbService.getInstance().getDatabase();
47356
- return new _FilesRepository(db);
47357
- }
47358
- // ─────────────────────────────────────────────────────────────────────────
47359
- // Overridden Methods (with domain-specific logic)
47360
- // ─────────────────────────────────────────────────────────────────────────
47361
- /**
47362
- * Find multiple entities with default sorting
47363
- */
47364
- // eslint-disable-next-line complexity
47365
- async findMany(options, config) {
47366
- const mergedOptions = {
47367
- sort: options?.sort ?? [{ field: "created_at", direction: "desc" }],
47368
- pagination: {
47369
- limit: options?.pagination?.limit ?? DEFAULT_LIMIT2,
47370
- offset: options?.pagination?.offset ?? 0
47371
- },
47372
- filter: options?.filter
47373
- };
47374
- return super.findMany(mergedOptions, config);
47375
- }
47376
- /**
47377
- * Create new file record with auto-generated ID and timestamps
47378
- */
47379
- // eslint-disable-next-line complexity
47380
- async create(data, config) {
47381
- const now = (/* @__PURE__ */ new Date()).toISOString();
47382
- const id = data.id ?? generateId();
47383
- const row = {
47384
- id,
47385
- user_id: data.user_id ?? "",
47386
- type: data.type ?? "OTHER",
47387
- filename: data.filename ?? "",
47388
- original_filename: data.original_filename ?? data.filename ?? "",
47389
- mime_type: data.mime_type ?? "application/octet-stream",
47390
- file_size: data.file_size ?? 0,
47391
- storage_path: data.storage_path ?? "",
47392
- cdn_url: data.cdn_url ?? null,
47393
- width: data.width ?? null,
47394
- height: data.height ?? null,
47395
- duration: data.duration ?? null,
47396
- is_virus_scanned: data.is_virus_scanned ?? false,
47397
- virus_scan_result: data.virus_scan_result ?? null,
47398
- metadata: data.metadata ?? null,
47399
- variants: data.variants ?? null,
47400
- entity_type: data.entity_type ?? null,
47401
- entity_id: data.entity_id ?? null,
47402
- access_level: data.access_level ?? null,
47403
- created_at: now,
47404
- updated_at: now,
47405
- deleted_at: null
47406
- };
47407
- return super.create(row, config);
47408
- }
47409
- /**
47410
- * Get the table name for this repository
47411
- */
47412
- getTableName() {
47413
- return TABLE_NAME;
47414
- }
47415
- // ─────────────────────────────────────────────────────────────────────────
47416
- // Domain-Specific Methods
47417
- // ─────────────────────────────────────────────────────────────────────────
47418
- /**
47419
- * Find all files for a user
47420
- */
47421
- async findByUserId(userId, options) {
47422
- return this.findMany({
47423
- filter: { field: "user_id", operator: "eq", value: userId },
47424
- pagination: {
47425
- limit: options?.limit ?? DEFAULT_LIMIT2,
47426
- offset: options?.offset ?? 0
47427
- }
47428
- });
47429
- }
47430
- /**
47431
- * Find files for a specific entity (polymorphic association)
47432
- */
47433
- async findByEntityId(entityType, entityId, options) {
47434
- return this.query().where("entity_type", "eq", entityType).andWhere("entity_id", "eq", entityId).whereNull("deleted_at").orderByDesc("created_at").paginate(
47435
- Math.floor((options?.offset ?? 0) / (options?.limit ?? DEFAULT_LIMIT2)) + 1,
47436
- options?.limit ?? DEFAULT_LIMIT2
47437
- ).execute();
47438
- }
47439
- /**
47440
- * Find file by storage path (for deduplication checks)
47441
- */
47442
- async findByStoragePath(storagePath) {
47443
- return this.findOne({ field: "storage_path", operator: "eq", value: storagePath });
47444
- }
47445
- /**
47446
- * Count files for a user
47447
- */
47448
- async countByUserId(userId) {
47449
- return this.count({ field: "user_id", operator: "eq", value: userId });
47450
- }
47451
- /**
47452
- * Find files by MIME type
47453
- */
47454
- async findByMimeType(mimeType, options) {
47455
- return this.findMany({
47456
- filter: { field: "mime_type", operator: "eq", value: mimeType },
47457
- pagination: {
47458
- limit: options?.limit ?? DEFAULT_LIMIT2,
47459
- offset: options?.offset ?? 0
47460
- }
47461
- });
47462
- }
47463
- /**
47464
- * Find files by type (IMAGE, VIDEO, DOCUMENT, AUDIO)
47465
- */
47466
- async findByType(type, options) {
47467
- return this.findMany({
47468
- filter: { field: "type", operator: "eq", value: type },
47469
- pagination: {
47470
- limit: options?.limit ?? DEFAULT_LIMIT2,
47471
- offset: options?.offset ?? 0
47472
- }
47473
- });
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;
47474
47564
  }
47475
- };
47476
-
47477
- // src/domain/files/mappers/FilesMapper.ts
47478
- var FilesMapperClass = class _FilesMapperClass extends BaseMapper {
47479
47565
  static {
47480
- __name(this, "FilesMapperClass");
47481
- }
47482
- /**
47483
- * Type guard for UploadResult from event payloads
47484
- * Validates that the value has the required structure for DB mapping
47485
- */
47486
- static isUploadResult(value) {
47487
- if (!isObject(value)) return false;
47488
- const obj = value;
47489
- if (!isObject(obj.metadata)) return false;
47490
- const metadata = obj.metadata;
47491
- return typeof metadata.fileId === "string" && typeof metadata.filename === "string" && typeof metadata.mimeType === "string" && typeof metadata.size === "number" && typeof metadata.path === "string";
47492
- }
47493
- /**
47494
- * Infer file type from MIME type
47495
- */
47496
- static inferFileType(mimeType) {
47497
- if (mimeType.startsWith("image/")) return "IMAGE";
47498
- if (mimeType.startsWith("video/")) return "VIDEO";
47499
- if (mimeType.startsWith("audio/")) return "AUDIO";
47500
- if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("text/")) {
47501
- return "DOCUMENT";
47502
- }
47503
- return "OTHER";
47504
- }
47505
- /**
47506
- * API Response → Domain Entity
47507
- */
47508
- toDomain(dto) {
47509
- const type = _FilesMapperClass.inferFileType(dto.mimeType);
47510
- return {
47511
- id: dto.id,
47512
- key: dto.key,
47513
- filename: dto.filename,
47514
- mimeType: dto.mimeType,
47515
- size: dto.size,
47516
- checksum: dto.checksum,
47517
- url: dto.url,
47518
- publicUrl: dto.publicUrl,
47519
- signedUrl: dto.signedUrl,
47520
- bucket: dto.bucket,
47521
- entityType: dto.entityType,
47522
- entityId: dto.entityId,
47523
- category: dto.category,
47524
- uploadedAt: new Date(dto.uploadedAt),
47525
- expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : void 0,
47526
- variants: dto.variants,
47527
- type,
47528
- isImage: type === "IMAGE",
47529
- isVideo: type === "VIDEO",
47530
- isDocument: type === "DOCUMENT",
47531
- isAudio: type === "AUDIO"
47532
- };
47533
- }
47534
- /**
47535
- * Domain Entity → Store State (serializable)
47536
- */
47537
- toStoreState(entity) {
47538
- return {
47539
- id: entity.id,
47540
- key: entity.key,
47541
- filename: entity.filename,
47542
- mimeType: entity.mimeType,
47543
- size: entity.size,
47544
- url: entity.url,
47545
- publicUrl: entity.publicUrl,
47546
- bucket: entity.bucket,
47547
- entityType: entity.entityType,
47548
- entityId: entity.entityId,
47549
- category: entity.category,
47550
- uploadedAt: entity.uploadedAt.toISOString(),
47551
- type: entity.type,
47552
- isImage: entity.isImage,
47553
- isVideo: entity.isVideo,
47554
- isDocument: entity.isDocument,
47555
- isAudio: entity.isAudio
47556
- };
47557
- }
47558
- /**
47559
- * Store State → Domain Entity
47560
- */
47561
- fromStoreState(state) {
47562
- return {
47563
- id: state.id,
47564
- key: state.key,
47565
- filename: state.filename,
47566
- mimeType: state.mimeType,
47567
- size: state.size,
47568
- url: state.url,
47569
- publicUrl: state.publicUrl,
47570
- bucket: state.bucket,
47571
- entityType: state.entityType,
47572
- entityId: state.entityId,
47573
- category: state.category,
47574
- uploadedAt: new Date(state.uploadedAt),
47575
- type: state.type,
47576
- isImage: state.isImage,
47577
- isVideo: state.isVideo,
47578
- isDocument: state.isDocument,
47579
- isAudio: state.isAudio
47580
- };
47581
- }
47582
- /**
47583
- * Database Row → API Response (for backend)
47584
- */
47585
- toResponseDTO(row) {
47586
- return {
47587
- id: row.id,
47588
- key: row.storage_path,
47589
- filename: row.filename,
47590
- mimeType: row.mime_type,
47591
- size: toNumber(row.file_size),
47592
- checksum: void 0,
47593
- url: row.cdn_url ?? void 0,
47594
- publicUrl: row.cdn_url ?? void 0,
47595
- signedUrl: void 0,
47596
- bucket: "media",
47597
- entityType: row.entity_type ?? void 0,
47598
- entityId: row.entity_id ?? void 0,
47599
- category: void 0,
47600
- uploadedAt: row.created_at,
47601
- expiresAt: void 0
47602
- };
47603
- }
47604
- /**
47605
- * Create GeneratedFileItem from generation result
47606
- */
47607
- toGeneratedFileItem(templateId, buffer, size, outputFormat = "pdf") {
47608
- return {
47609
- id: crypto.randomUUID(),
47610
- templateId,
47611
- buffer,
47612
- size,
47613
- mimeType: `application/${outputFormat}`,
47614
- filename: `${templateId}.${outputFormat}`,
47615
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
47616
- };
47617
- }
47618
- /**
47619
- * Convert upload result to database row format
47620
- * Used by event handlers to persist upload results to DB
47621
- *
47622
- * @param uploadResult - Result from storage upload
47623
- * @param userId - User ID who uploaded the file (from auth context)
47624
- * @returns Partial database row ready for repository.create()
47625
- */
47626
- // eslint-disable-next-line complexity
47627
- toDbRow(uploadResult, userId) {
47628
- const { metadata, variants } = uploadResult;
47629
- return {
47630
- id: metadata.fileId,
47631
- user_id: userId ?? "system",
47632
- type: _FilesMapperClass.inferFileType(metadata.mimeType),
47633
- filename: metadata.filename,
47634
- original_filename: metadata.filename,
47635
- mime_type: metadata.mimeType,
47636
- file_size: metadata.size,
47637
- storage_path: metadata.path,
47638
- cdn_url: metadata.url ?? null,
47639
- width: metadata.extractedMetadata?.width ?? null,
47640
- height: metadata.extractedMetadata?.height ?? null,
47641
- duration: metadata.extractedMetadata?.duration ?? null,
47642
- entity_type: metadata.entityType,
47643
- entity_id: metadata.entityId,
47644
- access_level: metadata.accessLevel,
47645
- is_virus_scanned: false,
47646
- virus_scan_result: null,
47647
- metadata: metadata.customMetadata ?? null,
47648
- variants: variants ?? null
47649
- };
47566
+ __name(this, "NotificationService");
47650
47567
  }
47651
- };
47652
- var FilesMapper = new FilesMapperClass();
47653
-
47654
- // src/domain/files/validators/FilesValidator.ts
47655
- var FilesValidatorClass = class extends BaseValidator {
47656
47568
  static {
47657
- __name(this, "FilesValidatorClass");
47658
- }
47659
- constructor() {
47660
- super({});
47569
+ this.instance = null;
47661
47570
  }
47571
+ // ─────────────────────────────────────────────────────────────────
47572
+ // Error Handling
47573
+ // ─────────────────────────────────────────────────────────────────
47662
47574
  /**
47663
- * Check if user has permission to access file
47664
- */
47665
- validateUserAccess(userId, fileOwnerId, accessLevel, isAdmin = false) {
47666
- if (isAdmin) return true;
47667
- if (userId === fileOwnerId) return true;
47668
- if (accessLevel === "public") return true;
47669
- if (accessLevel === "protected" && userId) return true;
47670
- return false;
47671
- }
47672
- };
47673
- new FilesValidatorClass();
47674
-
47675
- // src/domain/files/BackendFilesDomainService.ts
47676
- var BackendFilesDomainService = class _BackendFilesDomainService extends BaseBackendDomainService {
47677
- constructor(config = {}, injected) {
47678
- super({
47679
- serviceName: "BackendFilesDomainService",
47680
- supportedRuntimes: ["backend"],
47681
- serviceConfig: {
47682
- enabled: true,
47683
- throwOnValidationError: true,
47684
- throwOnRepositoryError: true,
47685
- emitEvents: true,
47686
- ...config
47687
- },
47688
- mapperClass: FilesMapperClass,
47689
- validatorClass: FilesValidatorClass,
47690
- injected
47691
- });
47692
- this.eventPrefix = "files";
47693
- 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);
47694
47587
  }
47695
- static {
47696
- __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
+ };
47697
47639
  }
47698
- static {
47699
- 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;
47700
47649
  }
47701
47650
  /**
47702
- * Lazy repository getter - creates repository on first access.
47703
- * Throws if DbService is not initialized.
47651
+ * Checks if the notification service has been initialized
47704
47652
  */
47705
- get repository() {
47706
- this._repository ??= FilesRepository.create();
47707
- return this._repository;
47653
+ static isInitialized() {
47654
+ return _NotificationService.instance?.initialized ?? false;
47708
47655
  }
47709
47656
  /**
47710
- * Create service instance
47657
+ * Resets the notification service by clearing the singleton instance
47711
47658
  */
47712
- // eslint-disable-next-line complexity
47713
- static async create(config = {}, options) {
47714
- this.registerEventHandlers();
47715
- const injected = {
47716
- cache: options?.cache?.instance,
47717
- db: options?.db?.instance,
47718
- api: options?.apiClient?.instance,
47719
- storage: options?.storage?.instance,
47720
- notifications: options?.notifications?.instance
47721
- };
47722
- 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
+ }
47723
47666
  }
47724
47667
  /**
47725
- * Register event handlers for file upload persistence.
47726
- * Called by CoreInitializer after storage and DB are initialized.
47727
- *
47728
- * Events handled:
47729
- * - files:upload:uploaded - Persist single file to DB
47730
- * - files:upload:bulk:uploaded - Persist multiple files to DB
47731
- *
47732
- * Deduplication: Uses unique constraint on storage_path.
47668
+ * Initializes the notification service
47733
47669
  *
47734
- * @param verbose - Enable verbose logging
47670
+ * @param config - Notification service configuration
47671
+ * @returns The initialized NotificationService instance
47735
47672
  */
47736
- static registerEventHandlers(verbose) {
47737
- if (BackendEventPersistenceHandler.isRegistered("files")) {
47738
- return;
47673
+ static async initialize(config) {
47674
+ const instance = _NotificationService.getInstance();
47675
+ if (instance.initialized) {
47676
+ return instance;
47739
47677
  }
47740
- BackendEventPersistenceHandler.register({
47741
- domain: "files",
47742
- events: {
47743
- "files:upload:uploaded": {
47744
- extractPayload: /* @__PURE__ */ __name((payload) => {
47745
- if (!isObject(payload)) return void 0;
47746
- const p = payload;
47747
- if (!FilesMapperClass.isUploadResult(p.result)) return void 0;
47748
- return p.result;
47749
- }, "extractPayload"),
47750
- isBulk: false,
47751
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
47752
- },
47753
- "files:upload:bulk:uploaded": {
47754
- extractPayload: /* @__PURE__ */ __name((payload) => {
47755
- if (!isObject(payload)) return [];
47756
- const p = payload;
47757
- const results = p.result?.results;
47758
- if (!Array.isArray(results)) return [];
47759
- return results.filter(FilesMapperClass.isUploadResult);
47760
- }, "extractPayload"),
47761
- isBulk: true,
47762
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
47763
- }
47764
- },
47765
- loadDependencies: /* @__PURE__ */ __name(async () => ({
47766
- repository: FilesRepository.create(),
47767
- mapper: FilesMapper
47768
- }), "loadDependencies"),
47769
- mapToDbRow: /* @__PURE__ */ __name((mapper, item) => mapper.toDbRow(item), "mapToDbRow"),
47770
- createRecord: /* @__PURE__ */ __name(async (repository, dbRow) => {
47771
- return repository.create(dbRow);
47772
- }, "createRecord"),
47773
- getUniqueKey: /* @__PURE__ */ __name((item) => item.metadata.path ?? item.metadata.fileId, "getUniqueKey"),
47774
- verbose
47678
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
47679
+ instance.config = config;
47680
+ instance.notificationService = new notifications.NotificationService({
47681
+ ...config,
47682
+ events: mergedEvents
47775
47683
  });
47684
+ instance.initialized = true;
47685
+ return instance;
47776
47686
  }
47777
- isAvailable() {
47778
- return this.isServiceEnabled;
47779
- }
47780
- dispose() {
47781
- 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;
47782
47702
  }
47783
- // ─────────────────────────────────────────────────────────────────────────
47784
- // Storage operations (via @plyaz/storage)
47785
- // ─────────────────────────────────────────────────────────────────────────
47703
+ // ─────────────────────────────────────────────────────────────────
47704
+ // Service Access
47705
+ // ─────────────────────────────────────────────────────────────────
47786
47706
  /**
47787
- * Download file content
47788
- * 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.
47789
47710
  *
47790
- * Follows same pattern as base class CRUD methods:
47791
- * 1. assertReady
47792
- * 2. Record start time
47793
- * 3. Log debug info
47794
- * 4. Emit starting event
47795
- * 5. Execute operation
47796
- * 6. Emit success event
47797
- * 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
+ * ```
47798
47717
  *
47799
- * @param params - Download parameters (fileId required)
47800
- * @returns Download result with buffer, filename, mimeType
47718
+ * @returns NotificationServiceImpl with automatic error handling
47801
47719
  */
47802
- // eslint-disable-next-line complexity
47803
- async downloadFile(params) {
47804
- this.assertReady();
47805
- const startTime = Date.now();
47806
- this.logDebug("Downloading file", { fileId: params.fileId });
47807
- this.emitEvent("download:downloading", { request: params });
47808
- try {
47809
- await this.beforeDownloadFile?.(params);
47810
- if (!this.storageService) {
47811
- throw new Error(
47812
- "Storage service not available. Ensure storage is configured in Core.initialize()."
47813
- );
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
+ };
47814
47747
  }
47815
- const storage = this.storageService.getStorage();
47816
- const result2 = await storage.downloadFile({
47817
- fileId: params.fileId
47818
- });
47819
- const response = {
47820
- buffer: result2?.file?.toString("base64") ?? "",
47821
- filename: result2?.metadata?.filename ?? "download",
47822
- 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
47823
47766
  };
47824
- await this.afterDownloadFile?.(response);
47825
- this.emitEvent("download:downloaded", { result: response });
47826
- this.emitEvent("complete", { success: true, operation: "downloadFile" });
47827
- await this.recordOperationMetrics("downloadFile", Date.now() - startTime, true);
47828
- return response;
47829
47767
  } catch (error) {
47830
- await this.recordOperationMetrics("downloadFile", Date.now() - startTime, false);
47831
- this.emitEvent("download:error", { error });
47832
- throw error;
47768
+ this.emitNotificationError(error, "healthCheck", { recoverable: true });
47769
+ return {
47770
+ isHealthy: false,
47771
+ error: error instanceof Error ? error.message : "Unknown error"
47772
+ };
47833
47773
  }
47834
47774
  }
47775
+ // ─────────────────────────────────────────────────────────────────
47776
+ // Lifecycle
47777
+ // ─────────────────────────────────────────────────────────────────
47778
+ /**
47779
+ * Gets the current configuration
47780
+ */
47781
+ getConfig() {
47782
+ return this.config;
47783
+ }
47835
47784
  /**
47836
- * Get signed URL for file
47837
- * 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)
47838
47794
  *
47839
- * Follows same pattern as base class CRUD methods.
47795
+ * Use this when you need an isolated notification service with its own configuration.
47840
47796
  *
47841
- * @param params - Signed URL parameters (fileId required)
47842
- * @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
47843
47799
  */
47844
- // eslint-disable-next-line complexity
47845
- async getSignedUrl(params) {
47846
- this.assertReady();
47847
- const startTime = Date.now();
47848
- this.logDebug("Getting signed URL", { fileId: params.fileId, expiresIn: params.expiresIn });
47849
- 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 {
47850
47820
  try {
47851
- await this.beforeGetSignedUrl?.(params);
47852
- if (!this.storageService) {
47853
- throw new Error(
47854
- "Storage service not available. Ensure storage is configured in Core.initialize()."
47855
- );
47856
- }
47857
- const storage = this.storageService.getStorage();
47858
- const result2 = await storage.getSignedUrl({
47859
- fileId: params.fileId,
47860
- expiresIn: params.expiresIn ?? config.DOWNLOAD_CONFIG.DEFAULT_SIGNED_URL_EXPIRY_SECONDS,
47861
- operation: "get"
47862
- });
47863
- const response = {
47864
- url: result2?.url ?? "",
47865
- expiresAt: result2?.expiresAt?.toISOString() ?? new Date(Date.now() + config.TIME_CONSTANTS.HOUR).toISOString()
47866
- };
47867
- await this.afterGetSignedUrl?.(response);
47868
- this.emitEvent("signedUrl:received", { result: response });
47869
- this.emitEvent("complete", { success: true, operation: "getSignedUrl" });
47870
- await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, true);
47871
- return response;
47872
- } catch (error) {
47873
- await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, false);
47874
- this.emitEvent("signedUrl:error", { error });
47875
- 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";
47876
47828
  }
47877
47829
  }
47878
- // ─────────────────────────────────────────────────────────────────────────
47879
- // NOTE: CRUD and Storage Methods Are Inherited from BaseBackendDomainService
47880
- // ─────────────────────────────────────────────────────────────────────────
47881
- //
47882
- // CRUD methods:
47883
- // - getById(id) - for GET /files/:id
47884
- // - getAll(query) - for listing files
47885
- // - delete(id) - for DELETE /files/:id
47886
- // - exists(id) - check if file exists
47887
- //
47888
- // Storage methods (inherited):
47889
- // - uploadFile(params) - for POST /files/upload
47890
- // - generateFile(params) - for POST /files/generate-document
47891
- // ─────────────────────────────────────────────────────────────────────────
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
+ }
47892
47909
  };
47893
- var backendFilesDomainService = new BackendFilesDomainService();
47894
- 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" });
47895
48062
  var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseFrontendDomainService {
47896
48063
  constructor(config = {}, options) {
47897
- const apiBasePath = config.apiBasePath || "/api";
48064
+ const apiBasePath = config.apiBasePath || "";
47898
48065
  super({
47899
48066
  serviceName: "FrontendFilesDomainService",
47900
48067
  supportedRuntimes: ["frontend"],
@@ -47957,7 +48124,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
47957
48124
  // The ?? operator only falls through on null/undefined, not empty strings.
47958
48125
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
47959
48126
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
47960
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api"
48127
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
47961
48128
  };
47962
48129
  return new _FrontendFilesDomainService(mergedConfig, options);
47963
48130
  }
@@ -47994,7 +48161,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
47994
48161
  */
47995
48162
  static registerEventHandlers(verbose) {
47996
48163
  if (this._eventHandlersRegistered) {
47997
- logger4.debug("Files frontend event handlers already registered");
48164
+ logger6.debug("Files frontend event handlers already registered");
47998
48165
  return () => {
47999
48166
  };
48000
48167
  }
@@ -48056,7 +48223,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
48056
48223
  verbose
48057
48224
  });
48058
48225
  this._eventHandlersRegistered = true;
48059
- logger4.info("Files frontend event handlers registered");
48226
+ logger6.info("Files frontend event handlers registered");
48060
48227
  return () => {
48061
48228
  FrontendEventPersistenceHandler.unregisterAll();
48062
48229
  this._eventHandlersRegistered = false;
@@ -48134,7 +48301,7 @@ var FilesModule = class {
48134
48301
  };
48135
48302
  __name(FilesModule, "FilesModule");
48136
48303
  FilesModule = __decorateClass([
48137
- (0, import_common17.Module)({
48304
+ (0, import_common16.Module)({
48138
48305
  controllers: [FilesController],
48139
48306
  providers: [
48140
48307
  // Provide BackendFilesDomainService via factory using the singleton instance
@@ -48425,10 +48592,10 @@ var FeatureFlagDomainService = class extends BaseDomainService {
48425
48592
  };
48426
48593
 
48427
48594
  // src/backend/featureFlags/feature-flag.module.ts
48428
- var import_common19 = __toESM(require_common(), 1);
48595
+ var import_common18 = __toESM(require_common(), 1);
48429
48596
 
48430
48597
  // src/backend/featureFlags/feature-flag.controller.ts
48431
- var import_common18 = __toESM(require_common(), 1);
48598
+ var import_common17 = __toESM(require_common(), 1);
48432
48599
  var FeatureFlagController = class {
48433
48600
  constructor(featureFlagService) {
48434
48601
  this.featureFlagService = featureFlagService;
@@ -48557,54 +48724,54 @@ var FeatureFlagController = class {
48557
48724
  };
48558
48725
  __name(FeatureFlagController, "FeatureFlagController");
48559
48726
  __decorateClass([
48560
- (0, import_common18.Post)(":key/evaluate"),
48561
- __decorateParam(0, (0, import_common18.Param)("key")),
48562
- __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)())
48563
48730
  ], FeatureFlagController.prototype, "evaluateFlag", 1);
48564
48731
  __decorateClass([
48565
- (0, import_common18.Post)(":key/enabled"),
48566
- __decorateParam(0, (0, import_common18.Param)("key")),
48567
- __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)())
48568
48735
  ], FeatureFlagController.prototype, "isEnabled", 1);
48569
48736
  __decorateClass([
48570
- (0, import_common18.Post)("evaluate-all"),
48571
- __decorateParam(0, (0, import_common18.Body)())
48737
+ (0, import_common17.Post)("evaluate-all"),
48738
+ __decorateParam(0, (0, import_common17.Body)())
48572
48739
  ], FeatureFlagController.prototype, "evaluateAllFlags", 1);
48573
48740
  __decorateClass([
48574
- (0, import_common18.Post)(),
48575
- __decorateParam(0, (0, import_common18.Body)())
48741
+ (0, import_common17.Post)(),
48742
+ __decorateParam(0, (0, import_common17.Body)())
48576
48743
  ], FeatureFlagController.prototype, "createFlag", 1);
48577
48744
  __decorateClass([
48578
- (0, import_common18.Put)(":key"),
48579
- __decorateParam(0, (0, import_common18.Param)("key")),
48580
- __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)())
48581
48748
  ], FeatureFlagController.prototype, "updateFlag", 1);
48582
48749
  __decorateClass([
48583
- (0, import_common18.Delete)(":key"),
48584
- __decorateParam(0, (0, import_common18.Param)("key"))
48750
+ (0, import_common17.Delete)(":key"),
48751
+ __decorateParam(0, (0, import_common17.Param)("key"))
48585
48752
  ], FeatureFlagController.prototype, "deleteFlag", 1);
48586
48753
  __decorateClass([
48587
- (0, import_common18.Post)(":key/override"),
48588
- __decorateParam(0, (0, import_common18.Param)("key")),
48589
- __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"))
48590
48757
  ], FeatureFlagController.prototype, "setOverride", 1);
48591
48758
  __decorateClass([
48592
- (0, import_common18.Delete)(":key/override"),
48593
- __decorateParam(0, (0, import_common18.Param)("key"))
48759
+ (0, import_common17.Delete)(":key/override"),
48760
+ __decorateParam(0, (0, import_common17.Param)("key"))
48594
48761
  ], FeatureFlagController.prototype, "removeOverride", 1);
48595
48762
  __decorateClass([
48596
- (0, import_common18.Get)(":key/rules"),
48597
- __decorateParam(0, (0, import_common18.Param)("key"))
48763
+ (0, import_common17.Get)(":key/rules"),
48764
+ __decorateParam(0, (0, import_common17.Param)("key"))
48598
48765
  ], FeatureFlagController.prototype, "getFlagRules", 1);
48599
48766
  __decorateClass([
48600
- (0, import_common18.Post)("refresh")
48767
+ (0, import_common17.Post)("refresh")
48601
48768
  ], FeatureFlagController.prototype, "refreshCache", 1);
48602
48769
  __decorateClass([
48603
- (0, import_common18.Get)("health")
48770
+ (0, import_common17.Get)("health")
48604
48771
  ], FeatureFlagController.prototype, "getHealth", 1);
48605
48772
  FeatureFlagController = __decorateClass([
48606
- (0, import_common18.Controller)("feature-flags"),
48607
- __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))
48608
48775
  ], FeatureFlagController);
48609
48776
 
48610
48777
  // src/backend/featureFlags/feature-flag.module.ts
@@ -48696,16 +48863,16 @@ var FeatureFlagModule = class {
48696
48863
  __name(FeatureFlagModule, "FeatureFlagModule");
48697
48864
  FeatureFlagModule.serviceInstance = null;
48698
48865
  FeatureFlagModule = __decorateClass([
48699
- (0, import_common19.Global)(),
48700
- (0, import_common19.Module)({
48866
+ (0, import_common18.Global)(),
48867
+ (0, import_common18.Module)({
48701
48868
  controllers: [FeatureFlagController]
48702
48869
  })
48703
48870
  ], FeatureFlagModule);
48704
48871
 
48705
48872
  // src/backend/featureFlags/decorators/feature-flag.decorator.ts
48706
- var import_common20 = __toESM(require_common(), 1);
48873
+ var import_common19 = __toESM(require_common(), 1);
48707
48874
  function FeatureFlag(key, expected = true) {
48708
- 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 });
48709
48876
  }
48710
48877
  __name(FeatureFlag, "FeatureFlag");
48711
48878
 
@@ -48722,7 +48889,7 @@ function FeatureEnabled(key) {
48722
48889
  __name(FeatureEnabled, "FeatureEnabled");
48723
48890
 
48724
48891
  // src/backend/featureFlags/guards/feature-flag.guard.ts
48725
- var import_common21 = __toESM(require_common(), 1);
48892
+ var import_common20 = __toESM(require_common(), 1);
48726
48893
  var FeatureFlagGuard = class {
48727
48894
  constructor(reflector, featureFlagService) {
48728
48895
  this.reflector = reflector;
@@ -48766,12 +48933,12 @@ var FeatureFlagGuard = class {
48766
48933
  };
48767
48934
  __name(FeatureFlagGuard, "FeatureFlagGuard");
48768
48935
  FeatureFlagGuard = __decorateClass([
48769
- (0, import_common21.Injectable)(),
48770
- __decorateParam(1, (0, import_common21.Inject)(FEATURE_FLAG_SERVICE))
48936
+ (0, import_common20.Injectable)(),
48937
+ __decorateParam(1, (0, import_common20.Inject)(FEATURE_FLAG_SERVICE))
48771
48938
  ], FeatureFlagGuard);
48772
48939
 
48773
48940
  // src/backend/featureFlags/middleware/feature-flag-middleware.ts
48774
- var import_common22 = __toESM(require_common(), 1);
48941
+ var import_common21 = __toESM(require_common(), 1);
48775
48942
  function isFeatureFlagKey(value) {
48776
48943
  return Object.keys(config.FEATURES).includes(value);
48777
48944
  }
@@ -48849,12 +49016,12 @@ var FeatureFlagMiddleware = class {
48849
49016
  };
48850
49017
  __name(FeatureFlagMiddleware, "FeatureFlagMiddleware");
48851
49018
  FeatureFlagMiddleware = __decorateClass([
48852
- (0, import_common22.Injectable)(),
48853
- __decorateParam(0, (0, import_common22.Inject)(FEATURE_FLAG_SERVICE))
49019
+ (0, import_common21.Injectable)(),
49020
+ __decorateParam(0, (0, import_common21.Inject)(FEATURE_FLAG_SERVICE))
48854
49021
  ], FeatureFlagMiddleware);
48855
49022
 
48856
49023
  // src/backend/featureFlags/interceptors/feature-flag-logging-interceptor.ts
48857
- var import_common23 = __toESM(require_common(), 1);
49024
+ var import_common22 = __toESM(require_common(), 1);
48858
49025
  var import_rxjs = __toESM(require_cjs(), 1);
48859
49026
  var FeatureFlagLoggingInterceptor = class {
48860
49027
  constructor() {
@@ -48887,11 +49054,11 @@ var FeatureFlagLoggingInterceptor = class {
48887
49054
  };
48888
49055
  __name(FeatureFlagLoggingInterceptor, "FeatureFlagLoggingInterceptor");
48889
49056
  FeatureFlagLoggingInterceptor = __decorateClass([
48890
- (0, import_common23.Injectable)()
49057
+ (0, import_common22.Injectable)()
48891
49058
  ], FeatureFlagLoggingInterceptor);
48892
49059
 
48893
49060
  // src/backend/featureFlags/interceptors/error-handling-interceptor.ts
48894
- var import_common24 = __toESM(require_common(), 1);
49061
+ var import_common23 = __toESM(require_common(), 1);
48895
49062
  var import_operators = __toESM(require_operators(), 1);
48896
49063
  var ErrorHandlingInterceptor = class {
48897
49064
  constructor() {
@@ -48920,9 +49087,9 @@ var ErrorHandlingInterceptor = class {
48920
49087
  };
48921
49088
  __name(ErrorHandlingInterceptor, "ErrorHandlingInterceptor");
48922
49089
  ErrorHandlingInterceptor = __decorateClass([
48923
- (0, import_common24.Injectable)()
49090
+ (0, import_common23.Injectable)()
48924
49091
  ], ErrorHandlingInterceptor);
48925
- var logger5 = new logger$1.PackageLogger({
49092
+ var logger7 = new logger$1.PackageLogger({
48926
49093
  packageName: "core",
48927
49094
  service: "FeatureFlagConfigValidator"
48928
49095
  });
@@ -49108,7 +49275,7 @@ var FeatureFlagConfigValidator = class {
49108
49275
  static getWarnings(config$1, environment) {
49109
49276
  const env = environment ?? (config$1.isLoggingEnabled ? types.NODE_ENVIRONMENTS.DEVELOPMENT : types.NODE_ENVIRONMENTS.PRODUCTION);
49110
49277
  if (config$1.cacheTtl > config.NUMERIC_CONSTANTS.ONE_HOUR_SECONDS) {
49111
- logger5.warn(
49278
+ logger7.warn(
49112
49279
  "Cache TTL is very high (>1 hour). Consider reducing for better responsiveness.",
49113
49280
  {
49114
49281
  field: "cacheTtl",
@@ -49118,13 +49285,13 @@ var FeatureFlagConfigValidator = class {
49118
49285
  );
49119
49286
  }
49120
49287
  if (env === types.NODE_ENVIRONMENTS.PRODUCTION && config$1.isLoggingEnabled) {
49121
- logger5.warn("Logging is enabled in production. Consider disabling for performance.", {
49288
+ logger7.warn("Logging is enabled in production. Consider disabling for performance.", {
49122
49289
  field: "isLoggingEnabled",
49123
49290
  code: "PRODUCTION_LOGGING_ENABLED"
49124
49291
  });
49125
49292
  }
49126
49293
  if (env === types.NODE_ENVIRONMENTS.DEVELOPMENT && !config$1.isCacheEnabled) {
49127
- 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.", {
49128
49295
  field: "isCacheEnabled",
49129
49296
  code: "DEVELOPMENT_CACHE_DISABLED"
49130
49297
  });
@@ -49267,7 +49434,7 @@ var FeatureFlagConfigFactory = class {
49267
49434
  };
49268
49435
 
49269
49436
  // src/base/cache/feature/caching.ts
49270
- var import_common25 = __toESM(require_common(), 1);
49437
+ var import_common24 = __toESM(require_common(), 1);
49271
49438
  var import_rxjs2 = __toESM(require_cjs(), 1);
49272
49439
  var Caching = class {
49273
49440
  constructor() {
@@ -49290,7 +49457,7 @@ var Caching = class {
49290
49457
  };
49291
49458
  __name(Caching, "Caching");
49292
49459
  Caching = __decorateClass([
49293
- (0, import_common25.Injectable)()
49460
+ (0, import_common24.Injectable)()
49294
49461
  ], Caching);
49295
49462
 
49296
49463
  // src/frontend/index.ts
@@ -49417,7 +49584,7 @@ function ApiProvider({
49417
49584
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
49418
49585
  }
49419
49586
  __name(ApiProvider, "ApiProvider");
49420
- var logger6 = new logger$1.PackageLogger({ packageName: "core", service: "PlyazProvider" });
49587
+ var logger8 = new logger$1.PackageLogger({ packageName: "core", service: "PlyazProvider" });
49421
49588
  var PlyazContext = react.createContext(null);
49422
49589
  var FeatureFlagStore = class {
49423
49590
  constructor(config = {}) {
@@ -49512,7 +49679,7 @@ function createStoreRegistry() {
49512
49679
  getStore(key) {
49513
49680
  const state = store.useRootStore.getState();
49514
49681
  if (!state) {
49515
- logger6.warn(
49682
+ logger8.warn(
49516
49683
  "Store state is undefined - store may not be hydrated yet. This can cause side effects if called during SSR or before initialization."
49517
49684
  );
49518
49685
  return void 0;
@@ -49528,7 +49695,10 @@ async function initializeServices(config) {
49528
49695
  globalThis.console.log("[PlyazProvider] Initializing domain services...");
49529
49696
  }
49530
49697
  await ServiceRegistry.initialize({
49698
+ environment: config.environment,
49699
+ runtime: config.runtime ?? detectRuntime(),
49531
49700
  apiClient: { baseURL: config.api.baseURL },
49701
+ observability: config.observability,
49532
49702
  services: config.services,
49533
49703
  // Provide store registry for injecting stores into services
49534
49704
  stores: createStoreRegistry()