@freshpointcz/fresh-core 0.0.16 → 0.0.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import dayjs, { Dayjs } from 'dayjs';
2
- import { Logger } from 'winston';
3
- import { BaseEntity, ColumnOptions, Repository, EntityTarget, EntityManager, DataSourceOptions } from 'typeorm';
2
+ import { BaseEntity, ColumnOptions, Repository, EntityTarget, EntityManager, ObjectLiteral, EntitySubscriberInterface, InsertEvent, UpdateEvent, SoftRemoveEvent, TransactionCommitEvent, DataSourceOptions } from 'typeorm';
4
3
  import { Job } from 'node-schedule';
5
4
  import * as _eslint_core from '@eslint/core';
6
5
  import * as typescript_eslint_dist_compatibility_types from 'typescript-eslint/dist/compatibility-types';
@@ -54,8 +53,6 @@ declare class SinglePromiseWaiter<T = any> {
54
53
  get hasPromise(): boolean;
55
54
  }
56
55
 
57
- declare const logger: Logger;
58
-
59
56
  declare function isValidCron(expr: string): boolean;
60
57
 
61
58
  /**
@@ -348,6 +345,50 @@ declare abstract class FreshDao<T extends BaseEntity> {
348
345
  protected getRepo(manager?: EntityManager, entity?: EntityTarget<T>): Repository<T>;
349
346
  }
350
347
 
348
+ /**
349
+ * Change event types for entity lifecycle tracking.
350
+ */
351
+ type EntityChangeEvent = "created" | "updated" | "deleted";
352
+ /**
353
+ * Abstract base class for TypeORM EntitySubscribers that need to:
354
+ * 1. Track entity changes (insert/update/soft-remove)
355
+ * 2. Batch pending notifications within a transaction
356
+ * 3. Send notifications only after transaction commit
357
+ *
358
+ * Subclasses must implement:
359
+ * - `listenTo()` — target entity class
360
+ * - `pendingKey` — unique key for storing pending events in `queryRunner.data`
361
+ * - `subscriberName` — name used in log messages
362
+ * - `handleNotification(id, changeEvent, manager)` — actual notification logic
363
+ */
364
+ declare abstract class BaseEntityChangeSubscriber<Entity extends ObjectLiteral, IdType = number> implements EntitySubscriberInterface<Entity> {
365
+ /**
366
+ * Unique key used to store pending change events in `queryRunner.data`.
367
+ * Must be unique per subscriber to avoid collisions when multiple subscribers
368
+ * share the same queryRunner.
369
+ */
370
+ protected abstract readonly PENDING_KEY: string;
371
+ /**
372
+ * Human-readable name for log/error messages.
373
+ */
374
+ protected abstract readonly SUBSCRIBER_NAME: string;
375
+ /**
376
+ * Return the entity class this subscriber listens to.
377
+ */
378
+ abstract listenTo(): Function;
379
+ /**
380
+ * Implement the actual notification/side-effect logic.
381
+ * Called after transaction commit (or immediately if no transaction is active).
382
+ */
383
+ protected abstract handleNotification(id: IdType, changeEvent: EntityChangeEvent, manager: EntityManager): Promise<void>;
384
+ afterInsert(event: InsertEvent<Entity>): void;
385
+ afterUpdate(event: UpdateEvent<Entity>): void;
386
+ afterSoftRemove(event: SoftRemoveEvent<Entity>): void;
387
+ afterTransactionCommit(event: TransactionCommitEvent): Promise<void>;
388
+ private addPending;
389
+ private sendNotification;
390
+ }
391
+
351
392
  declare class Category extends FreshEntity {
352
393
  }
353
394
 
@@ -433,6 +474,46 @@ declare function isNumberInRange(v: unknown, min: number, max: number, options?:
433
474
  includeMin?: boolean;
434
475
  includeMax?: boolean;
435
476
  }): v is number;
