@questwork/q-utilities 0.1.22 → 0.1.29

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.
@@ -4,12 +4,13 @@ on:
4
4
  tags:
5
5
  - 'v*'
6
6
  workflow_dispatch:
7
+
7
8
  jobs:
8
9
  publish:
9
10
  runs-on: ubuntu-latest
10
11
  permissions:
11
12
  contents: read
12
- packages: write
13
+ id-token: write # OIDC
13
14
  # environment: production
14
15
  steps:
15
16
  - uses: actions/checkout@v4
@@ -39,8 +40,21 @@ jobs:
39
40
  pnpm run build
40
41
  fi
41
42
 
43
+ # - name: Publish to npm
44
+ # # if: steps.version-check.outputs.should_publish == 'true'
45
+ # run: pnpm publish --no-git-checks --access public
46
+ # env:
47
+ # NODE_AUTH_TOKEN: ${{ secrets.QW_NPM_TOKEN }}
48
+
49
+ - name: Update npm
50
+ run: |
51
+ NPM_VERSION=$(npm -v)
52
+ echo "Current npm version: $NPM_VERSION"
53
+ if ! npx semver -r ">=11.5.1" "$NPM_VERSION"; then
54
+ echo "npm version $NPM_VERSION is too old. Installing latest npm..."
55
+ npm install -g npm@latest
56
+ echo "Updated npm version: $(npm -v)"
57
+ fi
58
+
42
59
  - name: Publish to npm
