@plyaz/core 1.10.1 → 1.11.1

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.
Files changed (36) hide show
  1. package/dist/base/observability/DatadogAdapter.d.ts.map +1 -1
  2. package/dist/domain/example/FrontendExampleDomainService.d.ts.map +1 -1
  3. package/dist/domain/featureFlags/module.d.ts.map +1 -1
  4. package/dist/domain/files/BackendFilesDomainService.d.ts +10 -13
  5. package/dist/domain/files/BackendFilesDomainService.d.ts.map +1 -1
  6. package/dist/entry-backend.js +382 -200
  7. package/dist/entry-backend.js.map +1 -1
  8. package/dist/entry-backend.mjs +280 -98
  9. package/dist/entry-backend.mjs.map +1 -1
  10. package/dist/entry-frontend-browser.js +122 -35
  11. package/dist/entry-frontend-browser.js.map +1 -1
  12. package/dist/entry-frontend-browser.mjs +123 -33
  13. package/dist/entry-frontend-browser.mjs.map +1 -1
  14. package/dist/entry-frontend.js +122 -35
  15. package/dist/entry-frontend.js.map +1 -1
  16. package/dist/entry-frontend.mjs +123 -33
  17. package/dist/entry-frontend.mjs.map +1 -1
  18. package/dist/frontend/providers/PlyazProvider.d.ts +1 -3
  19. package/dist/frontend/providers/PlyazProvider.d.ts.map +1 -1
  20. package/dist/index.js +1339 -1134
  21. package/dist/index.js.map +1 -1
  22. package/dist/index.mjs +1290 -1085
  23. package/dist/index.mjs.map +1 -1
  24. package/dist/init/CoreInitializer.d.ts +31 -1
  25. package/dist/init/CoreInitializer.d.ts.map +1 -1
  26. package/dist/init/nestjs/index.js +126 -14
  27. package/dist/init/nestjs/index.js.map +1 -1
  28. package/dist/init/nestjs/index.mjs +127 -15
  29. package/dist/init/nestjs/index.mjs.map +1 -1
  30. package/dist/services/DbService.d.ts +12 -0
  31. package/dist/services/DbService.d.ts.map +1 -1
  32. package/dist/services/NotificationService.d.ts +8 -0
  33. package/dist/services/NotificationService.d.ts.map +1 -1
  34. package/dist/services/StorageService.d.ts +8 -0
  35. package/dist/services/StorageService.d.ts.map +1 -1
  36. package/package.json +3 -3
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,
@@ -35497,6 +35497,24 @@ var Core = class _Core {
35497
35497
  */
35498
35498
  this._rootStore = null;
35499
35499
  }
35500
+ static {
35501
+ /**
35502
+ * Injected store hook for frontend (set via setRootStoreHook before initialize).
35503
+ * This is used instead of directly importing from @plyaz/store to prevent
35504
+ * duplicate store instances when bundlers create multiple module copies.
35505
+ */
35506
+ this._injectedStoreHook = null;
35507
+ }
35508
+ /**
35509
+ * Set the root store hook for frontend use.
35510
+ * Must be called before Core.initialize() when running in browser.
35511
+ * PlyazProvider calls this automatically with the store prop.
35512
+ *
35513
+ * @param store - The useRootStore hook from @plyaz/store
35514
+ */
35515
+ static setRootStoreHook(store) {
35516
+ _Core._injectedStoreHook = store;
35517
+ }
35500
35518
  /**
35501
35519
  * Setup environment and context
35502
35520
  */
@@ -36407,8 +36425,14 @@ var Core = class _Core {
36407
36425
  static async initializeRootStore(config, verbose) {
36408
36426
  const isFrontend = typeof window !== "undefined";
36409
36427
  if (isFrontend) {
36410
- _Core.log("Using frontend root store (Zustand)", verbose);
36411
- _Core._rootStore = store.useRootStore;
36428
+ if (!_Core._injectedStoreHook) {
36429
+ throw new errors.CorePackageError(
36430
+ "Root store hook not set. Call Core.setRootStoreHook(useRootStore) before Core.initialize(), or use PlyazProvider with the store prop: <PlyazProvider store={useRootStore} ...>",
36431
+ types.ERROR_CODES.CLIENT_INITIALIZATION_FAILED
36432
+ );
36433
+ }
36434
+ _Core.log("Using frontend root store (Zustand) - injected via setRootStoreHook", verbose);
36435
+ _Core._rootStore = _Core._injectedStoreHook;
36412
36436
  } else {
36413
36437
  _Core.log("Creating backend composite store (in-memory)", verbose);
36414
36438
  const ServerErrorMiddleware = getCoreDependency("ServerErrorMiddleware");
@@ -36517,26 +36541,76 @@ var Core = class _Core {
36517
36541
  errorStore,
36518
36542
  _Core.buildErrorHandlerConfig(_Core._errorConfig)
36519
36543
  );
36544
+ _Core.setupErrorEventSubscription(verbose);
36545
+ _Core.setupErrorStoreSubscription(verbose);
36546
+ _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
36547
+ if (_Core._errorConfig.httpHandler !== false) {
36548
+ await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
36549
+ }
36550
+ }
36551
+ /**
36552
+ * Log serialized errors with full details.
36553
+ */
36554
+ static logErrors(errors, prefix = "ErrorStore") {
36555
+ for (const err of errors) {
36556
+ _Core.logger.error(`[${prefix}] ${err.code}: ${err.message}`, {
36557
+ id: err.id,
36558
+ code: err.code,
36559
+ message: err.message,
36560
+ category: err.category,
36561
+ source: err.source,
36562
+ status: err.status,
36563
+ isRetryable: err.isRetryable,
36564
+ context: err.context,
36565
+ timestamp: err.timestamp
36566
+ });
36567
+ }
36568
+ }
36569
+ /**
36570
+ * Setup SYSTEM.ERROR event subscription for error store updates.
36571
+ * Backend: Also logs errors with full details.
36572
+ * Frontend: Only updates store (logging handled by store subscription).
36573
+ */
36574
+ static setupErrorEventSubscription(verbose) {
36575
+ const isBackend = types.BACKEND_RUNTIMES.includes(_Core._coreServices.runtime);
36520
36576
  const errorEventCleanup = CoreEventManager.on(
36521
36577
  core.CORE_EVENTS.SYSTEM.ERROR,
36522
36578
  (event) => {
36523
36579
  if (!_Core._rootStore) return;
36524
36580
  try {
36525
36581
  const { errors } = event.data;
36526
- if (errors && errors.length > 0) {
36527
- _Core._rootStore.getState().errors.addErrors(errors);
36528
- _Core.log(`Added ${errors.length} error(s) to store`, verbose);
36529
- }
36582
+ if (!errors || errors.length === 0) return;
36583
+ _Core._rootStore.getState().errors.addErrors(errors);
36584
+ if (isBackend) _Core.logErrors(errors);
36585
+ _Core.log(`Added ${errors.length} error(s) to store`, verbose);
36530
36586
  } catch (e) {
36531
36587
  _Core.logger.error("Failed to handle error event", { error: e });
36532
36588
  }
36533
36589
  }
36534
36590
  );
36535
36591
  _Core._eventCleanupFns.push(errorEventCleanup);
36536
- _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
36537
- if (_Core._errorConfig.httpHandler !== false) {
36538
- await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
36539
- }
36592
+ }
36593
+ /**
36594
+ * Setup error store subscription for frontend logging.
36595
+ * Logs new errors when they're added to the store.
36596
+ * Only active for non-backend runtimes (browser, nextjs, nuxt, edge).
36597
+ */
36598
+ static setupErrorStoreSubscription(verbose) {
36599
+ const isFrontend = !types.BACKEND_RUNTIMES.includes(_Core._coreServices.runtime);
36600
+ if (!isFrontend || !_Core._rootStore) return;
36601
+ let prevErrorCount = 0;
36602
+ const storeUnsubscribe = _Core._rootStore.subscribe((state) => {
36603
+ const currentCount = state.errors.errorCount;
36604
+ if (currentCount <= prevErrorCount) {
36605
+ prevErrorCount = currentCount;
36606
+ return;
36607
+ }
36608
+ const newErrors = state.errors.errors.slice(0, currentCount - prevErrorCount);
36609
+ _Core.logErrors(newErrors, "ErrorStore:FE");
36610
+ prevErrorCount = currentCount;
36611
+ });
36612
+ _Core._eventCleanupFns.push(storeUnsubscribe);
36613
+ _Core.log("Error store subscription initialized for frontend", verbose);
36540
36614
  }
36541
36615
  /**
36542
36616
  * Create HTTP error handler based on detected runtime.
@@ -38490,7 +38564,7 @@ var BaseFrontendDomainService = class _BaseFrontendDomainService extends BaseDom
38490
38564
  }
38491
38565
  };
38492
38566
  var logger2 = new logger$1.PackageLogger({ packageName: "core", service: "EventPersistence" });
38493
- var BackendEventPersistenceHandler = class {
38567
+ (class {
38494
38568
  static {
38495
38569
  __name(this, "BackendEventPersistenceHandler");
38496
38570
  }
@@ -38664,7 +38738,7 @@ var BackendEventPersistenceHandler = class {
38664
38738
  static getRegisteredDomains() {
38665
38739
  return Array.from(this.registeredDomains.keys());
38666
38740
  }
38667
- };
38741
+ });
38668
38742
  var logger3 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendEventPersistence" });
38669
38743
  var FrontendEventPersistenceHandler = class {
38670
38744
  static {
@@ -43341,6 +43415,7 @@ var ApiFeatureFlagProvider = class extends FeatureFlagProvider {
43341
43415
  return this.engine.getRules();
43342
43416
  }
43343
43417
  };
43418
+ var logger4 = new logger$1.PackageLogger({ packageName: "core", service: "DbService" });
43344
43419
  var DEFAULT_ENCRYPTION_FIELDS = {
43345
43420
  // User PII
43346
43421
  users: ["password_hash", "phone_number", "date_of_birth"],
@@ -43434,6 +43509,41 @@ var DbService = class _DbService {
43434
43509
  };
43435
43510
  CoreEventManager.emit(core.CORE_EVENTS.DATABASE.ERROR, payload);
43436
43511
  }
43512
+ /**
43513
+ * Creates merged event handlers that wrap user-provided handlers.
43514
+ * Adds Core-level logging and forwards to user handlers.
43515
+ *
43516
+ * Note: Unlike StorageService/NotificationService, DB events don't emit
43517
+ * to CoreEventManager by default (too verbose). User handlers can emit
43518
+ * if needed.
43519
+ *
43520
+ * @param userHandlers - User-provided event handlers from config
43521
+ * @returns Merged handlers with Core logging + user handlers
43522
+ */
43523
+ static createMergedEventHandlers(userHandlers) {
43524
+ return {
43525
+ ...userHandlers,
43526
+ onAfterWrite: /* @__PURE__ */ __name(async (event) => {
43527
+ logger4.debug("Database write completed", {
43528
+ operation: event.operation,
43529
+ table: event.table,
43530
+ duration: event.duration
43531
+ });
43532
+ if (userHandlers?.onAfterWrite) {
43533
+ await userHandlers.onAfterWrite(event);
43534
+ }
43535
+ }, "onAfterWrite"),
43536
+ onAfterRead: /* @__PURE__ */ __name(async (event) => {
43537
+ logger4.debug("Database read completed", {
43538
+ table: event.table,
43539
+ duration: event.duration
43540
+ });
43541
+ if (userHandlers?.onAfterRead) {
43542
+ await userHandlers.onAfterRead(event);
43543
+ }
43544
+ }, "onAfterRead")
43545
+ };
43546
+ }
43437
43547
  /**
43438
43548
  * Gets the singleton instance of DbService
43439
43549
  *
@@ -43638,6 +43748,8 @@ var DbService = class _DbService {
43638
43748
  if (cache) dbConfig.cache = cache;
43639
43749
  if (audit) dbConfig.audit = audit;
43640
43750
  if (encryption) dbConfig.encryption = encryption;
43751
+ const events = _DbService.createMergedEventHandlers(config.events);
43752
+ if (events) dbConfig.events = events;
43641
43753
  return dbConfig;
43642
43754
  }
43643
43755
  /**
@@ -44879,8 +44991,9 @@ function buildDatabaseProviderConfig(config) {
44879
44991
  __name(buildDatabaseProviderConfig, "buildDatabaseProviderConfig");
44880
44992
  function buildRedisProviderConfig(config) {
44881
44993
  if (!config.redis?.url) {
44882
- throw new Error(
44883
- 'Redis URL is required when using redis provider. Provide redis: { url: "redis://..." }'
44994
+ throw new errors.CorePackageError(
44995
+ 'Redis URL is required when using redis provider. Provide redis: { url: "redis://..." }',
44996
+ types.ERROR_CODES.CORE_CONFIGURATION_INVALID
44884
44997
  );
44885
44998
  }
44886
44999
  return {
@@ -46203,7 +46316,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46203
46316
  // Constructor
46204
46317
  // ─────────────────────────────────────────────────────────────────────────
46205
46318
  constructor(config = {}, options) {
46206
- const apiBasePath = config.apiBasePath || "/api/examples";
46319
+ const apiBasePath = config.apiBasePath || "";
46207
46320
  super({
46208
46321
  serviceName: "ExampleFrontendService",
46209
46322
  supportedRuntimes: ["frontend"],
@@ -46228,20 +46341,20 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46228
46341
  // Note: Use relative paths since apiClient.baseURL is already set to apiBasePath
46229
46342
  fetchers: {
46230
46343
  fetchAll: /* @__PURE__ */ __name(async (query) => {
46231
- return this.apiClient.get("", { params: query });
46344
+ return this.apiClient.get("/examples", { params: query });
46232
46345
  }, "fetchAll"),
46233
46346
  fetchById: /* @__PURE__ */ __name(async (id) => {
46234
- return this.apiClient.get(`/${id}`);
46347
+ return this.apiClient.get(`/examples/${id}`);
46235
46348
  }, "fetchById"),
46236
46349
  create: /* @__PURE__ */ __name(async (data) => {
46237
- return this.apiClient.post("", data);
46350
+ return this.apiClient.post("/examples", data);
46238
46351
  }, "create"),
46239
46352
  update: /* @__PURE__ */ __name(async (payload) => {
46240
46353
  const { id, data } = payload;
46241
- return this.apiClient.patch(`/${id}`, data);
46354
+ return this.apiClient.patch(`/examples/${id}`, data);
46242
46355
  }, "update"),
46243
46356
  delete: /* @__PURE__ */ __name(async (id) => {
46244
- return this.apiClient.delete(`/${id}`);
46357
+ return this.apiClient.delete(`/examples/${id}`);
46245
46358
  }, "delete")
46246
46359
  }
46247
46360
  // Store handlers - customize how data syncs to store
@@ -46309,7 +46422,7 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46309
46422
  // The ?? operator only falls through on null/undefined, not empty strings.
46310
46423
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
46311
46424
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
46312
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api/examples"
46425
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
46313
46426
  };
46314
46427
  const service = new _FrontendExampleDomainService(mergedConfig, options);
46315
46428
  if (mergedConfig.autoFetch) {
@@ -46470,7 +46583,10 @@ var FrontendExampleDomainService = class _FrontendExampleDomainService extends B
46470
46583
  const response = await this.apiClient.post(endpoint, data);
46471
46584
  if (!this.isResponseSuccess(response)) {
46472
46585
  const error = this.extractResponseError(response);
46473
- throw new Error(`Failed to send email: ${JSON.stringify(error)}`);
46586
+ throw new errors.CorePackageError(
46587
+ `Failed to send email: ${JSON.stringify(error)}`,
46588
+ types.ERROR_CODES.CORE_OPERATION_FAILED
46589
+ );
46474
46590
  }
46475
46591
  const result2 = this.unwrapResponseData(response.data);
46476
46592
  CoreEventManager.emit(`${this.eventPrefix}:email:sent`, { result: result2 });
@@ -46690,337 +46806,712 @@ var CacheService = class _CacheService {
46690
46806
  }
46691
46807
  };
46692
46808
  var getCacheService = /* @__PURE__ */ __name(() => CacheService.getInstance(), "getCacheService");