477
+ /**
478
+ * Numeric boolean flag type — either 0 or 1
479
+ */
480
+ type BinaryFlag = 0 | 1;
481
+ /**
482
+ * Converts a number, boolean, null, or undefined to a BinaryFlag (0 or 1)
483
+ *
484
+ * @example
485
+ * TO_BINARY_FLAG(true) // 1
486
+ * TO_BINARY_FLAG(1) // 1
487
+ * TO_BINARY_FLAG(false) // 0
488
+ * TO_BINARY_FLAG(null) // 0
489
+ */
490
+ declare const TO_BINARY_FLAG: (v: number | boolean | null | undefined) => BinaryFlag;
491
+
492
+ /**
493
+ * Runs async worker functions over items with a concurrency limit.
494
+ *
495
+ * @param items - Array of items to process
496
+ * @param concurrency - Maximum number of concurrent workers
497
+ * @param worker - Async function to run for each item
498
+ *
499
+ * @example
500
+ * await runWithConcurrency(productIds, 5, async (id) => {
501
+ * await processProduct(id);
502
+ * });
503
+ */
504
+ declare function runWithConcurrency<T>(items: T[], concurrency: number, worker: (item: T) => Promise<void>): Promise<void>;
505
+
506
+ type TransformMap<TIn, TOut, K extends keyof TIn & keyof TOut> = Partial<{
507
+ [P in K]: (val: TIn[P]) => TOut[P];
508
+ }>;
509
+ /**
510
+ * Builds a partial patch object from an input DTO, only including keys that are defined.
511
+ * Optionally applies transform functions to specific keys.
512
+ *
513
+ * @example
514
+ * const patch = buildPatch(data, ["name", "active"], { active: (v) => v ? 1 : 0 });
515
+ */
516
+ declare function buildPatch<TOut extends Record<string, any>, TIn extends Record<string, any>, K extends keyof TIn & keyof TOut = keyof TIn & keyof TOut>(data: Partial<TIn>, keys: readonly K[], transforms?: TransformMap<TIn, TOut, K>): Partial<Pick<TOut, K>>;
436
517
 
437
518
  type Maybe<T> = T | null;
438
519
  /**
@@ -564,6 +645,18 @@ declare class ApiError extends Error {
564
645
  constructor(statusCode: number, status: Status, detail?: string);
565
646
  }
566
647
 
648
+ /**
649
+ * Represents a non-fatal business rule violation that should be
650
+ * communicated to the caller without being treated as a system error.
651
+ *
652
+ * @example
653
+ * throw new BusinessWarning("ORDER_ALREADY_SENT", "Tato objednávka již byla odeslána.");
654
+ */
655
+ declare class BusinessWarning extends Error {
656
+ readonly code: string;
657
+ constructor(code: string, message: string);
658
+ }
659
+
567
660
  declare const FRESH_ESLINT_CONFIG: {
568
661
  files: string[];
569
662
  ignores: string[];
@@ -641,4 +734,4 @@ interface HealthCheckResult {
641
734
  };
642
735
  }
643
736
 
644
- export { AMOUNT_UNIT, ActionCommandCode, ApiError, type CardNumber, Category, DataHelper, DateUtils, type Deferred, DepotPoolStatus, Device, FreshDao, FreshEntity, FreshHyperEntity, FreshJob, FreshTranslationBase, type HealthCheckResult, HttpStatus, LanguageCode, Manufacturer, type Maybe, PaymentMethod, PG_DATA_SOURCE_OPTIONS as PgDataSourceOptions, Product, SinglePromiseWaiter, Singleton, type Status, StatusDto, Subcategory, TimestampColumn, TransactionType, createDeferred, FRESH_ESLINT_CONFIG as freshEslintConfig, hasOwn, isEnumValue, isFlag01, isMaybe, isNumber, isNumberInRange, isObject, isString, isValidCron, logger };
737
+ export { AMOUNT_UNIT, ActionCommandCode, ApiError, BaseEntityChangeSubscriber, type BinaryFlag, BusinessWarning, type CardNumber, Category, DataHelper, DateUtils, type Deferred, DepotPoolStatus, Device, type EntityChangeEvent, FreshDao, FreshEntity, FreshHyperEntity, FreshJob, FreshTranslationBase, type HealthCheckResult, HttpStatus, LanguageCode, Manufacturer, type Maybe, PaymentMethod, PG_DATA_SOURCE_OPTIONS as PgDataSourceOptions, Product, SinglePromiseWaiter, Singleton, type Status, StatusDto, Subcategory, TO_BINARY_FLAG, TimestampColumn, TransactionType, buildPatch, createDeferred, FRESH_ESLINT_CONFIG as freshEslintConfig, hasOwn, isEnumValue, isFlag01, isMaybe, isNumber, isNumberInRange, isObject, isString, isValidCron, runWithConcurrency };
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import dayjs, { Dayjs } from 'dayjs';
2
- import { Logger } from 'winston';
3
- import { BaseEntity, ColumnOptions, Repository, EntityTarget, EntityManager, DataSourceOptions } from 'typeorm';
2
+ import { BaseEntity, ColumnOptions, Repository, EntityTarget, EntityManager, ObjectLiteral, EntitySubscriberInterface, InsertEvent, UpdateEvent, SoftRemoveEvent, TransactionCommitEvent, DataSourceOptions } from 'typeorm';
4
3
  import { Job } from 'node-schedule';