43
- # if: steps.version-check.outputs.should_publish == 'true'
44
- run: pnpm publish --no-git-checks --access public
45
- env:
46
- NODE_AUTH_TOKEN: ${{ secrets.QW_NPM_TOKEN }}
60
+ run: npm publish --no-git-checks --access public
@@ -522,7 +522,147 @@ class AwsStsS3Client {
522
522
 
523
523
 
524
524
 
525
+ ;// ./lib/helpers/objectHelper/objectHelper.js
526
+ const objectHelper = {
527
+ get(obj, path) {
528
+ const parts = path.split('.')
529
+ return parts.reduce((acc, part) => {
530
+ if (part.endsWith('[]')) {
531
+ // 处理数组遍历
532
+ const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
533
+ if (Array.isArray(acc[key])) {
534
+ return acc[key] // 返回整个数组
535
+ }
536
+ return [] // 如果不是数组,返回空数组
537
+ }
538
+ if (part.includes('[') && part.includes(']')) {
539
+ // 处理数组索引
540
+ const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
541
+ if (arrayMatch) {
542
+ const key = arrayMatch[1]
543
+ const index = arrayMatch[2]
544
+ return acc && acc[key] && acc[key][index]
545
+ }
546
+ } else if (acc && Array.isArray(acc)) {
547
+ // 如果当前值是数组,提取每个对象的指定属性
548
+ return acc.map((item) => item[part])
549
+ } else {
550
+ // 处理普通属性
551
+ return acc && acc[part]
552
+ }
553
+ }, obj)
554
+ },
555
+ isPlainObject,
556
+ merge,
557
+ set(obj, path, value) {
558
+ const parts = path.split('.')
559
+ let current = obj
560
+
561
+ // 处理所有中间部分
562
+ for (let i = 0; i < parts.length - 1; i++) {
563
+ const part = parts[i]
564
+ let key, index
565
+
566
+ // 检查是否是数组索引格式,如key[0]
567
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
568
+ if (arrayMatch) {
569
+ key = arrayMatch[1]
570
+ index = Number.parseInt(arrayMatch[2], 10)
571
+ // 确保当前层级的数组存在
572
+ if (!current[key] || !Array.isArray(current[key])) {
573
+ current[key] = []
574
+ }
575
+ // 扩展数组到足够大
576
+ while (current[key].length <= index) {
577
+ current[key].push(undefined)
578
+ }
579
+ // 如果当前位置未定义或为null,初始化为对象
580
+ if (current[key][index] == null) {
581
+ current[key][index] = {}
582
+ }
583
+ current = current[key][index]
584
+ } else {
585
+ // 处理普通属性
586
+ if (!current[part]) {
587
+ current[part] = {}
588
+ }
589
+ current = current[part]
590
+ }
591
+ }
592
+
593
+ // 处理最后一部分
594
+ const lastPart = parts[parts.length - 1]
595
+ const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
596
+ if (arrayMatch) {
597
+ const key = arrayMatch[1]
598
+ const index = Number.parseInt(arrayMatch[2], 10)
599
+ // 确保数组存在
600
+ if (!current[key] || !Array.isArray(current[key])) {
601
+ current[key] = []
602
+ }
603
+ // 扩展数组到所需索引
604
+ while (current[key].length <= index) {
605
+ current[key].push(undefined)
606
+ }
607
+ current[key][index] = value
608
+ } else {
609
+ current[lastPart] = value
610
+ }
611
+ }
612
+ }
613
+
614
+ function isPlainObject(value) {
615
+ return (
616
+ value !== null
617
+ && typeof value === 'object'
618
+ && !Array.isArray(value) &&
619
+ !(value instanceof Date) &&
620
+ !(value instanceof RegExp) &&
621
+ // Optional: exclude other built-in objects
622
+ Object.prototype.toString.call(value) === '[object Object]'
623
+ )
624
+ }
625
+
626
+ function merge(target, ...sources) {
627
+ if (!sources.length)
628
+ return target
629
+
630
+ const source = sources.shift() // 取出第一个源对象
631
+
632
+ if (_isObject(target) && _isObject(source)) {
633
+ for (const key in source) {
634
+ if (_isObject(source[key])) {
635
+ if (!target[key]) {
636
+ // 如果目标对象没有该属性,创建一个空对象
637
+ target[key] = {}
638
+ }
639
+ // 递归合并
640
+ merge(target[key], source[key])
641
+ } else {
642
+ // 直接覆盖
643
+ target[key] = source[key]
644
+ }
645
+ }
646
+ }
647
+
648
+ // 继续合并剩余的源对象
649
+ return merge(target, ...sources)
650
+ }
651
+
652
+ function _isObject(obj) {
653
+ return obj && typeof obj === 'object' && !Array.isArray(obj)
654
+ }
655
+
656
+
657
+
658
+ ;// ./lib/helpers/objectHelper/index.js
659
+
660
+
661
+
662
+
525
663
  ;// ./lib/models/keyValueObject/keyValueObject.js
664
+
665
+
526
666
  class keyValueObject_KeyValueObject {
527
667
  constructor(options = {}) {
528
668
  options = options || {}
@@ -598,6 +738,19 @@ class keyValueObject_KeyValueObject {
598
738
  static getValueByKey(arr = [], key) {
599
739
  return this.foundValueByKey(arr, key)
600
740
  }
741
+ static getMetadataValueByKeyAsArray(arr = [], key) {
742
+ const _value = this.getValueByKey(arr, key)
743
+ if (!_value) {
744
+ return []
745
+ }
746
+ if (objectHelper.isPlainObject(_value)) {
747
+ return Object.keys(_value).reduce((acc, key) => {
748
+ acc.push({ key, value: _value[key] })
749
+ return acc
750
+ }, [])
751
+ }
752
+ return _value
753
+ }
601
754
  static getValueByKeyFromArray(arr = [], key) {
602
755
  if (arr.length === 0) {
603
756
  return null
@@ -1506,6 +1659,7 @@ class ActionRecord {
1506
1659
 
1507
1660
 
1508
1661
  const notUpdateAllowedProps = [
1662
+ '_ActionRecord',
1509
1663
  'created',
1510
1664
  // 'statusType'
1511
1665
  ]
@@ -2722,6 +2876,10 @@ class TenantAwareEntity extends TrackedEntity {
2722
2876
  }
2723
2877
 
2724
2878
  // instance methods
2879
+ insertOrUpdateMetadata(key, value) {
2880
+ this.metadata = Metadata.insertOrUpdateRecord(this.metadata, key, value)
2881
+ return this
2882
+ }
2725
2883
  getMetadata() {
2726
2884
  return this.metadata
2727
2885
  }
@@ -2732,6 +2890,21 @@ class TenantAwareEntity extends TrackedEntity {
2732
2890
  const found = this.getMetadataByKey(key)
2733
2891
  return found ? found.value : null
2734
2892
  }
2893
+ getMetadataValueByKeys(keys, kv) {
2894
+ if (!Array.isArray(keys)) {
2895
+ return null
2896
+ }
2897
+ kv = kv || this.metadata
2898
+ if (keys.length === 0) {
2899
+ return kv
2900
+ }
2901
+ const key = keys.shift()
2902
+ const _val = Metadata.getMetadataValueByKeyAsArray(kv, key)
2903
+ return this.getMetadataValueByKeys(keys, _val)
2904
+ }
2905
+ getMetadataValueByKeyAsArray(key) {
2906
+ return Metadata.getMetadataValueByKeyAsArray(this.metadata, key)
2907
+ }
2735
2908
  getRemarks() {
2736
2909
  return this.remarks
2737
2910
  }
@@ -3197,7 +3370,7 @@ function detectControlCharacters(input, options = {}) {
3197
3370
 
3198
3371
  return {
3199
3372
  hasControlChars: matches.length > 0,
3200
- matches: matches,
3373
+ matches,
3201
3374
  totalFound: matches.length,
3202
3375
  inputPreview: input.length > 50 ? input.substring(0, 50) + '...' : input,
3203
3376
  inputLength: input.length,
@@ -3423,131 +3596,6 @@ function extractEmails(dirtyArray) {
3423
3596
  ;// ./lib/helpers/extractEmails/index.js
3424
3597
 
3425
3598
 
3426
- ;// ./lib/helpers/objectHelper/objectHelper.js
3427
- const objectHelper = {
3428
- get(obj, path) {
3429
- const parts = path.split('.')
3430
- return parts.reduce((acc, part) => {
3431
- if (part.endsWith('[]')) {
3432
- // 处理数组遍历
3433
- const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
3434
- if (Array.isArray(acc[key])) {
3435
- return acc[key] // 返回整个数组
3436
- }
3437
- return [] // 如果不是数组,返回空数组
3438
- }
3439
- if (part.includes('[') && part.includes(']')) {
3440
- // 处理数组索引
3441
- const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
3442
- if (arrayMatch) {
3443
- const key = arrayMatch[1]
3444
- const index = arrayMatch[2]
3445
- return acc && acc[key] && acc[key][index]
3446
- }
3447
- } else if (acc && Array.isArray(acc)) {
3448
- // 如果当前值是数组,提取每个对象的指定属性
3449
- return acc.map((item) => item[part])
3450
- } else {
3451
- // 处理普通属性
3452
- return acc && acc[part]
3453
- }
3454
- }, obj)
3455
- },
3456
- merge,
3457
- set(obj, path, value) {
3458
- const parts = path.split('.')
3459
- let current = obj
3460
-
3461
- // 处理所有中间部分
3462
- for (let i = 0; i < parts.length - 1; i++) {
3463
- const part = parts[i]
3464
- let key, index
3465
-
3466
- // 检查是否是数组索引格式,如key[0]
3467
- const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
3468
- if (arrayMatch) {
3469
- key = arrayMatch[1]
3470
- index = Number.parseInt(arrayMatch[2], 10)
3471
- // 确保当前层级的数组存在
3472
- if (!current[key] || !Array.isArray(current[key])) {
3473
- current[key] = []
3474
- }
3475
- // 扩展数组到足够大
3476
- while (current[key].length <= index) {
3477
- current[key].push(undefined)
3478
- }
3479
- // 如果当前位置未定义或为null,初始化为对象
3480
- if (current[key][index] == null) {
3481
- current[key][index] = {}
3482
- }
3483
- current = current[key][index]
3484
- } else {
3485
- // 处理普通属性
3486
- if (!current[part]) {
3487
- current[part] = {}
3488
- }
3489
- current = current[part]
3490
- }
3491
- }
3492
-
3493
- // 处理最后一部分
3494
- const lastPart = parts[parts.length - 1]
3495
- const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
3496
- if (arrayMatch) {
3497
- const key = arrayMatch[1]
3498
- const index = Number.parseInt(arrayMatch[2], 10)
3499
- // 确保数组存在
3500
- if (!current[key] || !Array.isArray(current[key])) {
3501
- current[key] = []
3502
- }
3503
- // 扩展数组到所需索引
3504
- while (current[key].length <= index) {
3505
- current[key].push(undefined)
3506
- }
3507
- current[key][index] = value
3508
- } else {
3509
- current[lastPart] = value
3510
- }
3511
- }
3512
- }
3513
-
3514
- function merge(target, ...sources) {
3515
- if (!sources.length)
3516
- return target
3517
-
3518
- const source = sources.shift() // 取出第一个源对象
3519
-
3520
- if (_isObject(target) && _isObject(source)) {
3521
- for (const key in source) {
3522
- if (_isObject(source[key])) {
3523
- if (!target[key]) {
3524
- // 如果目标对象没有该属性,创建一个空对象
3525
- target[key] = {}
3526
- }
3527
- // 递归合并
3528
- merge(target[key], source[key])
3529
- } else {
3530
- // 直接覆盖
3531
- target[key] = source[key]
3532
- }
3533
- }
3534
- }
3535
-
3536
- // 继续合并剩余的源对象
3537
- return merge(target, ...sources)
3538
- }
3539
-
3540
- function _isObject(obj) {
3541
- return obj && typeof obj === 'object' && !Array.isArray(obj)
3542
- }
3543
-
3544
-
3545
-
3546
- ;// ./lib/helpers/objectHelper/index.js
3547
-
3548
-
3549
-
3550
-
3551
3599
  ;// ./lib/helpers/pReduce/pReduce.js
3552
3600
  async function pReduce(iterable, reducer, initialValue) {
3553
3601
  return new Promise((resolve, reject) => {
@@ -447,7 +447,147 @@ class AwsStsS3Client {
447
447
 
448
448
 
449
449
 
450
+ ;// ./lib/helpers/objectHelper/objectHelper.js
451
+ const objectHelper = {
452
+ get(obj, path) {
453
+ const parts = path.split('.')
454
+ return parts.reduce((acc, part) => {
455
+ if (part.endsWith('[]')) {
456
+ // 处理数组遍历
457
+ const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
458
+ if (Array.isArray(acc[key])) {
459
+ return acc[key] // 返回整个数组
460
+ }
461
+ return [] // 如果不是数组,返回空数组
462
+ }
463
+ if (part.includes('[') && part.includes(']')) {
464
+ // 处理数组索引
465
+ const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
466
+ if (arrayMatch) {
467
+ const key = arrayMatch[1]
468
+ const index = arrayMatch[2]
469
+ return acc && acc[key] && acc[key][index]
470
+ }
471
+ } else if (acc && Array.isArray(acc)) {
472
+ // 如果当前值是数组,提取每个对象的指定属性
473
+ return acc.map((item) => item[part])
474
+ } else {
475
+ // 处理普通属性
476
+ return acc && acc[part]
477
+ }
478
+ }, obj)
479
+ },
480
+ isPlainObject,
481
+ merge,
482
+ set(obj, path, value) {
483
+ const parts = path.split('.')
484
+ let current = obj
485
+
486
+ // 处理所有中间部分
487
+ for (let i = 0; i < parts.length - 1; i++) {
488
+ const part = parts[i]
489
+ let key, index
490
+
491
+ // 检查是否是数组索引格式,如key[0]
492
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
493
+ if (arrayMatch) {
494
+ key = arrayMatch[1]
495
+ index = Number.parseInt(arrayMatch[2], 10)
496
+ // 确保当前层级的数组存在
497
+ if (!current[key] || !Array.isArray(current[key])) {
498
+ current[key] = []
499
+ }
500
+ // 扩展数组到足够大
501
+ while (current[key].length <= index) {
502
+ current[key].push(undefined)
503
+ }
504
+ // 如果当前位置未定义或为null,初始化为对象
505
+ if (current[key][index] == null) {
506
+ current[key][index] = {}
507
+ }
508
+ current = current[key][index]
509
+ } else {
510
+ // 处理普通属性
511
+ if (!current[part]) {
512
+ current[part] = {}
513
+ }
514
+ current = current[part]
515
+ }
516
+ }
517
+
518
+ // 处理最后一部分
519
+ const lastPart = parts[parts.length - 1]
520
+ const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
521
+ if (arrayMatch) {
522
+ const key = arrayMatch[1]
523
+ const index = Number.parseInt(arrayMatch[2], 10)
524
+ // 确保数组存在
525
+ if (!current[key] || !Array.isArray(current[key])) {
526
+ current[key] = []
527
+ }
528
+ // 扩展数组到所需索引
529
+ while (current[key].length <= index) {
530
+ current[key].push(undefined)
531
+ }
532
+ current[key][index] = value
533
+ } else {
534
+ current[lastPart] = value
535
+ }
536
+ }
537
+ }
538
+
539
+ function isPlainObject(value) {
540
+ return (
541
+ value !== null
542
+ && typeof value === 'object'
543
+ && !Array.isArray(value) &&
544
+ !(value instanceof Date) &&
545
+ !(value instanceof RegExp) &&
546
+ // Optional: exclude other built-in objects
547
+ Object.prototype.toString.call(value) === '[object Object]'
548
+ )
549
+ }
550
+
551
+ function merge(target, ...sources) {
552
+ if (!sources.length)
553
+ return target
554
+
555
+ const source = sources.shift() // 取出第一个源对象
556
+
557
+ if (_isObject(target) && _isObject(source)) {
558
+ for (const key in source) {
559
+ if (_isObject(source[key])) {
560
+ if (!target[key]) {
561
+ // 如果目标对象没有该属性,创建一个空对象
562
+ target[key] = {}
563
+ }
564
+ // 递归合并
565
+ merge(target[key], source[key])
566
+ } else {
567
+ // 直接覆盖
568
+ target[key] = source[key]
569
+ }
570
+ }
571
+ }
572
+
573
+ // 继续合并剩余的源对象
574
+ return merge(target, ...sources)
575
+ }
576
+
577
+ function _isObject(obj) {
578
+ return obj && typeof obj === 'object' && !Array.isArray(obj)
579
+ }
580
+
581
+
582
+
583
+ ;// ./lib/helpers/objectHelper/index.js
584
+
585
+
586
+
587
+
450
588
  ;// ./lib/models/keyValueObject/keyValueObject.js
589
+
590
+
451
591
  class keyValueObject_KeyValueObject {
452
592
  constructor(options = {}) {
453
593
  options = options || {}
@@ -523,6 +663,19 @@ class keyValueObject_KeyValueObject {
523
663
  static getValueByKey(arr = [], key) {
524
664
  return this.foundValueByKey(arr, key)
525
665
  }
666
+ static getMetadataValueByKeyAsArray(arr = [], key) {
667
+ const _value = this.getValueByKey(arr, key)
668
+ if (!_value) {
669
+ return []
670
+ }
671
+ if (objectHelper.isPlainObject(_value)) {
672
+ return Object.keys(_value).reduce((acc, key) => {
673
+ acc.push({ key, value: _value[key] })
674
+ return acc
675
+ }, [])
676
+ }
677
+ return _value
678
+ }
526
679
  static getValueByKeyFromArray(arr = [], key) {
527
680
  if (arr.length === 0) {
528
681
  return null
@@ -1431,6 +1584,7 @@ class ActionRecord {
1431
1584
 
1432
1585
 
1433
1586
  const notUpdateAllowedProps = [
1587
+ '_ActionRecord',
1434
1588
  'created',
1435
1589
  // 'statusType'
1436
1590
  ]
@@ -2647,6 +2801,10 @@ class TenantAwareEntity extends TrackedEntity {
2647
2801
  }
2648
2802
 
2649
2803
  // instance methods
2804
+ insertOrUpdateMetadata(key, value) {
2805
+ this.metadata = Metadata.insertOrUpdateRecord(this.metadata, key, value)
2806
+ return this
2807
+ }
2650
2808
  getMetadata() {
2651
2809
  return this.metadata
2652
2810
  }
@@ -2657,6 +2815,21 @@ class TenantAwareEntity extends TrackedEntity {
2657
2815
  const found = this.getMetadataByKey(key)
2658
2816
  return found ? found.value : null
2659
2817
  }
2818
+ getMetadataValueByKeys(keys, kv) {
2819
+ if (!Array.isArray(keys)) {
2820
+ return null
2821
+ }
2822
+ kv = kv || this.metadata
2823
+ if (keys.length === 0) {
2824
+ return kv
2825
+ }
2826
+ const key = keys.shift()
2827
+ const _val = Metadata.getMetadataValueByKeyAsArray(kv, key)
2828
+ return this.getMetadataValueByKeys(keys, _val)
2829
+ }
2830
+ getMetadataValueByKeyAsArray(key) {
2831
+ return Metadata.getMetadataValueByKeyAsArray(this.metadata, key)
2832
+ }
2660
2833
  getRemarks() {
2661
2834
  return this.remarks
2662
2835
  }
@@ -3122,7 +3295,7 @@ function detectControlCharacters(input, options = {}) {
3122
3295
 
3123
3296
  return {
3124
3297
  hasControlChars: matches.length > 0,
3125
- matches: matches,
3298
+ matches,
3126
3299
  totalFound: matches.length,
3127
3300
  inputPreview: input.length > 50 ? input.substring(0, 50) + '...' : input,
3128
3301
  inputLength: input.length,
@@ -3348,131 +3521,6 @@ function extractEmails(dirtyArray) {
3348
3521
  ;// ./lib/helpers/extractEmails/index.js
3349
3522
 
3350
3523
 
3351
- ;// ./lib/helpers/objectHelper/objectHelper.js
3352
- const objectHelper = {
3353
- get(obj, path) {
3354
- const parts = path.split('.')
3355
- return parts.reduce((acc, part) => {
3356
- if (part.endsWith('[]')) {
3357
- // 处理数组遍历
3358
- const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
3359
- if (Array.isArray(acc[key])) {
3360
- return acc[key] // 返回整个数组
3361
- }
3362
- return [] // 如果不是数组,返回空数组
3363
- }
3364
- if (part.includes('[') && part.includes(']')) {
3365
- // 处理数组索引
3366
- const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
3367
- if (arrayMatch) {
3368
- const key = arrayMatch[1]
3369
- const index = arrayMatch[2]
3370
- return acc && acc[key] && acc[key][index]
3371
- }
3372
- } else if (acc && Array.isArray(acc)) {
3373
- // 如果当前值是数组,提取每个对象的指定属性
3374
- return acc.map((item) => item[part])
3375
- } else {
3376
- // 处理普通属性
3377
- return acc && acc[part]
3378
- }
3379
- }, obj)
3380
- },
3381
- merge,
3382
- set(obj, path, value) {
3383
- const parts = path.split('.')
3384
- let current = obj
3385
-
3386
- // 处理所有中间部分
3387
- for (let i = 0; i < parts.length - 1; i++) {
3388
- const part = parts[i]
3389
- let key, index
3390
-
3391
- // 检查是否是数组索引格式,如key[0]
3392
- const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
3393
- if (arrayMatch) {
3394
- key = arrayMatch[1]
3395
- index = Number.parseInt(arrayMatch[2], 10)
3396
- // 确保当前层级的数组存在
3397
- if (!current[key] || !Array.isArray(current[key])) {
3398
- current[key] = []
3399
- }
3400
- // 扩展数组到足够大
3401
- while (current[key].length <= index) {
3402
- current[key].push(undefined)
3403
- }
3404
- // 如果当前位置未定义或为null,初始化为对象
3405
- if (current[key][index] == null) {
3406
- current[key][index] = {}
3407
- }
3408
- current = current[key][index]
3409
- } else {
3410
- // 处理普通属性
3411
- if (!current[part]) {
3412
- current[part] = {}
3413
- }
3414
- current = current[part]
3415
- }
3416
- }
3417
-
3418
- // 处理最后一部分
3419
- const lastPart = parts[parts.length - 1]
3420
- const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
3421
- if (arrayMatch) {
3422
- const key = arrayMatch[1]
3423
- const index = Number.parseInt(arrayMatch[2], 10)
3424
- // 确保数组存在
3425
- if (!current[key] || !Array.isArray(current[key])) {
3426
- current[key] = []
3427
- }
3428
- // 扩展数组到所需索引
3429
- while (current[key].length <= index) {
3430
- current[key].push(undefined)
3431
- }
3432
- current[key][index] = value
3433
- } else {
3434
- current[lastPart] = value
3435
- }
3436
- }
3437
- }
3438
-
3439
- function merge(target, ...sources) {
3440
- if (!sources.length)
3441
- return target
3442
-
3443
- const source = sources.shift() // 取出第一个源对象
3444
-
3445
- if (_isObject(target) && _isObject(source)) {
3446
- for (const key in source) {
3447
- if (_isObject(source[key])) {
3448
- if (!target[key]) {
3449
- // 如果目标对象没有该属性,创建一个空对象
3450
- target[key] = {}
3451
- }
3452
- // 递归合并
3453
- merge(target[key], source[key])
3454
- } else {
3455
- // 直接覆盖
3456
- target[key] = source[key]
3457
- }
3458
- }
3459
- }
3460
-
3461
- // 继续合并剩余的源对象
3462
- return merge(target, ...sources)
3463
- }
3464
-
3465
- function _isObject(obj) {
3466
- return obj && typeof obj === 'object' && !Array.isArray(obj)
3467
- }
3468
-
3469
-
3470
-
3471
- ;// ./lib/helpers/objectHelper/index.js
3472
-
3473
-
3474
-
3475
-
3476
3524
  ;// ./lib/helpers/pReduce/pReduce.js
3477
3525
  async function pReduce(iterable, reducer, initialValue) {
3478
3526
  return new Promise((resolve, reject) => {
@@ -553,7 +553,147 @@ class AwsStsS3Client {
553
553
 
554
554
 
555
555
 
556
+ ;// ./lib/helpers/objectHelper/objectHelper.js
557
+ const objectHelper = {
558
+ get(obj, path) {
559
+ const parts = path.split('.')
560
+ return parts.reduce((acc, part) => {
561
+ if (part.endsWith('[]')) {
562
+ // 处理数组遍历
563
+ const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
564
+ if (Array.isArray(acc[key])) {
565
+ return acc[key] // 返回整个数组
566
+ }
567
+ return [] // 如果不是数组,返回空数组
568
+ }
569
+ if (part.includes('[') && part.includes(']')) {
570
+ // 处理数组索引
571
+ const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
572
+ if (arrayMatch) {
573
+ const key = arrayMatch[1]
574
+ const index = arrayMatch[2]
575
+ return acc && acc[key] && acc[key][index]
576
+ }
577
+ } else if (acc && Array.isArray(acc)) {
578
+ // 如果当前值是数组,提取每个对象的指定属性
579
+ return acc.map((item) => item[part])
580
+ } else {
581
+ // 处理普通属性
582
+ return acc && acc[part]
583
+ }
584
+ }, obj)
585
+ },
586
+ isPlainObject,
587
+ merge,
588
+ set(obj, path, value) {
589
+ const parts = path.split('.')
590
+ let current = obj
591
+
592
+ // 处理所有中间部分
593
+ for (let i = 0; i < parts.length - 1; i++) {
594
+ const part = parts[i]
595
+ let key, index
596
+
597
+ // 检查是否是数组索引格式,如key[0]
598
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
599
+ if (arrayMatch) {
600
+ key = arrayMatch[1]
601
+ index = Number.parseInt(arrayMatch[2], 10)
602
+ // 确保当前层级的数组存在
603
+ if (!current[key] || !Array.isArray(current[key])) {
604
+ current[key] = []
605
+ }
606
+ // 扩展数组到足够大
607
+ while (current[key].length <= index) {
608
+ current[key].push(undefined)
609
+ }
610
+ // 如果当前位置未定义或为null,初始化为对象
611
+ if (current[key][index] == null) {
612
+ current[key][index] = {}
613
+ }
614
+ current = current[key][index]
615
+ } else {
616
+ // 处理普通属性
617
+ if (!current[part]) {
618
+ current[part] = {}
619
+ }
620
+ current = current[part]
621
+ }
622
+ }
623
+
624
+ // 处理最后一部分
625
+ const lastPart = parts[parts.length - 1]
626
+ const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
627
+ if (arrayMatch) {
628
+ const key = arrayMatch[1]
629
+ const index = Number.parseInt(arrayMatch[2], 10)
630
+ // 确保数组存在
631
+ if (!current[key] || !Array.isArray(current[key])) {
632
+ current[key] = []
633
+ }
634
+ // 扩展数组到所需索引
635
+ while (current[key].length <= index) {
636
+ current[key].push(undefined)
637
+ }
638
+ current[key][index] = value
639
+ } else {
640
+ current[lastPart] = value
641
+ }
642
+ }
643
+ }
644
+
645
+ function isPlainObject(value) {
646
+ return (
647
+ value !== null
648
+ && typeof value === 'object'
649
+ && !Array.isArray(value) &&
650
+ !(value instanceof Date) &&
651
+ !(value instanceof RegExp) &&
652
+ // Optional: exclude other built-in objects
653
+ Object.prototype.toString.call(value) === '[object Object]'
654
+ )
655
+ }
656
+
657
+ function merge(target, ...sources) {
658
+ if (!sources.length)
659
+ return target
660
+
661
+ const source = sources.shift() // 取出第一个源对象
662
+
663
+ if (_isObject(target) && _isObject(source)) {
664
+ for (const key in source) {
665
+ if (_isObject(source[key])) {
666
+ if (!target[key]) {
667
+ // 如果目标对象没有该属性,创建一个空对象
668
+ target[key] = {}
669
+ }
670
+ // 递归合并
671
+ merge(target[key], source[key])
672
+ } else {
673
+ // 直接覆盖
674
+ target[key] = source[key]
675
+ }
676
+ }
677
+ }
678
+
679
+ // 继续合并剩余的源对象
680
+ return merge(target, ...sources)
681
+ }
682
+
683
+ function _isObject(obj) {
684
+ return obj && typeof obj === 'object' && !Array.isArray(obj)
685
+ }
686
+
687
+
688
+
689
+ ;// ./lib/helpers/objectHelper/index.js
690
+
691
+
692
+
693
+
556
694
  ;// ./lib/models/keyValueObject/keyValueObject.js
695
+
696
+
557
697
  class keyValueObject_KeyValueObject {
558
698
  constructor(options = {}) {
559
699
  options = options || {}
@@ -629,6 +769,19 @@ class keyValueObject_KeyValueObject {
629
769
  static getValueByKey(arr = [], key) {
630
770
  return this.foundValueByKey(arr, key)
631
771
  }
772
+ static getMetadataValueByKeyAsArray(arr = [], key) {
773
+ const _value = this.getValueByKey(arr, key)
774
+ if (!_value) {
775
+ return []
776
+ }
777
+ if (objectHelper.isPlainObject(_value)) {
778
+ return Object.keys(_value).reduce((acc, key) => {
779
+ acc.push({ key, value: _value[key] })
780
+ return acc
781
+ }, [])
782
+ }
783
+ return _value
784
+ }
632
785
  static getValueByKeyFromArray(arr = [], key) {
633
786
  if (arr.length === 0) {
634
787
  return null
@@ -1537,6 +1690,7 @@ class ActionRecord {
1537
1690
 
1538
1691
 
1539
1692
  const notUpdateAllowedProps = [
1693
+ '_ActionRecord',
1540
1694
  'created',
1541
1695
  // 'statusType'
1542
1696
  ]
@@ -2753,6 +2907,10 @@ class TenantAwareEntity extends TrackedEntity {
2753
2907
  }
2754
2908
 
2755
2909
  // instance methods
2910
+ insertOrUpdateMetadata(key, value) {
2911
+ this.metadata = Metadata.insertOrUpdateRecord(this.metadata, key, value)
2912
+ return this
2913
+ }
2756
2914
  getMetadata() {
2757
2915
  return this.metadata
2758
2916
  }
@@ -2763,6 +2921,21 @@ class TenantAwareEntity extends TrackedEntity {
2763
2921
  const found = this.getMetadataByKey(key)
2764
2922
  return found ? found.value : null
2765
2923
  }
2924
+ getMetadataValueByKeys(keys, kv) {
2925
+ if (!Array.isArray(keys)) {
2926
+ return null
2927
+ }
2928
+ kv = kv || this.metadata
2929
+ if (keys.length === 0) {
2930
+ return kv
2931
+ }
2932
+ const key = keys.shift()
2933
+ const _val = Metadata.getMetadataValueByKeyAsArray(kv, key)
2934
+ return this.getMetadataValueByKeys(keys, _val)
2935
+ }
2936
+ getMetadataValueByKeyAsArray(key) {
2937
+ return Metadata.getMetadataValueByKeyAsArray(this.metadata, key)
2938
+ }
2766
2939
  getRemarks() {
2767
2940
  return this.remarks
2768
2941
  }
@@ -3228,7 +3401,7 @@ function detectControlCharacters(input, options = {}) {
3228
3401
 
3229
3402
  return {
3230
3403
  hasControlChars: matches.length > 0,
3231
- matches: matches,
3404
+ matches,
3232
3405
  totalFound: matches.length,
3233
3406
  inputPreview: input.length > 50 ? input.substring(0, 50) + '...' : input,
3234
3407
  inputLength: input.length,
@@ -3454,131 +3627,6 @@ function extractEmails(dirtyArray) {
3454
3627
  ;// ./lib/helpers/extractEmails/index.js
3455
3628
 
3456
3629
 
3457
- ;// ./lib/helpers/objectHelper/objectHelper.js
3458
- const objectHelper = {
3459
- get(obj, path) {
3460
- const parts = path.split('.')
3461
- return parts.reduce((acc, part) => {
3462
- if (part.endsWith('[]')) {
3463
- // 处理数组遍历
3464
- const key = part.slice(0, -2) // 去掉 '[]' 得到属性名
3465
- if (Array.isArray(acc[key])) {
3466
- return acc[key] // 返回整个数组
3467
- }
3468
- return [] // 如果不是数组,返回空数组
3469
- }
3470
- if (part.includes('[') && part.includes(']')) {
3471
- // 处理数组索引
3472
- const arrayMatch = part.match(/(\w+)\[(\d+)\]/)
3473
- if (arrayMatch) {
3474
- const key = arrayMatch[1]
3475
- const index = arrayMatch[2]
3476
- return acc && acc[key] && acc[key][index]
3477
- }
3478
- } else if (acc && Array.isArray(acc)) {
3479
- // 如果当前值是数组,提取每个对象的指定属性
3480
- return acc.map((item) => item[part])
3481
- } else {
3482
- // 处理普通属性
3483
- return acc && acc[part]
3484
- }
3485
- }, obj)
3486
- },
3487
- merge,
3488
- set(obj, path, value) {
3489
- const parts = path.split('.')
3490
- let current = obj
3491
-
3492
- // 处理所有中间部分
3493
- for (let i = 0; i < parts.length - 1; i++) {
3494
- const part = parts[i]
3495
- let key, index
3496
-
3497
- // 检查是否是数组索引格式,如key[0]
3498
- const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
3499
- if (arrayMatch) {
3500
- key = arrayMatch[1]
3501
- index = Number.parseInt(arrayMatch[2], 10)
3502
- // 确保当前层级的数组存在
3503
- if (!current[key] || !Array.isArray(current[key])) {
3504
- current[key] = []
3505
- }
3506
- // 扩展数组到足够大
3507
- while (current[key].length <= index) {
3508
- current[key].push(undefined)
3509
- }
3510
- // 如果当前位置未定义或为null,初始化为对象
3511
- if (current[key][index] == null) {
3512
- current[key][index] = {}
3513
- }
3514
- current = current[key][index]
3515
- } else {
3516
- // 处理普通属性
3517
- if (!current[part]) {
3518
- current[part] = {}
3519
- }
3520
- current = current[part]
3521
- }
3522
- }
3523
-
3524
- // 处理最后一部分
3525
- const lastPart = parts[parts.length - 1]
3526
- const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
3527
- if (arrayMatch) {
3528
- const key = arrayMatch[1]
3529
- const index = Number.parseInt(arrayMatch[2], 10)
3530
- // 确保数组存在
3531
- if (!current[key] || !Array.isArray(current[key])) {
3532
- current[key] = []
3533
- }
3534
- // 扩展数组到所需索引
3535
- while (current[key].length <= index) {
3536
- current[key].push(undefined)
3537
- }
3538
- current[key][index] = value
3539
- } else {
3540
- current[lastPart] = value
3541
- }
3542
- }
3543
- }
3544
-
3545
- function merge(target, ...sources) {
3546
- if (!sources.length)
3547
- return target
3548
-
3549
- const source = sources.shift() // 取出第一个源对象
3550
-
3551
- if (_isObject(target) && _isObject(source)) {
3552
- for (const key in source) {
3553
- if (_isObject(source[key])) {
3554
- if (!target[key]) {
3555
- // 如果目标对象没有该属性,创建一个空对象
3556
- target[key] = {}
3557
- }
3558
- // 递归合并
3559
- merge(target[key], source[key])
3560
- } else {
3561
- // 直接覆盖
3562
- target[key] = source[key]
3563
- }
3564
- }
3565
- }
3566
-
3567
- // 继续合并剩余的源对象
3568
- return merge(target, ...sources)
3569
- }
3570
-
3571
- function _isObject(obj) {
3572
- return obj && typeof obj === 'object' && !Array.isArray(obj)
3573
- }
3574
-
3575
-
3576
-
3577
- ;// ./lib/helpers/objectHelper/index.js
3578
-
3579
-
3580
-
3581
-
3582
3630
  ;// ./lib/helpers/pReduce/pReduce.js
3583
3631
  async function pReduce(iterable, reducer, initialValue) {
3584
3632
  return new Promise((resolve, reject) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questwork/q-utilities",
3
- "version": "0.1.22",
3
+ "version": "0.1.29",
4
4
  "description": "Questwork QUtilities",
5
5
  "author": {
6
6
  "name": "Questwork Consulting Limited",
@@ -8,6 +8,13 @@
8
8
  "url": "https://questwork.com/"
9
9
  },
10
10
  "license": "MIT",
11
+ "scripts": {
12
+ "build": "cross-env NODE_ENV=production minimize=false gulp",
13
+ "build:wp": "cross-env NODE_ENV=production minimize=false gulp wp",
14
+ "lint": "eslint .",
15
+ "test:helpers": "NODE_ENV=test mocha --exit 'lib/helpers/test.setup.js' 'lib/helpers/**/*.spec.js'",
16
+ "test:models": "NODE_ENV=test mocha --exit 'lib/test.setup.js' 'lib/**/*.spec.js'"
17
+ },
11
18
  "type": "module",
12
19
  "exports": {
13
20
  ".": {
@@ -43,12 +50,5 @@
43
50
  },
44
51
  "engines": {
45
52
  "node": ">=10.0.0"
46
- },
47
- "scripts": {
48
- "build": "cross-env NODE_ENV=production minimize=false gulp",
49
- "build:wp": "cross-env NODE_ENV=production minimize=false gulp wp",
50
- "lint": "eslint .",
51
- "test:helpers": "NODE_ENV=test mocha --exit 'lib/helpers/test.setup.js' 'lib/helpers/**/*.spec.js'",
52
- "test:models": "NODE_ENV=test mocha --exit 'lib/test.setup.js' 'lib/**/*.spec.js'"
53
53
  }
54
- }
54
+ }