@plyaz/core 1.10.0 → 1.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { TIME_CONSTANTS, FORMAT_CONSTANTS, FILE_CHECK_INTERVAL_DEFAULT, FEATURE_FLAG_CACHE_TTL_DEFAULT, FEATURE_FLAG_FILE_PATHS, FEATURE_FLAG_PROVIDERS, NUMERIC_CONSTANTS, HTTP_STATUS as HTTP_STATUS$1, DOWNLOAD_CONFIG, MATH_CONSTANTS, ISO_STANDARDS, CACHE_MAX_SIZE_DEFAULT, CACHE_CLEANUP_INTERVAL_DEFAULT, DEVELOPMENT_CONFIG, STAGING_CONFIG, PRODUCTION_CONFIG, FNV_CONSTANTS, FEATURES, HASH_SEED_CONSTANTS } from '@plyaz/config';
1
+ import { TIME_CONSTANTS, FORMAT_CONSTANTS, FILE_CHECK_INTERVAL_DEFAULT, FEATURE_FLAG_CACHE_TTL_DEFAULT, FEATURE_FLAG_FILE_PATHS, FEATURE_FLAG_PROVIDERS, NUMERIC_CONSTANTS, DOWNLOAD_CONFIG, HTTP_STATUS as HTTP_STATUS$1, MATH_CONSTANTS, ISO_STANDARDS, CACHE_MAX_SIZE_DEFAULT, CACHE_CLEANUP_INTERVAL_DEFAULT, DEVELOPMENT_CONFIG, STAGING_CONFIG, PRODUCTION_CONFIG, FNV_CONSTANTS, FEATURES, HASH_SEED_CONSTANTS } from '@plyaz/config';
2
2
  import { ERROR_CODES as ERROR_CODES$1, HTTP_STATUS, NODE_ENVIRONMENTS, BACKEND_RUNTIMES, FRONTEND_RUNTIMES, UNIVERSAL_RUNTIMES, ERROR_CATEGORY as ERROR_CATEGORY$1, CORE_EVENTS as CORE_EVENTS$1, PACKAGE_STATUS_CODES, API_ERROR_CODES, OPERATIONS, FEATURE_FLAG_PROVIDERS as FEATURE_FLAG_PROVIDERS$2, FEATURE_FLAG_METADATA } from '@plyaz/types';
3
3
  import { EventEmitter } from 'events';
4
4
  import { evaluateAllFeatureFlags, createFeatureFlag, updateFeatureFlag, deleteFeatureFlag, fetchFeatureFlagRules, createApiClient, mergeConfigs, ApiPackageError, setDefaultApiClient } from '@plyaz/api/frontend';