46693
- var StorageService = class _StorageService {
46694
- constructor() {
46695
- this.storageService = null;
46696
- this.config = null;
46697
- this.initialized = false;
46698
- }
46809
+ var TABLE_NAME = "media";
46810
+ var DEFAULT_LIMIT2 = 100;
46811
+ var FilesRepository = class _FilesRepository extends db.BaseRepository {
46699
46812
  static {
46700
- __name(this, "StorageService");
46813
+ __name(this, "FilesRepository");
46701
46814
  }
46702
- static {
46703
- this.instance = null;
46815
+ constructor(db) {
46816
+ super(db, TABLE_NAME);
46704
46817
  }
46705
- // ─────────────────────────────────────────────────────────────────
46706
- // Error Handling
46707
- // ─────────────────────────────────────────────────────────────────
46818
+ // ─────────────────────────────────────────────────────────────────────────
46819
+ // Static Factory
46820
+ // ─────────────────────────────────────────────────────────────────────────
46708
46821
  /**
46709
- * Emits a storage error event via CoreEventManager.
46710
- * Called when storage operations fail to integrate with global error handling.
46822
+ * Create repository instance.
46823
+ * Uses DbService if initialized.
46711
46824
  */
46712
- emitStorageError(error, operation, options) {
46713
- const payload = {
46714
- error,
46715
- operation,
46716
- fileId: options?.fileId,
46717
- filename: options?.filename,
46718
- recoverable: options?.recoverable ?? false
46825
+ static create() {
46826
+ if (!DbService.isInitialized()) {
46827
+ throw new errors.DatabasePackageError(
46828
+ "DbService not initialized. Call DbService.initialize() first.",
46829
+ errors$1.DATABASE_ERROR_CODES.CONNECTION_FAILED
46830
+ );
46831
+ }
46832
+ const db = DbService.getInstance().getDatabase();
46833
+ return new _FilesRepository(db);
46834
+ }
46835
+ // ─────────────────────────────────────────────────────────────────────────
46836
+ // Overridden Methods (with domain-specific logic)
46837
+ // ─────────────────────────────────────────────────────────────────────────
46838
+ /**
46839
+ * Find multiple entities with default sorting
46840
+ */
46841
+ // eslint-disable-next-line complexity
46842
+ async findMany(options, config) {
46843
+ const mergedOptions = {
46844
+ sort: options?.sort ?? [{ field: "created_at", direction: "desc" }],
46845
+ pagination: {
46846
+ limit: options?.pagination?.limit ?? DEFAULT_LIMIT2,
46847
+ offset: options?.pagination?.offset ?? 0
46848
+ },
46849
+ filter: options?.filter
46719
46850
  };
46720
- CoreEventManager.emit(core.CORE_EVENTS.STORAGE.ERROR, payload);
46851
+ return super.findMany(mergedOptions, config);
46721
46852
  }
46722
- // ─────────────────────────────────────────────────────────────────
46723
- // Singleton Management
46724
- // ─────────────────────────────────────────────────────────────────
46725
46853
  /**
46726
- * Gets the singleton instance of StorageService
46854
+ * Create new file record with auto-generated ID and timestamps
46727
46855
  */
46728
- static getInstance() {
46729
- _StorageService.instance ??= new _StorageService();
46730
- return _StorageService.instance;
46856
+ // eslint-disable-next-line complexity
46857
+ async create(data, config) {
46858
+ const now = (/* @__PURE__ */ new Date()).toISOString();
46859
+ const id = data.id ?? generateId();
46860
+ const row = {
46861
+ id,
46862
+ user_id: data.user_id ?? "",
46863
+ type: data.type ?? "OTHER",
46864
+ filename: data.filename ?? "",
46865
+ original_filename: data.original_filename ?? data.filename ?? "",
46866
+ mime_type: data.mime_type ?? "application/octet-stream",
46867
+ file_size: data.file_size ?? 0,
46868
+ storage_path: data.storage_path ?? "",
46869
+ cdn_url: data.cdn_url ?? null,
46870
+ width: data.width ?? null,
46871
+ height: data.height ?? null,
46872
+ duration: data.duration ?? null,
46873
+ is_virus_scanned: data.is_virus_scanned ?? false,
46874
+ virus_scan_result: data.virus_scan_result ?? null,
46875
+ metadata: data.metadata ?? null,
46876
+ variants: data.variants ?? null,
46877
+ entity_type: data.entity_type ?? null,
46878
+ entity_id: data.entity_id ?? null,
46879
+ access_level: data.access_level ?? null,
46880
+ created_at: now,
46881
+ updated_at: now,
46882
+ deleted_at: null
46883
+ };
46884
+ return super.create(row, config);
46731
46885
  }
46732
46886
  /**
46733
- * Checks if the storage service has been initialized
46887
+ * Get the table name for this repository
46734
46888
  */
46735
- static isInitialized() {
46736
- return _StorageService.instance?.initialized ?? false;
46889
+ getTableName() {
46890
+ return TABLE_NAME;
46737
46891
  }
46892
+ // ─────────────────────────────────────────────────────────────────────────
46893
+ // Domain-Specific Methods
46894
+ // ─────────────────────────────────────────────────────────────────────────
46738
46895
  /**
46739
- * Resets the storage service by clearing the singleton instance
46896
+ * Find all files for a user
46740
46897
  */
46741
- static async reset() {
46742
- if (_StorageService.instance) {
46743
- _StorageService.instance.storageService = null;
46744
- _StorageService.instance.config = null;
46745
- _StorageService.instance.initialized = false;
46746
- _StorageService.instance = null;
46747
- }
46898
+ async findByUserId(userId, options) {
46899
+ return this.findMany({
46900
+ filter: { field: "user_id", operator: "eq", value: userId },
46901
+ pagination: {
46902
+ limit: options?.limit ?? DEFAULT_LIMIT2,
46903
+ offset: options?.offset ?? 0
46904
+ }
46905
+ });
46748
46906
  }
46749
46907
  /**
46750
- * Initializes the storage service
46751
- *
46752
- * @param config - Storage service configuration
46753
- * @returns The initialized StorageService instance
46908
+ * Find files for a specific entity (polymorphic association)
46754
46909
  */
46755
- static async initialize(config) {
46756
- const instance = _StorageService.getInstance();
46757
- if (instance.initialized) {
46758
- return instance;
46759
- }
46760
- instance.config = config;
46761
- instance.storageService = new storage$1.StorageService(config);
46762
- instance.initialized = true;
46763
- return instance;
46910
+ async findByEntityId(entityType, entityId, options) {
46911
+ return this.query().where("entity_type", "eq", entityType).andWhere("entity_id", "eq", entityId).whereNull("deleted_at").orderByDesc("created_at").paginate(
46912
+ Math.floor((options?.offset ?? 0) / (options?.limit ?? DEFAULT_LIMIT2)) + 1,
46913
+ options?.limit ?? DEFAULT_LIMIT2
46914
+ ).execute();
46764
46915
  }
46765
46916
  /**
46766
- * Gets the raw underlying storage service instance without error handling wrapper.
46767
- * Use this only if you need direct access to the underlying service.
46768
- *
46769
- * @returns The raw StorageService instance from @plyaz/storage
46770
- * @throws {StoragePackageError} When storage is not initialized
46917
+ * Find file by storage path (for deduplication checks)
46771
46918
  */
46772
- getRawStorage() {
46773
- if (!this.storageService) {
46774
- throw new errors.StoragePackageError(
46775
- "Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
46776
- errors$1.STORAGE_ERROR_CODES.INITIALIZATION_FAILED
46777
- );
46778
- }
46779
- return this.storageService;
46919
+ async findByStoragePath(storagePath) {
46920
+ return this.findOne({ field: "storage_path", operator: "eq", value: storagePath });
46780
46921
  }
46781
- // ─────────────────────────────────────────────────────────────────
46782
- // Service Access
46783
- // ─────────────────────────────────────────────────────────────────
46784
46922
  /**
46785
- * Gets the storage service with automatic error handling.
46786
- * All method calls are wrapped with try/catch and emit error events on failure.
46787
- * Any method added to @plyaz/storage will be automatically available.
46788
- *
46789
- * @example
46790
- * ```typescript
46791
- * const storage = StorageService.getInstance().getStorage();
46792
- * await storage.uploadFile({ file, filename: 'doc.pdf' });
46793
- * await storage.deleteFile({ fileId: '123' });
46794
- * ```
46795
- *
46796
- * @returns StorageServiceImpl with automatic error handling
46923
+ * Count files for a user
46797
46924
  */
46798
- getStorage() {
46799
- const self2 = this;
46800
- return new Proxy({}, {
46801
- get(_, prop) {
46802
- if (typeof prop === "symbol") {
46803
- return void 0;
46804
- }
46805
- const storage = self2.getRawStorage();
46806
- const value = storage[prop];
46807
- if (typeof value !== "function") {
46808
- return value;
46809
- }
46810
- return (...args) => {
46811
- try {
46812
- const result2 = value.apply(storage, args);
46813
- if (result2 instanceof Promise) {
46814
- return result2.catch((error) => {
46815
- self2.emitStorageError(error, prop, { recoverable: true });
46816
- throw error;
46817
- });
46818
- }
46819
- return result2;
46820
- } catch (error) {
46821
- self2.emitStorageError(error, prop, { recoverable: true });
46822
- throw error;
46823
- }
46824
- };
46825
- }
46826
- });
46925
+ async countByUserId(userId) {
46926
+ return this.count({ field: "user_id", operator: "eq", value: userId });
46827
46927
  }
46828
- // ─────────────────────────────────────────────────────────────────
46829
- // Health Check (special handling for response transformation)
46830
- // ─────────────────────────────────────────────────────────────────
46831
46928
  /**
46832
- * Performs a health check on the storage service by checking all adapter health.
46833
- * This method has special handling to transform the response format.
46929
+ * Find files by MIME type
46834
46930
  */
46835
- async healthCheck() {
46836
- const startTime = Date.now();
46837
- try {
46838
- const storage = this.getRawStorage();
46839
- if (typeof storage.checkAllAdaptersHealth === "function") {
46840
- await storage.checkAllAdaptersHealth();
46931
+ async findByMimeType(mimeType, options) {
46932
+ return this.findMany({
46933
+ filter: { field: "mime_type", operator: "eq", value: mimeType },
46934
+ pagination: {
46935
+ limit: options?.limit ?? DEFAULT_LIMIT2,
46936
+ offset: options?.offset ?? 0
46841
46937
  }
46842
- const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
46843
- const responseTime = Date.now() - startTime;
46844
- return {
46845
- // Check if all adapters are healthy (healthy count equals total count)
46846
- isHealthy: summary ? summary.healthy === summary.total : true,
46847
- responseTime,
46848
- error: void 0
46849
- };
46850
- } catch (error) {
46851
- this.emitStorageError(error, "healthCheck", { recoverable: true });
46852
- return {
46853
- isHealthy: false,
46854
- responseTime: Date.now() - startTime,
46855
- error: error instanceof Error ? error.message : "Unknown error"
46856
- };
46857
- }
46938
+ });
46858
46939
  }
46859
- // ─────────────────────────────────────────────────────────────────
46860
- // Lifecycle
46861
- // ─────────────────────────────────────────────────────────────────
46862
46940
  /**
46863
- * Gets the current configuration
46941
+ * Find files by type (IMAGE, VIDEO, DOCUMENT, AUDIO)
46864
46942
  */
46865
- getConfig() {
46866
- return this.config;
46943
+ async findByType(type, options) {
46944
+ return this.findMany({
46945
+ filter: { field: "type", operator: "eq", value: type },
46946
+ pagination: {
46947
+ limit: options?.limit ?? DEFAULT_LIMIT2,
46948
+ offset: options?.offset ?? 0
46949
+ }
46950
+ });
46951
+ }
46952
+ };
46953
+
46954
+ // src/domain/files/mappers/FilesMapper.ts
46955
+ var FilesMapperClass = class _FilesMapperClass extends BaseMapper {
46956
+ static {
46957
+ __name(this, "FilesMapperClass");
46867
46958
  }
46868
46959
  /**
46869
- * Closes the storage service and cleans up resources
46960
+ * Type guard for UploadResult from event payloads
46961
+ * Validates that the value has the required structure for DB mapping
46870
46962
  */
46871
- async close() {
46872
- if (this.storageService) {
46873
- await this.storageService.destroy();
46874
- }
46875
- this.storageService = null;
46876
- this.initialized = false;
46877
- this.config = null;
46963
+ static isUploadResult(value) {
46964
+ if (!isObject(value)) return false;
46965
+ const obj = value;
46966
+ if (!isObject(obj.metadata)) return false;
46967
+ const metadata = obj.metadata;
46968
+ return typeof metadata.fileId === "string" && typeof metadata.filename === "string" && typeof metadata.mimeType === "string" && typeof metadata.size === "number" && typeof metadata.path === "string";
46878
46969
  }
46879
46970
  /**
46880
- * Creates a dedicated storage service instance (NOT the singleton)
46881
- *
46882
- * Use this when you need an isolated storage connection with its own configuration.
46883
- *
46884
- * @param config - Storage service configuration
46885
- * @returns Promise that resolves to a new dedicated StorageService instance
46971
+ * Infer file type from MIME type
46886
46972
  */
46887
- static async createInstance(config) {
46888
- const dedicatedInstance = new _StorageService();
46889
- dedicatedInstance.config = config;
46890
- dedicatedInstance.storageService = new storage$1.StorageService(config);
46891
- dedicatedInstance.initialized = true;
46892
- return dedicatedInstance;
46893
- }
46894
- };
46895
- var NotificationService = class _NotificationService {
46896
- constructor() {
46897
- this.notificationService = null;
46898
- this.config = null;
46899
- this.initialized = false;
46900
- }
46901
- static {
46902
- __name(this, "NotificationService");
46903
- }
46904
- static {
46905
- this.instance = null;
46973
+ static inferFileType(mimeType) {
46974
+ if (mimeType.startsWith("image/")) return "IMAGE";
46975
+ if (mimeType.startsWith("video/")) return "VIDEO";
46976
+ if (mimeType.startsWith("audio/")) return "AUDIO";
46977
+ if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("text/")) {
46978
+ return "DOCUMENT";
46979
+ }
46980
+ return "OTHER";
46906
46981
  }
46907
- // ─────────────────────────────────────────────────────────────────
46908
- // Error Handling
46909
- // ─────────────────────────────────────────────────────────────────
46910
46982
  /**
46911
- * Emits a notification error event via CoreEventManager.
46912
- * Called when notification operations fail to integrate with global error handling.
46983
+ * API Response Domain Entity
46913
46984
  */
46914
- emitNotificationError(error, operation, options) {
46915
- const payload = {
46916
- error,
46917
- operation,
46918
- recipientId: options?.recipientId,
46919
- channel: options?.channel,
46920
- recoverable: options?.recoverable ?? false
46985
+ toDomain(dto) {
46986
+ const type = _FilesMapperClass.inferFileType(dto.mimeType);
46987
+ return {
46988
+ id: dto.id,
46989
+ key: dto.key,
46990
+ filename: dto.filename,
46991
+ mimeType: dto.mimeType,
46992
+ size: dto.size,
46993
+ checksum: dto.checksum,
46994
+ url: dto.url,
46995
+ publicUrl: dto.publicUrl,
46996
+ signedUrl: dto.signedUrl,
46997
+ bucket: dto.bucket,
46998
+ entityType: dto.entityType,
46999
+ entityId: dto.entityId,
47000
+ category: dto.category,
47001
+ uploadedAt: new Date(dto.uploadedAt),
47002
+ expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : void 0,
47003
+ variants: dto.variants,
47004
+ type,
47005
+ isImage: type === "IMAGE",
47006
+ isVideo: type === "VIDEO",
47007
+ isDocument: type === "DOCUMENT",
47008
+ isAudio: type === "AUDIO"
46921
47009
  };
46922
- CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.ERROR, payload);
46923
47010
  }
46924
- // ─────────────────────────────────────────────────────────────────
46925
- // Singleton Management
46926
- // ─────────────────────────────────────────────────────────────────
46927
47011
  /**
46928
- * Gets the singleton instance of NotificationService
47012
+ * Domain Entity Store State (serializable)
46929
47013
  */
46930
- static getInstance() {
46931
- _NotificationService.instance ??= new _NotificationService();
46932
- return _NotificationService.instance;
47014
+ toStoreState(entity) {
47015
+ return {
47016
+ id: entity.id,
47017
+ key: entity.key,
47018
+ filename: entity.filename,
47019
+ mimeType: entity.mimeType,
47020
+ size: entity.size,
47021
+ url: entity.url,
47022
+ publicUrl: entity.publicUrl,
47023
+ bucket: entity.bucket,
47024
+ entityType: entity.entityType,
47025
+ entityId: entity.entityId,
47026
+ category: entity.category,
47027
+ uploadedAt: entity.uploadedAt.toISOString(),
47028
+ type: entity.type,
47029
+ isImage: entity.isImage,
47030
+ isVideo: entity.isVideo,
47031
+ isDocument: entity.isDocument,
47032
+ isAudio: entity.isAudio
47033
+ };
46933
47034
  }
46934
47035
  /**
46935
- * Checks if the notification service has been initialized
47036
+ * Store State Domain Entity
46936
47037
  */
46937
- static isInitialized() {
46938
- return _NotificationService.instance?.initialized ?? false;
47038
+ fromStoreState(state) {
47039
+ return {
47040
+ id: state.id,
47041
+ key: state.key,
47042
+ filename: state.filename,
47043
+ mimeType: state.mimeType,
47044
+ size: state.size,
47045
+ url: state.url,
47046
+ publicUrl: state.publicUrl,
47047
+ bucket: state.bucket,
47048
+ entityType: state.entityType,
47049
+ entityId: state.entityId,
47050
+ category: state.category,
47051
+ uploadedAt: new Date(state.uploadedAt),
47052
+ type: state.type,
47053
+ isImage: state.isImage,
47054
+ isVideo: state.isVideo,
47055
+ isDocument: state.isDocument,
47056
+ isAudio: state.isAudio
47057
+ };
46939
47058
  }
46940
47059
  /**
46941
- * Resets the notification service by clearing the singleton instance
47060
+ * Database Row API Response (for backend)
46942
47061
  */
46943
- static async reset() {
46944
- if (_NotificationService.instance) {
46945
- _NotificationService.instance.notificationService = null;
46946
- _NotificationService.instance.config = null;
46947
- _NotificationService.instance.initialized = false;
46948
- _NotificationService.instance = null;
46949
- }
47062
+ toResponseDTO(row) {
47063
+ return {
47064
+ id: row.id,
47065
+ key: row.storage_path,
47066
+ filename: row.filename,
47067
+ mimeType: row.mime_type,
47068
+ size: toNumber(row.file_size),
47069
+ checksum: void 0,
47070
+ url: row.cdn_url ?? void 0,
47071
+ publicUrl: row.cdn_url ?? void 0,
47072
+ signedUrl: void 0,
47073
+ bucket: "media",
47074
+ entityType: row.entity_type ?? void 0,
47075
+ entityId: row.entity_id ?? void 0,
47076
+ category: void 0,
47077
+ uploadedAt: row.created_at,
47078
+ expiresAt: void 0
47079
+ };
46950
47080
  }
46951
47081
  /**
46952
- * Initializes the notification service
46953
- *
46954
- * @param config - Notification service configuration
46955
- * @returns The initialized NotificationService instance
47082
+ * Create GeneratedFileItem from generation result
46956
47083
  */
46957
- static async initialize(config) {
46958
- const instance = _NotificationService.getInstance();
46959
- if (instance.initialized) {
46960
- return instance;
46961
- }
46962
- instance.config = config;
46963
- instance.notificationService = new notifications.NotificationService(config);
46964
- instance.initialized = true;
46965
- return instance;
47084
+ toGeneratedFileItem(templateId, buffer, size, outputFormat = "pdf") {
47085
+ return {
47086
+ id: crypto.randomUUID(),
47087
+ templateId,
47088
+ buffer,
47089
+ size,
47090
+ mimeType: `application/${outputFormat}`,
47091
+ filename: `${templateId}.${outputFormat}`,
47092
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
47093
+ };
46966
47094
  }
46967
47095
  /**
46968
- * Gets the raw underlying notification service instance without error handling wrapper.
46969
- * Use this only if you need direct access to the underlying service.
47096
+ * Convert upload result to database row format
47097
+ * Used by event handlers to persist upload results to DB
46970
47098
  *
46971
- * @returns The raw NotificationService instance from @plyaz/notifications
46972
- * @throws {NotificationsPackageError} When notifications is not initialized
47099
+ * @param uploadResult - Result from storage upload
47100
+ * @param userId - User ID who uploaded the file (from auth context)
47101
+ * @returns Partial database row ready for repository.create()
46973
47102
  */
46974
- getRawNotifications() {
46975
- if (!this.notificationService) {
46976
- throw new errors.NotificationPackageError(
46977
- "Notifications not initialized. Call NotificationService.initialize() first or use Core.initialize() with notifications config.",
46978
- errors$1.NOTIFICATION_ERROR_CODES.INITIALIZATION_FAILED
47103
+ // eslint-disable-next-line complexity
47104
+ toDbRow(uploadResult, userId) {
47105
+ const { metadata, variants } = uploadResult;
47106
+ return {
47107
+ id: metadata.fileId,
47108
+ user_id: userId ?? "system",
47109
+ type: _FilesMapperClass.inferFileType(metadata.mimeType),
47110
+ filename: metadata.filename,
47111
+ original_filename: metadata.filename,
47112
+ mime_type: metadata.mimeType,
47113
+ file_size: metadata.size,
47114
+ storage_path: metadata.path,
47115
+ cdn_url: metadata.url ?? null,
47116
+ width: metadata.extractedMetadata?.width ?? null,
47117
+ height: metadata.extractedMetadata?.height ?? null,
47118
+ duration: metadata.extractedMetadata?.duration ?? null,
47119
+ entity_type: metadata.entityType,
47120
+ entity_id: metadata.entityId,
47121
+ access_level: metadata.accessLevel,
47122
+ is_virus_scanned: false,
47123
+ virus_scan_result: null,
47124
+ metadata: metadata.customMetadata ?? null,
47125
+ variants: variants ?? null
47126
+ };
47127
+ }
47128
+ };
47129
+ var FilesMapper = new FilesMapperClass();
47130
+
47131
+ // src/domain/files/validators/FilesValidator.ts
47132
+ var FilesValidatorClass = class extends BaseValidator {
47133
+ static {
47134
+ __name(this, "FilesValidatorClass");
47135
+ }
47136
+ constructor() {
47137
+ super({});
47138
+ }
47139
+ /**
47140
+ * Check if user has permission to access file
47141
+ */
47142
+ validateUserAccess(userId, fileOwnerId, accessLevel, isAdmin = false) {
47143
+ if (isAdmin) return true;
47144
+ if (userId === fileOwnerId) return true;
47145
+ if (accessLevel === "public") return true;
47146
+ if (accessLevel === "protected" && userId) return true;
47147
+ return false;
47148
+ }
47149
+ };
47150
+ new FilesValidatorClass();
47151
+
47152
+ // src/domain/files/BackendFilesDomainService.ts
47153
+ var BackendFilesDomainService = class _BackendFilesDomainService extends BaseBackendDomainService {
47154
+ constructor(config = {}, injected) {
47155
+ super({
47156
+ serviceName: "BackendFilesDomainService",
47157
+ supportedRuntimes: ["backend"],
47158
+ serviceConfig: {
47159
+ enabled: true,
47160
+ throwOnValidationError: true,
47161
+ throwOnRepositoryError: true,
47162
+ emitEvents: true,
47163
+ ...config
47164
+ },
47165
+ mapperClass: FilesMapperClass,
47166
+ validatorClass: FilesValidatorClass,
47167
+ injected
47168
+ });
47169
+ this.eventPrefix = "files";
47170
+ this.cachePrefix = "files";
47171
+ }
47172
+ static {
47173
+ __name(this, "BackendFilesDomainService");
47174
+ }
47175
+ static {
47176
+ this.serviceKey = "files";
47177
+ }
47178
+ /**
47179
+ * Lazy repository getter - creates repository on first access.
47180
+ * Throws if DbService is not initialized.
47181
+ */
47182
+ get repository() {
47183
+ this._repository ??= FilesRepository.create();
47184
+ return this._repository;
47185
+ }
47186
+ /**
47187
+ * Create service instance
47188
+ */
47189
+ // eslint-disable-next-line complexity
47190
+ static async create(config = {}, options) {
47191
+ const injected = {
47192
+ cache: options?.cache?.instance,
47193
+ db: options?.db?.instance,
47194
+ api: options?.apiClient?.instance,
47195
+ storage: options?.storage?.instance,
47196
+ notifications: options?.notifications?.instance
47197
+ };
47198
+ return new _BackendFilesDomainService(config, injected);
47199
+ }
47200
+ isAvailable() {
47201
+ return this.isServiceEnabled;
47202
+ }
47203
+ dispose() {
47204
+ this.logInfo("Service disposed");
47205
+ }
47206
+ // ─────────────────────────────────────────────────────────────────────────
47207
+ // Storage operations (via @plyaz/storage)
47208
+ // ─────────────────────────────────────────────────────────────────────────
47209
+ /**
47210
+ * Download file content
47211
+ * GET /files/:id/download
47212
+ *
47213
+ * Follows same pattern as base class CRUD methods:
47214
+ * 1. assertReady
47215
+ * 2. Record start time
47216
+ * 3. Log debug info
47217
+ * 4. Emit starting event
47218
+ * 5. Execute operation
47219
+ * 6. Emit success event
47220
+ * 7. Record metrics
47221
+ *
47222
+ * @param params - Download parameters (fileId required)
47223
+ * @returns Download result with buffer, filename, mimeType
47224
+ */
47225
+ // eslint-disable-next-line complexity
47226
+ async downloadFile(params) {
47227
+ this.assertReady();
47228
+ const startTime = Date.now();
47229
+ this.logDebug("Downloading file", { fileId: params.fileId });
47230
+ this.emitEvent("download:downloading", { request: params });
47231
+ try {
47232
+ await this.beforeDownloadFile?.(params);
47233
+ if (!this.storageService) {
47234
+ throw new errors.CorePackageError(
47235
+ "Storage service not available. Ensure storage is configured in Core.initialize().",
47236
+ types.ERROR_CODES.CORE_PROVIDER_INITIALIZATION_FAILED
47237
+ );
47238
+ }
47239
+ const storage = this.storageService.getStorage();
47240
+ const result2 = await storage.downloadFile({
47241
+ fileId: params.fileId
47242
+ });
47243
+ const response = {
47244
+ buffer: result2?.file?.toString("base64") ?? "",
47245
+ filename: result2?.metadata?.filename ?? "download",
47246
+ mimeType: result2?.metadata?.mimeType ?? "application/octet-stream"
47247
+ };
47248
+ await this.afterDownloadFile?.(response);
47249
+ this.emitEvent("download:downloaded", { result: response });
47250
+ this.emitEvent("complete", { success: true, operation: "downloadFile" });
47251
+ await this.recordOperationMetrics("downloadFile", Date.now() - startTime, true);
47252
+ return response;
47253
+ } catch (error) {
47254
+ await this.recordOperationMetrics("downloadFile", Date.now() - startTime, false);
47255
+ this.emitEvent("download:error", { error });
47256
+ throw error;
47257
+ }
47258
+ }
47259
+ /**
47260
+ * Get signed URL for file
47261
+ * GET /files/:id/signed-url
47262
+ *
47263
+ * Follows same pattern as base class CRUD methods.
47264
+ *
47265
+ * @param params - Signed URL parameters (fileId required)
47266
+ * @returns Signed URL result with url and expiresAt
47267
+ */
47268
+ // eslint-disable-next-line complexity
47269
+ async getSignedUrl(params) {
47270
+ this.assertReady();
47271
+ const startTime = Date.now();
47272
+ this.logDebug("Getting signed URL", { fileId: params.fileId, expiresIn: params.expiresIn });
47273
+ this.emitEvent("signedUrl:requesting", { request: params });
47274
+ try {
47275
+ await this.beforeGetSignedUrl?.(params);
47276
+ if (!this.storageService) {
47277
+ throw new errors.CorePackageError(
47278
+ "Storage service not available. Ensure storage is configured in Core.initialize().",
47279
+ types.ERROR_CODES.CORE_PROVIDER_INITIALIZATION_FAILED
47280
+ );
47281
+ }
47282
+ const storage = this.storageService.getStorage();
47283
+ const result2 = await storage.getSignedUrl({
47284
+ fileId: params.fileId,
47285
+ expiresIn: params.expiresIn ?? config.DOWNLOAD_CONFIG.DEFAULT_SIGNED_URL_EXPIRY_SECONDS,
47286
+ operation: "get"
47287
+ });
47288
+ const response = {
47289
+ url: result2?.url ?? "",
47290
+ expiresAt: result2?.expiresAt?.toISOString() ?? new Date(Date.now() + config.TIME_CONSTANTS.HOUR).toISOString()
47291
+ };
47292
+ await this.afterGetSignedUrl?.(response);
47293
+ this.emitEvent("signedUrl:received", { result: response });
47294
+ this.emitEvent("complete", { success: true, operation: "getSignedUrl" });
47295
+ await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, true);
47296
+ return response;
47297
+ } catch (error) {
47298
+ await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, false);
47299
+ this.emitEvent("signedUrl:error", { error });
47300
+ throw error;
47301
+ }
47302
+ }
47303
+ // ─────────────────────────────────────────────────────────────────────────
47304
+ // DB Persistence (called by StorageService on upload complete)
47305
+ // ─────────────────────────────────────────────────────────────────────────
47306
+ /**
47307
+ * Persist uploaded file metadata to the media table.
47308
+ * Called by StorageService.onFileUploaded event handler.
47309
+ *
47310
+ * @param metadata - File metadata from storage upload
47311
+ * @param variants - Optional file variants (thumbnails, etc.)
47312
+ * @param userId - Optional user ID who uploaded the file
47313
+ */
47314
+ async persistUploadedFile(metadata, variants, userId) {
47315
+ const startTime = Date.now();
47316
+ this.logDebug("Persisting uploaded file to DB", {
47317
+ fileId: metadata.fileId,
47318
+ filename: metadata.filename
47319
+ });
47320
+ try {
47321
+ const dbRow = FilesMapper.toDbRow({ metadata, variants }, userId);
47322
+ await this.repository.create(dbRow);
47323
+ this.logDebug("File persisted to media table", {
47324
+ fileId: metadata.fileId,
47325
+ filename: metadata.filename
47326
+ });
47327
+ await this.recordOperationMetrics("persistUploadedFile", Date.now() - startTime, true);
47328
+ } catch (error) {
47329
+ await this.recordOperationMetrics("persistUploadedFile", Date.now() - startTime, false);
47330
+ throw error;
47331
+ }
47332
+ }
47333
+ // ─────────────────────────────────────────────────────────────────────────
47334
+ // NOTE: CRUD and Storage Methods Are Inherited from BaseBackendDomainService
47335
+ // ─────────────────────────────────────────────────────────────────────────
47336
+ //
47337
+ // CRUD methods:
47338
+ // - getById(id) - for GET /files/:id
47339
+ // - getAll(query) - for listing files
47340
+ // - delete(id) - for DELETE /files/:id
47341
+ // - exists(id) - check if file exists
47342
+ //
47343
+ // Storage methods (inherited):
47344
+ // - uploadFile(params) - for POST /files/upload
47345
+ // - generateFile(params) - for POST /files/generate-document
47346
+ // ─────────────────────────────────────────────────────────────────────────
47347
+ };
47348
+ var backendFilesDomainService = new BackendFilesDomainService();
47349
+ var _instance = null;
47350
+ async function getBackendFilesDomainService(config, options) {
47351
+ _instance ??= await BackendFilesDomainService.create(config, options);
47352
+ return _instance;
47353
+ }
47354
+ __name(getBackendFilesDomainService, "getBackendFilesDomainService");
47355
+
47356
+ // src/services/StorageService.ts
47357
+ var logger5 = new logger$1.PackageLogger({ packageName: "core", service: "StorageService" });
47358
+ var StorageService = class _StorageService {
47359
+ constructor() {
47360
+ this.storageService = null;
47361
+ this.config = null;
47362
+ this.initialized = false;
47363
+ }
47364
+ static {
47365
+ __name(this, "StorageService");
47366
+ }
47367
+ static {
47368
+ this.instance = null;
47369
+ }
47370
+ // ─────────────────────────────────────────────────────────────────
47371
+ // Error Handling
47372
+ // ─────────────────────────────────────────────────────────────────
47373
+ /**
47374
+ * Emits a storage error event via CoreEventManager.
47375
+ * Called when storage operations fail to integrate with global error handling.
47376
+ */
47377
+ emitStorageError(error, operation, options) {
47378
+ const payload = {
47379
+ error,
47380
+ operation,
47381
+ fileId: options?.fileId,
47382
+ filename: options?.filename,
47383
+ recoverable: options?.recoverable ?? false
47384
+ };
47385
+ CoreEventManager.emit(core.CORE_EVENTS.STORAGE.ERROR, payload);
47386
+ }
47387
+ // ─────────────────────────────────────────────────────────────────
47388
+ // Singleton Management
47389
+ // ─────────────────────────────────────────────────────────────────
47390
+ /**
47391
+ * Gets the singleton instance of StorageService
47392
+ */
47393
+ static getInstance() {
47394
+ _StorageService.instance ??= new _StorageService();
47395
+ return _StorageService.instance;
47396
+ }
47397
+ /**
47398
+ * Checks if the storage service has been initialized
47399
+ */
47400
+ static isInitialized() {
47401
+ return _StorageService.instance?.initialized ?? false;
47402
+ }
47403
+ /**
47404
+ * Resets the storage service by clearing the singleton instance
47405
+ */
47406
+ static async reset() {
47407
+ if (_StorageService.instance) {
47408
+ _StorageService.instance.storageService = null;
47409
+ _StorageService.instance.config = null;
47410
+ _StorageService.instance.initialized = false;
47411
+ _StorageService.instance = null;
47412
+ }
47413
+ }
47414
+ /**
47415
+ * Creates merged event handlers that persist uploads to the media table.
47416
+ * Merges Core's internal DB persistence handler with user-provided handlers.
47417
+ *
47418
+ * @param userHandlers - User-provided event handlers from config
47419
+ * @returns Merged handlers with DB persistence + user handlers
47420
+ */
47421
+ static createMergedEventHandlers(userHandlers) {
47422
+ return {
47423
+ ...userHandlers,
47424
+ // Persist uploaded files to media table, then call user handler
47425
+ onFileUploaded: /* @__PURE__ */ __name(async (payload) => {
47426
+ if (payload.metadata) {
47427
+ const filesService = await getBackendFilesDomainService();
47428
+ await filesService.persistUploadedFile(payload.metadata);
47429
+ }
47430
+ if (userHandlers?.onFileUploaded) {
47431
+ await userHandlers.onFileUploaded(payload);
47432
+ }
47433
+ }, "onFileUploaded")
47434
+ };
47435
+ }
47436
+ /**
47437
+ * Initializes the storage service
47438
+ *
47439
+ * @param config - Storage service configuration
47440
+ * @returns The initialized StorageService instance
47441
+ */
47442
+ static async initialize(config) {
47443
+ const instance = _StorageService.getInstance();
47444
+ if (instance.initialized) {
47445
+ return instance;
47446
+ }
47447
+ instance.config = config;
47448
+ const mergedHandlers = _StorageService.createMergedEventHandlers(config.handlers);
47449
+ const mergedConfig = {
47450
+ ...config,
47451
+ handlers: mergedHandlers
47452
+ };
47453
+ logger5.info("[StorageService] Initializing with merged event handlers for DB persistence");
47454
+ instance.storageService = new storage$1.StorageService(mergedConfig);
47455
+ instance.initialized = true;
47456
+ return instance;
47457
+ }
47458
+ /**
47459
+ * Gets the raw underlying storage service instance without error handling wrapper.
47460
+ * Use this only if you need direct access to the underlying service.
47461
+ *
47462
+ * @returns The raw StorageService instance from @plyaz/storage
47463
+ * @throws {StoragePackageError} When storage is not initialized
47464
+ */
47465
+ getRawStorage() {
47466
+ if (!this.storageService) {
47467
+ throw new errors.StoragePackageError(
47468
+ "Storage not initialized. Call StorageService.initialize() first or use Core.initialize() with storage config.",
47469
+ errors$1.STORAGE_ERROR_CODES.INITIALIZATION_FAILED
46979
47470
  );
46980
47471
  }
46981
- return this.notificationService;
47472
+ return this.storageService;
46982
47473
  }
46983
47474
  // ─────────────────────────────────────────────────────────────────
46984
47475
  // Service Access
46985
47476
  // ─────────────────────────────────────────────────────────────────
46986
47477
  /**
46987
- * Gets the notification service with automatic error handling.
47478
+ * Gets the storage service with automatic error handling.
46988
47479
  * All method calls are wrapped with try/catch and emit error events on failure.
46989
- * Any method added to @plyaz/notifications will be automatically available.
47480
+ * Any method added to @plyaz/storage will be automatically available.
46990
47481
  *
46991
47482
  * @example
46992
47483
  * ```typescript
46993
- * const notifications = NotificationService.getInstance().getNotifications();
46994
- * await notifications.sendEmail({ to: 'user@example.com', templateId: 'welcome' });
46995
- * await notifications.sendSMS({ to: '+1234567890', message: 'Hello!' });
47484
+ * const storage = StorageService.getInstance().getStorage();
47485
+ * await storage.uploadFile({ file, filename: 'doc.pdf' });
47486
+ * await storage.deleteFile({ fileId: '123' });
46996
47487
  * ```
46997
47488
  *
46998
- * @returns NotificationServiceImpl with automatic error handling
47489
+ * @returns StorageServiceImpl with automatic error handling
46999
47490
  */
47000
- getNotifications() {
47491
+ getStorage() {
47001
47492
  const self2 = this;
47002
47493
  return new Proxy({}, {
47003
47494
  get(_, prop) {
47004
47495
  if (typeof prop === "symbol") {
47005
47496
  return void 0;
47006
47497
  }
47007
- const notifications = self2.getRawNotifications();
47008
- const value = notifications[prop];
47498
+ const storage = self2.getRawStorage();
47499
+ const value = storage[prop];
47009
47500
  if (typeof value !== "function") {
47010
47501
  return value;
47011
47502
  }
47012
47503
  return (...args) => {
47013
47504
  try {
47014
- const result2 = value.apply(notifications, args);
47505
+ const result2 = value.apply(storage, args);
47015
47506
  if (result2 instanceof Promise) {
47016
47507
  return result2.catch((error) => {
47017
- self2.emitNotificationError(error, prop, { recoverable: true });
47508
+ self2.emitStorageError(error, prop, { recoverable: true });
47018
47509
  throw error;
47019
47510
  });
47020
47511
  }
47021
47512
  return result2;
47022
47513
  } catch (error) {
47023
- self2.emitNotificationError(error, prop, { recoverable: true });
47514
+ self2.emitStorageError(error, prop, { recoverable: true });
47024
47515
  throw error;
47025
47516
  }
47026
47517
  };
@@ -47031,23 +47522,29 @@ var NotificationService = class _NotificationService {
47031
47522
  // Health Check (special handling for response transformation)
47032
47523
  // ─────────────────────────────────────────────────────────────────
47033
47524
  /**
47034
- * Performs a health check on the notification service.
47525
+ * Performs a health check on the storage service by checking all adapter health.
47035
47526
  * This method has special handling to transform the response format.
47036
47527
  */
47037
47528
  async healthCheck() {
47529
+ const startTime = Date.now();
47038
47530
  try {
47039
- const notifications = this.getRawNotifications();
47040
- const result2 = await notifications.healthCheck();
47531
+ const storage = this.getRawStorage();
47532
+ if (typeof storage.checkAllAdaptersHealth === "function") {
47533
+ await storage.checkAllAdaptersHealth();
47534
+ }
47535
+ const summary = typeof storage.getHealthSummary === "function" ? storage.getHealthSummary() : null;
47536
+ const responseTime = Date.now() - startTime;
47041
47537
  return {
47042
- isHealthy: result2.healthy,
47043
- providers: result2.providers,
47044
- // Use optional chaining since error may not exist on all health check results
47045
- error: "error" in result2 ? result2.error : void 0
47538
+ // Check if all adapters are healthy (healthy count equals total count)
47539
+ isHealthy: summary ? summary.healthy === summary.total : true,
47540
+ responseTime,
47541
+ error: void 0
47046
47542
  };
47047
47543
  } catch (error) {
47048
- this.emitNotificationError(error, "healthCheck", { recoverable: true });
47544
+ this.emitStorageError(error, "healthCheck", { recoverable: true });
47049
47545
  return {
47050
47546
  isHealthy: false,
47547
+ responseTime: Date.now() - startTime,
47051
47548
  error: error instanceof Error ? error.message : "Unknown error"
47052
47549
  };
47053
47550
  }
@@ -47062,844 +47559,539 @@ var NotificationService = class _NotificationService {
47062
47559
  return this.config;
47063
47560
  }
47064
47561
  /**
47065
- * Closes the notification service and cleans up resources
47562
+ * Closes the storage service and cleans up resources
47066
47563
  */
47067
47564
  async close() {
47068
- this.notificationService = null;
47565
+ if (this.storageService) {
47566
+ await this.storageService.destroy();
47567
+ }
47568
+ this.storageService = null;
47069
47569
  this.initialized = false;
47070
47570
  this.config = null;
47071
47571
  }
47072
47572
  /**
47073
- * Creates a dedicated notification service instance (NOT the singleton)
47573
+ * Creates a dedicated storage service instance (NOT the singleton)
47074
47574
  *
47075
- * Use this when you need an isolated notification service with its own configuration.
47575
+ * Use this when you need an isolated storage connection with its own configuration.
47076
47576
  *
47077
- * @param config - Notification service configuration
47078
- * @returns Promise that resolves to a new dedicated NotificationService instance
47577
+ * @param config - Storage service configuration
47578
+ * @returns Promise that resolves to a new dedicated StorageService instance
47079
47579
  */
47080
47580
  static async createInstance(config) {
47081
- const dedicatedInstance = new _NotificationService();
47581
+ const dedicatedInstance = new _StorageService();
47082
47582
  dedicatedInstance.config = config;
47083
- dedicatedInstance.notificationService = new notifications.NotificationService(config);
47583
+ dedicatedInstance.storageService = new storage$1.StorageService(config);
47084
47584
  dedicatedInstance.initialized = true;
47085
47585
  return dedicatedInstance;
47086
47586
  }
47087
47587
  };
47088
- var cachedVersion = "";
47089
- function getPackageVersion() {
47090
- if (cachedVersion) return cachedVersion;
47091
- try {
47092
- const require2 = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
47093
- const pkg = require2("../package.json");
47094
- cachedVersion = pkg.version ?? "0.0.0";
47095
- } catch {
47096
- try {
47097
- const currentFile = url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.js', document.baseURI).href)));
47098
- const currentDir = path.dirname(currentFile);
47099
- const pkgPath = path.join(currentDir, "..", "package.json");
47100
- const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
47101
- cachedVersion = pkg.version ?? "0.0.0";
47102
- } catch {
47103
- cachedVersion = "0.0.0";
47104
- }
47105
- }
47106
- return cachedVersion;
47107
- }
47108
- __name(getPackageVersion, "getPackageVersion");
47109
- var VERSION = getPackageVersion();
47110
- var PACKAGE_NAME = "@plyaz/core";
47111
-
47112
- // src/backend/index.ts
47113
- var backend_exports = {};
47114
- __export(backend_exports, {
47115
- BACKEND_EXAMPLE_DOMAIN_SERVICE: () => BACKEND_EXAMPLE_DOMAIN_SERVICE,
47116
- BACKEND_FILES_DOMAIN_SERVICE: () => BACKEND_FILES_DOMAIN_SERVICE,
47117
- BackendExampleDomainService: () => BackendExampleDomainService,
47118
- Caching: () => Caching,
47119
- ErrorHandlingInterceptor: () => ErrorHandlingInterceptor,
47120
- ExampleController: () => ExampleController,
47121
- ExampleModule: () => ExampleModule,
47122
- FeatureDisabled: () => FeatureDisabled,
47123
- FeatureEnabled: () => FeatureEnabled,
47124
- FeatureFlagConfigFactory: () => FeatureFlagConfigFactory,
47125
- FeatureFlagConfigValidator: () => FeatureFlagConfigValidator,
47126
- FeatureFlagController: () => FeatureFlagController,
47127
- FeatureFlagDatabaseRepository: () => FeatureFlagDatabaseRepository,
47128
- FeatureFlagDomainService: () => FeatureFlagDomainService,
47129
- FeatureFlagGuard: () => FeatureFlagGuard,
47130
- FeatureFlagLoggingInterceptor: () => FeatureFlagLoggingInterceptor,
47131
- FeatureFlagMiddleware: () => FeatureFlagMiddleware,
47132
- FeatureFlagModule: () => FeatureFlagModule,
47133
- FeatureFlagService: () => FeatureFlagService,
47134
- FeatureFlagServiceFactory: () => FeatureFlagServiceFactory,
47135
- FilesController: () => FilesController,
47136
- FilesModule: () => FilesModule
47137
- });
47138
-
47139
- // src/backend/example/example.module.ts
47140
- var import_common11 = __toESM(require_common(), 1);
47141
-
47142
- // src/backend/example/example.controller.ts
47143
- var import_common10 = __toESM(require_common(), 1);
47144
- var BACKEND_EXAMPLE_DOMAIN_SERVICE = "BACKEND_EXAMPLE_DOMAIN_SERVICE";
47145
- var ExampleController = class {
47146
- constructor(exampleService) {
47147
- this.exampleService = exampleService;
47148
- }
47149
- health() {
47150
- return errors.SuccessResponseStandard("Service is healthy", {
47151
- service: this.exampleService.isAvailable(),
47152
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
47153
- });
47154
- }
47155
- async getEntity(id) {
47156
- const entity = await this.exampleService.getById(id);
47157
- return errors.SuccessResponseStandard("Entity retrieved successfully", entity);
47158
- }
47159
- async createEntity(dto) {
47160
- const entity = await this.exampleService.create(dto);
47161
- return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47162
- }
47163
- async updateEntity(id, dto) {
47164
- const entity = await this.exampleService.patch(id, dto);
47165
- return errors.SuccessResponseStandard("Entity updated successfully", entity);
47166
- }
47167
- async deleteEntity(id) {
47168
- await this.exampleService.delete(id);
47169
- return errors.SuccessResponseStandard("Entity deleted successfully", null);
47170
- }
47171
- async createEntityWithValidation(dto) {
47172
- const entity = await this.exampleService.create(dto);
47173
- return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47174
- }
47175
- async demoSingleError() {
47176
- return await this.exampleService.demoSingleValidationError();
47177
- }
47178
- async demoArrayErrors() {
47179
- return await this.exampleService.demoMultipleValidationErrors();
47180
- }
47181
- async sendEmail(dto) {
47182
- const result2 = await this.exampleService.sendEmail(dto);
47183
- return errors.SuccessResponseStandard("Email sent successfully", result2);
47184
- }
47185
- };
47186
- __name(ExampleController, "ExampleController");
47187
- __decorateClass([
47188
- (0, import_common10.Get)("health")
47189
- ], ExampleController.prototype, "health", 1);
47190
- __decorateClass([
47191
- (0, import_common10.Get)("entities/:id"),
47192
- __decorateParam(0, (0, import_common10.Param)("id"))
47193
- ], ExampleController.prototype, "getEntity", 1);
47194
- __decorateClass([
47195
- (0, import_common10.Post)("entities"),
47196
- (0, import_common10.HttpCode)(import_common10.HttpStatus.CREATED),
47197
- __decorateParam(0, (0, import_common10.Body)())
47198
- ], ExampleController.prototype, "createEntity", 1);
47199
- __decorateClass([
47200
- (0, import_common10.Patch)("entities/:id"),
47201
- __decorateParam(0, (0, import_common10.Param)("id")),
47202
- __decorateParam(1, (0, import_common10.Body)())
47203
- ], ExampleController.prototype, "updateEntity", 1);
47204
- __decorateClass([
47205
- (0, import_common10.Delete)("entities/:id"),
47206
- (0, import_common10.HttpCode)(import_common10.HttpStatus.OK),
47207
- __decorateParam(0, (0, import_common10.Param)("id"))
47208
- ], ExampleController.prototype, "deleteEntity", 1);
47209
- __decorateClass([
47210
- (0, import_common10.Post)("entities/validated"),
47211
- (0, import_common10.HttpCode)(import_common10.HttpStatus.CREATED),
47212
- __decorateParam(0, (0, import_common10.Body)())
47213
- ], ExampleController.prototype, "createEntityWithValidation", 1);
47214
- __decorateClass([
47215
- (0, import_common10.Get)("errors/single")
47216
- ], ExampleController.prototype, "demoSingleError", 1);
47217
- __decorateClass([
47218
- (0, import_common10.Get)("errors/array")
47219
- ], ExampleController.prototype, "demoArrayErrors", 1);
47220
- __decorateClass([
47221
- (0, import_common10.Post)("email"),
47222
- (0, import_common10.HttpCode)(import_common10.HttpStatus.OK),
47223
- __decorateParam(0, (0, import_common10.Body)())
47224
- ], ExampleController.prototype, "sendEmail", 1);
47225
- ExampleController = __decorateClass([
47226
- (0, import_common10.Controller)("example"),
47227
- __decorateParam(0, (0, import_common10.Inject)(BACKEND_EXAMPLE_DOMAIN_SERVICE))
47228
- ], ExampleController);
47229
-
47230
- // src/backend/example/example.module.ts
47231
- var ExampleModule = class {
47232
- };
47233
- __name(ExampleModule, "ExampleModule");
47234
- ExampleModule = __decorateClass([
47235
- (0, import_common11.Module)({
47236
- controllers: [ExampleController],
47237
- providers: [
47238
- // Provide BackendExampleDomainService via factory using the singleton instance
47239
- // This ensures the service is shared across the application
47240
- {
47241
- provide: BACKEND_EXAMPLE_DOMAIN_SERVICE,
47242
- useFactory: /* @__PURE__ */ __name(() => backendExampleDomainService, "useFactory")
47243
- }
47244
- ],
47245
- exports: [BACKEND_EXAMPLE_DOMAIN_SERVICE]
47246
- })
47247
- ], ExampleModule);
47248
-
47249
- // src/backend/files/files.module.ts
47250
- var import_common17 = __toESM(require_common(), 1);
47251
-
47252
- // src/backend/files/files.controller.ts
47253
- var import_common12 = __toESM(require_common(), 1);
47254
- var BACKEND_FILES_DOMAIN_SERVICE = "BACKEND_FILES_DOMAIN_SERVICE";
47255
- var FilesController = class {
47256
- constructor(filesService) {
47257
- this.filesService = filesService;
47258
- }
47259
- health() {
47260
- return errors.SuccessResponseStandard("Files service is healthy", {
47261
- service: this.filesService.isAvailable(),
47262
- timestamp: (/* @__PURE__ */ new Date()).toISOString()
47263
- });
47264
- }
47265
- async uploadFile(dto) {
47266
- const result2 = await this.filesService.uploadFile(dto);
47267
- return errors.SuccessResponseStandard("File uploaded successfully", result2);
47268
- }
47269
- async uploadFiles(dto) {
47270
- const results = await this.filesService.uploadFiles(dto.files);
47271
- return errors.SuccessResponseStandard("Files uploaded successfully", results);
47272
- }
47273
- async generateDocument(dto) {
47274
- const buffer = await this.filesService.generateFile(dto);
47275
- return errors.SuccessResponseStandard("Document generated successfully", {
47276
- buffer: buffer.toString("base64"),
47277
- size: buffer.length
47278
- });
47279
- }
47280
- async getFile(id) {
47281
- const entity = await this.filesService.getById(id);
47282
- return errors.SuccessResponseStandard("File retrieved", entity);
47283
- }
47284
- async downloadFile(id) {
47285
- const result2 = await this.filesService.downloadFile({ fileId: id });
47286
- return errors.SuccessResponseStandard("File downloaded", result2);
47287
- }
47288
- async getSignedUrl(id) {
47289
- const result2 = await this.filesService.getSignedUrl({ fileId: id });
47290
- return errors.SuccessResponseStandard("Signed URL generated", result2);
47291
- }
47292
- async deleteFile(id) {
47293
- await this.filesService.delete(id);
47294
- return errors.SuccessResponseStandard("File deleted", null);
47295
- }
47296
- };
47297
- __name(FilesController, "FilesController");
47298
- __decorateClass([
47299
- (0, import_common12.Get)("health")
47300
- ], FilesController.prototype, "health", 1);
47301
- __decorateClass([
47302
- (0, import_common12.Post)("upload"),
47303
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47304
- __decorateParam(0, (0, import_common12.Body)())
47305
- ], FilesController.prototype, "uploadFile", 1);
47306
- __decorateClass([
47307
- (0, import_common12.Post)("upload/bulk"),
47308
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47309
- __decorateParam(0, (0, import_common12.Body)())
47310
- ], FilesController.prototype, "uploadFiles", 1);
47311
- __decorateClass([
47312
- (0, import_common12.Post)("generate-document"),
47313
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47314
- __decorateParam(0, (0, import_common12.Body)())
47315
- ], FilesController.prototype, "generateDocument", 1);
47316
- __decorateClass([
47317
- (0, import_common12.Get)(":id"),
47318
- __decorateParam(0, (0, import_common12.Param)("id"))
47319
- ], FilesController.prototype, "getFile", 1);
47320
- __decorateClass([
47321
- (0, import_common12.Get)(":id/download"),
47322
- __decorateParam(0, (0, import_common12.Param)("id"))
47323
- ], FilesController.prototype, "downloadFile", 1);
47324
- __decorateClass([
47325
- (0, import_common12.Get)(":id/signed-url"),
47326
- __decorateParam(0, (0, import_common12.Param)("id"))
47327
- ], FilesController.prototype, "getSignedUrl", 1);
47328
- __decorateClass([
47329
- (0, import_common12.Delete)(":id"),
47330
- (0, import_common12.HttpCode)(import_common12.HttpStatus.OK),
47331
- __decorateParam(0, (0, import_common12.Param)("id"))
47332
- ], FilesController.prototype, "deleteFile", 1);
47333
- FilesController = __decorateClass([
47334
- (0, import_common12.Controller)("files"),
47335
- __decorateParam(0, (0, import_common12.Inject)(BACKEND_FILES_DOMAIN_SERVICE))
47336
- ], FilesController);
47337
- var TABLE_NAME = "media";
47338
- var DEFAULT_LIMIT2 = 100;
47339
- var FilesRepository = class _FilesRepository extends db.BaseRepository {
47340
- static {
47341
- __name(this, "FilesRepository");
47342
- }
47343
- constructor(db) {
47344
- super(db, TABLE_NAME);
47345
- }
47346
- // ─────────────────────────────────────────────────────────────────────────
47347
- // Static Factory
47348
- // ─────────────────────────────────────────────────────────────────────────
47349
- /**
47350
- * Create repository instance.
47351
- * Uses DbService if initialized.
47352
- */
47353
- static create() {
47354
- if (!DbService.isInitialized()) {
47355
- throw new errors.DatabasePackageError(
47356
- "DbService not initialized. Call DbService.initialize() first.",
47357
- errors$1.DATABASE_ERROR_CODES.CONNECTION_FAILED
47358
- );
47359
- }
47360
- const db = DbService.getInstance().getDatabase();
47361
- return new _FilesRepository(db);
47362
- }
47363
- // ─────────────────────────────────────────────────────────────────────────
47364
- // Overridden Methods (with domain-specific logic)
47365
- // ─────────────────────────────────────────────────────────────────────────
47366
- /**
47367
- * Find multiple entities with default sorting
47368
- */
47369
- // eslint-disable-next-line complexity
47370
- async findMany(options, config) {
47371
- const mergedOptions = {
47372
- sort: options?.sort ?? [{ field: "created_at", direction: "desc" }],
47373
- pagination: {
47374
- limit: options?.pagination?.limit ?? DEFAULT_LIMIT2,
47375
- offset: options?.pagination?.offset ?? 0
47376
- },
47377
- filter: options?.filter
47378
- };
47379
- return super.findMany(mergedOptions, config);
47380
- }
47381
- /**
47382
- * Create new file record with auto-generated ID and timestamps
47383
- */
47384
- // eslint-disable-next-line complexity
47385
- async create(data, config) {
47386
- const now = (/* @__PURE__ */ new Date()).toISOString();
47387
- const id = data.id ?? generateId();
47388
- const row = {
47389
- id,
47390
- user_id: data.user_id ?? "",
47391
- type: data.type ?? "OTHER",
47392
- filename: data.filename ?? "",
47393
- original_filename: data.original_filename ?? data.filename ?? "",
47394
- mime_type: data.mime_type ?? "application/octet-stream",
47395
- file_size: data.file_size ?? 0,
47396
- storage_path: data.storage_path ?? "",
47397
- cdn_url: data.cdn_url ?? null,
47398
- width: data.width ?? null,
47399
- height: data.height ?? null,
47400
- duration: data.duration ?? null,
47401
- is_virus_scanned: data.is_virus_scanned ?? false,
47402
- virus_scan_result: data.virus_scan_result ?? null,
47403
- metadata: data.metadata ?? null,
47404
- variants: data.variants ?? null,
47405
- entity_type: data.entity_type ?? null,
47406
- entity_id: data.entity_id ?? null,
47407
- access_level: data.access_level ?? null,
47408
- created_at: now,
47409
- updated_at: now,
47410
- deleted_at: null
47411
- };
47412
- return super.create(row, config);
47413
- }
47414
- /**
47415
- * Get the table name for this repository
47416
- */
47417
- getTableName() {
47418
- return TABLE_NAME;
47419
- }
47420
- // ─────────────────────────────────────────────────────────────────────────
47421
- // Domain-Specific Methods
47422
- // ─────────────────────────────────────────────────────────────────────────
47423
- /**
47424
- * Find all files for a user
47425
- */
47426
- async findByUserId(userId, options) {
47427
- return this.findMany({
47428
- filter: { field: "user_id", operator: "eq", value: userId },
47429
- pagination: {
47430
- limit: options?.limit ?? DEFAULT_LIMIT2,
47431
- offset: options?.offset ?? 0
47432
- }
47433
- });
47434
- }
47435
- /**
47436
- * Find files for a specific entity (polymorphic association)
47437
- */
47438
- async findByEntityId(entityType, entityId, options) {
47439
- return this.query().where("entity_type", "eq", entityType).andWhere("entity_id", "eq", entityId).whereNull("deleted_at").orderByDesc("created_at").paginate(
47440
- Math.floor((options?.offset ?? 0) / (options?.limit ?? DEFAULT_LIMIT2)) + 1,
47441
- options?.limit ?? DEFAULT_LIMIT2
47442
- ).execute();
47443
- }
47444
- /**
47445
- * Find file by storage path (for deduplication checks)
47446
- */
47447
- async findByStoragePath(storagePath) {
47448
- return this.findOne({ field: "storage_path", operator: "eq", value: storagePath });
47449
- }
47450
- /**
47451
- * Count files for a user
47452
- */
47453
- async countByUserId(userId) {
47454
- return this.count({ field: "user_id", operator: "eq", value: userId });
47455
- }
47456
- /**
47457
- * Find files by MIME type
47458
- */
47459
- async findByMimeType(mimeType, options) {
47460
- return this.findMany({
47461
- filter: { field: "mime_type", operator: "eq", value: mimeType },
47462
- pagination: {
47463
- limit: options?.limit ?? DEFAULT_LIMIT2,
47464
- offset: options?.offset ?? 0
47465
- }
47466
- });
47467
- }
47468
- /**
47469
- * Find files by type (IMAGE, VIDEO, DOCUMENT, AUDIO)
47470
- */
47471
- async findByType(type, options) {
47472
- return this.findMany({
47473
- filter: { field: "type", operator: "eq", value: type },
47474
- pagination: {
47475
- limit: options?.limit ?? DEFAULT_LIMIT2,
47476
- offset: options?.offset ?? 0
47477
- }
47478
- });
47588
+ var MAX_FAIL_ATTEMPTS = 3;
47589
+ var NotificationService = class _NotificationService {
47590
+ constructor() {
47591
+ this.notificationService = null;
47592
+ this.config = null;
47593
+ this.initialized = false;
47479
47594
  }
47480
- };
47481
-
47482
- // src/domain/files/mappers/FilesMapper.ts
47483
- var FilesMapperClass = class _FilesMapperClass extends BaseMapper {
47484
47595
  static {
47485
- __name(this, "FilesMapperClass");
47486
- }
47487
- /**
47488
- * Type guard for UploadResult from event payloads
47489
- * Validates that the value has the required structure for DB mapping
47490
- */
47491
- static isUploadResult(value) {
47492
- if (!isObject(value)) return false;
47493
- const obj = value;
47494
- if (!isObject(obj.metadata)) return false;
47495
- const metadata = obj.metadata;
47496
- return typeof metadata.fileId === "string" && typeof metadata.filename === "string" && typeof metadata.mimeType === "string" && typeof metadata.size === "number" && typeof metadata.path === "string";
47497
- }
47498
- /**
47499
- * Infer file type from MIME type
47500
- */
47501
- static inferFileType(mimeType) {
47502
- if (mimeType.startsWith("image/")) return "IMAGE";
47503
- if (mimeType.startsWith("video/")) return "VIDEO";
47504
- if (mimeType.startsWith("audio/")) return "AUDIO";
47505
- if (mimeType.includes("pdf") || mimeType.includes("document") || mimeType.includes("text/")) {
47506
- return "DOCUMENT";
47507
- }
47508
- return "OTHER";
47509
- }
47510
- /**
47511
- * API Response → Domain Entity
47512
- */
47513
- toDomain(dto) {
47514
- const type = _FilesMapperClass.inferFileType(dto.mimeType);
47515
- return {
47516
- id: dto.id,
47517
- key: dto.key,
47518
- filename: dto.filename,
47519
- mimeType: dto.mimeType,
47520
- size: dto.size,
47521
- checksum: dto.checksum,
47522
- url: dto.url,
47523
- publicUrl: dto.publicUrl,
47524
- signedUrl: dto.signedUrl,
47525
- bucket: dto.bucket,
47526
- entityType: dto.entityType,
47527
- entityId: dto.entityId,
47528
- category: dto.category,
47529
- uploadedAt: new Date(dto.uploadedAt),
47530
- expiresAt: dto.expiresAt ? new Date(dto.expiresAt) : void 0,
47531
- variants: dto.variants,
47532
- type,
47533
- isImage: type === "IMAGE",
47534
- isVideo: type === "VIDEO",
47535
- isDocument: type === "DOCUMENT",
47536
- isAudio: type === "AUDIO"
47537
- };
47538
- }
47539
- /**
47540
- * Domain Entity → Store State (serializable)
47541
- */
47542
- toStoreState(entity) {
47543
- return {
47544
- id: entity.id,
47545
- key: entity.key,
47546
- filename: entity.filename,
47547
- mimeType: entity.mimeType,
47548
- size: entity.size,
47549
- url: entity.url,
47550
- publicUrl: entity.publicUrl,
47551
- bucket: entity.bucket,
47552
- entityType: entity.entityType,
47553
- entityId: entity.entityId,
47554
- category: entity.category,
47555
- uploadedAt: entity.uploadedAt.toISOString(),
47556
- type: entity.type,
47557
- isImage: entity.isImage,
47558
- isVideo: entity.isVideo,
47559
- isDocument: entity.isDocument,
47560
- isAudio: entity.isAudio
47561
- };
47562
- }
47563
- /**
47564
- * Store State → Domain Entity
47565
- */
47566
- fromStoreState(state) {
47567
- return {
47568
- id: state.id,
47569
- key: state.key,
47570
- filename: state.filename,
47571
- mimeType: state.mimeType,
47572
- size: state.size,
47573
- url: state.url,
47574
- publicUrl: state.publicUrl,
47575
- bucket: state.bucket,
47576
- entityType: state.entityType,
47577
- entityId: state.entityId,
47578
- category: state.category,
47579
- uploadedAt: new Date(state.uploadedAt),
47580
- type: state.type,
47581
- isImage: state.isImage,
47582
- isVideo: state.isVideo,
47583
- isDocument: state.isDocument,
47584
- isAudio: state.isAudio
47585
- };
47586
- }
47587
- /**
47588
- * Database Row → API Response (for backend)
47589
- */
47590
- toResponseDTO(row) {
47591
- return {
47592
- id: row.id,
47593
- key: row.storage_path,
47594
- filename: row.filename,
47595
- mimeType: row.mime_type,
47596
- size: toNumber(row.file_size),
47597
- checksum: void 0,
47598
- url: row.cdn_url ?? void 0,
47599
- publicUrl: row.cdn_url ?? void 0,
47600
- signedUrl: void 0,
47601
- bucket: "media",
47602
- entityType: row.entity_type ?? void 0,
47603
- entityId: row.entity_id ?? void 0,
47604
- category: void 0,
47605
- uploadedAt: row.created_at,
47606
- expiresAt: void 0
47607
- };
47608
- }
47609
- /**
47610
- * Create GeneratedFileItem from generation result
47611
- */
47612
- toGeneratedFileItem(templateId, buffer, size, outputFormat = "pdf") {
47613
- return {
47614
- id: crypto.randomUUID(),
47615
- templateId,
47616
- buffer,
47617
- size,
47618
- mimeType: `application/${outputFormat}`,
47619
- filename: `${templateId}.${outputFormat}`,
47620
- generatedAt: (/* @__PURE__ */ new Date()).toISOString()
47621
- };
47622
- }
47623
- /**
47624
- * Convert upload result to database row format
47625
- * Used by event handlers to persist upload results to DB
47626
- *
47627
- * @param uploadResult - Result from storage upload
47628
- * @param userId - User ID who uploaded the file (from auth context)
47629
- * @returns Partial database row ready for repository.create()
47630
- */
47631
- // eslint-disable-next-line complexity
47632
- toDbRow(uploadResult, userId) {
47633
- const { metadata, variants } = uploadResult;
47634
- return {
47635
- id: metadata.fileId,
47636
- user_id: userId ?? "system",
47637
- type: _FilesMapperClass.inferFileType(metadata.mimeType),
47638
- filename: metadata.filename,
47639
- original_filename: metadata.filename,
47640
- mime_type: metadata.mimeType,
47641
- file_size: metadata.size,
47642
- storage_path: metadata.path,
47643
- cdn_url: metadata.url ?? null,
47644
- width: metadata.extractedMetadata?.width ?? null,
47645
- height: metadata.extractedMetadata?.height ?? null,
47646
- duration: metadata.extractedMetadata?.duration ?? null,
47647
- entity_type: metadata.entityType,
47648
- entity_id: metadata.entityId,
47649
- access_level: metadata.accessLevel,
47650
- is_virus_scanned: false,
47651
- virus_scan_result: null,
47652
- metadata: metadata.customMetadata ?? null,
47653
- variants: variants ?? null
47654
- };
47596
+ __name(this, "NotificationService");
47655
47597
  }
47656
- };
47657
- var FilesMapper = new FilesMapperClass();
47658
-
47659
- // src/domain/files/validators/FilesValidator.ts
47660
- var FilesValidatorClass = class extends BaseValidator {
47661
47598
  static {
47662
- __name(this, "FilesValidatorClass");
47663
- }
47664
- constructor() {
47665
- super({});
47599
+ this.instance = null;
47666
47600
  }
47601
+ // ─────────────────────────────────────────────────────────────────
47602
+ // Error Handling
47603
+ // ─────────────────────────────────────────────────────────────────
47667
47604
  /**
47668
- * Check if user has permission to access file
47605
+ * Emits a notification error event via CoreEventManager.
47606
+ * Called when notification operations fail to integrate with global error handling.
47669
47607
  */
47670
- validateUserAccess(userId, fileOwnerId, accessLevel, isAdmin = false) {
47671
- if (isAdmin) return true;
47672
- if (userId === fileOwnerId) return true;
47673
- if (accessLevel === "public") return true;
47674
- if (accessLevel === "protected" && userId) return true;
47675
- return false;
47676
- }
47677
- };
47678
- new FilesValidatorClass();
47679
-
47680
- // src/domain/files/BackendFilesDomainService.ts
47681
- var BackendFilesDomainService = class _BackendFilesDomainService extends BaseBackendDomainService {
47682
- constructor(config = {}, injected) {
47683
- super({
47684
- serviceName: "BackendFilesDomainService",
47685
- supportedRuntimes: ["backend"],
47686
- serviceConfig: {
47687
- enabled: true,
47688
- throwOnValidationError: true,
47689
- throwOnRepositoryError: true,
47690
- emitEvents: true,
47691
- ...config
47692
- },
47693
- mapperClass: FilesMapperClass,
47694
- validatorClass: FilesValidatorClass,
47695
- injected
47696
- });
47697
- this.eventPrefix = "files";
47698
- this.cachePrefix = "files";
47608
+ emitNotificationError(error, operation, options) {
47609
+ const payload = {
47610
+ error,
47611
+ operation,
47612
+ recipientId: options?.recipientId,
47613
+ channel: options?.channel,
47614
+ recoverable: options?.recoverable ?? false
47615
+ };
47616
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.ERROR, payload);
47699
47617
  }
47700
- static {
47701
- __name(this, "BackendFilesDomainService");
47618
+ // ─────────────────────────────────────────────────────────────────
47619
+ // Merged Event Handlers
47620
+ // ─────────────────────────────────────────────────────────────────
47621
+ /**
47622
+ * Creates merged event handlers that emit to CoreEventManager.
47623
+ * Merges Core's internal handlers with user-provided handlers.
47624
+ *
47625
+ * @param userHandlers - User-provided event handlers from config
47626
+ * @returns Merged handlers with Core event emission + user handlers
47627
+ */
47628
+ static createMergedEventHandlers(userHandlers) {
47629
+ return {
47630
+ ...userHandlers,
47631
+ // Emit to CoreEventManager on sent, then call user handler
47632
+ onSent: /* @__PURE__ */ __name(async (payload) => {
47633
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.SENT, {
47634
+ notificationId: payload.notificationId,
47635
+ recipientId: payload.recipientId,
47636
+ channel: payload.channel,
47637
+ provider: payload.provider
47638
+ });
47639
+ if (userHandlers?.onSent) {
47640
+ await userHandlers.onSent(payload);
47641
+ }
47642
+ }, "onSent"),
47643
+ // Emit to CoreEventManager on failed, then call user handler
47644
+ onFailed: /* @__PURE__ */ __name(async (payload) => {
47645
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.FAILED, {
47646
+ notificationId: payload.notificationId,
47647
+ recipientId: payload.recipientId,
47648
+ channel: payload.channel,
47649
+ error: payload.error,
47650
+ retryable: payload.retryCount < MAX_FAIL_ATTEMPTS
47651
+ });
47652
+ if (userHandlers?.onFailed) {
47653
+ await userHandlers.onFailed(payload);
47654
+ }
47655
+ }, "onFailed"),
47656
+ // Emit to CoreEventManager on delivered, then call user handler
47657
+ onDelivered: /* @__PURE__ */ __name(async (payload) => {
47658
+ CoreEventManager.emit(core.CORE_EVENTS.NOTIFICATION.DELIVERED, {
47659
+ notificationId: payload.notificationId,
47660
+ recipientId: payload.recipientId,
47661
+ channel: payload.channel,
47662
+ deliveredAt: payload.deliveredAt
47663
+ });
47664
+ if (userHandlers?.onDelivered) {
47665
+ await userHandlers.onDelivered(payload);
47666
+ }
47667
+ }, "onDelivered")
47668
+ };
47702
47669
  }
47703
- static {
47704
- this.serviceKey = "files";
47670
+ // ─────────────────────────────────────────────────────────────────
47671
+ // Singleton Management
47672
+ // ─────────────────────────────────────────────────────────────────
47673
+ /**
47674
+ * Gets the singleton instance of NotificationService
47675
+ */
47676
+ static getInstance() {
47677
+ _NotificationService.instance ??= new _NotificationService();
47678
+ return _NotificationService.instance;
47705
47679
  }
47706
47680
  /**
47707
- * Lazy repository getter - creates repository on first access.
47708
- * Throws if DbService is not initialized.
47681
+ * Checks if the notification service has been initialized
47709
47682
  */
47710
- get repository() {
47711
- this._repository ??= FilesRepository.create();
47712
- return this._repository;
47683
+ static isInitialized() {
47684
+ return _NotificationService.instance?.initialized ?? false;
47713
47685
  }
47714
47686
  /**
47715
- * Create service instance
47687
+ * Resets the notification service by clearing the singleton instance
47716
47688
  */
47717
- // eslint-disable-next-line complexity
47718
- static async create(config = {}, options) {
47719
- this.registerEventHandlers();
47720
- const injected = {
47721
- cache: options?.cache?.instance,
47722
- db: options?.db?.instance,
47723
- api: options?.apiClient?.instance,
47724
- storage: options?.storage?.instance,
47725
- notifications: options?.notifications?.instance
47726
- };
47727
- return new _BackendFilesDomainService(config, injected);
47689
+ static async reset() {
47690
+ if (_NotificationService.instance) {
47691
+ _NotificationService.instance.notificationService = null;
47692
+ _NotificationService.instance.config = null;
47693
+ _NotificationService.instance.initialized = false;
47694
+ _NotificationService.instance = null;
47695
+ }
47728
47696
  }
47729
47697
  /**
47730
- * Register event handlers for file upload persistence.
47731
- * Called by CoreInitializer after storage and DB are initialized.
47732
- *
47733
- * Events handled:
47734
- * - files:upload:uploaded - Persist single file to DB
47735
- * - files:upload:bulk:uploaded - Persist multiple files to DB
47736
- *
47737
- * Deduplication: Uses unique constraint on storage_path.
47698
+ * Initializes the notification service
47738
47699
  *
47739
- * @param verbose - Enable verbose logging
47700
+ * @param config - Notification service configuration
47701
+ * @returns The initialized NotificationService instance
47740
47702
  */
47741
- static registerEventHandlers(verbose) {
47742
- if (BackendEventPersistenceHandler.isRegistered("files")) {
47743
- return;
47703
+ static async initialize(config) {
47704
+ const instance = _NotificationService.getInstance();
47705
+ if (instance.initialized) {
47706
+ return instance;
47744
47707
  }
47745
- BackendEventPersistenceHandler.register({
47746
- domain: "files",
47747
- events: {
47748
- "files:upload:uploaded": {
47749
- extractPayload: /* @__PURE__ */ __name((payload) => {
47750
- if (!isObject(payload)) return void 0;
47751
- const p = payload;
47752
- if (!FilesMapperClass.isUploadResult(p.result)) return void 0;
47753
- return p.result;
47754
- }, "extractPayload"),
47755
- isBulk: false,
47756
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
47757
- },
47758
- "files:upload:bulk:uploaded": {
47759
- extractPayload: /* @__PURE__ */ __name((payload) => {
47760
- if (!isObject(payload)) return [];
47761
- const p = payload;
47762
- const results = p.result?.results;
47763
- if (!Array.isArray(results)) return [];
47764
- return results.filter(FilesMapperClass.isUploadResult);
47765
- }, "extractPayload"),
47766
- isBulk: true,
47767
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
47768
- }
47769
- },
47770
- loadDependencies: /* @__PURE__ */ __name(async () => ({
47771
- repository: FilesRepository.create(),
47772
- mapper: FilesMapper
47773
- }), "loadDependencies"),
47774
- mapToDbRow: /* @__PURE__ */ __name((mapper, item) => mapper.toDbRow(item), "mapToDbRow"),
47775
- createRecord: /* @__PURE__ */ __name(async (repository, dbRow) => {
47776
- return repository.create(dbRow);
47777
- }, "createRecord"),
47778
- getUniqueKey: /* @__PURE__ */ __name((item) => item.metadata.path ?? item.metadata.fileId, "getUniqueKey"),
47779
- verbose
47708
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
47709
+ instance.config = config;
47710
+ instance.notificationService = new notifications.NotificationService({
47711
+ ...config,
47712
+ events: mergedEvents
47780
47713
  });
47714
+ instance.initialized = true;
47715
+ return instance;
47781
47716
  }
47782
- isAvailable() {
47783
- return this.isServiceEnabled;
47784
- }
47785
- dispose() {
47786
- this.logInfo("Service disposed");
47717
+ /**
47718
+ * Gets the raw underlying notification service instance without error handling wrapper.
47719
+ * Use this only if you need direct access to the underlying service.
47720
+ *
47721
+ * @returns The raw NotificationService instance from @plyaz/notifications
47722
+ * @throws {NotificationsPackageError} When notifications is not initialized
47723
+ */
47724
+ getRawNotifications() {
47725
+ if (!this.notificationService) {
47726
+ throw new errors.NotificationPackageError(
47727
+ "Notifications not initialized. Call NotificationService.initialize() first or use Core.initialize() with notifications config.",
47728
+ errors$1.NOTIFICATION_ERROR_CODES.INITIALIZATION_FAILED
47729
+ );
47730
+ }
47731
+ return this.notificationService;
47787
47732
  }
47788
- // ─────────────────────────────────────────────────────────────────────────
47789
- // Storage operations (via @plyaz/storage)
47790
- // ─────────────────────────────────────────────────────────────────────────
47733
+ // ─────────────────────────────────────────────────────────────────
47734
+ // Service Access
47735
+ // ─────────────────────────────────────────────────────────────────
47791
47736
  /**
47792
- * Download file content
47793
- * GET /files/:id/download
47737
+ * Gets the notification service with automatic error handling.
47738
+ * All method calls are wrapped with try/catch and emit error events on failure.
47739
+ * Any method added to @plyaz/notifications will be automatically available.
47794
47740
  *
47795
- * Follows same pattern as base class CRUD methods:
47796
- * 1. assertReady
47797
- * 2. Record start time
47798
- * 3. Log debug info
47799
- * 4. Emit starting event
47800
- * 5. Execute operation
47801
- * 6. Emit success event
47802
- * 7. Record metrics
47741
+ * @example
47742
+ * ```typescript
47743
+ * const notifications = NotificationService.getInstance().getNotifications();
47744
+ * await notifications.sendEmail({ to: 'user@example.com', templateId: 'welcome' });
47745
+ * await notifications.sendSMS({ to: '+1234567890', message: 'Hello!' });
47746
+ * ```
47803
47747
  *
47804
- * @param params - Download parameters (fileId required)
47805
- * @returns Download result with buffer, filename, mimeType
47748
+ * @returns NotificationServiceImpl with automatic error handling
47806
47749
  */
47807
- // eslint-disable-next-line complexity
47808
- async downloadFile(params) {
47809
- this.assertReady();
47810
- const startTime = Date.now();
47811
- this.logDebug("Downloading file", { fileId: params.fileId });
47812
- this.emitEvent("download:downloading", { request: params });
47813
- try {
47814
- await this.beforeDownloadFile?.(params);
47815
- if (!this.storageService) {
47816
- throw new Error(
47817
- "Storage service not available. Ensure storage is configured in Core.initialize()."
47818
- );
47750
+ getNotifications() {
47751
+ const self2 = this;
47752
+ return new Proxy({}, {
47753
+ get(_, prop) {
47754
+ if (typeof prop === "symbol") {
47755
+ return void 0;
47756
+ }
47757
+ const notifications = self2.getRawNotifications();
47758
+ const value = notifications[prop];
47759
+ if (typeof value !== "function") {
47760
+ return value;
47761
+ }
47762
+ return (...args) => {
47763
+ try {
47764
+ const result2 = value.apply(notifications, args);
47765
+ if (result2 instanceof Promise) {
47766
+ return result2.catch((error) => {
47767
+ self2.emitNotificationError(error, prop, { recoverable: true });
47768
+ throw error;
47769
+ });
47770
+ }
47771
+ return result2;
47772
+ } catch (error) {
47773
+ self2.emitNotificationError(error, prop, { recoverable: true });
47774
+ throw error;
47775
+ }
47776
+ };
47819
47777
  }
47820
- const storage = this.storageService.getStorage();
47821
- const result2 = await storage.downloadFile({
47822
- fileId: params.fileId
47823
- });
47824
- const response = {
47825
- buffer: result2?.file?.toString("base64") ?? "",
47826
- filename: result2?.metadata?.filename ?? "download",
47827
- mimeType: result2?.metadata?.mimeType ?? "application/octet-stream"
47778
+ });
47779
+ }
47780
+ // ─────────────────────────────────────────────────────────────────
47781
+ // Health Check (special handling for response transformation)
47782
+ // ─────────────────────────────────────────────────────────────────
47783
+ /**
47784
+ * Performs a health check on the notification service.
47785
+ * This method has special handling to transform the response format.
47786
+ */
47787
+ async healthCheck() {
47788
+ try {
47789
+ const notifications = this.getRawNotifications();
47790
+ const result2 = await notifications.healthCheck();
47791
+ return {
47792
+ isHealthy: result2.healthy,
47793
+ providers: result2.providers,
47794
+ // Use optional chaining since error may not exist on all health check results
47795
+ error: "error" in result2 ? result2.error : void 0
47828
47796
  };
47829
- await this.afterDownloadFile?.(response);
47830
- this.emitEvent("download:downloaded", { result: response });
47831
- this.emitEvent("complete", { success: true, operation: "downloadFile" });
47832
- await this.recordOperationMetrics("downloadFile", Date.now() - startTime, true);
47833
- return response;
47834
47797
  } catch (error) {
47835
- await this.recordOperationMetrics("downloadFile", Date.now() - startTime, false);
47836
- this.emitEvent("download:error", { error });
47837
- throw error;
47798
+ this.emitNotificationError(error, "healthCheck", { recoverable: true });
47799
+ return {
47800
+ isHealthy: false,
47801
+ error: error instanceof Error ? error.message : "Unknown error"
47802
+ };
47838
47803
  }
47839
47804
  }
47805
+ // ─────────────────────────────────────────────────────────────────
47806
+ // Lifecycle
47807
+ // ─────────────────────────────────────────────────────────────────
47840
47808
  /**
47841
- * Get signed URL for file
47842
- * GET /files/:id/signed-url
47809
+ * Gets the current configuration
47810
+ */
47811
+ getConfig() {
47812
+ return this.config;
47813
+ }
47814
+ /**
47815
+ * Closes the notification service and cleans up resources
47816
+ */
47817
+ async close() {
47818
+ this.notificationService = null;
47819
+ this.initialized = false;
47820
+ this.config = null;
47821
+ }
47822
+ /**
47823
+ * Creates a dedicated notification service instance (NOT the singleton)
47843
47824
  *
47844
- * Follows same pattern as base class CRUD methods.
47825
+ * Use this when you need an isolated notification service with its own configuration.
47845
47826
  *
47846
- * @param params - Signed URL parameters (fileId required)
47847
- * @returns Signed URL result with url and expiresAt
47827
+ * @param config - Notification service configuration
47828
+ * @returns Promise that resolves to a new dedicated NotificationService instance
47848
47829
  */
47849
- // eslint-disable-next-line complexity
47850
- async getSignedUrl(params) {
47851
- this.assertReady();
47852
- const startTime = Date.now();
47853
- this.logDebug("Getting signed URL", { fileId: params.fileId, expiresIn: params.expiresIn });
47854
- this.emitEvent("signedUrl:requesting", { request: params });
47830
+ static async createInstance(config) {
47831
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
47832
+ const dedicatedInstance = new _NotificationService();
47833
+ dedicatedInstance.config = config;
47834
+ dedicatedInstance.notificationService = new notifications.NotificationService({
47835
+ ...config,
47836
+ events: mergedEvents
47837
+ });
47838
+ dedicatedInstance.initialized = true;
47839
+ return dedicatedInstance;
47840
+ }
47841
+ };
47842
+ var cachedVersion = "";
47843
+ function getPackageVersion() {
47844
+ if (cachedVersion) return cachedVersion;
47845
+ try {
47846
+ 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)));
47847
+ const pkg = require2("../package.json");
47848
+ cachedVersion = pkg.version ?? "0.0.0";
47849
+ } catch {
47855
47850
  try {
47856
- await this.beforeGetSignedUrl?.(params);
47857
- if (!this.storageService) {
47858
- throw new Error(
47859
- "Storage service not available. Ensure storage is configured in Core.initialize()."
47860
- );
47861
- }
47862
- const storage = this.storageService.getStorage();
47863
- const result2 = await storage.getSignedUrl({
47864
- fileId: params.fileId,
47865
- expiresIn: params.expiresIn ?? config.DOWNLOAD_CONFIG.DEFAULT_SIGNED_URL_EXPIRY_SECONDS,
47866
- operation: "get"
47867
- });
47868
- const response = {
47869
- url: result2?.url ?? "",
47870
- expiresAt: result2?.expiresAt?.toISOString() ?? new Date(Date.now() + config.TIME_CONSTANTS.HOUR).toISOString()
47871
- };
47872
- await this.afterGetSignedUrl?.(response);
47873
- this.emitEvent("signedUrl:received", { result: response });
47874
- this.emitEvent("complete", { success: true, operation: "getSignedUrl" });
47875
- await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, true);
47876
- return response;
47877
- } catch (error) {
47878
- await this.recordOperationMetrics("getSignedUrl", Date.now() - startTime, false);
47879
- this.emitEvent("signedUrl:error", { error });
47880
- throw error;
47851
+ 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)));
47852
+ const currentDir = path.dirname(currentFile);
47853
+ const pkgPath = path.join(currentDir, "..", "package.json");
47854
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
47855
+ cachedVersion = pkg.version ?? "0.0.0";
47856
+ } catch {
47857
+ cachedVersion = "0.0.0";
47881
47858
  }
47882
47859
  }
47883
- // ─────────────────────────────────────────────────────────────────────────
47884
- // NOTE: CRUD and Storage Methods Are Inherited from BaseBackendDomainService
47885
- // ─────────────────────────────────────────────────────────────────────────
47886
- //
47887
- // CRUD methods:
47888
- // - getById(id) - for GET /files/:id
47889
- // - getAll(query) - for listing files
47890
- // - delete(id) - for DELETE /files/:id
47891
- // - exists(id) - check if file exists
47892
- //
47893
- // Storage methods (inherited):
47894
- // - uploadFile(params) - for POST /files/upload
47895
- // - generateFile(params) - for POST /files/generate-document
47896
- // ─────────────────────────────────────────────────────────────────────────
47860
+ return cachedVersion;
47861
+ }
47862
+ __name(getPackageVersion, "getPackageVersion");
47863
+ var VERSION = getPackageVersion();
47864
+ var PACKAGE_NAME = "@plyaz/core";
47865
+
47866
+ // src/backend/index.ts
47867
+ var backend_exports = {};
47868
+ __export(backend_exports, {
47869
+ BACKEND_EXAMPLE_DOMAIN_SERVICE: () => BACKEND_EXAMPLE_DOMAIN_SERVICE,
47870
+ BACKEND_FILES_DOMAIN_SERVICE: () => BACKEND_FILES_DOMAIN_SERVICE,
47871
+ BackendExampleDomainService: () => BackendExampleDomainService,
47872
+ Caching: () => Caching,
47873
+ ErrorHandlingInterceptor: () => ErrorHandlingInterceptor,
47874
+ ExampleController: () => ExampleController,
47875
+ ExampleModule: () => ExampleModule,
47876
+ FeatureDisabled: () => FeatureDisabled,
47877
+ FeatureEnabled: () => FeatureEnabled,
47878
+ FeatureFlagConfigFactory: () => FeatureFlagConfigFactory,
47879
+ FeatureFlagConfigValidator: () => FeatureFlagConfigValidator,
47880
+ FeatureFlagController: () => FeatureFlagController,
47881
+ FeatureFlagDatabaseRepository: () => FeatureFlagDatabaseRepository,
47882
+ FeatureFlagDomainService: () => FeatureFlagDomainService,
47883
+ FeatureFlagGuard: () => FeatureFlagGuard,
47884
+ FeatureFlagLoggingInterceptor: () => FeatureFlagLoggingInterceptor,
47885
+ FeatureFlagMiddleware: () => FeatureFlagMiddleware,
47886
+ FeatureFlagModule: () => FeatureFlagModule,
47887
+ FeatureFlagService: () => FeatureFlagService,
47888
+ FeatureFlagServiceFactory: () => FeatureFlagServiceFactory,
47889
+ FilesController: () => FilesController,
47890
+ FilesModule: () => FilesModule
47891
+ });
47892
+
47893
+ // src/backend/example/example.module.ts
47894
+ var import_common14 = __toESM(require_common(), 1);
47895
+
47896
+ // src/backend/example/example.controller.ts
47897
+ var import_common13 = __toESM(require_common(), 1);
47898
+ var BACKEND_EXAMPLE_DOMAIN_SERVICE = "BACKEND_EXAMPLE_DOMAIN_SERVICE";
47899
+ var ExampleController = class {
47900
+ constructor(exampleService) {
47901
+ this.exampleService = exampleService;
47902
+ }
47903
+ health() {
47904
+ return errors.SuccessResponseStandard("Service is healthy", {
47905
+ service: this.exampleService.isAvailable(),
47906
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
47907
+ });
47908
+ }
47909
+ async getEntity(id) {
47910
+ const entity = await this.exampleService.getById(id);
47911
+ return errors.SuccessResponseStandard("Entity retrieved successfully", entity);
47912
+ }
47913
+ async createEntity(dto) {
47914
+ const entity = await this.exampleService.create(dto);
47915
+ return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47916
+ }
47917
+ async updateEntity(id, dto) {
47918
+ const entity = await this.exampleService.patch(id, dto);
47919
+ return errors.SuccessResponseStandard("Entity updated successfully", entity);
47920
+ }
47921
+ async deleteEntity(id) {
47922
+ await this.exampleService.delete(id);
47923
+ return errors.SuccessResponseStandard("Entity deleted successfully", null);
47924
+ }
47925
+ async createEntityWithValidation(dto) {
47926
+ const entity = await this.exampleService.create(dto);
47927
+ return errors.SuccessResponseStandard("Entity created successfully", entity, types.HTTP_STATUS.CREATED);
47928
+ }
47929
+ async demoSingleError() {
47930
+ return await this.exampleService.demoSingleValidationError();
47931
+ }
47932
+ async demoArrayErrors() {
47933
+ return await this.exampleService.demoMultipleValidationErrors();
47934
+ }
47935
+ async sendEmail(dto) {
47936
+ const result2 = await this.exampleService.sendEmail(dto);
47937
+ return errors.SuccessResponseStandard("Email sent successfully", result2);
47938
+ }
47897
47939
  };
47898
- var backendFilesDomainService = new BackendFilesDomainService();
47899
- var logger4 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendFilesDomainService" });
47940
+ __name(ExampleController, "ExampleController");
47941
+ __decorateClass([
47942
+ (0, import_common13.Get)("health")
47943
+ ], ExampleController.prototype, "health", 1);
47944
+ __decorateClass([
47945
+ (0, import_common13.Get)("entities/:id"),
47946
+ __decorateParam(0, (0, import_common13.Param)("id"))
47947
+ ], ExampleController.prototype, "getEntity", 1);
47948
+ __decorateClass([
47949
+ (0, import_common13.Post)("entities"),
47950
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.CREATED),
47951
+ __decorateParam(0, (0, import_common13.Body)())
47952
+ ], ExampleController.prototype, "createEntity", 1);
47953
+ __decorateClass([
47954
+ (0, import_common13.Patch)("entities/:id"),
47955
+ __decorateParam(0, (0, import_common13.Param)("id")),
47956
+ __decorateParam(1, (0, import_common13.Body)())
47957
+ ], ExampleController.prototype, "updateEntity", 1);
47958
+ __decorateClass([
47959
+ (0, import_common13.Delete)("entities/:id"),
47960
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.OK),
47961
+ __decorateParam(0, (0, import_common13.Param)("id"))
47962
+ ], ExampleController.prototype, "deleteEntity", 1);
47963
+ __decorateClass([
47964
+ (0, import_common13.Post)("entities/validated"),
47965
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.CREATED),
47966
+ __decorateParam(0, (0, import_common13.Body)())
47967
+ ], ExampleController.prototype, "createEntityWithValidation", 1);
47968
+ __decorateClass([
47969
+ (0, import_common13.Get)("errors/single")
47970
+ ], ExampleController.prototype, "demoSingleError", 1);
47971
+ __decorateClass([
47972
+ (0, import_common13.Get)("errors/array")
47973
+ ], ExampleController.prototype, "demoArrayErrors", 1);
47974
+ __decorateClass([
47975
+ (0, import_common13.Post)("email"),
47976
+ (0, import_common13.HttpCode)(import_common13.HttpStatus.OK),
47977
+ __decorateParam(0, (0, import_common13.Body)())
47978
+ ], ExampleController.prototype, "sendEmail", 1);
47979
+ ExampleController = __decorateClass([
47980
+ (0, import_common13.Controller)("example"),
47981
+ __decorateParam(0, (0, import_common13.Inject)(BACKEND_EXAMPLE_DOMAIN_SERVICE))
47982
+ ], ExampleController);
47983
+
47984
+ // src/backend/example/example.module.ts
47985
+ var ExampleModule = class {
47986
+ };
47987
+ __name(ExampleModule, "ExampleModule");
47988
+ ExampleModule = __decorateClass([
47989
+ (0, import_common14.Module)({
47990
+ controllers: [ExampleController],
47991
+ providers: [
47992
+ // Provide BackendExampleDomainService via factory using the singleton instance
47993
+ // This ensures the service is shared across the application
47994
+ {
47995
+ provide: BACKEND_EXAMPLE_DOMAIN_SERVICE,
47996
+ useFactory: /* @__PURE__ */ __name(() => backendExampleDomainService, "useFactory")
47997
+ }
47998
+ ],
47999
+ exports: [BACKEND_EXAMPLE_DOMAIN_SERVICE]
48000
+ })
48001
+ ], ExampleModule);
48002
+
48003
+ // src/backend/files/files.module.ts
48004
+ var import_common16 = __toESM(require_common(), 1);
48005
+
48006
+ // src/backend/files/files.controller.ts
48007
+ var import_common15 = __toESM(require_common(), 1);
48008
+ var BACKEND_FILES_DOMAIN_SERVICE = "BACKEND_FILES_DOMAIN_SERVICE";
48009
+ var FilesController = class {
48010
+ constructor(filesService) {
48011
+ this.filesService = filesService;
48012
+ }
48013
+ health() {
48014
+ return errors.SuccessResponseStandard("Files service is healthy", {
48015
+ service: this.filesService.isAvailable(),
48016
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
48017
+ });
48018
+ }
48019
+ async uploadFile(dto) {
48020
+ const result2 = await this.filesService.uploadFile(dto);
48021
+ return errors.SuccessResponseStandard("File uploaded successfully", result2);
48022
+ }
48023
+ async uploadFiles(dto) {
48024
+ const results = await this.filesService.uploadFiles(dto.files);
48025
+ return errors.SuccessResponseStandard("Files uploaded successfully", results);
48026
+ }
48027
+ async generateDocument(dto) {
48028
+ const buffer = await this.filesService.generateFile(dto);
48029
+ return errors.SuccessResponseStandard("Document generated successfully", {
48030
+ buffer: buffer.toString("base64"),
48031
+ size: buffer.length
48032
+ });
48033
+ }
48034
+ async getFile(id) {
48035
+ const entity = await this.filesService.getById(id);
48036
+ return errors.SuccessResponseStandard("File retrieved", entity);
48037
+ }
48038
+ async downloadFile(id) {
48039
+ const result2 = await this.filesService.downloadFile({ fileId: id });
48040
+ return errors.SuccessResponseStandard("File downloaded", result2);
48041
+ }
48042
+ async getSignedUrl(id) {
48043
+ const result2 = await this.filesService.getSignedUrl({ fileId: id });
48044
+ return errors.SuccessResponseStandard("Signed URL generated", result2);
48045
+ }
48046
+ async deleteFile(id) {
48047
+ await this.filesService.delete(id);
48048
+ return errors.SuccessResponseStandard("File deleted", null);
48049
+ }
48050
+ };
48051
+ __name(FilesController, "FilesController");
48052
+ __decorateClass([
48053
+ (0, import_common15.Get)("health")
48054
+ ], FilesController.prototype, "health", 1);
48055
+ __decorateClass([
48056
+ (0, import_common15.Post)("upload"),
48057
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48058
+ __decorateParam(0, (0, import_common15.Body)())
48059
+ ], FilesController.prototype, "uploadFile", 1);
48060
+ __decorateClass([
48061
+ (0, import_common15.Post)("upload/bulk"),
48062
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48063
+ __decorateParam(0, (0, import_common15.Body)())
48064
+ ], FilesController.prototype, "uploadFiles", 1);
48065
+ __decorateClass([
48066
+ (0, import_common15.Post)("generate-document"),
48067
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48068
+ __decorateParam(0, (0, import_common15.Body)())
48069
+ ], FilesController.prototype, "generateDocument", 1);
48070
+ __decorateClass([
48071
+ (0, import_common15.Get)(":id"),
48072
+ __decorateParam(0, (0, import_common15.Param)("id"))
48073
+ ], FilesController.prototype, "getFile", 1);
48074
+ __decorateClass([
48075
+ (0, import_common15.Get)(":id/download"),
48076
+ __decorateParam(0, (0, import_common15.Param)("id"))
48077
+ ], FilesController.prototype, "downloadFile", 1);
48078
+ __decorateClass([
48079
+ (0, import_common15.Get)(":id/signed-url"),
48080
+ __decorateParam(0, (0, import_common15.Param)("id"))
48081
+ ], FilesController.prototype, "getSignedUrl", 1);
48082
+ __decorateClass([
48083
+ (0, import_common15.Delete)(":id"),
48084
+ (0, import_common15.HttpCode)(import_common15.HttpStatus.OK),
48085
+ __decorateParam(0, (0, import_common15.Param)("id"))
48086
+ ], FilesController.prototype, "deleteFile", 1);
48087
+ FilesController = __decorateClass([
48088
+ (0, import_common15.Controller)("files"),
48089
+ __decorateParam(0, (0, import_common15.Inject)(BACKEND_FILES_DOMAIN_SERVICE))
48090
+ ], FilesController);
48091
+ var logger6 = new logger$1.PackageLogger({ packageName: "core", service: "FrontendFilesDomainService" });
47900
48092
  var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseFrontendDomainService {
47901
48093
  constructor(config = {}, options) {
47902
- const apiBasePath = config.apiBasePath || "/api";
48094
+ const apiBasePath = config.apiBasePath || "";
47903
48095
  super({
47904
48096
  serviceName: "FrontendFilesDomainService",
47905
48097
  supportedRuntimes: ["frontend"],
@@ -47962,7 +48154,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
47962
48154
  // The ?? operator only falls through on null/undefined, not empty strings.
47963
48155
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
47964
48156
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
47965
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api"
48157
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
47966
48158
  };
47967
48159
  return new _FrontendFilesDomainService(mergedConfig, options);
47968
48160
  }
@@ -47999,7 +48191,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
47999
48191
  */
48000
48192
  static registerEventHandlers(verbose) {
48001
48193
  if (this._eventHandlersRegistered) {
48002
- logger4.debug("Files frontend event handlers already registered");
48194
+ logger6.debug("Files frontend event handlers already registered");
48003
48195
  return () => {
48004
48196
  };
48005
48197
  }
@@ -48061,7 +48253,7 @@ var FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseF
48061
48253
  verbose
48062
48254
  });
48063
48255
  this._eventHandlersRegistered = true;
48064
- logger4.info("Files frontend event handlers registered");
48256
+ logger6.info("Files frontend event handlers registered");
48065
48257
  return () => {
48066
48258
  FrontendEventPersistenceHandler.unregisterAll();
48067
48259
  this._eventHandlersRegistered = false;
@@ -48139,7 +48331,7 @@ var FilesModule = class {
48139
48331
  };
48140
48332
  __name(FilesModule, "FilesModule");
48141
48333
  FilesModule = __decorateClass([
48142
- (0, import_common17.Module)({
48334
+ (0, import_common16.Module)({
48143
48335
  controllers: [FilesController],
48144
48336
  providers: [
48145
48337
  // Provide BackendFilesDomainService via factory using the singleton instance
@@ -48430,10 +48622,10 @@ var FeatureFlagDomainService = class extends BaseDomainService {
48430
48622
  };
48431
48623
 
48432
48624
  // src/backend/featureFlags/feature-flag.module.ts
48433
- var import_common19 = __toESM(require_common(), 1);
48625
+ var import_common18 = __toESM(require_common(), 1);
48434
48626
 
48435
48627
  // src/backend/featureFlags/feature-flag.controller.ts
48436
- var import_common18 = __toESM(require_common(), 1);
48628
+ var import_common17 = __toESM(require_common(), 1);
48437
48629
  var FeatureFlagController = class {
48438
48630
  constructor(featureFlagService) {
48439
48631
  this.featureFlagService = featureFlagService;
@@ -48562,54 +48754,54 @@ var FeatureFlagController = class {
48562
48754
  };
48563
48755
  __name(FeatureFlagController, "FeatureFlagController");
48564
48756
  __decorateClass([
48565
- (0, import_common18.Post)(":key/evaluate"),
48566
- __decorateParam(0, (0, import_common18.Param)("key")),
48567
- __decorateParam(1, (0, import_common18.Body)())
48757
+ (0, import_common17.Post)(":key/evaluate"),
48758
+ __decorateParam(0, (0, import_common17.Param)("key")),
48759
+ __decorateParam(1, (0, import_common17.Body)())
48568
48760
  ], FeatureFlagController.prototype, "evaluateFlag", 1);
48569
48761
  __decorateClass([
48570
- (0, import_common18.Post)(":key/enabled"),
48571
- __decorateParam(0, (0, import_common18.Param)("key")),
48572
- __decorateParam(1, (0, import_common18.Body)())
48762
+ (0, import_common17.Post)(":key/enabled"),
48763
+ __decorateParam(0, (0, import_common17.Param)("key")),
48764
+ __decorateParam(1, (0, import_common17.Body)())
48573
48765
  ], FeatureFlagController.prototype, "isEnabled", 1);
48574
48766
  __decorateClass([
48575
- (0, import_common18.Post)("evaluate-all"),
48576
- __decorateParam(0, (0, import_common18.Body)())
48767
+ (0, import_common17.Post)("evaluate-all"),
48768
+ __decorateParam(0, (0, import_common17.Body)())
48577
48769
  ], FeatureFlagController.prototype, "evaluateAllFlags", 1);
48578
48770
  __decorateClass([
48579
- (0, import_common18.Post)(),
48580
- __decorateParam(0, (0, import_common18.Body)())
48771
+ (0, import_common17.Post)(),
48772
+ __decorateParam(0, (0, import_common17.Body)())
48581
48773
  ], FeatureFlagController.prototype, "createFlag", 1);
48582
48774
  __decorateClass([
48583
- (0, import_common18.Put)(":key"),
48584
- __decorateParam(0, (0, import_common18.Param)("key")),
48585
- __decorateParam(1, (0, import_common18.Body)())
48775
+ (0, import_common17.Put)(":key"),
48776
+ __decorateParam(0, (0, import_common17.Param)("key")),
48777
+ __decorateParam(1, (0, import_common17.Body)())
48586
48778
  ], FeatureFlagController.prototype, "updateFlag", 1);
48587
48779
  __decorateClass([
48588
- (0, import_common18.Delete)(":key"),
48589
- __decorateParam(0, (0, import_common18.Param)("key"))
48780
+ (0, import_common17.Delete)(":key"),
48781
+ __decorateParam(0, (0, import_common17.Param)("key"))
48590
48782
  ], FeatureFlagController.prototype, "deleteFlag", 1);
48591
48783
  __decorateClass([
48592
- (0, import_common18.Post)(":key/override"),
48593
- __decorateParam(0, (0, import_common18.Param)("key")),
48594
- __decorateParam(1, (0, import_common18.Body)("value"))
48784
+ (0, import_common17.Post)(":key/override"),
48785
+ __decorateParam(0, (0, import_common17.Param)("key")),
48786
+ __decorateParam(1, (0, import_common17.Body)("value"))
48595
48787
  ], FeatureFlagController.prototype, "setOverride", 1);
48596
48788
  __decorateClass([
48597
- (0, import_common18.Delete)(":key/override"),
48598
- __decorateParam(0, (0, import_common18.Param)("key"))
48789
+ (0, import_common17.Delete)(":key/override"),
48790
+ __decorateParam(0, (0, import_common17.Param)("key"))
48599
48791
  ], FeatureFlagController.prototype, "removeOverride", 1);
48600
48792
  __decorateClass([
48601
- (0, import_common18.Get)(":key/rules"),
48602
- __decorateParam(0, (0, import_common18.Param)("key"))
48793
+ (0, import_common17.Get)(":key/rules"),
48794
+ __decorateParam(0, (0, import_common17.Param)("key"))
48603
48795
  ], FeatureFlagController.prototype, "getFlagRules", 1);
48604
48796
  __decorateClass([
48605
- (0, import_common18.Post)("refresh")
48797
+ (0, import_common17.Post)("refresh")
48606
48798
  ], FeatureFlagController.prototype, "refreshCache", 1);
48607
48799
  __decorateClass([
48608
- (0, import_common18.Get)("health")
48800
+ (0, import_common17.Get)("health")
48609
48801
  ], FeatureFlagController.prototype, "getHealth", 1);
48610
48802
  FeatureFlagController = __decorateClass([
48611
- (0, import_common18.Controller)("feature-flags"),
48612
- __decorateParam(0, (0, import_common18.Inject)(FEATURE_FLAG_SERVICE))
48803
+ (0, import_common17.Controller)("feature-flags"),
48804
+ __decorateParam(0, (0, import_common17.Inject)(FEATURE_FLAG_SERVICE))
48613
48805
  ], FeatureFlagController);
48614
48806
 
48615
48807
  // src/backend/featureFlags/feature-flag.module.ts
@@ -48701,16 +48893,16 @@ var FeatureFlagModule = class {
48701
48893
  __name(FeatureFlagModule, "FeatureFlagModule");
48702
48894
  FeatureFlagModule.serviceInstance = null;
48703
48895
  FeatureFlagModule = __decorateClass([
48704
- (0, import_common19.Global)(),
48705
- (0, import_common19.Module)({
48896
+ (0, import_common18.Global)(),
48897
+ (0, import_common18.Module)({
48706
48898
  controllers: [FeatureFlagController]
48707
48899
  })
48708
48900
  ], FeatureFlagModule);
48709
48901
 
48710
48902
  // src/backend/featureFlags/decorators/feature-flag.decorator.ts
48711
- var import_common20 = __toESM(require_common(), 1);
48903
+ var import_common19 = __toESM(require_common(), 1);
48712
48904
  function FeatureFlag(key, expected = true) {
48713
- return (0, import_common20.SetMetadata)(types.FEATURE_FLAG_METADATA.FLAG_CHECK, { key, expected });
48905
+ return (0, import_common19.SetMetadata)(types.FEATURE_FLAG_METADATA.FLAG_CHECK, { key, expected });
48714
48906
  }
48715
48907
  __name(FeatureFlag, "FeatureFlag");
48716
48908
 
@@ -48727,7 +48919,7 @@ function FeatureEnabled(key) {
48727
48919
  __name(FeatureEnabled, "FeatureEnabled");
48728
48920
 
48729
48921
  // src/backend/featureFlags/guards/feature-flag.guard.ts
48730
- var import_common21 = __toESM(require_common(), 1);
48922
+ var import_common20 = __toESM(require_common(), 1);
48731
48923
  var FeatureFlagGuard = class {
48732
48924
  constructor(reflector, featureFlagService) {
48733
48925
  this.reflector = reflector;
@@ -48771,12 +48963,12 @@ var FeatureFlagGuard = class {
48771
48963
  };
48772
48964
  __name(FeatureFlagGuard, "FeatureFlagGuard");
48773
48965
  FeatureFlagGuard = __decorateClass([
48774
- (0, import_common21.Injectable)(),
48775
- __decorateParam(1, (0, import_common21.Inject)(FEATURE_FLAG_SERVICE))
48966
+ (0, import_common20.Injectable)(),
48967
+ __decorateParam(1, (0, import_common20.Inject)(FEATURE_FLAG_SERVICE))
48776
48968
  ], FeatureFlagGuard);
48777
48969
 
48778
48970
  // src/backend/featureFlags/middleware/feature-flag-middleware.ts
48779
- var import_common22 = __toESM(require_common(), 1);
48971
+ var import_common21 = __toESM(require_common(), 1);
48780
48972
  function isFeatureFlagKey(value) {
48781
48973
  return Object.keys(config.FEATURES).includes(value);
48782
48974
  }
@@ -48854,12 +49046,12 @@ var FeatureFlagMiddleware = class {
48854
49046
  };
48855
49047
  __name(FeatureFlagMiddleware, "FeatureFlagMiddleware");
48856
49048
  FeatureFlagMiddleware = __decorateClass([
48857
- (0, import_common22.Injectable)(),
48858
- __decorateParam(0, (0, import_common22.Inject)(FEATURE_FLAG_SERVICE))
49049
+ (0, import_common21.Injectable)(),
49050
+ __decorateParam(0, (0, import_common21.Inject)(FEATURE_FLAG_SERVICE))
48859
49051
  ], FeatureFlagMiddleware);
48860
49052
 
48861
49053
  // src/backend/featureFlags/interceptors/feature-flag-logging-interceptor.ts
48862
- var import_common23 = __toESM(require_common(), 1);
49054
+ var import_common22 = __toESM(require_common(), 1);
48863
49055
  var import_rxjs = __toESM(require_cjs(), 1);
48864
49056
  var FeatureFlagLoggingInterceptor = class {
48865
49057
  constructor() {
@@ -48892,11 +49084,11 @@ var FeatureFlagLoggingInterceptor = class {
48892
49084
  };
48893
49085
  __name(FeatureFlagLoggingInterceptor, "FeatureFlagLoggingInterceptor");
48894
49086
  FeatureFlagLoggingInterceptor = __decorateClass([
48895
- (0, import_common23.Injectable)()
49087
+ (0, import_common22.Injectable)()
48896
49088
  ], FeatureFlagLoggingInterceptor);
48897
49089
 
48898
49090
  // src/backend/featureFlags/interceptors/error-handling-interceptor.ts
48899
- var import_common24 = __toESM(require_common(), 1);
49091
+ var import_common23 = __toESM(require_common(), 1);
48900
49092
  var import_operators = __toESM(require_operators(), 1);
48901
49093
  var ErrorHandlingInterceptor = class {
48902
49094
  constructor() {
@@ -48925,9 +49117,9 @@ var ErrorHandlingInterceptor = class {
48925
49117
  };
48926
49118
  __name(ErrorHandlingInterceptor, "ErrorHandlingInterceptor");
48927
49119
  ErrorHandlingInterceptor = __decorateClass([
48928
- (0, import_common24.Injectable)()
49120
+ (0, import_common23.Injectable)()
48929
49121
  ], ErrorHandlingInterceptor);
48930
- var logger5 = new logger$1.PackageLogger({
49122
+ var logger7 = new logger$1.PackageLogger({
48931
49123
  packageName: "core",
48932
49124
  service: "FeatureFlagConfigValidator"
48933
49125
  });
@@ -49113,7 +49305,7 @@ var FeatureFlagConfigValidator = class {
49113
49305
  static getWarnings(config$1, environment) {
49114
49306
  const env = environment ?? (config$1.isLoggingEnabled ? types.NODE_ENVIRONMENTS.DEVELOPMENT : types.NODE_ENVIRONMENTS.PRODUCTION);
49115
49307
  if (config$1.cacheTtl > config.NUMERIC_CONSTANTS.ONE_HOUR_SECONDS) {
49116
- logger5.warn(
49308
+ logger7.warn(
49117
49309
  "Cache TTL is very high (>1 hour). Consider reducing for better responsiveness.",
49118
49310
  {
49119
49311
  field: "cacheTtl",
@@ -49123,13 +49315,13 @@ var FeatureFlagConfigValidator = class {
49123
49315
  );
49124
49316
  }
49125
49317
  if (env === types.NODE_ENVIRONMENTS.PRODUCTION && config$1.isLoggingEnabled) {
49126
- logger5.warn("Logging is enabled in production. Consider disabling for performance.", {
49318
+ logger7.warn("Logging is enabled in production. Consider disabling for performance.", {
49127
49319
  field: "isLoggingEnabled",
49128
49320
  code: "PRODUCTION_LOGGING_ENABLED"
49129
49321
  });
49130
49322
  }
49131
49323
  if (env === types.NODE_ENVIRONMENTS.DEVELOPMENT && !config$1.isCacheEnabled) {
49132
- logger5.warn("Cache is disabled in development. This may impact performance testing.", {
49324
+ logger7.warn("Cache is disabled in development. This may impact performance testing.", {
49133
49325
  field: "isCacheEnabled",
49134
49326
  code: "DEVELOPMENT_CACHE_DISABLED"
49135
49327
  });
@@ -49272,7 +49464,7 @@ var FeatureFlagConfigFactory = class {
49272
49464
  };
49273
49465
 
49274
49466
  // src/base/cache/feature/caching.ts
49275
- var import_common25 = __toESM(require_common(), 1);
49467
+ var import_common24 = __toESM(require_common(), 1);
49276
49468
  var import_rxjs2 = __toESM(require_cjs(), 1);
49277
49469
  var Caching = class {
49278
49470
  constructor() {
@@ -49295,7 +49487,7 @@ var Caching = class {
49295
49487
  };
49296
49488
  __name(Caching, "Caching");
49297
49489
  Caching = __decorateClass([
49298
- (0, import_common25.Injectable)()
49490
+ (0, import_common24.Injectable)()
49299
49491
  ], Caching);
49300
49492
 
49301
49493
  // src/frontend/index.ts
@@ -49331,7 +49523,6 @@ __export(frontend_exports, {
49331
49523
  useHasService: () => useHasService,
49332
49524
  usePlyaz: () => usePlyaz,
49333
49525
  usePlyazReady: () => usePlyazReady,
49334
- useRootStore: () => store.useRootStore,
49335
49526
  useService: () => useService,
49336
49527
  useServiceAsync: () => useServiceAsync,
49337
49528
  useServiceKeys: () => useServiceKeys,
@@ -49422,7 +49613,7 @@ function ApiProvider({
49422
49613
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children });
49423
49614
  }
49424
49615
  __name(ApiProvider, "ApiProvider");
49425
- var logger6 = new logger$1.PackageLogger({ packageName: "core", service: "PlyazProvider" });
49616
+ var logger8 = new logger$1.PackageLogger({ packageName: "core", service: "PlyazProvider" });
49426
49617
  var PlyazContext = react.createContext(null);
49427
49618
  var FeatureFlagStore = class {
49428
49619
  constructor(config = {}) {
@@ -49512,22 +49703,27 @@ async function initializeCore(config) {
49512
49703
  });
49513
49704
  }
49514
49705
  __name(initializeCore, "initializeCore");
49515
- function createStoreRegistry() {
49706
+ function createStoreRegistry(store) {
49516
49707
  return {
49517
49708
  getStore(key) {
49518
- const state = store.useRootStore.getState();
49709
+ const state = store.getState();
49519
49710
  if (!state) {
49520
- logger6.warn(
49711
+ logger8.warn(
49521
49712
  "Store state is undefined - store may not be hydrated yet. This can cause side effects if called during SSR or before initialization."
49522
49713
  );
49523
49714
  return void 0;
49524
49715
  }
49525
- return state[key];
49716
+ const slice = state[key];
49717
+ logger8.debug(`[StoreRegistry] getStore('${key}')`, {
49718
+ hasSlice: !!slice,
49719
+ sliceKeys: slice ? Object.keys(slice) : []
49720
+ });
49721
+ return slice;
49526
49722
  }
49527
49723
  };
49528
49724
  }
49529
49725
  __name(createStoreRegistry, "createStoreRegistry");
49530
- async function initializeServices(config) {
49726
+ async function initializeServices(config, store) {
49531
49727
  if (!config.services || config.services.length === 0) return;
49532
49728
  if (config.verbose) {
49533
49729
  globalThis.console.log("[PlyazProvider] Initializing domain services...");
@@ -49539,7 +49735,7 @@ async function initializeServices(config) {
49539
49735
  observability: config.observability,
49540
49736
  services: config.services,
49541
49737
  // Provide store registry for injecting stores into services
49542
- stores: createStoreRegistry()
49738
+ stores: createStoreRegistry(store)
49543
49739
  });
49544
49740
  if (config.verbose) {
49545
49741
  globalThis.console.log(
@@ -49576,6 +49772,7 @@ function createServicesObject(config, featureFlagStore) {
49576
49772
  __name(createServicesObject, "createServicesObject");
49577
49773
  function PlyazProvider({
49578
49774
  children,
49775
+ store,
49579
49776
  config,
49580
49777
  loading,
49581
49778
  error: errorComponent,
@@ -49589,8 +49786,9 @@ function PlyazProvider({
49589
49786
  const initialize = react.useCallback(async () => {
49590
49787
  try {
49591
49788
  setError(null);
49789
+ Core.setRootStoreHook(store);
49592
49790
  await initializeCore(config);
49593
- await initializeServices(config);
49791
+ await initializeServices(config, store);
49594
49792
  await initializeFeatureFlags(featureFlagStore);
49595
49793
  setIsReady(true);
49596
49794
  onReady?.(createServicesObject(config, featureFlagStore));
@@ -49600,7 +49798,7 @@ function PlyazProvider({
49600
49798
  onError?.(initError);
49601
49799
  globalThis.console.error("[PlyazProvider] Initialization failed:", initError);
49602
49800
  }
49603
- }, [config, featureFlagStore, onReady, onError]);
49801
+ }, [config, store, featureFlagStore, onReady, onError]);
49604
49802
  const reinitialize = react.useCallback(async () => {
49605
49803
  setIsReady(false);
49606
49804
  ServiceRegistry.disposeAll();
@@ -49649,8 +49847,9 @@ __name(PlyazProvider, "PlyazProvider");
49649
49847
  function usePlyaz() {
49650
49848
  const context = react.useContext(PlyazContext);
49651
49849
  if (!context) {
49652
- throw new Error(
49653
- "usePlyaz must be used within a PlyazProvider. Wrap your app with <PlyazProvider config={...}>...</PlyazProvider>"
49850
+ throw new errors.CorePackageError(
49851
+ "usePlyaz must be used within a PlyazProvider. Wrap your app with <PlyazProvider config={...}>...</PlyazProvider>",
49852
+ types.ERROR_CODES.CORE_PROVIDER_NOT_FOUND
49654
49853
  );
49655
49854
  }
49656
49855
  return context;
@@ -49659,7 +49858,10 @@ __name(usePlyaz, "usePlyaz");
49659
49858
  function useApi() {
49660
49859
  const { api, isReady } = usePlyaz();
49661
49860
  if (!isReady || !api) {
49662
- throw new Error("API client is not ready. Make sure PlyazProvider has finished initializing.");
49861
+ throw new errors.CorePackageError(
49862
+ "API client is not ready. Make sure PlyazProvider has finished initializing.",
49863
+ types.ERROR_CODES.CORE_PROVIDER_INITIALIZATION_FAILED
49864
+ );
49663
49865
  }
49664
49866
  return api;
49665
49867
  }
@@ -49702,7 +49904,10 @@ __name(useEnvironment, "useEnvironment");
49702
49904
  function useService(key) {
49703
49905
  const { getService, isReady } = usePlyaz();
49704
49906
  if (!isReady) {
49705
- throw new Error(`PlyazProvider not ready. Cannot get service '${key}'.`);
49907
+ throw new errors.CorePackageError(
49908
+ `PlyazProvider not ready. Cannot get service '${key}'.`,
49909
+ types.ERROR_CODES.CORE_PROVIDER_INITIALIZATION_FAILED
49910
+ );
49706
49911
  }
49707
49912
  return react.useMemo(() => getService(key), [getService, key]);
49708
49913
  }