@questwork/q-utilities 0.1.19 → 0.1.21

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.
@@ -49,6 +49,7 @@ __webpack_require__.r(__webpack_exports__);
49
49
 
50
50
  // EXPORTS
51
51
  __webpack_require__.d(__webpack_exports__, {
52
+ ActionRecord: () => (/* reexport */ ActionRecord),
52
53
  ApiResponse: () => (/* reexport */ ApiResponse),
53
54
  AwsStsS3Client: () => (/* reexport */ AwsStsS3Client),
54
55
  KeyValueObject: () => (/* reexport */ KeyValueObject),
@@ -57,6 +58,8 @@ __webpack_require__.d(__webpack_exports__, {
57
58
  QMeta: () => (/* reexport */ QMeta),
58
59
  Repo: () => (/* reexport */ Repo),
59
60
  Service: () => (/* reexport */ Service),
61
+ Status: () => (/* reexport */ Status),
62
+ StatusDocument: () => (/* reexport */ StatusDocument),
60
63
  TemplateCompiler: () => (/* reexport */ TemplateCompiler),
61
64
  TenantAwareEntity: () => (/* reexport */ TenantAwareEntity),
62
65
  TrackedEntity: () => (/* reexport */ TrackedEntity),
@@ -66,6 +69,7 @@ __webpack_require__.d(__webpack_exports__, {
66
69
  changeCreatorOwner: () => (/* reexport */ changeCreatorOwner),
67
70
  concatStringByArray: () => (/* reexport */ concatStringByArray),
68
71
  convertString: () => (/* reexport */ convertString),
72
+ detectControlCharacters: () => (/* reexport */ detectControlCharacters),
69
73
  escapeRegex: () => (/* reexport */ escapeRegex),
70
74
  expressHelper: () => (/* reexport */ expressHelper),
71
75
  extractEmails: () => (/* reexport */ extractEmails),
@@ -77,12 +81,14 @@ __webpack_require__.d(__webpack_exports__, {
77
81
  init: () => (/* reexport */ init),
78
82
  initFromArray: () => (/* reexport */ initFromArray),
79
83
  initOnlyValidFromArray: () => (/* reexport */ initOnlyValidFromArray),
84
+ isConvertibleToNumber: () => (/* reexport */ isConvertibleToNumber),
80
85
  makeApiResponse: () => (/* reexport */ makeApiResponse),
81
86
  makeService: () => (/* reexport */ makeService),
82
87
  mergeArraysByKey: () => (/* reexport */ mergeArraysByKey),
83
88
  objectHelper: () => (/* reexport */ objectHelper),
84
89
  pReduce: () => (/* reexport */ pReduce),
85
90
  padZeros: () => (/* reexport */ padZeros),
91
+ printControlCharReport: () => (/* reexport */ printControlCharReport),
86
92
  replacePlaceholders: () => (/* reexport */ replacePlaceholders),
87
93
  sanitizeText: () => (/* reexport */ sanitizeText),
88
94
  shuffleArray: () => (/* reexport */ shuffleArray),
@@ -117,14 +123,14 @@ function authorize({ allowCoordinator, allowOwner, query = {}, required, user })
117
123
  if (query.registrationGroupCode && user.myManagedRegistrationGroupCodes.includes(query.registrationGroupCode)) {
118
124
  query.__ALLOW_COORDINATOR = true
119
125
  } else {
120
- if (!scopes.includes('EVENT')) {
121
- query.eventRegistrationCode = user.eventRegistrationCode
122
- }
123
- }
124
- } else {
125
126
  if (!scopes.includes('EVENT')) {
126
127
  query.eventRegistrationCode = user.eventRegistrationCode
127
128
  }
129
+ }
130
+ } else {
131
+ if (!scopes.includes('EVENT')) {
132
+ query.eventRegistrationCode = user.eventRegistrationCode
133
+ }
128
134
  }
129
135
  if (allowOwner) {
130
136
  query.__ALLOW_OWNER = true
@@ -156,14 +162,14 @@ function calculateAge(timestamp, reference) {
156
162
  }
157
163
 
158
164
  // Calculate raw difference
159
- let age = refDate.getFullYear() - birthDate.getFullYear();
160
- const monthDiff = refDate.getMonth() - birthDate.getMonth();
161
-
165
+ let age = refDate.getFullYear() - birthDate.getFullYear()
166
+ const monthDiff = refDate.getMonth() - birthDate.getMonth()
167
+
162
168
  // Adjust if birthday hasn't occurred yet this year
163
169
  if (monthDiff < 0 || (monthDiff === 0 && refDate.getDate() < birthDate.getDate())) {
164
170
  age--
165
171
  }
166
-
172
+
167
173
  return age
168
174
  }
169
175
 
@@ -193,7 +199,18 @@ function changeCreatorOwner(that, { source, target }) {
193
199
  ;// ./lib/helpers/changeCreatorOwner/index.js
194
200
 
195
201
 
202
+ ;// ./lib/helpers/isConvertibleToNumber/isConvertibleToNumber.js
203
+ function isConvertibleToNumber(value) {
204
+ return value !== null
205
+ && value !== undefined
206
+ && typeof value !== 'boolean'
207
+ && String(value).trim() !== ''
208
+ && !isNaN(Number(value))
209
+ }
210
+
196
211
  ;// ./lib/helpers/getValidation/getValidation.js
212
+
213
+
197
214
  function getValidation(rule, data, getDataByKey, KeyValueObject) {
198
215
  if (!rule) {
199
216
  return true
@@ -207,10 +224,10 @@ function getValidation(rule, data, getDataByKey, KeyValueObject) {
207
224
  if (!key && typeof placeholder === 'undefined') {
208
225
  switch (valueAttribute) {
209
226
  case '$and': {
210
- return value['$and'].reduce((acc, item) => (acc && getValidation(item, data, getDataByKey, KeyValueObject)), true)
227
+ return value.$and.reduce((acc, item) => (acc && getValidation(item, data, getDataByKey, KeyValueObject)), true)
211
228
  }
212
229
  case '$or': {
213
- return value['$or'].reduce((acc, item) => (acc || getValidation(item, data, getDataByKey, KeyValueObject)), false)
230
+ return value.$or.reduce((acc, item) => (acc || getValidation(item, data, getDataByKey, KeyValueObject)), false)
214
231
  }
215
232
  default:
216
233
  return false
@@ -225,39 +242,53 @@ function getValidation(rule, data, getDataByKey, KeyValueObject) {
225
242
  }
226
243
 
227
244
  switch (valueAttribute) {
245
+ case '$after': {
246
+ if (isConvertibleToNumber(value?.$after)) {
247
+ const _value = Number(String(value?.$after))
248
+ return Date.now() > _value
249
+ }
250
+ return false
251
+ }
252
+ case '$before': {
253
+ if (isConvertibleToNumber(value?.$before)) {
254
+ const _value = Number(String(value?.$before))
255
+ return Date.now() < _value
256
+ }
257
+ return false
258
+ }
228
259
  case '$empty': {
229
260
  const isEmpty = rowValue === null || rowValue === undefined
230
- return isEmpty === value['$empty']
261
+ return isEmpty === value.$empty
231
262
  }
232
263
  case '$eq': {
233
- return rowValue === value['$eq']
264
+ return rowValue === value.$eq
234
265
  }
235
266
  case '$gt': {
236
- return rowValue > value['$gt']
267
+ return rowValue > value.$gt
237
268
  }
238
269
  case '$gte': {
239
- return rowValue >= value['$gte']
270
+ return rowValue >= value.$gte
240
271
  }
241
272
  case '$hasOverlap': {
242
- return _hasOverlap(rowValue, value['$hasOverlap'])
273
+ return _hasOverlap(rowValue, value.$hasOverlap)
243
274
  }
244
275
  case '$lt': {
245
- return rowValue < value['$lt']
276
+ return rowValue < value.$lt
246
277
  }
247
278
  case '$lte': {
248
- return rowValue <= value['$lte']
279
+ return rowValue <= value.$lte
249
280
  }
250
281
  case '$in': {
251
282
  if (Array.isArray(rowValue)) {
252
- return !!rowValue.find((e) => (value['$in'].includes(e)))
283
+ return !!rowValue.find((e) => (value.$in.includes(e)))
253
284
  }
254
285
  if (typeof rowValue !== 'object') {
255
- return !!value['$in'].includes(rowValue)
286
+ return !!value.$in.includes(rowValue)
256
287
  }
257
288
  return false
258
289
  }
259
290
  case '$inValue': {
260
- const result = getDataByKey(value['$inValue'], data)
291
+ const result = getDataByKey(value.$inValue, data)
261
292
  const _value = Array.isArray(result) ? result : []
262
293
  if (Array.isArray(rowValue)) {
263
294
  return !!rowValue.find((e) => (_value.includes(e)))
@@ -268,36 +299,36 @@ function getValidation(rule, data, getDataByKey, KeyValueObject) {
268
299
  return false
269
300
  }
270
301
  case '$ne': {
271
- return rowValue !== value['$ne']
302
+ return rowValue !== value.$ne
272
303
  }
273
304
  case '$notIn': {
274
305
  if (Array.isArray(rowValue)) {
275
- return !rowValue.find((e) => (value['$notIn'].includes(e)))
306
+ return !rowValue.find((e) => (value.$notIn.includes(e)))
276
307
  }
277
308
  if (typeof rowValue !== 'object') {
278
- return !value['$notIn'].includes(rowValue)
309
+ return !value.$notIn.includes(rowValue)
279
310
  }
280
311
  return false
281
312
  }
282
313
  case '$intervalTimeGt': {
283
314
  const now = new Date().getTime()
284
315
  const timestamp = new Date(rowValue).getTime()
285
- return (now - timestamp) > value['$intervalTimeGt']
316
+ return (now - timestamp) > value.$intervalTimeGt
286
317
  }
287
318
  case '$intervalTimeLt': {
288
319
  const now = new Date().getTime()
289
320
  const timestamp = new Date(rowValue).getTime()
290
- return (now - timestamp) < value['$intervalTimeLt']
321
+ return (now - timestamp) < value.$intervalTimeLt
291
322
  }
292
323
  case '$isToday': {
293
324
  const currentDate = new Date()
294
- const start = currentDate.setHours(0,0,0,0)
295
- const end = currentDate.setHours(23,59,59,59)
325
+ const start = currentDate.setHours(0, 0, 0, 0)
326
+ const end = currentDate.setHours(23, 59, 59, 59)
296
327
  const dateValue = new Date(rowValue).getTime()
297
- return (start <= dateValue && end >= dateValue) === value['$isToday']
328
+ return (start <= dateValue && end >= dateValue) === value.$isToday
298
329
  }
299
330
  case '$notInValue': {
300
- const result = getDataByKey(value['$notInValue'], data)
331
+ const result = getDataByKey(value.$notInValue, data)
301
332
  const _value = Array.isArray(result) ? result : []
302
333
  if (Array.isArray(rowValue)) {
303
334
  return !rowValue.find((e) => (_value.includes(e)))
@@ -308,7 +339,7 @@ function getValidation(rule, data, getDataByKey, KeyValueObject) {
308
339
  return false
309
340
  }
310
341
  case '$range': {
311
- const [min, max] = value['$range']
342
+ const [min, max] = value.$range
312
343
  if (typeof min === 'number' && typeof max === 'number' && rowValue >= min && rowValue <= max) {
313
344
  return true
314
345
  }
@@ -317,7 +348,6 @@ function getValidation(rule, data, getDataByKey, KeyValueObject) {
317
348
  default:
318
349
  return false
319
350
  }
320
-
321
351
  }
322
352
 
323
353
  function _hasOverlap(item1, item2) {
@@ -342,12 +372,11 @@ function _hasOverlap(item1, item2) {
342
372
 
343
373
 
344
374
  ;// ./lib/helpers/formatDate/formatDate.js
345
-
346
375
  function formatDate(date, format) {
347
376
  const _date = date && date instanceof Date ? date : new Date(date)
348
- const dayMapChi = ['日','一','二','三','四','五','六']
349
- const dayMapEng = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday']
350
- const dayMapEngShort = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
377
+ const dayMapChi = ['日', '一', '二', '三', '四', '五', '六']
378
+ const dayMapEng = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
379
+ const dayMapEngShort = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
351
380
  const _format = format || 'YYYY/MM/DD hh:mm'
352
381
  const e = _date.getDay()
353
382
  const ee = dayMapEngShort[e]
@@ -382,13 +411,11 @@ function padding(m) {
382
411
  return m < 10 ? `0${m}` : m
383
412
  }
384
413
 
385
-
386
414
  /* harmony default export */ const formatDate_formatDate = ({
387
415
  formatDate
388
416
  });
389
417
 
390
418
 
391
-
392
419
  ;// ./lib/helpers/formatDate/index.js
393
420
 
394
421
 
@@ -419,7 +446,6 @@ function getValueByKeys_getValueByKeys(keys, data) {
419
446
  return _data[firstKey]
420
447
  }
421
448
  return _data
422
-
423
449
  }
424
450
  /* harmony default export */ const getValueByKeys = ({
425
451
  getValueByKeys: getValueByKeys_getValueByKeys
@@ -471,6 +497,7 @@ const _FN_NAMES = [
471
497
  'map',
472
498
  'neq',
473
499
  'removeHtml',
500
+ 'sum',
474
501
  'toLowerCase',
475
502
  'toUpperCase',
476
503
  ]
@@ -623,10 +650,12 @@ function _existObject(data, args) {
623
650
 
624
651
  function _performOperation(arg, value) {
625
652
  // the arg is undefined
626
- if (arg === undefined && value === undefined) return true
653
+ if (arg === undefined && value === undefined)
654
+ return true
627
655
 
628
656
  // the arg is null
629
- if (arg === null && value === null) return true
657
+ if (arg === null && value === null)
658
+ return true
630
659
 
631
660
  // the arg is boolean
632
661
  if (typeof arg === 'boolean') {
@@ -702,17 +731,18 @@ function _splitOperator(str) {
702
731
  const operators = ['!=', '>=', '<=', '>', '<']
703
732
 
704
733
  const matchedOp = operators.find((op) => str.startsWith(op))
705
- if (!matchedOp) return { operator: null, value: null }
734
+ if (!matchedOp)
735
+ return { operator: null, value: null }
706
736
 
707
737
  const remaining = str.slice(matchedOp.length)
708
738
 
709
739
  // '>Primary' or '<Primary' is invalid
710
- if (/^[a-zA-Z]*$/.test(remaining) && matchedOp !== '!=') {
740
+ if (/^[a-z]*$/i.test(remaining) && matchedOp !== '!=') {
711
741
  return { operator: null, value: null }
712
742
  }
713
743
 
714
744
  // if it is a number it is converted to a number
715
- const value = (!Number.isNaN(parseFloat(remaining)) && !Number.isNaN(remaining)) ? Number(remaining) : remaining
745
+ const value = (!Number.isNaN(Number.parseFloat(remaining)) && !Number.isNaN(remaining)) ? Number(remaining) : remaining
716
746
 
717
747
  return {
718
748
  operator: matchedOp,
@@ -924,11 +954,14 @@ function _isNotEmpty(data, args) {
924
954
  }
925
955
 
926
956
 
957
+
927
958
  ;// ./lib/models/templateCompiler/helpers/_join.js
928
959
  function _join(data, delimiter) {
929
960
  try {
930
- if (data.length === 0) return ''
931
- if (data.length === 1) return _stringifyObject(data[0])
961
+ if (data.length === 0)
962
+ return ''
963
+ if (data.length === 1)
964
+ return _stringifyObject(data[0])
932
965
  return data.map((item) => _stringifyObject(item)).join(delimiter)
933
966
  } catch (e) {
934
967
  throw e
@@ -1075,13 +1108,13 @@ function _removeHtml(html, args) {
1075
1108
 
1076
1109
  function _htmlToPlainText(html, delimiter = '\n') {
1077
1110
  if (typeof delimiter !== 'string') {
1078
- delimiter = '\n'; // Fallback to default if not a string
1111
+ delimiter = '\n' // Fallback to default if not a string
1079
1112
  }
1080
1113
 
1081
1114
  // First decode HTML entities and normalize whitespace
1082
1115
  const decodedHtml = html
1083
1116
  .replace(/&nbsp;/g, ' ')
1084
- .replace(/\s+/g, ' '); // Collapse all whitespace to single spaces
1117
+ .replace(/\s+/g, ' ') // Collapse all whitespace to single spaces
1085
1118
 
1086
1119
  // Process HTML tags
1087
1120
  let text = decodedHtml
@@ -1092,29 +1125,36 @@ function _htmlToPlainText(html, delimiter = '\n') {
1092
1125
  // Remove all other tags
1093
1126
  .replace(/<[^>]+>/g, '')
1094
1127
  // Convert markers to specified delimiter
1095
- .replace(/~~~+/g, delimiter)
1128
+ .replace(/~{3,}/g, delimiter)
1096
1129
  // Trim and clean whitespace
1097
- .trim();
1130
+ .trim()
1098
1131
 
1099
1132
  // Special handling for empty delimiter
1100
1133
  if (delimiter === '') {
1101
1134
  // Collapse all whitespace to single space
1102
- text = text.replace(/\s+/g, ' ');
1135
+ text = text.replace(/\s+/g, ' ')
1103
1136
  } else {
1104
-
1105
-
1106
1137
  // Collapse multiple delimiters to single
1107
- text = text.replace(new RegExp(`${escapeRegExp(delimiter)}+`, 'g'), delimiter);
1138
+ text = text.replace(new RegExp(`${escapeRegExp(delimiter)}+`, 'g'), delimiter)
1108
1139
  // Remove leading/trailing delimiters
1109
- text = text.replace(new RegExp(`^${escapeRegExp(delimiter)}|${escapeRegExp(delimiter)}$`, 'g'), '');
1140
+ text = text.replace(new RegExp(`^${escapeRegExp(delimiter)}|${escapeRegExp(delimiter)}$`, 'g'), '')
1110
1141
  }
1111
1142
 
1112
- return text;
1143
+ return text
1113
1144
  }
1114
1145
 
1115
-
1116
1146
  function escapeRegExp(string) {
1117
- return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
1147
+ return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
1148
+ }
1149
+
1150
+
1151
+
1152
+ ;// ./lib/models/templateCompiler/helpers/_sum.js
1153
+ function _sum(data, args) {
1154
+ if (Number.isNaN(data) || data === null || (typeof data === 'undefined') || data === '') {
1155
+ return data
1156
+ }
1157
+ return args.reduce((acc, e) => (acc + e), data)
1118
1158
  }
1119
1159
 
1120
1160
 
@@ -1174,6 +1214,7 @@ function _toUpperCase(data, args) {
1174
1214
 
1175
1215
 
1176
1216
 
1217
+
1177
1218
 
1178
1219
 
1179
1220
  ;// ./lib/models/templateCompiler/templateCompiler.js
@@ -1247,6 +1288,9 @@ class TemplateCompiler {
1247
1288
  static removeHtml(data, args) {
1248
1289
  return _removeHtml(data, args)
1249
1290
  }
1291
+ static sum(data, args) {
1292
+ return _sum(data, args)
1293
+ }
1250
1294
  static toLowerCase(data, args) {
1251
1295
  return _toLowerCase(data, args)
1252
1296
  }
@@ -1307,7 +1351,7 @@ function _parseFunction(expression, existFunctionNames) {
1307
1351
 
1308
1352
  function _parseParams(parameters) {
1309
1353
  const _parameters = parameters.trim()
1310
- const regExp = new RegExp(/^[^\w\d\s]+$/)
1354
+ const regExp = new RegExp(/^[^\w\s]+$/)
1311
1355
  const match = _parameters.match(regExp)
1312
1356
  if (match !== null) {
1313
1357
  return [_parameters.substring(1, _parameters.length - 1)]
@@ -1322,13 +1366,13 @@ function _parseParams(parameters) {
1322
1366
 
1323
1367
  function _splitIgnoringBrackets(input) {
1324
1368
  const regExp2 = new RegExp(/^\d+(\.\d+)?$/)
1325
- const regExp = new RegExp(/(?![^\[]*\])\s*,\s*/)
1369
+ const regExp = new RegExp(/(?![^[]*\])\s*,\s*/)
1326
1370
  const parts = input.split(regExp)
1327
1371
  return parts.map((part) => {
1328
1372
  const _part = part.trim()
1329
1373
  if (_part !== '' && !!_part.match(regExp2)) {
1330
1374
  // 如果是数字,转换为 num 类型
1331
- return Number.isNaN(_part) ? _part : parseInt(_part, 10)
1375
+ return Number.isNaN(_part) ? _part : Number.parseInt(_part, 10)
1332
1376
  }
1333
1377
  // 否则当作字符串处理
1334
1378
  return _part
@@ -1341,7 +1385,7 @@ function _parseSinglePart(input) {
1341
1385
  // 去掉双引号,返回
1342
1386
  return input.substring(1, input.length - 1)
1343
1387
  }
1344
- if (input.startsWith("'") && input.endsWith("'")) {
1388
+ if (input.startsWith('\'') && input.endsWith('\'')) {
1345
1389
  // 去掉双引号,返回
1346
1390
  return input.substring(1, input.length - 1)
1347
1391
  }
@@ -1372,7 +1416,7 @@ function _toBasicType(input) {
1372
1416
  // 去掉双引号,返回
1373
1417
  return input.substring(1, input.length - 1)
1374
1418
  }
1375
- if (input.startsWith("'") && input.endsWith("'")) {
1419
+ if (input.startsWith('\'') && input.endsWith('\'')) {
1376
1420
  // 去掉双引号,返回
1377
1421
  return input.substring(1, input.length - 1)
1378
1422
  }
@@ -1440,6 +1484,8 @@ function _callFunction(data, functionName, parameters) {
1440
1484
  return _neq(data, parameters)
1441
1485
  case 'removeHtml':
1442
1486
  return _removeHtml(data, parameters)
1487
+ case 'sum':
1488
+ return _sum(data, parameters)
1443
1489
  case 'toLowerCase':
1444
1490
  return _toLowerCase(data)
1445
1491
  case 'toUpperCase':
@@ -1469,14 +1515,14 @@ function concatStringByArray(arrTemplate, data) {
1469
1515
  return arrTemplate.reduce((acc, item) => {
1470
1516
  const { type, value = '', restriction, template, format, showMinutes } = item
1471
1517
  switch (type) {
1472
- case('array'): {
1518
+ case ('array'): {
1473
1519
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1474
1520
  const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || []
1475
1521
  acc += _value.reduce((_acc, item) => {
1476
1522
  return _acc += concatStringByArray(template, item)
1477
1523
  }, '')
1478
1524
  }
1479
- break
1525
+ break
1480
1526
  }
1481
1527
  case ('date'): {
1482
1528
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
@@ -1485,7 +1531,7 @@ function concatStringByArray(arrTemplate, data) {
1485
1531
  }
1486
1532
  break
1487
1533
  }
1488
- case('ellipsis'): {
1534
+ case ('ellipsis'): {
1489
1535
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1490
1536
  const { maxLength } = item
1491
1537
  const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
@@ -1495,15 +1541,15 @@ function concatStringByArray(arrTemplate, data) {
1495
1541
  acc += `${_value.substr(0, maxLength)}...`
1496
1542
  }
1497
1543
  }
1498
- break
1544
+ break
1499
1545
  }
1500
- case('group'): {
1546
+ case ('group'): {
1501
1547
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1502
1548
  return concatStringByArray(value, data)
1503
1549
  }
1504
1550
  break
1505
1551
  }
1506
- case('label'): {
1552
+ case ('label'): {
1507
1553
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1508
1554
  acc += (value.toString())
1509
1555
  }
@@ -1516,12 +1562,12 @@ function concatStringByArray(arrTemplate, data) {
1516
1562
  }
1517
1563
  break
1518
1564
  }
1519
- case('value'): {
1565
+ case ('value'): {
1520
1566
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1521
1567
  const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1522
1568
  acc += (_value.toString())
1523
1569
  }
1524
- break
1570
+ break
1525
1571
  }
1526
1572
  }
1527
1573
  return acc
@@ -1532,7 +1578,6 @@ function concatStringByArray(arrTemplate, data) {
1532
1578
  });
1533
1579
 
1534
1580
 
1535
-
1536
1581
  ;// ./lib/helpers/concatStringByArray/index.js
1537
1582
 
1538
1583
 
@@ -1543,7 +1588,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1543
1588
  if (!string) {
1544
1589
  return ''
1545
1590
  }
1546
- let _getValueByKeys = typeof getValueByKeys === 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1591
+ const _getValueByKeys = typeof getValueByKeys === 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1547
1592
  const reg = new RegExp(patternMatch, 'g')
1548
1593
  return string.replace(reg, (match, key) => {
1549
1594
  const result = _getValueByKeys({ keys: key.split('.'), obj: value })
@@ -1562,9 +1607,110 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1562
1607
  ;// ./lib/helpers/convertString/index.js
1563
1608
 
1564
1609
 
1610
+ ;// ./lib/helpers/detectControlCharacters/detectControlCharacters.js
1611
+ /**
1612
+ * Detects and reports hidden/control characters in a string without modifying it.
1613
+ * @param {string} input - The string to analyze.
1614
+ * @param {Object} [options] - Configuration options.
1615
+ * @param {boolean} [options.preserveBasicWhitespace=true] - Whether to consider basic whitespace as valid.
1616
+ * @returns {Object} Report object with detection results.
1617
+ */
1618
+ function detectControlCharacters(input, options = {}) {
1619
+ const {
1620
+ preserveBasicWhitespace = true,
1621
+ removeNewlines = false
1622
+ } = options
1623
+
1624
+ if (typeof input !== 'string') {
1625
+ return {
1626
+ hasControlChars: false,
1627
+ matches: [],
1628
+ inputType: typeof input,
1629
+ message: 'Input is not a string'
1630
+ }
1631
+ }
1632
+
1633
+ const matches = []
1634
+ let regex
1635
+
1636
+ if (preserveBasicWhitespace && !removeNewlines) {
1637
+ // Same regex as Phase 1 preserve mode - keep tab (\t), newline (\n), carriage return (\r)
1638
+ regex = /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g
1639
+ } else {
1640
+ // Same regex as Phase 1 full removal mode - use consistent escape sequences
1641
+ regex = /[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g
1642
+ }
1643
+
1644
+ // Use a replacer function to capture matches without modifying the string
1645
+ input.replace(regex, (match, offset) => {
1646
+ matches.push({
1647
+ character: match,
1648
+ code: match.charCodeAt(0),
1649
+ hex: '0x' + match.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0'),
1650
+ position: offset,
1651
+ context: getContext(input, offset, 10) // Show surrounding text
1652
+ })
1653
+ return '' // Return empty but we don't use the result
1654
+ })
1655
+
1656
+ return {
1657
+ hasControlChars: matches.length > 0,
1658
+ matches: matches,
1659
+ totalFound: matches.length,
1660
+ inputPreview: input.length > 50 ? input.substring(0, 50) + '...' : input,
1661
+ inputLength: input.length,
1662
+ optionsUsed: { preserveBasicWhitespace },
1663
+ regexPattern: regex.toString()
1664
+ }
1665
+ }
1666
+
1667
+ /**
1668
+ * Helper function to get context around a match
1669
+ */
1670
+ function getContext(str, position, contextLength = 10) {
1671
+ const start = Math.max(0, position - contextLength)
1672
+ const end = Math.min(str.length, position + contextLength + 1)
1673
+ let context = str.substring(start, end)
1674
+
1675
+ // Replace control characters with their escape sequences for readability
1676
+ context = context.replace(/[\x00-\x1F\x7F-\x9F]/g, (match) => {
1677
+ return '\\u' + match.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')
1678
+ })
1679
+
1680
+ return context
1681
+ }
1682
+
1683
+ /**
1684
+ * Pretty print the detection results to console
1685
+ */
1686
+ function printControlCharReport(report) {
1687
+ console.log('=== Control Character Detection Report ===')
1688
+ console.log(`Input: "${report.inputPreview}" (${report.inputLength} chars)`)
1689
+ console.log(`Options: preserveBasicWhitespace = ${report.optionsUsed.preserveBasicWhitespace}`)
1690
+ console.log(`Regex pattern: ${report.regexPattern}`)
1691
+ console.log(`Control characters found: ${report.totalFound}`)
1692
+
1693
+ if (report.hasControlChars) {
1694
+ console.log('\n📋 Matches found:')
1695
+ report.matches.forEach((match, index) => {
1696
+ console.log(`\n${index + 1}. Character: ${JSON.stringify(match.character)}`)
1697
+ console.log(` Code: ${match.code} (${match.hex})`)
1698
+ console.log(` Position: ${match.position}`)
1699
+ console.log(` Context: "...${match.context}..."`)
1700
+ })
1701
+ } else {
1702
+ console.log('✅ No control characters detected in Phase 1 range')
1703
+ }
1704
+
1705
+ console.log('=== End Report ===')
1706
+ }
1707
+
1708
+ ;// ./lib/helpers/detectControlCharacters/index.js
1709
+
1710
+
1565
1711
  ;// ./lib/helpers/escapeRegex/escapeRegex.js
1566
1712
  function escapeRegex(string) {
1567
- return String(string).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1713
+ return String(string).replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
1568
1714
  }
1569
1715
 
1570
1716
  ;// ./lib/helpers/escapeRegex/index.js
@@ -1667,7 +1813,6 @@ function updateOneResult({ responseHelper, service }) {
1667
1813
 
1668
1814
 
1669
1815
 
1670
-
1671
1816
  const expressHelper = {
1672
1817
  customHandler: customHandler,
1673
1818
  findAllResult: findAllResult,
@@ -1683,27 +1828,29 @@ const expressHelper = {
1683
1828
  * @returns {Array} Sorted array of unique, lowercase email addresses
1684
1829
  */
1685
1830
  function extractEmails(dirtyArray) {
1686
- const emailRegex = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g
1831
+ const emailRegex = /[\w.%+-]+@[a-z0-9.-]+\.[a-z]{2,}/gi
1687
1832
  const emails = new Set()
1688
1833
 
1689
1834
  // Handle null/undefined input array
1690
- if (!dirtyArray) return []
1835
+ if (!dirtyArray)
1836
+ return []
1691
1837
 
1692
- dirtyArray.forEach(entry => {
1838
+ dirtyArray.forEach((entry) => {
1693
1839
  // Skip null, undefined, empty, or whitespace-only entries
1694
- if (!entry || typeof entry !== 'string' || !entry.trim()) return
1840
+ if (!entry || typeof entry !== 'string' || !entry.trim())
1841
+ return
1695
1842
 
1696
1843
  try {
1697
1844
  const cleanEntry = entry
1698
1845
  .replace(/[\u200B-\u200D\uFEFF\u202A-\u202E]/g, '') // Remove hidden chars
1699
- .replace(/[<>]/g, ' ') // Convert email delimiters to spaces
1700
- .replace(/\s+/g, ' ') // Collapse multiple whitespace
1846
+ .replace(/[<>]/g, ' ') // Convert email delimiters to spaces
1847
+ .replace(/\s+/g, ' ') // Collapse multiple whitespace
1701
1848
  .trim()
1702
1849
 
1703
1850
  // Extract all email matches
1704
1851
  const matches = cleanEntry.match(emailRegex)
1705
1852
  if (matches) {
1706
- matches.forEach(email => emails.add(email.toLowerCase())) // Normalize to lowercase
1853
+ matches.forEach((email) => emails.add(email.toLowerCase())) // Normalize to lowercase
1707
1854
  }
1708
1855
  } catch (e) {
1709
1856
  console.warn('Failed to process entry:', entry, e)
@@ -1749,64 +1896,65 @@ const objectHelper = {
1749
1896
  },
1750
1897
  merge,
1751
1898
  set(obj, path, value) {
1752
- const parts = path.split('.');
1753
- let current = obj;
1899
+ const parts = path.split('.')
1900
+ let current = obj
1754
1901
 
1755
1902
  // 处理所有中间部分
1756
1903
  for (let i = 0; i < parts.length - 1; i++) {
1757
- const part = parts[i];
1758
- let key, index;
1904
+ const part = parts[i]
1905
+ let key, index
1759
1906
 
1760
1907
  // 检查是否是数组索引格式,如key[0]
1761
- const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/);
1908
+ const arrayMatch = part.match(/^(\w+)\[(\d+)\]$/)
1762
1909
  if (arrayMatch) {
1763
- key = arrayMatch[1];
1764
- index = parseInt(arrayMatch[2], 10);
1910
+ key = arrayMatch[1]
1911
+ index = Number.parseInt(arrayMatch[2], 10)
1765
1912
  // 确保当前层级的数组存在
1766
1913
  if (!current[key] || !Array.isArray(current[key])) {
1767
- current[key] = [];
1914
+ current[key] = []
1768
1915
  }
1769
1916
  // 扩展数组到足够大
1770
1917
  while (current[key].length <= index) {
1771
- current[key].push(undefined);
1918
+ current[key].push(undefined)
1772
1919
  }
1773
1920
  // 如果当前位置未定义或为null,初始化为对象
1774
1921
  if (current[key][index] == null) {
1775
- current[key][index] = {};
1922
+ current[key][index] = {}
1776
1923
  }
1777
- current = current[key][index];
1924
+ current = current[key][index]
1778
1925
  } else {
1779
1926
  // 处理普通属性
1780
1927
  if (!current[part]) {
1781
- current[part] = {};
1928
+ current[part] = {}
1782
1929
  }
1783
- current = current[part];
1930
+ current = current[part]
1784
1931
  }
1785
1932
  }
1786
1933
 
1787
1934
  // 处理最后一部分
1788
- const lastPart = parts[parts.length - 1];
1789
- const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/);
1935
+ const lastPart = parts[parts.length - 1]
1936
+ const arrayMatch = lastPart.match(/^(\w+)\[(\d+)\]$/)
1790
1937
  if (arrayMatch) {
1791
- const key = arrayMatch[1];
1792
- const index = parseInt(arrayMatch[2], 10);
1938
+ const key = arrayMatch[1]
1939
+ const index = Number.parseInt(arrayMatch[2], 10)
1793
1940
  // 确保数组存在
1794
1941
  if (!current[key] || !Array.isArray(current[key])) {
1795
- current[key] = [];
1942
+ current[key] = []
1796
1943
  }
1797
1944
  // 扩展数组到所需索引
1798
1945
  while (current[key].length <= index) {
1799
- current[key].push(undefined);
1946
+ current[key].push(undefined)
1800
1947
  }
1801
- current[key][index] = value;
1948
+ current[key][index] = value
1802
1949
  } else {
1803
- current[lastPart] = value;
1950
+ current[lastPart] = value
1804
1951
  }
1805
1952
  }
1806
1953
  }
1807
1954
 
1808
1955
  function merge(target, ...sources) {
1809
- if (!sources.length) return target
1956
+ if (!sources.length)
1957
+ return target
1810
1958
 
1811
1959
  const source = sources.shift() // 取出第一个源对象
1812
1960
 
@@ -1843,28 +1991,28 @@ function _isObject(obj) {
1843
1991
 
1844
1992
  ;// ./lib/helpers/pReduce/pReduce.js
1845
1993
  async function pReduce(iterable, reducer, initialValue) {
1846
- return new Promise((resolve, reject) => {
1847
- const iterator = iterable[Symbol.iterator]()
1848
- let index = 0
1994
+ return new Promise((resolve, reject) => {
1995
+ const iterator = iterable[Symbol.iterator]()
1996
+ let index = 0
1849
1997
 
1850
- const next = async total => {
1851
- const element = iterator.next()
1998
+ const next = async (total) => {
1999
+ const element = iterator.next()
1852
2000
 
1853
- if (element.done) {
1854
- resolve(total)
1855
- return
1856
- }
2001
+ if (element.done) {
2002
+ resolve(total)
2003
+ return
2004
+ }
1857
2005
 
1858
- try {
1859
- const [resolvedTotal, resolvedValue] = await Promise.all([total, element.value])
1860
- next(reducer(resolvedTotal, resolvedValue, index++))
1861
- } catch (error) {
1862
- reject(error)
1863
- }
1864
- }
2006
+ try {
2007
+ const [resolvedTotal, resolvedValue] = await Promise.all([total, element.value])
2008
+ next(reducer(resolvedTotal, resolvedValue, index++))
2009
+ } catch (error) {
2010
+ reject(error)
2011
+ }
2012
+ }
1865
2013
 
1866
- next(initialValue)
1867
- })
2014
+ next(initialValue)
2015
+ })
1868
2016
  }
1869
2017
 
1870
2018
 
@@ -2011,16 +2159,17 @@ class Repo {
2011
2159
  const promise = typeof this.model.saveAll === 'function'
2012
2160
  ? this.model.saveAll({ config, docs })
2013
2161
  : Promise.all(docs.map(async (doc) => {
2014
- if (doc) {
2015
- const result = await this.saveOne({ config, doc })
2016
- isNew = result.isNew
2017
- const _data = result._data || result.data
2018
- return _data[0]
2019
- }
2020
- return null
2021
- }))
2162
+ if (doc) {
2163
+ const result = await this.saveOne({ config, doc })
2164
+ isNew = result.isNew
2165
+ const _data = result._data || result.data
2166
+ return _data[0]
2167
+ }
2168
+ return null
2169
+ }))
2022
2170
  return promise.then((savedData) => {
2023
- if (savedData.length !== 1) isNew = null
2171
+ if (savedData.length !== 1)
2172
+ isNew = null
2024
2173
  const result = {
2025
2174
  data: savedData,
2026
2175
  isNew,
@@ -2354,6 +2503,9 @@ function initOnlyValidFromArray(_class, arr) {
2354
2503
  ;// ./lib/helpers/initOnlyValidFromArray/index.js
2355
2504
 
2356
2505
 
2506
+ ;// ./lib/helpers/isConvertibleToNumber/index.js
2507
+
2508
+
2357
2509
  ;// ./lib/helpers/mergeArraysByKey/mergeArraysByKey.js
2358
2510
  function mergeArraysByKey(arr1, arr2) {
2359
2511
  // Handle undefined/null inputs by defaulting to empty arrays
@@ -2364,40 +2516,41 @@ function mergeArraysByKey(arr1, arr2) {
2364
2516
 
2365
2517
  // Helper function to merge values based on their type
2366
2518
  const mergeValues = (existingValue, newValue) => {
2367
- if (existingValue === undefined) return newValue
2368
-
2519
+ if (existingValue === undefined)
2520
+ return newValue
2521
+
2369
2522
  // Handle arrays by concatenating
2370
2523
  if (Array.isArray(existingValue) && Array.isArray(newValue)) {
2371
2524
  return [...new Set([...existingValue, ...newValue])]
2372
2525
  }
2373
-
2526
+
2374
2527
  // Handle objects by merging
2375
- if (typeof existingValue === 'object' && typeof newValue === 'object' &&
2376
- !Array.isArray(existingValue) && !Array.isArray(newValue)) {
2528
+ if (typeof existingValue === 'object' && typeof newValue === 'object'
2529
+ && !Array.isArray(existingValue) && !Array.isArray(newValue)) {
2377
2530
  return { ...existingValue, ...newValue }
2378
2531
  }
2379
-
2532
+
2380
2533
  // // Handle numbers by adding
2381
2534
  // if (typeof existingValue === 'number' && typeof newValue === 'number') {
2382
2535
  // return existingValue
2383
2536
  // }
2384
-
2537
+
2385
2538
  // // Handle strings by concatenating
2386
2539
  // if (typeof existingValue === 'string' && typeof newValue === 'string') {
2387
2540
  // return existingValue
2388
2541
  // }
2389
-
2542
+
2390
2543
  // Default: use the new value
2391
2544
  return newValue
2392
2545
  }
2393
2546
 
2394
2547
  // Process first array
2395
- safeArr1.forEach(item => {
2548
+ safeArr1.forEach((item) => {
2396
2549
  mergedMap.set(item.key, item.value)
2397
2550
  })
2398
2551
 
2399
2552
  // Process second array and merge values
2400
- safeArr2.forEach(item => {
2553
+ safeArr2.forEach((item) => {
2401
2554
  const existingValue = mergedMap.get(item.key)
2402
2555
  mergedMap.set(item.key, mergeValues(existingValue, item.value))
2403
2556
  })
@@ -2416,7 +2569,7 @@ function mergeArraysByKey(arr1, arr2) {
2416
2569
  function padZeros(num, minLength = 6) {
2417
2570
  num = num.toString()
2418
2571
  if (num.length < minLength) {
2419
- return padZeros('0' + num, minLength)
2572
+ return padZeros(`0${num}`, minLength)
2420
2573
  }
2421
2574
  return num
2422
2575
  }
@@ -2436,26 +2589,26 @@ function padZeros(num, minLength = 6) {
2436
2589
  ;// ./lib/helpers/replacePlaceholders/replacePlaceholders.js
2437
2590
  function replacePlaceholders({ content, mapping }) {
2438
2591
  let isObjectMode = false
2439
-
2592
+
2440
2593
  if (typeof content === 'object' && content !== null) {
2441
2594
  content = JSON.stringify(content)
2442
2595
  isObjectMode = true
2443
2596
  }
2444
2597
 
2445
2598
  // [[ eventRegistration.eventRegistrationCode | 0 ]]
2446
- const regex = /(\[*)\[\[\s*([\w.\-_]+)(?:\s*\|\s*([^:\]\s]+))?(?:\s*:\s*([^\]\s]+))?\s*\]\](\]*)/g;
2599
+ const regex = /(\[*)\[\[\s*([\w.\-]+)(?:\s*\|\s*([^:\]\s]+))?(?:\s*:\s*([^\]\s]+))?\s*\]\](\]*)/g
2447
2600
 
2448
2601
  const result = content.replace(regex, (match, leadingBrackets, path, defaultValue, type, trailingBrackets) => {
2449
-
2450
2602
  // Split the path into parts
2451
2603
  const keys = path.trim().split('.')
2452
-
2604
+
2453
2605
  // Traverse the nested object structure
2454
2606
  let value = mapping
2455
2607
  for (const key of keys) {
2456
2608
  // Handle empty keys (in case of double dots or leading/trailing dots)
2457
- if (!key) continue
2458
-
2609
+ if (!key)
2610
+ continue
2611
+
2459
2612
  value = value?.[key]
2460
2613
  if (value === undefined) {
2461
2614
  break
@@ -2463,10 +2616,12 @@ function replacePlaceholders({ content, mapping }) {
2463
2616
  }
2464
2617
 
2465
2618
  // Apply default if missing
2466
- if (value === undefined) value = defaultValue?.trim();
2467
- if (value === undefined) return isObjectMode ? undefined : match;
2468
-
2469
- value = value !== undefined
2619
+ if (value === undefined)
2620
+ value = defaultValue?.trim()
2621
+ if (value === undefined)
2622
+ return isObjectMode ? undefined : match
2623
+
2624
+ value = value !== undefined
2470
2625
  ? leadingBrackets + value + trailingBrackets
2471
2626
  : match
2472
2627
 
@@ -2487,10 +2642,11 @@ function replacePlaceholders({ content, mapping }) {
2487
2642
  /**
2488
2643
  * Sanitizes input by removing hidden/control characters with customizable whitespace handling.
2489
2644
  * @param {string} input - The string to sanitize.
2490
- * @param {Object} [options] - Configuration options.
2491
- * @param {boolean} [options.normalizeWhitespace=true] - Collapse multiple spaces/tabs into one space.
2492
- * @param {boolean} [options.removeNewlines=false] - If true, replaces newlines with spaces.
2493
- * @param {boolean} [options.trim=true] - If true, trims leading/trailing whitespace.
2645
+ * @param {object} [options] - Configuration options.
2646
+ * @param {boolean} [options.normalizeWhitespace] - Collapse multiple spaces/tabs into one space.
2647
+ * @param {boolean} [options.removeNewlines] - If true, replaces newlines with spaces.
2648
+ * @param {boolean} [options.trim] - If true, trims leading/trailing whitespace.
2649
+ * @param {boolean} [options.debug] - If true, logs debug information about removed characters.
2494
2650
  * @returns {string} The sanitized string.
2495
2651
  */
2496
2652
  function sanitizeText(input, options = {}) {
@@ -2498,7 +2654,8 @@ function sanitizeText(input, options = {}) {
2498
2654
  normalizeWhitespace = true,
2499
2655
  removeNewlines = false,
2500
2656
  trim = true,
2501
- preserveBasicWhitespace = true, // new option to keep tabs, newlines if removeNewlines=false
2657
+ preserveBasicWhitespace = true,
2658
+ debug = false, // new option for debugging
2502
2659
  } = options
2503
2660
 
2504
2661
  if (typeof input !== 'string') {
@@ -2507,27 +2664,106 @@ function sanitizeText(input, options = {}) {
2507
2664
 
2508
2665
  let result = input
2509
2666
 
2667
+ if (debug) {
2668
+ console.log('Original input:', JSON.stringify(input))
2669
+ console.log('Options:', { normalizeWhitespace, removeNewlines, trim, preserveBasicWhitespace })
2670
+ }
2671
+
2510
2672
  // Phase 1: Remove all control characters except basic whitespace if requested
2511
2673
  if (preserveBasicWhitespace && !removeNewlines) {
2512
- // Keep tab (\t), newline (\n), carriage return (\r)
2513
- result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2674
+ if (debug) {
2675
+ const before = result
2676
+ const matches = []
2677
+ // Use a replacer function to capture what's being removed
2678
+ result = result.replace(/[\x00-\x08\v\f\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, (match) => {
2679
+ matches.push({
2680
+ char: match,
2681
+ code: match.charCodeAt(0),
2682
+ hex: `0x${match.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`
2683
+ })
2684
+ return ''
2685
+ })
2686
+ if (matches.length > 0) {
2687
+ console.log('Phase 1 (preserve mode) - Removed characters:')
2688
+ matches.forEach((m) => {
2689
+ console.log(` - Character: ${JSON.stringify(m.char)}, Code: ${m.code}, Hex: ${m.hex}`)
2690
+ })
2691
+ console.log(`Removed ${matches.length} control character(s)`)
2692
+ } else {
2693
+ console.log('Phase 1 (preserve mode) - No control characters found')
2694
+ }
2695
+ } else {
2696
+ result = result.replace(/[\x00-\x08\v\f\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2697
+ }
2514
2698
  } else {
2515
- // Remove all control characters including basic whitespace
2516
- result = result.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2699
+ if (debug) {
2700
+ const before = result
2701
+ const matches = []
2702
+ result = result.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, (match) => {
2703
+ matches.push({
2704
+ char: match,
2705
+ code: match.charCodeAt(0),
2706
+ hex: `0x${match.charCodeAt(0).toString(16).toUpperCase().padStart(4, '0')}`
2707
+ })
2708
+ return ''
2709
+ })
2710
+ if (matches.length > 0) {
2711
+ console.log('Phase 1 (full removal mode) - Removed characters:')
2712
+ matches.forEach((m) => {
2713
+ console.log(` - Character: ${JSON.stringify(m.char)}, Code: ${m.code}, Hex: ${m.hex}`)
2714
+ })
2715
+ console.log(`Removed ${matches.length} control character(s)`)
2716
+ } else {
2717
+ console.log('Phase 1 (full removal mode) - No control characters found')
2718
+ }
2719
+ } else {
2720
+ result = result.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2721
+ }
2722
+ }
2723
+
2724
+ if (debug) {
2725
+ console.log('After Phase 1:', JSON.stringify(result))
2517
2726
  }
2518
2727
 
2519
2728
  // Phase 2: Handle whitespace transformations
2520
2729
  if (removeNewlines) {
2521
- result = result.replace(/[\r\n]+/g, ' ') // Convert newlines to spaces
2730
+ if (debug) {
2731
+ const before = result
2732
+ result = result.replace(/[\r\n]+/g, ' ')
2733
+ console.log('Phase 2 - Converted newlines to spaces')
2734
+ } else {
2735
+ result = result.replace(/[\r\n]+/g, ' ')
2736
+ }
2522
2737
  }
2523
2738
 
2524
2739
  if (normalizeWhitespace) {
2525
- result = result.replace(/[ \t]+/g, ' ') // Collapse spaces/tabs to single space
2740
+ if (debug) {
2741
+ const before = result
2742
+ result = result.replace(/[ \t]+/g, ' ')
2743
+ console.log('Phase 2 - Normalized whitespace')
2744
+ } else {
2745
+ result = result.replace(/[ \t]+/g, ' ')
2746
+ }
2747
+ }
2748
+
2749
+ if (debug) {
2750
+ console.log('After Phase 2:', JSON.stringify(result))
2526
2751
  }
2527
2752
 
2528
2753
  // Phase 3: Final trimming
2529
2754
  if (trim) {
2530
- result = result.trim()
2755
+ if (debug) {
2756
+ const before = result
2757
+ result = result.trim()
2758
+ console.log('Phase 3 - Trimmed leading/trailing whitespace')
2759
+ } else {
2760
+ result = result.trim()
2761
+ }
2762
+ }
2763
+
2764
+ if (debug) {
2765
+ console.log('Final result:', JSON.stringify(result))
2766
+ console.log('--- Sanitization complete ---')
2531
2767
  }
2532
2768
 
2533
2769
  return result
@@ -2538,12 +2774,12 @@ function sanitizeText(input, options = {}) {
2538
2774
 
2539
2775
  ;// ./lib/helpers/shuffleArray/shuffleArray.js
2540
2776
  function shuffleArray(array) {
2541
- const arr = [...array];
2542
- for (let i = arr.length - 1; i >= 0; i--) { // Changed `i > 0` to `i >= 0`
2777
+ const arr = [...array]
2778
+ for (let i = arr.length - 1; i >= 0; i--) { // Changed `i > 0` to `i >= 0`
2543
2779
  const j = Math.floor(Math.random() * (i + 1));
2544
- [arr[i], arr[j]] = [arr[j], arr[i]];
2780
+ [arr[i], arr[j]] = [arr[j], arr[i]]
2545
2781
  }
2546
- return arr;
2782
+ return arr
2547
2783
  }
2548
2784
 
2549
2785
  ;// ./lib/helpers/shuffleArray/index.js
@@ -2605,7 +2841,7 @@ function indexCharset(str) {
2605
2841
  for (let i = 0; i < length; i++) {
2606
2842
  char = str[i]
2607
2843
  byCode[i] = char
2608
- byChar[char] = i;
2844
+ byChar[char] = i
2609
2845
  }
2610
2846
  return { byCode, byChar, length }
2611
2847
  }
@@ -2636,7 +2872,7 @@ function randomString({ len = 16, pattern = 'a1' } = {}) {
2636
2872
  str += mark
2637
2873
  }
2638
2874
  const chars = [...str]
2639
- return [...Array(len)].map(i => {
2875
+ return [...new Array(len)].map((i) => {
2640
2876
  return chars[(Math.random() * chars.length) | 0]
2641
2877
  }).join``
2642
2878
  }
@@ -2660,12 +2896,14 @@ function setCode(base = 34) {
2660
2896
  }
2661
2897
 
2662
2898
  function toCamelCase(str) {
2663
- if (!str) return ''
2899
+ if (!str)
2900
+ return ''
2664
2901
  return str
2665
2902
  .trim()
2666
2903
  .split(/\s+/)
2667
2904
  .map((word, index) => {
2668
- if (!word) return ''
2905
+ if (!word)
2906
+ return ''
2669
2907
  if (index === 0) {
2670
2908
  return word.toLowerCase()
2671
2909
  }
@@ -2675,7 +2913,8 @@ function toCamelCase(str) {
2675
2913
  }
2676
2914
 
2677
2915
  function toLowerCase(str) {
2678
- if (!str) return ''
2916
+ if (!str)
2917
+ return ''
2679
2918
  return str
2680
2919
  .trim()
2681
2920
  .toLowerCase()
@@ -2710,7 +2949,7 @@ function trackingPlugin(schema, options) {
2710
2949
  })
2711
2950
 
2712
2951
  // Auto-update hook
2713
- schema.pre('save', function(next) {
2952
+ schema.pre('save', function (next) {
2714
2953
  this.meta.modified = Date.now()
2715
2954
  next()
2716
2955
  })
@@ -2722,9 +2961,9 @@ function trackingPlugin(schema, options) {
2722
2961
  }, {
2723
2962
  name: 'tracking_status_index',
2724
2963
  background: true,
2725
- partialFilterExpression: {
2964
+ partialFilterExpression: {
2726
2965
  'meta.active': true,
2727
- 'meta.deleted': false
2966
+ 'meta.deleted': false
2728
2967
  }
2729
2968
  })
2730
2969
 
@@ -2753,7 +2992,7 @@ function tenantPlugin(schema, options) {
2753
2992
 
2754
2993
  // Add core indexes
2755
2994
  schema.index({
2756
- 'tenantCode': 1
2995
+ tenantCode: 1
2757
2996
  }, {
2758
2997
  name: 'tenant_core_index',
2759
2998
  background: true
@@ -2761,15 +3000,15 @@ function tenantPlugin(schema, options) {
2761
3000
 
2762
3001
  // 1. ENHANCE EXISTING TRACKING INDEXES
2763
3002
  const existingIndexes = schema.indexes()
2764
-
3003
+
2765
3004
  // Check if tracking_status_index exists
2766
- const hasTenantStatusIndex = existingIndexes.some(idx =>
3005
+ const hasTenantStatusIndex = existingIndexes.some((idx) =>
2767
3006
  idx.name === 'tenant_status_index' // Check by name for reliability
2768
3007
  )
2769
3008
 
2770
3009
  if (!hasTenantStatusIndex) {
2771
3010
  schema.index({
2772
- 'tenantCode': 1, // Unique field first
3011
+ tenantCode: 1, // Unique field first
2773
3012
  _type: 1, // Low-cardinality field last
2774
3013
  }, {
2775
3014
  name: 'tenant_status_index',
@@ -2777,7 +3016,7 @@ function tenantPlugin(schema, options) {
2777
3016
  partialFilterExpression: {
2778
3017
  '_type': 'Tenant',
2779
3018
  'meta.active': true,
2780
- 'meta.deleted': false
3019
+ 'meta.deleted': false
2781
3020
  }
2782
3021
  })
2783
3022
  }
@@ -2815,6 +3054,8 @@ function tenantPlugin(schema, options) {
2815
3054
 
2816
3055
 
2817
3056
 
3057
+
3058
+
2818
3059
 
2819
3060
 
2820
3061
 
@@ -2873,10 +3114,11 @@ class AwsStsS3Client {
2873
3114
  }
2874
3115
 
2875
3116
  get isExpired() {
2876
- if (!this.expiration) return true;
2877
- const now = new Date();
2878
- const bufferMs = 1 * 60 * 1000; // 一分钟缓冲
2879
- return now >= new Date(this.expiration.getTime() - bufferMs);
3117
+ if (!this.expiration)
3118
+ return true
3119
+ const now = new Date()
3120
+ const bufferMs = 1 * 60 * 1000 // 一分钟缓冲
3121
+ return now >= new Date(this.expiration.getTime() - bufferMs)
2880
3122
  }
2881
3123
 
2882
3124
  get isValid() {
@@ -2911,7 +3153,7 @@ class AwsStsS3Client {
2911
3153
  throw new Error(`Missing ${component} in AWS awsClientS3 client configuration`)
2912
3154
  }
2913
3155
  }
2914
-
3156
+
2915
3157
  return true
2916
3158
  }
2917
3159
 
@@ -2921,9 +3163,9 @@ class AwsStsS3Client {
2921
3163
  if (!webIdentityToken) {
2922
3164
  throw new Error('getIdToken function returned empty or invalid token')
2923
3165
  }
2924
-
3166
+
2925
3167
  const stsClient = new this.awsClientSts.STSClient({ region: this.region })
2926
-
3168
+
2927
3169
  const stsResponse = await stsClient.send(
2928
3170
  new this.awsClientSts.AssumeRoleWithWebIdentityCommand({
2929
3171
  RoleArn: this.roleArn,
@@ -3230,29 +3472,30 @@ class KeyValueObject {
3230
3472
  }
3231
3473
 
3232
3474
  function _mergeValues(existingValue, newValue) {
3233
- if (existingValue === undefined) return newValue
3234
-
3475
+ if (existingValue === undefined)
3476
+ return newValue
3477
+
3235
3478
  // Handle arrays by concatenating
3236
3479
  if (Array.isArray(existingValue) && Array.isArray(newValue)) {
3237
3480
  return [...new Set([...existingValue, ...newValue])]
3238
3481
  }
3239
-
3482
+
3240
3483
  // Handle objects by merging
3241
- if (typeof existingValue === 'object' && typeof newValue === 'object' &&
3242
- !Array.isArray(existingValue) && !Array.isArray(newValue)) {
3484
+ if (typeof existingValue === 'object' && typeof newValue === 'object'
3485
+ && !Array.isArray(existingValue) && !Array.isArray(newValue)) {
3243
3486
  return { ...existingValue, ...newValue }
3244
3487
  }
3245
-
3488
+
3246
3489
  // // Handle numbers by adding
3247
3490
  // if (typeof existingValue === 'number' && typeof newValue === 'number') {
3248
3491
  // return existingValue
3249
3492
  // }
3250
-
3493
+
3251
3494
  // // Handle strings by concatenating
3252
3495
  // if (typeof existingValue === 'string' && typeof newValue === 'string') {
3253
3496
  // return existingValue
3254
3497
  // }
3255
-
3498
+
3256
3499
  // Default: use the new value
3257
3500
  return newValue
3258
3501
  }
@@ -3312,29 +3555,30 @@ function metadata_isSame(key1, key2) {
3312
3555
  }
3313
3556
 
3314
3557
  function metadata_mergeValues(existingValue, newValue) {
3315
- if (existingValue === undefined) return newValue
3316
-
3558
+ if (existingValue === undefined)
3559
+ return newValue
3560
+
3317
3561
  // Handle arrays by concatenating
3318
3562
  if (Array.isArray(existingValue) && Array.isArray(newValue)) {
3319
3563
  return [...new Set([...existingValue, ...newValue])]
3320
3564
  }
3321
-
3565
+
3322
3566
  // Handle objects by merging
3323
- if (typeof existingValue === 'object' && typeof newValue === 'object' &&
3324
- !Array.isArray(existingValue) && !Array.isArray(newValue)) {
3567
+ if (typeof existingValue === 'object' && typeof newValue === 'object'
3568
+ && !Array.isArray(existingValue) && !Array.isArray(newValue)) {
3325
3569
  return { ...existingValue, ...newValue }
3326
3570
  }
3327
-
3571
+
3328
3572
  // // Handle numbers by adding
3329
3573
  // if (typeof existingValue === 'number' && typeof newValue === 'number') {
3330
3574
  // return existingValue
3331
3575
  // }
3332
-
3576
+
3333
3577
  // // Handle strings by concatenating
3334
3578
  // if (typeof existingValue === 'string' && typeof newValue === 'string') {
3335
3579
  // return existingValue
3336
3580
  // }
3337
-
3581
+
3338
3582
  // Default: use the new value
3339
3583
  return newValue
3340
3584
  }
@@ -3355,14 +3599,14 @@ class TrackedEntity {
3355
3599
  const timestamp = Date.now()
3356
3600
  this.meta = {
3357
3601
  active: options.meta?.active ?? options.active ?? true,
3358
- created: options.meta?.created ?? (options.created
3359
- ? new Date(options.created).getTime()
3360
- : timestamp),
3602
+ created: options.meta?.created ?? (options.created
3603
+ ? new Date(options.created).getTime()
3604
+ : timestamp),
3361
3605
  creator: options.meta?.creator ?? options.creator ?? '',
3362
3606
  deleted: options.meta?.deleted ?? options.deleted ?? false,
3363
- modified: options.meta?.modified ?? (options.modified
3364
- ? new Date(options.modified).getTime()
3365
- : timestamp),
3607
+ modified: options.meta?.modified ?? (options.modified
3608
+ ? new Date(options.modified).getTime()
3609
+ : timestamp),
3366
3610
  owner: options.meta?.owner ?? options.owner ?? '',
3367
3611
  }
3368
3612
 
@@ -3529,7 +3773,6 @@ class PushEnvelope extends TrackedEntity {
3529
3773
  get isValid() {
3530
3774
  return super.isValid && this.data
3531
3775
  }
3532
-
3533
3776
  }
3534
3777
 
3535
3778
  ;// ./lib/models/pushEnvelope/index.js
@@ -3596,6 +3839,191 @@ class QMeta {
3596
3839
 
3597
3840
 
3598
3841
 
3842
+ ;// ./lib/models/status/actionRecord.js
3843
+
3844
+
3845
+ class ActionRecord {
3846
+ constructor(options) {
3847
+ options = options || {}
3848
+
3849
+ const { _Actor } = options._constructor || {}
3850
+ this._Actor = _Actor
3851
+
3852
+ this._actor = options._actor
3853
+
3854
+ this.actorCode = typeof options === 'number' ? null : (options.actorCode || null)
3855
+ this.timestamp = typeof options === 'number' ? options : (options.timestamp || null)
3856
+ }
3857
+
3858
+ static get _classname() {
3859
+ return 'ActionRecord'
3860
+ }
3861
+ static get _superclass() {
3862
+ return 'ActionRecord'
3863
+ }
3864
+ static dummyData() {
3865
+ return {
3866
+ timestamp: (new Date()).valueOf(),
3867
+ }
3868
+ }
3869
+ static init(options = {}) {
3870
+ return init(this, options)
3871
+ }
3872
+
3873
+ get _classname() {
3874
+ return 'ActionRecord'
3875
+ }
3876
+
3877
+ get _superclass() {
3878
+ return 'ActionRecord'
3879
+ }
3880
+
3881
+ get actor() {
3882
+ return this._Actor && typeof this._Actor.init === 'function' ? this._Actor.init(this._actor) : this._actor
3883
+ }
3884
+
3885
+ get isValid() {
3886
+ return !!this.timestamp
3887
+ }
3888
+
3889
+ update(update) {
3890
+ if (typeof update === 'number') {
3891
+ this.timestamp = update
3892
+ return this
3893
+ }
3894
+ Object.keys(update).forEach((key) => {
3895
+ this[key] = update[key]
3896
+ })
3897
+ return this
3898
+ }
3899
+ }
3900
+
3901
+ ;// ./lib/models/status/status.js
3902
+
3903
+
3904
+
3905
+ const notUpdateAllowedProps = [
3906
+ 'created',
3907
+ // 'statusType'
3908
+ ]
3909
+
3910
+ class Status {
3911
+ constructor(options) {
3912
+ options = options || {}
3913
+
3914
+ const { _ActionRecord } = options._constructor || {}
3915
+ this._ActionRecord = _ActionRecord && (_ActionRecord._superclass === ActionRecord._superclass) ? _ActionRecord : ActionRecord
3916
+
3917
+ this.created = this._ActionRecord.init(options.created || { timestamp: (new Date()).valueOf() })
3918
+ // this.statusType = options.statusType || 'Status'
3919
+ }
3920
+
3921
+ static get _classname() {
3922
+ return 'Status'
3923
+ }
3924
+ static get _superclass() {
3925
+ return 'Status'
3926
+ }
3927
+ static dummyData() {
3928
+ return {}
3929
+ }
3930
+ static init(options = {}) {
3931
+ return init(this, options)
3932
+ }
3933
+ static initFromArray(arr = []) {
3934
+ return initFromArray(this, arr)
3935
+ }
3936
+ static initOnlyValidFromArray(arr = []) {
3937
+ return initOnlyValidFromArray(this, arr)
3938
+ }
3939
+
3940
+ get _classname() {
3941
+ return 'Status'
3942
+ }
3943
+ get _superclass() {
3944
+ return 'Status'
3945
+ }
3946
+ get isCreated() {
3947
+ return this.created?.timestamp !== null
3948
+ }
3949
+ get isValid() {
3950
+ return !!this
3951
+ }
3952
+
3953
+ setValue(t, actorCode, key) {
3954
+ const timestamp = t || Date.now()
3955
+ this[key] = this[key] instanceof this._ActionRecord ? this[key].update({ actorCode, timestamp }) : this._ActionRecord.init({ actorCode, timestamp })
3956
+ return this
3957
+ }
3958
+
3959
+ update(update) {
3960
+ Object.keys(update).forEach((key) => {
3961
+ if (!notUpdateAllowedProps.includes(key)) {
3962
+ this[key] = this[key] instanceof this._ActionRecord ? this[key].update(update[key]) : this._ActionRecord.init(update[key])
3963
+ }
3964
+ })
3965
+ return this
3966
+ }
3967
+ }
3968
+
3969
+ ;// ./lib/models/status/statusDocument.js
3970
+
3971
+
3972
+ class StatusDocument extends Status {
3973
+ constructor(options) {
3974
+ options = options || {}
3975
+ super(options)
3976
+
3977
+ this.archived = this._ActionRecord.init(options.archived)
3978
+ this.completed = this._ActionRecord.init(options.completed)
3979
+ this.discarded = this._ActionRecord.init(options.discarded)
3980
+ this.drafted = this._ActionRecord.init(options.drafted)
3981
+ // this.statusType = 'StatusDocument'
3982
+ }
3983
+
3984
+ static get _classname() {
3985
+ return 'StatusDocument'
3986
+ }
3987
+ get _classname() {
3988
+ return 'StatusDocument'
3989
+ }
3990
+ get isArchived() {
3991
+ return this.created?.timestamp !== null
3992
+ }
3993
+ get isCompleted() {
3994
+ return this.completed?.timestamp !== null
3995
+ }
3996
+ get isDiscarded() {
3997
+ return this.discarded?.timestamp !== null
3998
+ }
3999
+ get isDrafted() {
4000
+ return this.drafted?.timestamp !== null
4001
+ }
4002
+ get isValid() {
4003
+ return super.isValid
4004
+ }
4005
+ setArchived(value, actorCode) {
4006
+ // const timestamp = value || Date.now()
4007
+ // this.archived = this.archived instanceof this._ActionRecord ? this.archived.update({ actorCode, timestamp }) : this._ActionRecord.init({ actorCode, timestamp })
4008
+ // return this
4009
+ return this.setValue(value, actorCode, 'archived')
4010
+ }
4011
+ setCompleted(value, actorCode) {
4012
+ return this.setValue(value, actorCode, 'completed')
4013
+ }
4014
+ setDiscarded(value, actorCode) {
4015
+ return this.setValue(value, actorCode, 'discarded')
4016
+ }
4017
+ setDrafted(value, actorCode) {
4018
+ return this.setValue(value, actorCode, 'drafted')
4019
+ }
4020
+ }
4021
+
4022
+ ;// ./lib/models/status/index.js
4023
+
4024
+
4025
+
4026
+
3599
4027
  ;// ./lib/models/tenantAwareEntity/tenantAwareEntity.js
3600
4028
 
3601
4029
 
@@ -3736,6 +4164,7 @@ function _makeSetCode(fieldName, options) {
3736
4164
 
3737
4165
 
3738
4166
 
4167
+
3739
4168
  ;// ./lib/index.js
3740
4169
 
3741
4170