5
4
  import * as _eslint_core from '@eslint/core';
6
5
  import * as typescript_eslint_dist_compatibility_types from 'typescript-eslint/dist/compatibility-types';
@@ -54,8 +53,6 @@ declare class SinglePromiseWaiter<T = any> {
54
53
  get hasPromise(): boolean;
55
54
  }
56
55
 
57
- declare const logger: Logger;
58
-
59
56
  declare function isValidCron(expr: string): boolean;
60
57
 
61
58
  /**
@@ -348,6 +345,50 @@ declare abstract class FreshDao<T extends BaseEntity> {
348
345
  protected getRepo(manager?: EntityManager, entity?: EntityTarget<T>): Repository<T>;
349
346
  }
350
347
 
348
+ /**
349
+ * Change event types for entity lifecycle tracking.
350
+ */
351
+ type EntityChangeEvent = "created" | "updated" | "deleted";
352
+ /**
353
+ * Abstract base class for TypeORM EntitySubscribers that need to:
354
+ * 1. Track entity changes (insert/update/soft-remove)
355
+ * 2. Batch pending notifications within a transaction
356
+ * 3. Send notifications only after transaction commit
357
+ *
358
+ * Subclasses must implement:
359
+ * - `listenTo()` — target entity class
360
+ * - `pendingKey` — unique key for storing pending events in `queryRunner.data`
361
+ * - `subscriberName` — name used in log messages
362
+ * - `handleNotification(id, changeEvent, manager)` — actual notification logic
363
+ */
364
+ declare abstract class BaseEntityChangeSubscriber<Entity extends ObjectLiteral, IdType = number> implements EntitySubscriberInterface<Entity> {
365
+ /**
366
+ * Unique key used to store pending change events in `queryRunner.data`.
367
+ * Must be unique per subscriber to avoid collisions when multiple subscribers
368
+ * share the same queryRunner.
369
+ */
370
+ protected abstract readonly PENDING_KEY: string;
371
+ /**
372
+ * Human-readable name for log/error messages.
373
+ */
374
+ protected abstract readonly SUBSCRIBER_NAME: string;
375
+ /**
376
+ * Return the entity class this subscriber listens to.
377
+ */
378
+ abstract listenTo(): Function;
379
+ /**
380
+ * Implement the actual notification/side-effect logic.
381
+ * Called after transaction commit (or immediately if no transaction is active).
382
+ */
383
+ protected abstract handleNotification(id: IdType, changeEvent: EntityChangeEvent, manager: EntityManager): Promise<void>;
384
+ afterInsert(event: InsertEvent<Entity>): void;
385
+ afterUpdate(event: UpdateEvent<Entity>): void;
386
+ afterSoftRemove(event: SoftRemoveEvent<Entity>): void;
387
+ afterTransactionCommit(event: TransactionCommitEvent): Promise<void>;
388
+ private addPending;
389
+ private sendNotification;
390
+ }
391
+
351
392
  declare class Category extends FreshEntity {
352
393
  }
353
394
 
@@ -433,6 +474,46 @@ declare function isNumberInRange(v: unknown, min: number, max: number, options?:
433
474
  includeMin?: boolean;
434
475
  includeMax?: boolean;
435
476
  }): v is number;