@@ -1356,7 +1356,7 @@ var init_LoggerAdapter = __esm({
1356
1356
  let status = "unset";
1357
1357
  let statusMessage;
1358
1358
  const logSpans = this.logSpans;
1359
- const logger6 = this.observabilityLogger;
1359
+ const logger8 = this.observabilityLogger;
1360
1360
  return {
1361
1361
  context,
1362
1362
  setAttribute: /* @__PURE__ */ __name((key, value) => {
@@ -1368,7 +1368,7 @@ var init_LoggerAdapter = __esm({
1368
1368
  addEvent: /* @__PURE__ */ __name((event) => {
1369
1369
  events.push(event);
1370
1370
  if (logSpans) {
1371
- logger6.debug(`[SPAN EVENT] ${options.name}: ${event.name}`, {
1371
+ logger8.debug(`[SPAN EVENT] ${options.name}: ${event.name}`, {
1372
1372
  traceId: context.traceId,
1373
1373
  spanId: context.spanId,
1374
1374
  eventAttributes: event.attributes
@@ -1383,7 +1383,7 @@ var init_LoggerAdapter = __esm({
1383
1383
  const duration = (endTime ?? Date.now()) - startTime;
1384
1384
  if (logSpans) {
1385
1385
  const logMethod = status === "error" ? "warn" : "debug";
1386
- logger6[logMethod](`[SPAN END] ${options.name}`, {
1386
+ logger8[logMethod](`[SPAN END] ${options.name}`, {
1387
1387
  traceId: context.traceId,
1388
1388
  spanId: context.spanId,
1389
1389
  durationMs: duration,
@@ -1396,7 +1396,7 @@ var init_LoggerAdapter = __esm({
1396
1396
  }, "end"),
1397
1397
  recordException: /* @__PURE__ */ __name((error) => {
1398
1398
  if (logSpans) {
1399
- logger6.error(`[SPAN ERROR] ${options.name}`, {
1399
+ logger8.error(`[SPAN ERROR] ${options.name}`, {
1400
1400
  traceId: context.traceId,
1401
1401
  spanId: context.spanId,
1402
1402
  error: error.message,
@@ -3144,6 +3144,11 @@ var init_CoreInitializer = __esm({
3144
3144
  environment: globalEnvironment,
3145
3145
  runtime,
3146
3146
  apiClient: apiConfig ? { baseURL: apiConfig.baseURL, ...apiConfig } : void 0,
3147
+ db: options.db,
3148
+ cache: options.cache,
3149
+ storage: options.storage,
3150
+ notifications: options.notifications,
3151
+ observability: options.observability,
3147
3152
  services: mergedServices,
3148
3153
  stores: {
3149
3154
  // Returns specific slice from namespaced root store (type-safe)
@@ -3953,26 +3958,76 @@ var init_CoreInitializer = __esm({
3953
3958
  errorStore,
3954
3959
  _Core.buildErrorHandlerConfig(_Core._errorConfig)
3955
3960
  );
3961
+ _Core.setupErrorEventSubscription(verbose);
3962
+ _Core.setupErrorStoreSubscription(verbose);
3963
+ _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
3964
+ if (_Core._errorConfig.httpHandler !== false) {
3965
+ await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
3966
+ }
3967
+ }
3968
+ /**
3969
+ * Log serialized errors with full details.
3970
+ */
3971
+ static logErrors(errors, prefix = "ErrorStore") {
3972
+ for (const err of errors) {
3973
+ _Core.logger.error(`[${prefix}] ${err.code}: ${err.message}`, {
3974
+ id: err.id,
3975
+ code: err.code,
3976
+ message: err.message,
3977
+ category: err.category,
3978
+ source: err.source,
3979
+ status: err.status,
3980
+ isRetryable: err.isRetryable,
3981
+ context: err.context,
3982
+ timestamp: err.timestamp
3983
+ });
3984
+ }
3985
+ }
3986
+ /**
3987
+ * Setup SYSTEM.ERROR event subscription for error store updates.
3988
+ * Backend: Also logs errors with full details.
3989
+ * Frontend: Only updates store (logging handled by store subscription).
3990
+ */
3991
+ static setupErrorEventSubscription(verbose) {
3992
+ const isBackend = BACKEND_RUNTIMES.includes(_Core._coreServices.runtime);
3956
3993
  const errorEventCleanup = CoreEventManager.on(
3957
3994
  CORE_EVENTS.SYSTEM.ERROR,
3958
3995
  (event) => {
3959
3996
  if (!_Core._rootStore) return;
3960
3997
  try {
3961
3998
  const { errors } = event.data;
3962
- if (errors && errors.length > 0) {
3963
- _Core._rootStore.getState().errors.addErrors(errors);
3964
- _Core.log(`Added ${errors.length} error(s) to store`, verbose);
3965
- }
3999
+ if (!errors || errors.length === 0) return;
4000
+ _Core._rootStore.getState().errors.addErrors(errors);
4001
+ if (isBackend) _Core.logErrors(errors);
4002
+ _Core.log(`Added ${errors.length} error(s) to store`, verbose);
3966
4003
  } catch (e) {
3967
4004
  _Core.logger.error("Failed to handle error event", { error: e });
3968
4005
  }
3969
4006
  }
3970
4007
  );
3971
4008
  _Core._eventCleanupFns.push(errorEventCleanup);
3972
- _Core.log("Global error handler initialized with CoreEventManager integration", verbose);
3973
- if (_Core._errorConfig.httpHandler !== false) {
3974
- await _Core.createHttpErrorHandler(_Core._errorConfig, verbose);
3975
- }
4009
+ }
4010
+ /**
4011
+ * Setup error store subscription for frontend logging.
4012
+ * Logs new errors when they're added to the store.
4013
+ * Only active for non-backend runtimes (browser, nextjs, nuxt, edge).
4014
+ */
4015
+ static setupErrorStoreSubscription(verbose) {
4016
+ const isFrontend = !BACKEND_RUNTIMES.includes(_Core._coreServices.runtime);
4017
+ if (!isFrontend || !_Core._rootStore) return;
4018
+ let prevErrorCount = 0;
4019
+ const storeUnsubscribe = _Core._rootStore.subscribe((state) => {
4020
+ const currentCount = state.errors.errorCount;
4021
+ if (currentCount <= prevErrorCount) {
4022
+ prevErrorCount = currentCount;
4023
+ return;
4024
+ }
4025
+ const newErrors = state.errors.errors.slice(0, currentCount - prevErrorCount);
4026
+ _Core.logErrors(newErrors, "ErrorStore:FE");
4027
+ prevErrorCount = currentCount;
4028
+ });
4029
+ _Core._eventCleanupFns.push(storeUnsubscribe);
4030
+ _Core.log("Error store subscription initialized for frontend", verbose);
3976
4031
  }
3977
4032
  /**
3978
4033
  * Create HTTP error handler based on detected runtime.
@@ -8690,7 +8745,7 @@ var init_FrontendExampleDomainService = __esm({
8690
8745
  // Constructor
8691
8746
  // ─────────────────────────────────────────────────────────────────────────
8692
8747
  constructor(config = {}, options) {
8693
- const apiBasePath = config.apiBasePath || "/api/examples";
8748
+ const apiBasePath = config.apiBasePath || "";
8694
8749
  super({
8695
8750
  serviceName: "ExampleFrontendService",
8696
8751
  supportedRuntimes: ["frontend"],
@@ -8715,20 +8770,20 @@ var init_FrontendExampleDomainService = __esm({
8715
8770
  // Note: Use relative paths since apiClient.baseURL is already set to apiBasePath
8716
8771
  fetchers: {
8717
8772
  fetchAll: /* @__PURE__ */ __name(async (query) => {
8718
- return this.apiClient.get("", { params: query });
8773
+ return this.apiClient.get("/examples", { params: query });
8719
8774
  }, "fetchAll"),
8720
8775
  fetchById: /* @__PURE__ */ __name(async (id) => {
8721
- return this.apiClient.get(`/${id}`);
8776
+ return this.apiClient.get(`/examples/${id}`);
8722
8777
  }, "fetchById"),
8723
8778
  create: /* @__PURE__ */ __name(async (data) => {
8724
- return this.apiClient.post("", data);
8779
+ return this.apiClient.post("/examples", data);
8725
8780
  }, "create"),
8726
8781
  update: /* @__PURE__ */ __name(async (payload) => {
8727
8782
  const { id, data } = payload;
8728
- return this.apiClient.patch(`/${id}`, data);
8783
+ return this.apiClient.patch(`/examples/${id}`, data);
8729
8784
  }, "update"),
8730
8785
  delete: /* @__PURE__ */ __name(async (id) => {
8731
- return this.apiClient.delete(`/${id}`);
8786
+ return this.apiClient.delete(`/examples/${id}`);
8732
8787
  }, "delete")
8733
8788
  }
8734
8789
  // Store handlers - customize how data syncs to store
@@ -8796,7 +8851,7 @@ var init_FrontendExampleDomainService = __esm({
8796
8851
  // The ?? operator only falls through on null/undefined, not empty strings.
8797
8852
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
8798
8853
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
8799
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api/examples"
8854
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
8800
8855
  };
8801
8856
  const service = new _FrontendExampleDomainService(mergedConfig, options);
8802
8857
  if (mergedConfig.autoFetch) {
@@ -9200,7 +9255,7 @@ var init_FrontendFilesDomainService = __esm({
9200
9255
  logger4 = new PackageLogger({ packageName: "core", service: "FrontendFilesDomainService" });
9201
9256
  FrontendFilesDomainService = class _FrontendFilesDomainService extends BaseFrontendDomainService {
9202
9257
  constructor(config = {}, options) {
9203
- const apiBasePath = config.apiBasePath || "/api";
9258
+ const apiBasePath = config.apiBasePath || "";
9204
9259
  super({
9205
9260
  serviceName: "FrontendFilesDomainService",
9206
9261
  supportedRuntimes: ["frontend"],
@@ -9263,7 +9318,7 @@ var init_FrontendFilesDomainService = __esm({
9263
9318
  // The ?? operator only falls through on null/undefined, not empty strings.
9264
9319
  // An empty string is not a valid API path, so we treat it as "not provided" and fall through to default.
9265
9320
  // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
9266
- apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || "/api"
9321
+ apiBasePath: config.apiBasePath || options?.apiClient?.options?.baseURL || ""
9267
9322
  };
9268
9323
  return new _FrontendFilesDomainService(mergedConfig, options);
9269
9324
  }
@@ -9439,10 +9494,11 @@ var init_FrontendFilesDomainService = __esm({
9439
9494
  __name(resetFilesFrontendService, "resetFilesFrontendService");
9440
9495
  }
9441
9496
  });
9442
- var DEFAULT_ENCRYPTION_FIELDS, DEFAULT_CACHE_TTL_SECONDS, DEFAULT_AUDIT_RETENTION_DAYS, DEFAULT_POOL_SIZE, DEFAULT_CONFIG, TABLE_REGISTRY, DbService;
9497
+ var logger5, DEFAULT_ENCRYPTION_FIELDS, DEFAULT_CACHE_TTL_SECONDS, DEFAULT_AUDIT_RETENTION_DAYS, DEFAULT_POOL_SIZE, DEFAULT_CONFIG, TABLE_REGISTRY, DbService;
9443
9498
  var init_DbService = __esm({
9444
9499
  "src/services/DbService.ts"() {
9445
9500
  init_CoreEventManager();
9501
+ logger5 = new PackageLogger({ packageName: "core", service: "DbService" });
9446
9502
  DEFAULT_ENCRYPTION_FIELDS = {
9447
9503
  // User PII
9448
9504
  users: ["password_hash", "phone_number", "date_of_birth"],
@@ -9536,6 +9592,41 @@ var init_DbService = __esm({
9536
9592
  };
9537
9593
  CoreEventManager.emit(CORE_EVENTS.DATABASE.ERROR, payload);
9538
9594
  }
9595
+ /**
9596
+ * Creates merged event handlers that wrap user-provided handlers.
9597
+ * Adds Core-level logging and forwards to user handlers.
9598
+ *
9599
+ * Note: Unlike StorageService/NotificationService, DB events don't emit
9600
+ * to CoreEventManager by default (too verbose). User handlers can emit
9601
+ * if needed.
9602
+ *
9603
+ * @param userHandlers - User-provided event handlers from config
9604
+ * @returns Merged handlers with Core logging + user handlers
9605
+ */
9606
+ static createMergedEventHandlers(userHandlers) {
9607
+ return {
9608
+ ...userHandlers,
9609
+ onAfterWrite: /* @__PURE__ */ __name(async (event) => {
9610
+ logger5.debug("Database write completed", {
9611
+ operation: event.operation,
9612
+ table: event.table,
9613
+ duration: event.duration
9614
+ });
9615
+ if (userHandlers?.onAfterWrite) {
9616
+ await userHandlers.onAfterWrite(event);
9617
+ }
9618
+ }, "onAfterWrite"),
9619
+ onAfterRead: /* @__PURE__ */ __name(async (event) => {
9620
+ logger5.debug("Database read completed", {
9621
+ table: event.table,
9622
+ duration: event.duration
9623
+ });
9624
+ if (userHandlers?.onAfterRead) {
9625
+ await userHandlers.onAfterRead(event);
9626
+ }
9627
+ }, "onAfterRead")
9628
+ };
9629
+ }
9539
9630
  /**
9540
9631
  * Gets the singleton instance of DbService
9541
9632
  *
@@ -9740,6 +9831,8 @@ var init_DbService = __esm({
9740
9831
  if (cache) dbConfig.cache = cache;
9741
9832
  if (audit) dbConfig.audit = audit;
9742
9833
  if (encryption) dbConfig.encryption = encryption;
9834
+ const events = _DbService.createMergedEventHandlers(config.events);
9835
+ if (events) dbConfig.events = events;
9743
9836
  return dbConfig;
9744
9837
  }
9745
9838
  /**
@@ -10963,11 +11056,10 @@ var BackendFilesDomainService, backendFilesDomainService, _instance2;
10963
11056
  var init_BackendFilesDomainService = __esm({
10964
11057
  "src/domain/files/BackendFilesDomainService.ts"() {
10965
11058
  init_base();
10966
- init_events();
10967
11059
  init_files();
10968
11060
  init_FilesMapper();
10969
11061
  init_FilesValidator();
10970
- init_common();
11062
+ init_FilesMapper();
10971
11063
  BackendFilesDomainService = class _BackendFilesDomainService extends BaseBackendDomainService {
10972
11064
  constructor(config = {}, injected) {
10973
11065
  super({
@@ -11006,7 +11098,6 @@ var init_BackendFilesDomainService = __esm({
11006
11098
  */
11007
11099
  // eslint-disable-next-line complexity
11008
11100
  static async create(config = {}, options) {
11009
- this.registerEventHandlers();
11010
11101
  const injected = {
11011
11102
  cache: options?.cache?.instance,
11012
11103
  db: options?.db?.instance,
@@ -11016,59 +11107,6 @@ var init_BackendFilesDomainService = __esm({
11016
11107
  };
11017
11108
  return new _BackendFilesDomainService(config, injected);
11018
11109
  }
11019
- /**
11020
- * Register event handlers for file upload persistence.
11021
- * Called by CoreInitializer after storage and DB are initialized.
11022
- *
11023
- * Events handled:
11024
- * - files:upload:uploaded - Persist single file to DB
11025
- * - files:upload:bulk:uploaded - Persist multiple files to DB
11026
- *
11027
- * Deduplication: Uses unique constraint on storage_path.
11028
- *
11029
- * @param verbose - Enable verbose logging
11030
- */
11031
- static registerEventHandlers(verbose) {
11032
- if (BackendEventPersistenceHandler.isRegistered("files")) {
11033
- return;
11034
- }
11035
- BackendEventPersistenceHandler.register({
11036
- domain: "files",
11037
- events: {
11038
- "files:upload:uploaded": {
11039
- extractPayload: /* @__PURE__ */ __name((payload) => {
11040
- if (!isObject(payload)) return void 0;
11041
- const p = payload;
11042
- if (!FilesMapperClass.isUploadResult(p.result)) return void 0;
11043
- return p.result;
11044
- }, "extractPayload"),
11045
- isBulk: false,
11046
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
11047
- },
11048
- "files:upload:bulk:uploaded": {
11049
- extractPayload: /* @__PURE__ */ __name((payload) => {
11050
- if (!isObject(payload)) return [];
11051
- const p = payload;
11052
- const results = p.result?.results;
11053
- if (!Array.isArray(results)) return [];
11054
- return results.filter(FilesMapperClass.isUploadResult);
11055
- }, "extractPayload"),
11056
- isBulk: true,
11057
- validate: /* @__PURE__ */ __name((item) => Boolean(item.metadata?.fileId), "validate")
11058
- }
11059
- },
11060
- loadDependencies: /* @__PURE__ */ __name(async () => ({
11061
- repository: FilesRepository.create(),
11062
- mapper: FilesMapper
11063
- }), "loadDependencies"),
11064
- mapToDbRow: /* @__PURE__ */ __name((mapper, item) => mapper.toDbRow(item), "mapToDbRow"),
11065
- createRecord: /* @__PURE__ */ __name(async (repository, dbRow) => {
11066
- return repository.create(dbRow);
11067
- }, "createRecord"),
11068
- getUniqueKey: /* @__PURE__ */ __name((item) => item.metadata.path ?? item.metadata.fileId, "getUniqueKey"),
11069
- verbose
11070
- });
11071
- }
11072
11110
  isAvailable() {
11073
11111
  return this.isServiceEnabled;
11074
11112
  }
@@ -11171,6 +11209,36 @@ var init_BackendFilesDomainService = __esm({
11171
11209
  }
11172
11210
  }
11173
11211
  // ─────────────────────────────────────────────────────────────────────────
11212
+ // DB Persistence (called by StorageService on upload complete)
11213
+ // ─────────────────────────────────────────────────────────────────────────
11214
+ /**
11215
+ * Persist uploaded file metadata to the media table.
11216
+ * Called by StorageService.onFileUploaded event handler.
11217
+ *
11218
+ * @param metadata - File metadata from storage upload
11219
+ * @param variants - Optional file variants (thumbnails, etc.)
11220
+ * @param userId - Optional user ID who uploaded the file
11221
+ */
11222
+ async persistUploadedFile(metadata, variants, userId) {
11223
+ const startTime = Date.now();
11224
+ this.logDebug("Persisting uploaded file to DB", {
11225
+ fileId: metadata.fileId,
11226
+ filename: metadata.filename
11227
+ });
11228
+ try {
11229
+ const dbRow = FilesMapper.toDbRow({ metadata, variants }, userId);
11230
+ await this.repository.create(dbRow);
11231
+ this.logDebug("File persisted to media table", {
11232
+ fileId: metadata.fileId,
11233
+ filename: metadata.filename
11234
+ });
11235
+ await this.recordOperationMetrics("persistUploadedFile", Date.now() - startTime, true);
11236
+ } catch (error) {
11237
+ await this.recordOperationMetrics("persistUploadedFile", Date.now() - startTime, false);
11238
+ throw error;
11239
+ }
11240
+ }
11241
+ // ─────────────────────────────────────────────────────────────────────────
11174
11242
  // NOTE: CRUD and Storage Methods Are Inherited from BaseBackendDomainService
11175
11243
  // ─────────────────────────────────────────────────────────────────────────
11176
11244
  //
@@ -11431,6 +11499,8 @@ var getCacheService = /* @__PURE__ */ __name(() => CacheService.getInstance(), "
11431
11499
 
11432
11500
  // src/services/StorageService.ts
11433
11501
  init_CoreEventManager();
11502
+ init_BackendFilesDomainService();
11503
+ var logger6 = new PackageLogger({ packageName: "core", service: "StorageService" });
11434
11504
  var StorageService = class _StorageService {
11435
11505
  constructor() {
11436
11506
  this.storageService = null;
@@ -11487,6 +11557,28 @@ var StorageService = class _StorageService {
11487
11557
  _StorageService.instance = null;
11488
11558
  }
11489
11559
  }
11560
+ /**
11561
+ * Creates merged event handlers that persist uploads to the media table.
11562
+ * Merges Core's internal DB persistence handler with user-provided handlers.
11563
+ *
11564
+ * @param userHandlers - User-provided event handlers from config
11565
+ * @returns Merged handlers with DB persistence + user handlers
11566
+ */
11567
+ static createMergedEventHandlers(userHandlers) {
11568
+ return {
11569
+ ...userHandlers,
11570
+ // Persist uploaded files to media table, then call user handler
11571
+ onFileUploaded: /* @__PURE__ */ __name(async (payload) => {
11572
+ if (payload.metadata) {
11573
+ const filesService = await getBackendFilesDomainService();
11574
+ await filesService.persistUploadedFile(payload.metadata);
11575
+ }
11576
+ if (userHandlers?.onFileUploaded) {
11577
+ await userHandlers.onFileUploaded(payload);
11578
+ }
11579
+ }, "onFileUploaded")
11580
+ };
11581
+ }
11490
11582
  /**
11491
11583
  * Initializes the storage service
11492
11584
  *
@@ -11499,7 +11591,13 @@ var StorageService = class _StorageService {
11499
11591
  return instance;
11500
11592
  }
11501
11593
  instance.config = config;
11502
- instance.storageService = new StorageService$1(config);
11594
+ const mergedHandlers = _StorageService.createMergedEventHandlers(config.handlers);
11595
+ const mergedConfig = {
11596
+ ...config,
11597
+ handlers: mergedHandlers
11598
+ };
11599
+ logger6.info("[StorageService] Initializing with merged event handlers for DB persistence");
11600
+ instance.storageService = new StorageService$1(mergedConfig);
11503
11601
  instance.initialized = true;
11504
11602
  return instance;
11505
11603
  }
@@ -11636,6 +11734,7 @@ var StorageService = class _StorageService {
11636
11734
 
11637
11735
  // src/services/NotificationService.ts
11638
11736
  init_CoreEventManager();
11737
+ var MAX_FAIL_ATTEMPTS = 3;
11639
11738
  var NotificationService = class _NotificationService {
11640
11739
  constructor() {
11641
11740
  this.notificationService = null;
@@ -11666,6 +11765,58 @@ var NotificationService = class _NotificationService {
11666
11765
  CoreEventManager.emit(CORE_EVENTS.NOTIFICATION.ERROR, payload);
11667
11766
  }
11668
11767
  // ─────────────────────────────────────────────────────────────────
11768
+ // Merged Event Handlers
11769
+ // ─────────────────────────────────────────────────────────────────
11770
+ /**
11771
+ * Creates merged event handlers that emit to CoreEventManager.
11772
+ * Merges Core's internal handlers with user-provided handlers.
11773
+ *
11774
+ * @param userHandlers - User-provided event handlers from config
11775
+ * @returns Merged handlers with Core event emission + user handlers
11776
+ */
11777
+ static createMergedEventHandlers(userHandlers) {
11778
+ return {
11779
+ ...userHandlers,
11780
+ // Emit to CoreEventManager on sent, then call user handler
11781
+ onSent: /* @__PURE__ */ __name(async (payload) => {
11782
+ CoreEventManager.emit(CORE_EVENTS.NOTIFICATION.SENT, {
11783
+ notificationId: payload.notificationId,
11784
+ recipientId: payload.recipientId,
11785
+ channel: payload.channel,
11786
+ provider: payload.provider
11787
+ });
11788
+ if (userHandlers?.onSent) {
11789
+ await userHandlers.onSent(payload);
11790
+ }
11791
+ }, "onSent"),
11792
+ // Emit to CoreEventManager on failed, then call user handler
11793
+ onFailed: /* @__PURE__ */ __name(async (payload) => {
11794
+ CoreEventManager.emit(CORE_EVENTS.NOTIFICATION.FAILED, {
11795
+ notificationId: payload.notificationId,
11796
+ recipientId: payload.recipientId,
11797
+ channel: payload.channel,
11798
+ error: payload.error,
11799
+ retryable: payload.retryCount < MAX_FAIL_ATTEMPTS
11800
+ });
11801
+ if (userHandlers?.onFailed) {
11802
+ await userHandlers.onFailed(payload);
11803
+ }
11804
+ }, "onFailed"),
11805
+ // Emit to CoreEventManager on delivered, then call user handler
11806
+ onDelivered: /* @__PURE__ */ __name(async (payload) => {
11807
+ CoreEventManager.emit(CORE_EVENTS.NOTIFICATION.DELIVERED, {
11808
+ notificationId: payload.notificationId,
11809
+ recipientId: payload.recipientId,
11810
+ channel: payload.channel,
11811
+ deliveredAt: payload.deliveredAt
11812
+ });
11813
+ if (userHandlers?.onDelivered) {
11814
+ await userHandlers.onDelivered(payload);
11815
+ }
11816
+ }, "onDelivered")
11817
+ };
11818
+ }
11819
+ // ─────────────────────────────────────────────────────────────────
11669
11820
  // Singleton Management
11670
11821
  // ─────────────────────────────────────────────────────────────────
11671
11822
  /**
@@ -11703,8 +11854,12 @@ var NotificationService = class _NotificationService {
11703
11854
  if (instance.initialized) {
11704
11855
  return instance;
11705
11856
  }
11857
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
11706
11858
  instance.config = config;
11707
- instance.notificationService = new NotificationService$1(config);
11859
+ instance.notificationService = new NotificationService$1({
11860
+ ...config,
11861
+ events: mergedEvents
11862
+ });
11708
11863
  instance.initialized = true;
11709
11864
  return instance;
11710
11865
  }
@@ -11822,9 +11977,13 @@ var NotificationService = class _NotificationService {
11822
11977
  * @returns Promise that resolves to a new dedicated NotificationService instance
11823
11978
  */
11824
11979
  static async createInstance(config) {
11980
+ const mergedEvents = _NotificationService.createMergedEventHandlers(config.events);
11825
11981
  const dedicatedInstance = new _NotificationService();
11826
11982
  dedicatedInstance.config = config;
11827
- dedicatedInstance.notificationService = new NotificationService$1(config);
11983
+ dedicatedInstance.notificationService = new NotificationService$1({
11984
+ ...config,
11985
+ events: mergedEvents
11986
+ });
11828
11987
  dedicatedInstance.initialized = true;
11829
11988
  return dedicatedInstance;
11830
11989
  }
@@ -16800,7 +16959,7 @@ ErrorHandlingInterceptor = __decorateClass([
16800
16959
 
16801
16960
  // src/backend/featureFlags/config/validation.ts
16802
16961
  init_validation();
16803
- var logger5 = new PackageLogger({
16962
+ var logger7 = new PackageLogger({
16804
16963
  packageName: "core",
16805
16964
  service: "FeatureFlagConfigValidator"
16806
16965
  });
@@ -16986,7 +17145,7 @@ var FeatureFlagConfigValidator = class {
16986
17145
  static getWarnings(config, environment) {
16987
17146
  const env = environment ?? (config.isLoggingEnabled ? NODE_ENVIRONMENTS.DEVELOPMENT : NODE_ENVIRONMENTS.PRODUCTION);
16988
17147
  if (config.cacheTtl > NUMERIC_CONSTANTS.ONE_HOUR_SECONDS) {
16989
- logger5.warn(
17148
+ logger7.warn(
16990
17149
  "Cache TTL is very high (>1 hour). Consider reducing for better responsiveness.",
16991
17150
  {
16992
17151
  field: "cacheTtl",
@@ -16996,13 +17155,13 @@ var FeatureFlagConfigValidator = class {
16996
17155
  );
16997
17156
  }
16998
17157
  if (env === NODE_ENVIRONMENTS.PRODUCTION && config.isLoggingEnabled) {
16999
- logger5.warn("Logging is enabled in production. Consider disabling for performance.", {
17158
+ logger7.warn("Logging is enabled in production. Consider disabling for performance.", {
17000
17159
  field: "isLoggingEnabled",
17001
17160
  code: "PRODUCTION_LOGGING_ENABLED"
17002
17161
  });
17003
17162
  }
17004
17163
  if (env === NODE_ENVIRONMENTS.DEVELOPMENT && !config.isCacheEnabled) {
17005
- logger5.warn("Cache is disabled in development. This may impact performance testing.", {
17164
+ logger7.warn("Cache is disabled in development. This may impact performance testing.", {
17006
17165
  field: "isCacheEnabled",
17007
17166
  code: "DEVELOPMENT_CACHE_DISABLED"
17008
17167
  });