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