477
+ /**
478
+ * Numeric boolean flag type — either 0 or 1
479
+ */
480
+ type BinaryFlag = 0 | 1;
481
+ /**
482
+ * Converts a number, boolean, null, or undefined to a BinaryFlag (0 or 1)
483
+ *
484
+ * @example
485
+ * TO_BINARY_FLAG(true) // 1
486
+ * TO_BINARY_FLAG(1) // 1
487
+ * TO_BINARY_FLAG(false) // 0
488
+ * TO_BINARY_FLAG(null) // 0
489
+ */
490
+ declare const TO_BINARY_FLAG: (v: number | boolean | null | undefined) => BinaryFlag;
491
+
492
+ /**
493
+ * Runs async worker functions over items with a concurrency limit.
494
+ *
495
+ * @param items - Array of items to process
496
+ * @param concurrency - Maximum number of concurrent workers
497
+ * @param worker - Async function to run for each item
498
+ *
499
+ * @example
500
+ * await runWithConcurrency(productIds, 5, async (id) => {
501
+ * await processProduct(id);
502
+ * });
503
+ */
504
+ declare function runWithConcurrency<T>(items: T[], concurrency: number, worker: (item: T) => Promise<void>): Promise<void>;
505
+
506
+ type TransformMap<TIn, TOut, K extends keyof TIn & keyof TOut> = Partial<{
507
+ [P in K]: (val: TIn[P]) => TOut[P];
508
+ }>;
509
+ /**
510
+ * Builds a partial patch object from an input DTO, only including keys that are defined.
511
+ * Optionally applies transform functions to specific keys.
512
+ *
513
+ * @example
514
+ * const patch = buildPatch(data, ["name", "active"], { active: (v) => v ? 1 : 0 });
515
+ */
516
+ declare function buildPatch<TOut extends Record<string, any>, TIn extends Record<string, any>, K extends keyof TIn & keyof TOut = keyof TIn & keyof TOut>(data: Partial<TIn>, keys: readonly K[], transforms?: TransformMap<TIn, TOut, K>): Partial<Pick<TOut, K>>;
436
517
 
437
518
  type Maybe<T> = T | null;
438
519
  /**
@@ -564,6 +645,18 @@ declare class ApiError extends Error {
564
645
  constructor(statusCode: number, status: Status, detail?: string);
565
646
  }
566
647
 
648
+ /**
649
+ * Represents a non-fatal business rule violation that should be
650
+ * communicated to the caller without being treated as a system error.
651
+ *
652
+ * @example
653
+ * throw new BusinessWarning("ORDER_ALREADY_SENT", "Tato objednávka již byla odeslána.");
654
+ */
655
+ declare class BusinessWarning extends Error {
656
+ readonly code: string;
657
+ constructor(code: string, message: string);
658
+ }
659
+
567
660
  declare const FRESH_ESLINT_CONFIG: {
568
661
  files: string[];
569
662
  ignores: string[];
@@ -641,4 +734,4 @@ interface HealthCheckResult {
641
734
  };
642
735
  }
643
736
 
