@quatrain/core 1.2.9 → 1.2.11

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,6 +1,8 @@
1
1
  import { BaseObject } from './BaseObject';
2
+ import { User } from './User';
2
3
  import { BaseObjectType } from './types/BaseObjectType';
3
4
  export interface EntityType extends BaseObjectType {
5
+ users?: User[];
4
6
  }
5
7
  export declare class Entity extends BaseObject {
6
8
  static COLLECTION: string;
@@ -1 +1 @@
1
- {"version":3,"file":"Entity.d.ts","sourceRoot":"","sources":["../../src/components/Entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAMzC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEvD,MAAM,WAAW,UAAW,SAAQ,cAAc;CAEjD;AAMD,qBAAa,MAAO,SAAQ,UAAU;IAEnC,MAAM,CAAC,UAAU,SAAa;IAG9B,MAAM,CAAC,gBAAgB,QAiBtB;WAQY,OAAO,CAAC,GAAG,GAAE,GAAe,GAAG,OAAO,CAAC,MAAM,CAAC;CAG7D"}
1
+ {"version":3,"file":"Entity.d.ts","sourceRoot":"","sources":["../../src/components/Entity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAA;AAEzC,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAA;AAI7B,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAEvD,MAAM,WAAW,UAAW,SAAQ,cAAc;IAC/C,KAAK,CAAC,EAAE,IAAI,EAAE,CAAA;CAChB;AAMD,qBAAa,MAAO,SAAQ,UAAU;IAEnC,MAAM,CAAC,UAAU,SAAa;IAG9B,MAAM,CAAC,gBAAgB,QAiBtB;WAQY,OAAO,CAAC,GAAG,GAAE,GAAe,GAAG,OAAO,CAAC,MAAM,CAAC;CAG7D"}
@@ -36,6 +36,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.Entity = void 0;
37
37
  const BaseObject_1 = require("./BaseObject");
38
38
  const BaseObjectProperties_1 = require("./BaseObjectProperties");
39
+ const User_1 = require("./User");
40
+ const CollectionProperty_1 = require("../properties/CollectionProperty");
39
41
  const StringProperty_1 = require("../properties/StringProperty");
40
42
  const htmlType = __importStar(require("../properties/types/PropertyHTMLType"));
41
43
  class Entity extends BaseObject_1.BaseObject {
@@ -49,6 +51,13 @@ class Entity extends BaseObject_1.BaseObject {
49
51
  minLength: 1,
50
52
  htmlType: htmlType.ORG,
51
53
  },
54
+ {
55
+ name: 'users',
56
+ mandatory: false,
57
+ type: CollectionProperty_1.CollectionProperty.TYPE,
58
+ instanceOf: User_1.User,
59
+ parentKey: 'entity',
60
+ },
52
61
  ];
53
62
  static async factory(src = undefined) {
54
63
  return super.factory(src, Entity);
@@ -1 +1 @@
1
- {"version":3,"file":"Entity.js","sourceRoot":"","sources":["../../src/components/Entity.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAAyC;AACzC,iEAA6D;AAG7D,iEAA6D;AAC7D,+EAAgE;AAWhE,MAAa,MAAO,SAAQ,uBAAU;IAEnC,MAAM,CAAC,UAAU,GAAG,UAAU,CAAA;IAG9B,MAAM,CAAC,gBAAgB,GAAG;QACvB,GAAG,2CAAoB;QACvB;YAEG,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,+BAAc,CAAC,IAAI;YACzB,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,QAAQ,CAAC,GAAG;SACxB;KAQH,CAAA;IAQD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAW,SAAS;QACtC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;;AAhCJ,wBAiCC"}
1
+ {"version":3,"file":"Entity.js","sourceRoot":"","sources":["../../src/components/Entity.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6CAAyC;AACzC,iEAA6D;AAC7D,iCAA6B;AAC7B,yEAAqE;AACrE,iEAA6D;AAC7D,+EAAgE;AAWhE,MAAa,MAAO,SAAQ,uBAAU;IAEnC,MAAM,CAAC,UAAU,GAAG,UAAU,CAAA;IAG9B,MAAM,CAAC,gBAAgB,GAAG;QACvB,GAAG,2CAAoB;QACvB;YAEG,IAAI,EAAE,MAAM;YACZ,IAAI,EAAE,+BAAc,CAAC,IAAI;YACzB,SAAS,EAAE,IAAI;YACf,SAAS,EAAE,CAAC;YACZ,QAAQ,EAAE,QAAQ,CAAC,GAAG;SACxB;QACD;YACG,IAAI,EAAE,OAAO;YACb,SAAS,EAAE,KAAK;YAChB,IAAI,EAAE,uCAAkB,CAAC,IAAI;YAC7B,UAAU,EAAE,WAAI;YAChB,SAAS,EAAE,QAAQ;SACrB;KACH,CAAA;IAQD,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,MAAW,SAAS;QACtC,OAAO,KAAK,CAAC,OAAO,CAAC,GAAG,EAAE,MAAM,CAAC,CAAA;IACpC,CAAC;;AAhCJ,wBAiCC"}
@@ -15,5 +15,14 @@ export declare class CollectionProperty extends BaseProperty {
15
15
  constructor(config: CollectionPropertyType);
16
16
  set(value: Array<any>, setChanged?: boolean): this;
17
17
  toJSON(): any[] | DataObjectClass<any>[] | ObjectUri[] | undefined;
18
+ sum(property: string, items?: any[]): number | Promise<number>;
19
+ average(property: string, items?: any[]): number | Promise<number>;
20
+ distinct(property: string, items?: any[]): any[] | Promise<any[]>;
21
+ min(property: string, items?: any[]): number | undefined | Promise<number | undefined>;
22
+ max(property: string, items?: any[]): number | undefined | Promise<number | undefined>;
23
+ groupBy(property: string, items?: any[]): Record<string, any[]> | Promise<Record<string, any[]>>;
24
+ pluck(property: string, items?: any[]): any[] | Promise<any[]>;
25
+ count(predicate?: (item: any) => boolean, items?: any[]): number | Promise<number>;
26
+ apply(fn: (item: any) => any | Promise<any>, items?: any[]): any[] | Promise<any[]>;
18
27
  }
19
28
  //# sourceMappingURL=CollectionProperty.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionProperty.d.ts","sourceRoot":"","sources":["../../src/properties/CollectionProperty.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAYrD,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC7D,UAAU,EAAE,OAAO,UAAU,CAAA;IAC7B,OAAO,CAAC,EAAE,GAAG,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACpB;AAcD,qBAAa,kBAAmB,SAAQ,YAAY;IAEjD,MAAM,CAAC,IAAI,SAAe;IAC1B,SAAS,CAAC,MAAM,EACX,KAAK,CAAC,GAAG,CAAC,GACV,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,GAC3B,KAAK,CAAC,SAAS,CAAC,GAChB,SAAS,CAAY;IAC1B,SAAS,CAAC,WAAW,EAAE,OAAO,UAAU,CAAA;IACxC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAA;gBAEhB,MAAM,EAAE,sBAAsB;IAoB1C,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,UAAO;IASxC,MAAM;CAGR"}
1
+ {"version":3,"file":"CollectionProperty.d.ts","sourceRoot":"","sources":["../../src/properties/CollectionProperty.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAC/D,OAAO,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AAEnD,OAAO,EAAE,eAAe,EAAE,MAAM,qCAAqC,CAAA;AACrE,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAA;AAYrD,MAAM,WAAW,sBAAuB,SAAQ,gBAAgB;IAC7D,UAAU,EAAE,OAAO,UAAU,CAAA;IAC7B,OAAO,CAAC,EAAE,GAAG,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;CACpB;AAcD,qBAAa,kBAAmB,SAAQ,YAAY;IAEjD,MAAM,CAAC,IAAI,SAAe;IAC1B,SAAS,CAAC,MAAM,EACX,KAAK,CAAC,GAAG,CAAC,GACV,KAAK,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,GAC3B,KAAK,CAAC,SAAS,CAAC,GAChB,SAAS,CAAY;IAC1B,SAAS,CAAC,WAAW,EAAE,OAAO,UAAU,CAAA;IACxC,SAAS,CAAC,UAAU,EAAE,MAAM,CAAA;gBAEhB,MAAM,EAAE,sBAAsB;IAoB1C,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,UAAU,UAAO;IASxC,MAAM;IAoBN,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAgBjF,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAiBrF,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAepF,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAuBzG,GAAG,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,MAAM,GAAG,SAAS,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;IAuBzG,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,CAAC;IAqBnH,KAAK,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAcjF,KAAK,CAAC,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,OAAO,EAAE,KAAK,GAAE,GAAG,EAAsB,GAAG,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAcrG,KAAK,CACF,EAAE,EAAE,CAAC,IAAI,EAAE,GAAG,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,EACrC,KAAK,GAAE,GAAG,EAAsB,GAChC,GAAG,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;CAO3B"}
@@ -26,6 +26,87 @@ class CollectionProperty extends BaseProperty_1.BaseProperty {
26
26
  toJSON() {
27
27
  return this._value;
28
28
  }
29
+ sum(property, items = this._value || []) {
30
+ return items.reduce((acc, item) => {
31
+ const val = typeof item.val === 'function' ? item.val(property) : item[property];
32
+ const num = Number(val);
33
+ return acc + (isNaN(num) ? 0 : num);
34
+ }, 0);
35
+ }
36
+ average(property, items = this._value || []) {
37
+ if (items.length === 0)
38
+ return 0;
39
+ const sumVal = this.sum(property, items);
40
+ if (sumVal && typeof sumVal.then === 'function') {
41
+ return sumVal.then((s) => s / items.length);
42
+ }
43
+ return sumVal / items.length;
44
+ }
45
+ distinct(property, items = this._value || []) {
46
+ const values = items.map((item) => {
47
+ return typeof item.val === 'function' ? item.val(property) : item[property];
48
+ });
49
+ return Array.from(new Set(values));
50
+ }
51
+ min(property, items = this._value || []) {
52
+ if (items.length === 0)
53
+ return undefined;
54
+ let minVal = undefined;
55
+ items.forEach((item) => {
56
+ const val = typeof item.val === 'function' ? item.val(property) : item[property];
57
+ const num = Number(val);
58
+ if (!isNaN(num)) {
59
+ if (minVal === undefined || num < minVal) {
60
+ minVal = num;
61
+ }
62
+ }
63
+ });
64
+ return minVal;
65
+ }
66
+ max(property, items = this._value || []) {
67
+ if (items.length === 0)
68
+ return undefined;
69
+ let maxVal = undefined;
70
+ items.forEach((item) => {
71
+ const val = typeof item.val === 'function' ? item.val(property) : item[property];
72
+ const num = Number(val);
73
+ if (!isNaN(num)) {
74
+ if (maxVal === undefined || num > maxVal) {
75
+ maxVal = num;
76
+ }
77
+ }
78
+ });
79
+ return maxVal;
80
+ }
81
+ groupBy(property, items = this._value || []) {
82
+ const groups = {};
83
+ items.forEach((item) => {
84
+ const val = typeof item.val === 'function' ? item.val(property) : item[property];
85
+ const key = String(val ?? 'undefined');
86
+ if (!groups[key]) {
87
+ groups[key] = [];
88
+ }
89
+ groups[key].push(item);
90
+ });
91
+ return groups;
92
+ }
93
+ pluck(property, items = this._value || []) {
94
+ return items.map((item) => {
95
+ return typeof item.val === 'function' ? item.val(property) : item[property];
96
+ });
97
+ }
98
+ count(predicate, items = this._value || []) {
99
+ if (!predicate)
100
+ return items.length;
101
+ return items.filter(predicate).length;
102
+ }
103
+ apply(fn, items = this._value || []) {
104
+ const results = items.map((item) => fn(item));
105
+ if (results.some((r) => r && typeof r.then === 'function')) {
106
+ return Promise.all(results);
107
+ }
108
+ return results;
109
+ }
29
110
  }
30
111
  exports.CollectionProperty = CollectionProperty;
31
112
  //# sourceMappingURL=CollectionProperty.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CollectionProperty.js","sourceRoot":"","sources":["../../src/properties/CollectionProperty.ts"],"names":[],"mappings":";;;AAAA,iDAA+D;AAE/D,kCAA8B;AAgC9B,MAAa,kBAAmB,SAAQ,2BAAY;IAEjD,MAAM,CAAC,IAAI,GAAG,YAAY,CAAA;IAChB,MAAM,GAIC,SAAS,CAAA;IAChB,WAAW,CAAmB;IAC9B,UAAU,CAAQ;IAE5B,YAAY,MAA8B;QACvC,KAAK,CAAC,MAAM,CAAC,CAAA;QACb,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;QACzD,CAAC;QACD,IAAI,CAAC,WAAW;YACb,OAAO,MAAM,CAAC,UAAU,KAAK,QAAQ;gBAClC,CAAC,CAAC,WAAI,CAAC,aAAa,CAAC,MAAM,CAAC,UAAU,CAAC;gBACvC,CAAC,CAAC,MAAM,CAAC,UAAU,CAAA;QACzB,IAAI,CAAC,UAAU;YACZ,MAAM,CAAC,SAAS,IAAI,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,IAAI,SAAS,CAAA;IACpE,CAAC;IASD,GAAG,CAAC,KAAiB,EAAE,UAAU,GAAG,IAAI;QACrC,OAAO,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IACtC,CAAC;IAOD,MAAM;QACH,OAAO,IAAI,CAAC,MAAM,CAAA;IACrB,CAAC;;AA1CJ,gDA2CC"}
1
+ {"version":3,"file":"CollectionProperty.js","sourceRoot":"","sources":["../../src/properties/CollectionProperty.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"HashProperty.d.ts","sourceRoot":"","sources":["../../src/properties/HashProperty.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGrE,MAAM,MAAM,iBAAiB,GACxB,OAAO,YAAY,CAAC,aAAa,GACjC,OAAO,YAAY,CAAC,cAAc,GAClC,OAAO,YAAY,CAAC,gBAAgB,GACpC,OAAO,YAAY,CAAC,gBAAgB,CAAA;AAYzC,MAAM,WAAW,gBAAiB,SAAQ,kBAAkB;IACzD,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACpB;AAoBD,qBAAa,YAAa,SAAQ,cAAc;IAE7C,MAAM,CAAC,IAAI,SAAS;IAEpB,MAAM,CAAC,aAAa,SAAQ;IAE5B,MAAM,CAAC,cAAc,SAAS;IAE9B,MAAM,CAAC,gBAAgB,SAAW;IAElC,MAAM,CAAC,gBAAgB,SAAW;IAElC,SAAS,CAAC,UAAU,EAAE,iBAAiB,CAAA;IACvC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAK;IAC5B,SAAS,CAAC,SAAS,UAAQ;gBAEf,MAAM,EAAE,gBAAgB;IAepC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IA2C5B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,UAAO;IAWpC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGjC"}
1
+ {"version":3,"file":"HashProperty.d.ts","sourceRoot":"","sources":["../../src/properties/HashProperty.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGrE,MAAM,MAAM,iBAAiB,GACxB,OAAO,YAAY,CAAC,aAAa,GACjC,OAAO,YAAY,CAAC,cAAc,GAClC,OAAO,YAAY,CAAC,gBAAgB,GACpC,OAAO,YAAY,CAAC,gBAAgB,CAAA;AAYzC,MAAM,WAAW,gBAAiB,SAAQ,kBAAkB;IACzD,SAAS,CAAC,EAAE,iBAAiB,CAAA;IAC7B,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,QAAQ,CAAC,EAAE,OAAO,CAAA;CACpB;AAoBD,qBAAa,YAAa,SAAQ,cAAc;IAE7C,MAAM,CAAC,IAAI,SAAS;IAEpB,MAAM,CAAC,aAAa,SAAQ;IAE5B,MAAM,CAAC,cAAc,SAAS;IAE9B,MAAM,CAAC,gBAAgB,SAAW;IAElC,MAAM,CAAC,gBAAgB,SAAW;IAElC,SAAS,CAAC,UAAU,EAAE,iBAAiB,CAAA;IACvC,SAAS,CAAC,KAAK,EAAE,MAAM,CAAK;IAC5B,SAAS,CAAC,SAAS,UAAQ;gBAEf,MAAM,EAAE,gBAAgB;IA8BpC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IA2C5B,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,UAAU,UAAO;IAWpC,OAAO,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO;CAGjC"}
@@ -15,9 +15,18 @@ class HashProperty extends StringProperty_1.StringProperty {
15
15
  _prefixed = false;
16
16
  constructor(config) {
17
17
  super(config);
18
- this._algorithm = config.algorithm || HashProperty.ALGORITHM_MD5;
18
+ this._algorithm = config.algorithm || HashProperty.ALGORITHM_SHA256;
19
19
  this._salt = config.salt || '';
20
20
  this._prefixed = config.prefixed || false;
21
+ if (this._algorithm === HashProperty.ALGORITHM_MD5 ||
22
+ this._algorithm === HashProperty.ALGORITHM_SHA1) {
23
+ const sensitiveTerms = ['password', 'pwd', 'secret', 'token', 'credential'];
24
+ const nameLower = this._name.toLowerCase();
25
+ const isSensitive = sensitiveTerms.some((term) => nameLower.includes(term));
26
+ if (isSensitive) {
27
+ throw new Error(`SecurityError: Insecure hashing algorithm "${this._algorithm}" cannot be used for sensitive property "${this._name}". Please use SHA256 or another secure algorithm.`);
28
+ }
29
+ }
21
30
  }
22
31
  _hash(value) {
23
32
  let algo;
@@ -1 +1 @@
1
- {"version":3,"file":"HashProperty.js","sourceRoot":"","sources":["../../src/properties/HashProperty.ts"],"names":[],"mappings":";;;AAAA,6CAAwC;AACxC,qDAAqE;AACrE,kCAA8B;AA0C9B,MAAa,YAAa,SAAQ,+BAAc;IAE7C,MAAM,CAAC,IAAI,GAAG,MAAM,CAAA;IAEpB,MAAM,CAAC,aAAa,GAAG,KAAK,CAAA;IAE5B,MAAM,CAAC,cAAc,GAAG,MAAM,CAAA;IAE9B,MAAM,CAAC,gBAAgB,GAAG,QAAQ,CAAA;IAElC,MAAM,CAAC,gBAAgB,GAAG,QAAQ,CAAA;IAExB,UAAU,CAAmB;IAC7B,KAAK,GAAW,EAAE,CAAA;IAClB,SAAS,GAAG,KAAK,CAAA;IAE3B,YAAY,MAAwB;QACjC,KAAK,CAAC,MAAM,CAAC,CAAA;QACb,IAAI,CAAC,UAAU,GAAG,MAAM,CAAC,SAAS,IAAI,YAAY,CAAC,aAAa,CAAA;QAChE,IAAI,CAAC,KAAK,GAAG,MAAM,CAAC,IAAI,IAAI,EAAE,CAAA;QAC9B,IAAI,CAAC,SAAS,GAAG,MAAM,CAAC,QAAQ,IAAI,KAAK,CAAA;IAC5C,CAAC;IAUD,KAAK,CAAC,KAAa;QAChB,IAAI,IAAI,CAAA;QACR,QAAQ,IAAI,CAAC,UAAU,EAAE,CAAC;YACvB,KAAK,YAAY,CAAC,aAAa;gBAC5B,IAAI,GAAG,IAAA,wBAAU,EAAC,KAAK,CAAC,CAAA;gBACxB,WAAI,CAAC,IAAI,CACN,sDAAsD,CACxD,CAAA;gBACD,MAAK;YAER,KAAK,YAAY,CAAC,cAAc;gBAC7B,IAAI,GAAG,IAAA,wBAAU,EAAC,MAAM,CAAC,CAAA;gBACzB,WAAI,CAAC,IAAI,CACN,uDAAuD,CACzD,CAAA;gBACD,MAAK;YAER,KAAK,YAAY,CAAC,gBAAgB;gBAC/B,IAAI,GAAG,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAA;gBAC3B,MAAK;YAER;gBACG,MAAM,IAAI,KAAK,CACZ,0CAA0C,IAAI,CAAC,UAAU,EAAE,CAC7D,CAAA;QACP,CAAC;QAED,IAAI,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,UAAU,GAAG,CAAC,CAAC,CAAC,EAAE,CAAA;QACtD,IAAI,IAAI,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,GAAG,KAAK,EAAE,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA;QAE1D,IAAI,CAAC,SAAS,GAAG,KAAK,CAAA;QAEtB,OAAO,IAAI,CAAA;IACd,CAAC;IAUD,GAAG,CAAC,KAAa,EAAE,UAAU,GAAG,IAAI;QACjC,OAAO,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,UAAU,CAAC,CAAA;IAClD,CAAC;IASD,OAAO,CAAC,KAAa;QAClB,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,MAAM,CAAA;IAC3C,CAAC;;AAvFJ,oCAwFC"}
1
+ {"version":3,"file":"HashProperty.js","sourceRoot":"","sources":["../../src/properties/HashProperty.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quatrain/core",
3
- "version": "1.2.9",
3
+ "version": "1.2.11",
4
4
  "description": "Core objects for business apps",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -33,7 +33,7 @@
33
33
  "typescript": "^5.2.2"
34
34
  },
35
35
  "dependencies": {
36
- "@quatrain/log": "^1.2.2",
36
+ "@quatrain/log": "^1.2.3",
37
37
  "node-persist": "^4.0.4",
38
38
  "which": "^5.0.0"
39
39
  },
@@ -1,13 +1,13 @@
1
1
  import { BaseObject } from './BaseObject'
2
2
  import { BaseObjectProperties } from './BaseObjectProperties'
3
- //import { User } from './User'
4
- //import { CollectionProperty } from '../properties/CollectionProperty'
3
+ import { User } from './User'
4
+ import { CollectionProperty } from '../properties/CollectionProperty'
5
5
  import { StringProperty } from '../properties/StringProperty'
6
6
  import * as htmlType from '../properties/types/PropertyHTMLType'
7
7
  import { BaseObjectType } from './types/BaseObjectType'
8
8
 
9
9
  export interface EntityType extends BaseObjectType {
10
- // users?: User[]
10
+ users?: User[]
11
11
  }
12
12
 
13
13
  /**
@@ -29,13 +29,13 @@ export class Entity extends BaseObject {
29
29
  minLength: 1,
30
30
  htmlType: htmlType.ORG,
31
31
  },
32
- // {
33
- // name: 'users',
34
- // mandatory: true,
35
- // type: CollectionProperty.TYPE,
36
- // instanceOf: User,
37
- // parentKey: 'entity',
38
- // },
32
+ {
33
+ name: 'users',
34
+ mandatory: false,
35
+ type: CollectionProperty.TYPE,
36
+ instanceOf: User,
37
+ parentKey: 'entity',
38
+ },
39
39
  ]
40
40
 
41
41
  /**
@@ -1,22 +1,118 @@
1
1
  import { Entity } from '../components/Entity'
2
2
  import { User } from '../components/User'
3
- import { DataGenerator } from '@quatrain/testing'
4
- import * as statuses from '../common/statuses'
3
+ import { CollectionProperty } from './CollectionProperty'
4
+ import { Core } from '../Core'
5
5
 
6
6
  describe('Collection Property', () => {
7
- test('can retrieve user records matching relation', async () => {
8
- // const entity = await Entity.factory()
9
- // const user = await User.factory()
7
+ test('should throw an error if instantiated without instanceOf', () => {
8
+ expect(() => new CollectionProperty({ name: 'users' } as any)).toThrow("Parameter 'instanceOf' is mandatory")
9
+ })
10
+
11
+ test('should instantiate correctly with a class constructor', () => {
12
+ const prop = new CollectionProperty({
13
+ name: 'users',
14
+ instanceOf: User,
15
+ })
16
+ expect(prop.name).toBe('users')
17
+ })
10
18
 
11
- // // Generate 3 users not associated to any entity
12
- // await DataGenerator(user, 3, { status: statuses.CREATED })
19
+ test('should instantiate correctly with a string class name from Core registry', () => {
20
+ Core.addClass('User', User)
21
+ const prop = new CollectionProperty({
22
+ name: 'users',
23
+ instanceOf: 'User',
24
+ })
25
+ expect(prop.name).toBe('users')
26
+ })
13
27
 
14
- // // Generate 3 users associated with entity 1
15
- // await DataGenerator(user, 3, { status: statuses.CREATED, entity })
28
+ test('should set and get values using set() and val()', async () => {
29
+ const prop = new CollectionProperty({
30
+ name: 'users',
31
+ instanceOf: User,
32
+ })
33
+ const user1 = await User.factory()
34
+ const user2 = await User.factory()
35
+ prop.set([user1, user2])
36
+ expect(prop.val()).toEqual([user1, user2])
37
+ })
38
+
39
+ test('should serialize correctly using toJSON()', async () => {
40
+ const prop = new CollectionProperty({
41
+ name: 'users',
42
+ instanceOf: User,
43
+ })
44
+ const user1 = await User.factory()
45
+ prop.set([user1])
46
+ expect(prop.toJSON()).toEqual([user1])
47
+ })
16
48
 
17
- // const value = await entity.val('users')
18
- // expect(value).toBeInstanceOf(Array)
19
- // expect(value.length).toBe(3)
20
- //
49
+ test('Entity should have users collection property', async () => {
50
+ const entity = await Entity.factory({ name: 'My Company' })
51
+ const usersProp = entity.dataObject.get('users')
52
+ expect(usersProp).toBeDefined()
53
+ expect(usersProp.constructor.name).toBe('CollectionProperty')
54
+ })
55
+
56
+ test('should correctly compute composite methods (sum, average, distinct, min, max, groupBy, pluck, count)', () => {
57
+ const prop = new CollectionProperty({
58
+ name: 'users',
59
+ instanceOf: User,
60
+ })
61
+
62
+ // We use items that mimic BaseObject's val() method
63
+ const items = [
64
+ { val: (p: string) => (p === 'score' ? 10 : p === 'group' ? 'A' : undefined) },
65
+ { val: (p: string) => (p === 'score' ? 20 : p === 'group' ? 'B' : undefined) },
66
+ { val: (p: string) => (p === 'score' ? 30 : p === 'group' ? 'A' : undefined) },
67
+ ]
68
+
69
+ prop.set(items)
70
+
71
+ // sum
72
+ expect(prop.sum('score')).toBe(60)
73
+
74
+ // average
75
+ expect(prop.average('score')).toBe(20)
76
+
77
+ // distinct
78
+ expect(prop.distinct('group').sort()).toEqual(['A', 'B'])
79
+
80
+ // min / max
81
+ expect(prop.min('score')).toBe(10)
82
+ expect(prop.max('score')).toBe(30)
83
+
84
+ // groupBy
85
+ const grouped = prop.groupBy('group')
86
+ expect(grouped['A'].length).toBe(2)
87
+ expect(grouped['B'].length).toBe(1)
88
+
89
+ // pluck
90
+ expect(prop.pluck('score')).toEqual([10, 20, 30])
91
+
92
+ // count
93
+ expect(prop.count()).toBe(3)
94
+ expect(prop.count((x) => x.val('score') > 15)).toBe(2)
95
+ })
96
+
97
+ test('should apply synchronous and asynchronous callbacks using apply()', async () => {
98
+ const prop = new CollectionProperty({
99
+ name: 'users',
100
+ instanceOf: User,
101
+ })
102
+
103
+ const items = [{ id: 1 }, { id: 2 }]
104
+ prop.set(items)
105
+
106
+ // Sync apply
107
+ const syncResult = prop.apply((item) => item.id * 10)
108
+ expect(syncResult).toEqual([10, 20])
109
+
110
+ // Async apply
111
+ const asyncResult = await prop.apply(async (item) => {
112
+ return Promise.resolve(item.id * 100)
113
+ })
114
+ expect(asyncResult).toEqual([100, 200])
21
115
  })
22
116
  })
117
+
118
+
@@ -75,4 +75,174 @@ export class CollectionProperty extends BaseProperty {
75
75
  toJSON() {
76
76
  return this._value
77
77
  }
78
+
79
+ /**
80
+ * Sums the numeric values of a property across items in the collection.
81
+ * Can aggregate either the internal collection value or an external array.
82
+ *
83
+ * @param property - The name of the property to sum.
84
+ * @param items - Optional external items array. Defaults to the internal collection array.
85
+ * @returns The sum of all numeric values.
86
+ */
87
+ /**
88
+ * Sums the numeric values of a property across items in the collection.
89
+ * Can aggregate either the internal collection value or an external array.
90
+ *
91
+ * @param property - The name of the property to sum.
92
+ * @param items - Optional external items array. Defaults to the internal collection array.
93
+ * @returns The sum of all numeric values.
94
+ */
95
+ sum(property: string, items: any[] = this._value || []): number | Promise<number> {
96
+ return items.reduce((acc, item) => {
97
+ const val = typeof item.val === 'function' ? item.val(property) : item[property]
98
+ const num = Number(val)
99
+ return acc + (isNaN(num) ? 0 : num)
100
+ }, 0)
101
+ }
102
+
103
+ /**
104
+ * Calculates the average of the numeric values of a property across items in the collection.
105
+ * Can aggregate either the internal collection value or an external array.
106
+ *
107
+ * @param property - The name of the property to average.
108
+ * @param items - Optional external items array. Defaults to the internal collection array.
109
+ * @returns The average of all numeric values, or 0 if empty.
110
+ */
111
+ average(property: string, items: any[] = this._value || []): number | Promise<number> {
112
+ if (items.length === 0) return 0
113
+ const sumVal = this.sum(property, items)
114
+ if (sumVal && typeof (sumVal as any).then === 'function') {
115
+ return (sumVal as any).then((s: number) => s / items.length)
116
+ }
117
+ return (sumVal as number) / items.length
118
+ }
119
+
120
+ /**
121
+ * Retrieves all distinct values of a property across the collection items.
122
+ * Can aggregate either the internal collection value or an external array.
123
+ *
124
+ * @param property - The name of the property.
125
+ * @param items - Optional external items array. Defaults to the internal collection array.
126
+ * @returns An array of unique property values.
127
+ */
128
+ distinct(property: string, items: any[] = this._value || []): any[] | Promise<any[]> {
129
+ const values = items.map((item) => {
130
+ return typeof item.val === 'function' ? item.val(property) : item[property]
131
+ })
132
+ return Array.from(new Set(values))
133
+ }
134
+
135
+ /**
136
+ * Returns the minimum value of a numeric property across the collection items.
137
+ * Can aggregate either the internal collection value or an external array.
138
+ *
139
+ * @param property - The name of the property.
140
+ * @param items - Optional external items array. Defaults to the internal collection array.
141
+ * @returns The minimum numeric value found, or undefined if no valid numbers are present.
142
+ */
143
+ min(property: string, items: any[] = this._value || []): number | undefined | Promise<number | undefined> {
144
+ if (items.length === 0) return undefined
145
+ let minVal: number | undefined = undefined
146
+ items.forEach((item) => {
147
+ const val = typeof item.val === 'function' ? item.val(property) : item[property]
148
+ const num = Number(val)
149
+ if (!isNaN(num)) {
150
+ if (minVal === undefined || num < minVal) {
151
+ minVal = num
152
+ }
153
+ }
154
+ })
155
+ return minVal
156
+ }
157
+
158
+ /**
159
+ * Returns the maximum value of a numeric property across the collection items.
160
+ * Can aggregate either the internal collection value or an external array.
161
+ *
162
+ * @param property - The name of the property.
163
+ * @param items - Optional external items array. Defaults to the internal collection array.
164
+ * @returns The maximum numeric value found, or undefined if no valid numbers are present.
165
+ */
166
+ max(property: string, items: any[] = this._value || []): number | undefined | Promise<number | undefined> {
167
+ if (items.length === 0) return undefined
168
+ let maxVal: number | undefined = undefined
169
+ items.forEach((item) => {
170
+ const val = typeof item.val === 'function' ? item.val(property) : item[property]
171
+ const num = Number(val)
172
+ if (!isNaN(num)) {
173
+ if (maxVal === undefined || num > maxVal) {
174
+ maxVal = num
175
+ }
176
+ }
177
+ })
178
+ return maxVal
179
+ }
180
+
181
+ /**
182
+ * Groups the collection items by the values of a specific property.
183
+ * Can aggregate either the internal collection value or an external array.
184
+ *
185
+ * @param property - The name of the property to group by.
186
+ * @param items - Optional external items array. Defaults to the internal collection array.
187
+ * @returns A dictionary object where keys are the property values and values are arrays of matching items.
188
+ */
189
+ groupBy(property: string, items: any[] = this._value || []): Record<string, any[]> | Promise<Record<string, any[]>> {
190
+ const groups: Record<string, any[]> = {}
191
+ items.forEach((item) => {
192
+ const val = typeof item.val === 'function' ? item.val(property) : item[property]
193
+ const key = String(val ?? 'undefined')
194
+ if (!groups[key]) {
195
+ groups[key] = []
196
+ }
197
+ groups[key].push(item)
198
+ })
199
+ return groups
200
+ }
201
+
202
+ /**
203
+ * Plucks a specific property from each item in the collection.
204
+ * Can aggregate either the internal collection value or an external array.
205
+ *
206
+ * @param property - The name of the property to extract.
207
+ * @param items - Optional external items array. Defaults to the internal collection array.
208
+ * @returns An array containing the extracted property values.
209
+ */
210
+ pluck(property: string, items: any[] = this._value || []): any[] | Promise<any[]> {
211
+ return items.map((item) => {
212
+ return typeof item.val === 'function' ? item.val(property) : item[property]
213
+ })
214
+ }
215
+
216
+ /**
217
+ * Returns the count of items in the collection, optionally filtered by a predicate callback.
218
+ * Can aggregate either the internal collection value or an external array.
219
+ *
220
+ * @param predicate - An optional filter callback to run on each item.
221
+ * @param items - Optional external items array. Defaults to the internal collection array.
222
+ * @returns The count of matching items.
223
+ */
224
+ count(predicate?: (item: any) => boolean, items: any[] = this._value || []): number | Promise<number> {
225
+ if (!predicate) return items.length
226
+ return items.filter(predicate).length
227
+ }
228
+
229
+ /**
230
+ * Applies an anonymous function to each item in the collection.
231
+ * Can execute either on the internal collection value or an external array.
232
+ * Supporting both synchronous and asynchronous callback functions.
233
+ *
234
+ * @param fn - The anonymous callback function to apply to each item.
235
+ * @param items - Optional external items array. Defaults to the internal collection array.
236
+ * @returns The results of the function applications (or a Promise resolving to the results if async).
237
+ */
238
+ apply(
239
+ fn: (item: any) => any | Promise<any>,
240
+ items: any[] = this._value || []
241
+ ): any[] | Promise<any[]> {
242
+ const results = items.map((item) => fn(item))
243
+ if (results.some((r) => r && typeof r.then === 'function')) {
244
+ return Promise.all(results)
245
+ }
246
+ return results
247
+ }
78
248
  }
@@ -106,3 +106,49 @@ describe('Hash Property allows comparison of string and hash value', () => {
106
106
  expect(prop.set('azertyuiop').compare('qwertz')).toBe(false)
107
107
  })
108
108
  })
109
+
110
+ describe('Hash Property Security Policies', () => {
111
+ test('defaults to SHA256 when no algorithm is provided', () => {
112
+ const prop = new HashProperty({
113
+ name: 'my_hash',
114
+ })
115
+ expect(prop.set('azertyuiop').get()).toBe(
116
+ 'aa3d2fe4f6d301dbd6b8fb2d2fddfb7aeebf3bec53ffff4b39a0967afa88c609'
117
+ )
118
+ })
119
+
120
+ test('throws error if MD5 is used for sensitive property names', () => {
121
+ expect(() => {
122
+ new HashProperty({
123
+ name: 'userPassword',
124
+ algorithm: HashProperty.ALGORITHM_MD5,
125
+ })
126
+ }).toThrow('SecurityError: Insecure hashing algorithm')
127
+
128
+ expect(() => {
129
+ new HashProperty({
130
+ name: 'client_pwd',
131
+ algorithm: HashProperty.ALGORITHM_MD5,
132
+ })
133
+ }).toThrow('SecurityError: Insecure hashing algorithm')
134
+ })
135
+
136
+ test('throws error if SHA1 is used for sensitive property names', () => {
137
+ expect(() => {
138
+ new HashProperty({
139
+ name: 'secretToken',
140
+ algorithm: HashProperty.ALGORITHM_SHA1,
141
+ })
142
+ }).toThrow('SecurityError: Insecure hashing algorithm')
143
+ })
144
+
145
+ test('allows SHA256 for sensitive property names', () => {
146
+ const prop = new HashProperty({
147
+ name: 'password',
148
+ algorithm: HashProperty.ALGORITHM_SHA256,
149
+ })
150
+ expect(prop.set('azertyuiop').get()).toBe(
151
+ 'aa3d2fe4f6d301dbd6b8fb2d2fddfb7aeebf3bec53ffff4b39a0967afa88c609'
152
+ )
153
+ })
154
+ })
@@ -14,7 +14,7 @@ export type HashPropertyAlgos =
14
14
  *
15
15
  * | Parameter | Type | Description | Default |
16
16
  * | :--- | :--- | :--- | :--- |
17
- * | `algorithm` | HashPropertyAlgos | The cryptographic hashing algorithm to use. | `ALGORITHM_MD5` |
17
+ * | `algorithm` | HashPropertyAlgos | The cryptographic hashing algorithm to use. | `ALGORITHM_SHA256` |
18
18
  * | `salt` | string | An optional salt string appended to the value before hashing. | `""` |
19
19
  * | `prefixed` | boolean | If true, prepends the algorithm name to the output (e.g. `md5-abc...`). | `false` |
20
20
  */
@@ -60,9 +60,24 @@ export class HashProperty extends StringProperty {
60
60
 
61
61
  constructor(config: HashPropertyType) {
62
62
  super(config)
63
- this._algorithm = config.algorithm || HashProperty.ALGORITHM_MD5
63
+ this._algorithm = config.algorithm || HashProperty.ALGORITHM_SHA256
64
64
  this._salt = config.salt || ''
65
65
  this._prefixed = config.prefixed || false
66
+
67
+ // Prevent legacy/insecure algorithms (MD5, SHA1) on sensitive properties
68
+ if (
69
+ this._algorithm === HashProperty.ALGORITHM_MD5 ||
70
+ this._algorithm === HashProperty.ALGORITHM_SHA1
71
+ ) {
72
+ const sensitiveTerms = ['password', 'pwd', 'secret', 'token', 'credential']
73
+ const nameLower = this._name.toLowerCase()
74
+ const isSensitive = sensitiveTerms.some((term) => nameLower.includes(term))
75
+ if (isSensitive) {
76
+ throw new Error(
77
+ `SecurityError: Insecure hashing algorithm "${this._algorithm}" cannot be used for sensitive property "${this._name}". Please use SHA256 or another secure algorithm.`
78
+ )
79
+ }
80
+ }
66
81
  }
67
82
 
68
83
  /**