644
- export { AMOUNT_UNIT, ActionCommandCode, ApiError, type CardNumber, Category, DataHelper, DateUtils, type Deferred, DepotPoolStatus, Device, FreshDao, FreshEntity, FreshHyperEntity, FreshJob, FreshTranslationBase, type HealthCheckResult, HttpStatus, LanguageCode, Manufacturer, type Maybe, PaymentMethod, PG_DATA_SOURCE_OPTIONS as PgDataSourceOptions, Product, SinglePromiseWaiter, Singleton, type Status, StatusDto, Subcategory, TimestampColumn, TransactionType, createDeferred, FRESH_ESLINT_CONFIG as freshEslintConfig, hasOwn, isEnumValue, isFlag01, isMaybe, isNumber, isNumberInRange, isObject, isString, isValidCron, logger };
737
+ export { AMOUNT_UNIT, ActionCommandCode, ApiError, BaseEntityChangeSubscriber, type BinaryFlag, BusinessWarning, type CardNumber, Category, DataHelper, DateUtils, type Deferred, DepotPoolStatus, Device, type EntityChangeEvent, FreshDao, FreshEntity, FreshHyperEntity, FreshJob, FreshTranslationBase, type HealthCheckResult, HttpStatus, LanguageCode, Manufacturer, type Maybe, PaymentMethod, PG_DATA_SOURCE_OPTIONS as PgDataSourceOptions, Product, SinglePromiseWaiter, Singleton, type Status, StatusDto, Subcategory, TO_BINARY_FLAG, TimestampColumn, TransactionType, buildPatch, createDeferred, FRESH_ESLINT_CONFIG as freshEslintConfig, hasOwn, isEnumValue, isFlag01, isMaybe, isNumber, isNumberInRange, isObject, isString, isValidCron, runWithConcurrency };
package/dist/index.js CHANGED
@@ -108,7 +108,7 @@ var require_main = __commonJS({
108
108
  var fs = require("fs");
109
109
  var path2 = require("path");
110
110
  var os = require("os");
111
- var crypto3 = require("crypto");
111
+ var crypto = require("crypto");
112
112
  var packageJson = require_package();
113
113
  var version = packageJson.version;
114
114
  var LINE = /(?:^|^)\s*(?:export\s+)?([\w.-]+)(?:\s*=\s*?|:\s+?)(\s*'(?:\\'|[^'])*'|\s*"(?:\\"|[^"])*"|\s*`(?:\\`|[^`])*`|[^#\r\n]+)?\s*(?:#.*)?(?:$|$)/mg;
@@ -355,7 +355,7 @@ var require_main = __commonJS({
355
355
  const authTag = ciphertext.subarray(-16);
356
356
  ciphertext = ciphertext.subarray(12, -16);
357
357
  try {
358
- const aesgcm = crypto3.createDecipheriv("aes-256-gcm", key, nonce);
358
+ const aesgcm = crypto.createDecipheriv("aes-256-gcm", key, nonce);
359
359
  aesgcm.setAuthTag(authTag);
360
360
  return `${aesgcm.update(ciphertext)}${aesgcm.final()}`;
361
361
  } catch (error) {
@@ -428,6 +428,8 @@ __export(index_exports, {
428
428
  AMOUNT_UNIT: () => AMOUNT_UNIT,
429
429
  ActionCommandCode: () => ActionCommandCode,
430
430
  ApiError: () => ApiError,
431
+ BaseEntityChangeSubscriber: () => BaseEntityChangeSubscriber,
432
+ BusinessWarning: () => BusinessWarning,
431
433
  Category: () => Category,
432
434
  DataHelper: () => DataHelper,
433
435
  DateUtils: () => DateUtils,
@@ -448,8 +450,10 @@ __export(index_exports, {
448
450
  Singleton: () => Singleton,
449
451
  StatusDto: () => StatusDto,
450
452
  Subcategory: () => Subcategory,
453
+ TO_BINARY_FLAG: () => TO_BINARY_FLAG,
451
454
  TimestampColumn: () => TimestampColumn,
452
455
  TransactionType: () => TransactionType,
456
+ buildPatch: () => buildPatch,
453
457
  createDeferred: () => createDeferred,
454
458
  freshEslintConfig: () => eslint_config_default,
455
459
  hasOwn: () => hasOwn,
@@ -461,7 +465,7 @@ __export(index_exports, {
461
465
  isObject: () => isObject,
462
466
  isString: () => isString,
463
467
  isValidCron: () => isValidCron,
464
- logger: () => logger
468
+ runWithConcurrency: () => runWithConcurrency
465
469
  });
466
470
  module.exports = __toCommonJS(index_exports);
467
471
 
@@ -691,56 +695,6 @@ __name(_SinglePromiseWaiter, "SinglePromiseWaiter");
691
695
  __publicField(_SinglePromiseWaiter, "_instance");
692
696
  var SinglePromiseWaiter = _SinglePromiseWaiter;
693
697
 
694
- // src/common/winston.ts
695
- var import_winston = __toESM(require("winston"));
696
- var SERVICE = process.env.SERVICE_NAME || "depot-ordering-service";
697
- var ENV = process.env.NODE_ENV || "localhost";
698
- var levelToSeverity = {
699
- silly: "debug",
700
- verbose: "debug",
701
- debug: "debug",
702
- http: "info",
703
- info: "info",
704
- warn: "warn",
705
- error: "error"
706
- };
707
- var baseFormat = import_winston.default.format.combine(import_winston.default.format.timestamp({
708
- format: /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISOString(), "format")
709
- }), import_winston.default.format.errors({
710
- stack: true
711
- }), import_winston.default.format.printf((info) => {
712
- const payload = {
713
- ts: info.timestamp,
714
- level: info.level,
715
- severity: levelToSeverity[info.level] || info.level,
716
- message: info.message,
717
- service: SERVICE,
718
- env: ENV,
719
- process_name: process.title || process.argv[1] || "node",
720
- pid: process.pid,
721
- device: process.env.DEVICE_ID || void 0,
722
- traceId: info.traceId,
723
- spanId: info.spanId,
724
- extra: info.extra || info.meta || void 0,
725
- stack: info.stack
726
- };
727
- return JSON.stringify(payload);
728
- }));
729
- var transports = [
730
- new import_winston.default.transports.Console({
731
- level: process.env.LOG_LEVEL || "info"
732
- })
733
- ];
734
- var logger = import_winston.default.createLogger({
735
- level: process.env.LOG_LEVEL || "info",
736
- format: baseFormat,
737
- transports,
738
- exitOnError: false
739
- });
740
- logger.stream = {
741
- write: /* @__PURE__ */ __name((message) => logger.info(message.trim()), "write")
742
- };
743
-
744
698
  // src/common/utils/is-cron-valid.ts
745
699
  function isValidCron(expr) {
746
700
  const parts = expr.trim().split(/\s+/);
@@ -865,46 +819,53 @@ var import_typeorm5 = require("typeorm");
865
819
  // src/database/entities/fresh-entity.ts
866
820
  var import_typeorm = require("typeorm");
867
821
 
868
- // ../../node_modules/uuid/dist/esm-node/rng.js
869
- var import_crypto = __toESM(require("crypto"));
822
+ // ../../node_modules/uuid/dist/esm/stringify.js
823
+ var byteToHex = [];
824
+ for (let i = 0; i < 256; ++i) {
825
+ byteToHex.push((i + 256).toString(16).slice(1));
826
+ }
827
+ function unsafeStringify(arr, offset = 0) {
828
+ return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
829
+ }
830
+ __name(unsafeStringify, "unsafeStringify");
831
+
832
+ // ../../node_modules/uuid/dist/esm/rng.js
833
+ var import_crypto = require("crypto");
870
834
  var rnds8Pool = new Uint8Array(256);
871
835
  var poolPtr = rnds8Pool.length;
872
836
  function rng() {
873
837
  if (poolPtr > rnds8Pool.length - 16) {
874
- import_crypto.default.randomFillSync(rnds8Pool);
838
+ (0, import_crypto.randomFillSync)(rnds8Pool);
875
839
  poolPtr = 0;
876
840
  }
877
841
  return rnds8Pool.slice(poolPtr, poolPtr += 16);
878
842
  }
879
843
  __name(rng, "rng");
880
844
 
881
- // ../../node_modules/uuid/dist/esm-node/stringify.js
882
- var byteToHex = [];
883
- for (let i = 0; i < 256; ++i) {
884
- byteToHex.push((i + 256).toString(16).slice(1));
885
- }
886
- function unsafeStringify(arr, offset = 0) {
887
- return byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + "-" + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + "-" + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + "-" + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + "-" + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]];
888
- }
889
- __name(unsafeStringify, "unsafeStringify");
890
-
891
- // ../../node_modules/uuid/dist/esm-node/native.js
892
- var import_crypto2 = __toESM(require("crypto"));
845
+ // ../../node_modules/uuid/dist/esm/native.js
846
+ var import_crypto2 = require("crypto");
893
847
  var native_default = {
894
- randomUUID: import_crypto2.default.randomUUID
848
+ randomUUID: import_crypto2.randomUUID
895
849
  };
896
850
 
897
- // ../../node_modules/uuid/dist/esm-node/v4.js
851
+ // ../../node_modules/uuid/dist/esm/v4.js
898
852
  function v4(options, buf, offset) {
853
+ var _a, _b, _c;
899
854
  if (native_default.randomUUID && !buf && !options) {
900
855
  return native_default.randomUUID();
901
856
  }
902
857
  options = options || {};
903
- const rnds = options.random || (options.rng || rng)();
858
+ const rnds = (_c = (_b = options.random) != null ? _b : (_a = options.rng) == null ? void 0 : _a.call(options)) != null ? _c : rng();
859
+ if (rnds.length < 16) {
860
+ throw new Error("Random bytes length must be >= 16");
861
+ }
904
862
  rnds[6] = rnds[6] & 15 | 64;
905
863
  rnds[8] = rnds[8] & 63 | 128;
906
864
  if (buf) {
907
865
  offset = offset || 0;
866
+ if (offset < 0 || offset + 16 > buf.length) {
867
+ throw new RangeError(`UUID byte range ${offset}:${offset + 15} is out of buffer bounds`);
868
+ }
908
869
  for (let i = 0; i < 16; ++i) {
909
870
  buf[offset + i] = rnds[i];
910
871
  }
@@ -1207,6 +1168,83 @@ var _FreshDao = class _FreshDao {
1207
1168
  __name(_FreshDao, "FreshDao");
1208
1169
  var FreshDao = _FreshDao;
1209
1170
 
1171
+ // src/database/subscribers/base-entity-change.subscriber.ts
1172
+ var _BaseEntityChangeSubscriber = class _BaseEntityChangeSubscriber {
1173
+ // ─── TypeORM lifecycle hooks ────────────────────────────────────────
1174
+ afterInsert(event) {
1175
+ var _a;
1176
+ const id = (_a = event.entity) == null ? void 0 : _a.id;
1177
+ if (!id) {
1178
+ return;
1179
+ }
1180
+ if (event.queryRunner.isTransactionActive) {
1181
+ this.addPending(event, id, "created");
1182
+ } else {
1183
+ console.warn(`${this.SUBSCRIBER_NAME} - Notification sent outside transaction for id=${id}`);
1184
+ this.sendNotification(id, "created", event.queryRunner.manager);
1185
+ }
1186
+ }
1187
+ afterUpdate(event) {
1188
+ var _a, _b, _c;
1189
+ const id = (_c = (_a = event.entity) == null ? void 0 : _a.id) != null ? _c : (_b = event.databaseEntity) == null ? void 0 : _b.id;
1190
+ if (!id) {
1191
+ return;
1192
+ }
1193
+ if (event.queryRunner.isTransactionActive) {
1194
+ this.addPending(event, id, "updated");
1195
+ } else {
1196
+ console.warn(`${this.SUBSCRIBER_NAME} - Notification sent outside transaction for id=${id}`);
1197
+ this.sendNotification(id, "updated", event.queryRunner.manager);
1198
+ }
1199
+ }
1200
+ afterSoftRemove(event) {
1201
+ var _a, _b, _c;
1202
+ const id = (_c = (_a = event.entity) == null ? void 0 : _a.id) != null ? _c : (_b = event.databaseEntity) == null ? void 0 : _b.id;
1203
+ if (!id) {
1204
+ return;
1205
+ }
1206
+ if (event.queryRunner.isTransactionActive) {
1207
+ this.addPending(event, id, "deleted");
1208
+ } else {
1209
+ console.warn(`${this.SUBSCRIBER_NAME} - Notification sent outside transaction for id=${id}`);
1210
+ this.sendNotification(id, "deleted", event.queryRunner.manager);
1211
+ }
1212
+ }
1213
+ async afterTransactionCommit(event) {
1214
+ var _a;
1215
+ const pending = (_a = event.queryRunner.data) == null ? void 0 : _a[this.PENDING_KEY];
1216
+ if (!pending || pending.size === 0) {
1217
+ return;
1218
+ }
1219
+ event.queryRunner.data[this.PENDING_KEY] = /* @__PURE__ */ new Map();
1220
+ for (const [id, changeEvent] of pending) {
1221
+ await this.sendNotification(id, changeEvent, event.connection.manager);
1222
+ }
1223
+ }
1224
+ // ─── Private helpers ───────────────────────────────────────────────
1225
+ addPending(event, id, changeEvent) {
1226
+ if (!event.queryRunner.data) {
1227
+ event.queryRunner.data = {};
1228
+ }
1229
+ if (!event.queryRunner.data[this.PENDING_KEY]) {
1230
+ event.queryRunner.data[this.PENDING_KEY] = /* @__PURE__ */ new Map();
1231
+ }
1232
+ const existing = event.queryRunner.data[this.PENDING_KEY].get(id);
1233
+ if (!existing || changeEvent === "created" || changeEvent === "deleted") {
1234
+ event.queryRunner.data[this.PENDING_KEY].set(id, changeEvent);
1235
+ }
1236
+ }
1237
+ async sendNotification(id, changeEvent, manager) {
1238
+ try {
1239
+ await this.handleNotification(id, changeEvent, manager);
1240
+ } catch (error) {
1241
+ console.error(`${this.SUBSCRIBER_NAME} - Failed to send notification: ${error instanceof Error ? error.message : error}`);
1242
+ }
1243
+ }
1244
+ };
1245
+ __name(_BaseEntityChangeSubscriber, "BaseEntityChangeSubscriber");
1246
+ var BaseEntityChangeSubscriber = _BaseEntityChangeSubscriber;
1247
+
1210
1248
  // src/common/schema/entities/category.entity.ts
1211
1249
  function _ts_decorate4(decorators, target, key, desc) {
1212
1250
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -1340,6 +1378,40 @@ function isNumberInRange(v, min, max, options) {
1340
1378
  return true;
1341
1379
  }
1342
1380
  __name(isNumberInRange, "isNumberInRange");
1381
+ var TO_BINARY_FLAG = /* @__PURE__ */ __name((v) => v === 1 || v === true ? 1 : 0, "TO_BINARY_FLAG");
1382
+
1383
+ // src/common/utils/async.utils.ts
1384
+ async function runWithConcurrency(items, concurrency, worker) {
1385
+ const queue = [
1386
+ ...items
1387
+ ];
1388
+ const runNext = /* @__PURE__ */ __name(async () => {
1389
+ const item = queue.shift();
1390
+ if (!item) {
1391
+ return;
1392
+ }
1393
+ await worker(item);
1394
+ await runNext();
1395
+ }, "runNext");
1396
+ await Promise.all(Array.from({
1397
+ length: Math.min(concurrency, items.length)
1398
+ }, runNext));
1399
+ }
1400
+ __name(runWithConcurrency, "runWithConcurrency");
1401
+
1402
+ // src/common/utils/patch.utils.ts
1403
+ function buildPatch(data, keys, transforms) {
1404
+ const patch = {};
1405
+ for (const k of keys) {
1406
+ const value = data[k];
1407
+ if (value === void 0) {
1408
+ continue;
1409
+ }
1410
+ patch[k] = (transforms == null ? void 0 : transforms[k]) ? transforms[k](value) : value;
1411
+ }
1412
+ return patch;
1413
+ }
1414
+ __name(buildPatch, "buildPatch");
1343
1415
 
1344
1416
  // src/core/data-helper.ts
1345
1417
  var _DataHelper = class _DataHelper {
@@ -1514,6 +1586,18 @@ var _ApiError = class _ApiError extends Error {
1514
1586
  __name(_ApiError, "ApiError");
1515
1587
  var ApiError = _ApiError;
1516
1588
 
1589
+ // src/core/errors/business-warning.ts
1590
+ var _BusinessWarning = class _BusinessWarning extends Error {
1591
+ constructor(code, message) {
1592
+ super(message);
1593
+ __publicField(this, "code");
1594
+ this.code = code;
1595
+ this.name = "BusinessWarning";
1596
+ }
1597
+ };
1598
+ __name(_BusinessWarning, "BusinessWarning");
1599
+ var BusinessWarning = _BusinessWarning;
1600
+
1517
1601
  // src/types/maybe-type.ts
1518
1602
  function isMaybe(v, inner) {
1519
1603
  return v === null || inner(v);
@@ -1707,6 +1791,8 @@ var datasource_default = PG_DATA_SOURCE_OPTIONS;
1707
1791
  AMOUNT_UNIT,
1708
1792
  ActionCommandCode,
1709
1793
  ApiError,
1794
+ BaseEntityChangeSubscriber,
1795
+ BusinessWarning,
1710
1796
  Category,
1711
1797
  DataHelper,
1712
1798
  DateUtils,
@@ -1727,8 +1813,10 @@ var datasource_default = PG_DATA_SOURCE_OPTIONS;
1727
1813
  Singleton,
1728
1814
  StatusDto,
1729
1815
  Subcategory,
1816
+ TO_BINARY_FLAG,
1730
1817
  TimestampColumn,
1731
1818
  TransactionType,
1819
+ buildPatch,
1732
1820
  createDeferred,
1733
1821
  freshEslintConfig,
1734
1822
  hasOwn,
@@ -1740,6 +1828,6 @@ var datasource_default = PG_DATA_SOURCE_OPTIONS;
1740
1828
  isObject,
1741
1829
  isString,
1742
1830
  isValidCron,
1743
- logger
1831
+ runWithConcurrency
1744
1832
  });
1745
1833
  //# sourceMappingURL=index.js.map