@questwork/q-utilities 0.1.15 → 0.1.17

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.
@@ -84,20 +84,21 @@ __webpack_require__.d(__webpack_exports__, {
84
84
  sanitizeText: () => (/* reexport */ sanitizeText),
85
85
  stringFormatter: () => (/* reexport */ stringFormatter),
86
86
  stringHelper: () => (/* reexport */ stringHelper),
87
+ tenantPlugin: () => (/* reexport */ tenantPlugin),
87
88
  trackingPlugin: () => (/* reexport */ trackingPlugin)
88
89
  });
89
90
 
90
91
  ;// ./lib/helpers/authorize/authorize.js
91
- function authorize({ allowOwner, query = {}, required, user }) {
92
+ function authorize({ allowCoordinator, allowOwner, query = {}, required, user }) {
92
93
  if (!user) {
93
94
  throw new Error('Require login.')
94
95
  }
95
96
  if (!user.permission) {
96
- throw new Error('You do not have any permission.')
97
+ // throw new Error('You do not have any permission.')
97
98
  }
98
- const scopes = user.permission.getScopes(required || {})
99
+ const scopes = user.permission.getScopes(required || {}) || []
99
100
  if (!scopes || scopes.length === 0) {
100
- throw new Error('You are not allowed in this scope.')
101
+ // throw new Error('You are not allowed in this scope.')
101
102
  }
102
103
  if (!scopes.includes('*')) {
103
104
  query.tenantCode = user.tenantCode
@@ -105,8 +106,21 @@ function authorize({ allowOwner, query = {}, required, user }) {
105
106
  if (!scopes.includes('TENANT')) {
106
107
  query.eventShortCode = user.eventShortCode
107
108
  }
108
- if (!scopes.includes('EVENT')) {
109
- query.eventRegistrationCode = user.eventRegistrationCode
109
+ // if (!scopes.includes('EVENT')) {
110
+ // query.eventRegistrationCode = user.eventRegistrationCode
111
+ // }
112
+ if (allowCoordinator) {
113
+ if (query.registrationGroupCode && user.myManagedRegistrationGroupCodes.includes(query.registrationGroupCode)) {
114
+ query.__ALLOW_COORDINATOR = true
115
+ } else {
116
+ if (!scopes.includes('EVENT')) {
117
+ query.eventRegistrationCode = user.eventRegistrationCode
118
+ }
119
+ }
120
+ } else {
121
+ if (!scopes.includes('EVENT')) {
122
+ query.eventRegistrationCode = user.eventRegistrationCode
123
+ }
110
124
  }
111
125
  if (allowOwner) {
112
126
  query.__ALLOW_OWNER = true
@@ -365,6 +379,9 @@ function getValueByKeys_getValueByKeys(keys, data) {
365
379
  return getValueByKeys_getValueByKeys(_keys, _data[firstKey])
366
380
  }
367
381
  if (_data && firstKey) {
382
+ if (_keys.length > 0) {
383
+ return getValueByKeys_getValueByKeys(_keys, _data[firstKey])
384
+ }
368
385
  return _data[firstKey]
369
386
  }
370
387
  return _data
@@ -1418,19 +1435,6 @@ function concatStringByArray(arrTemplate, data) {
1418
1435
  return arrTemplate.reduce((acc, item) => {
1419
1436
  const { type, value = '', restriction, template, format, showMinutes } = item
1420
1437
  switch (type) {
1421
- case('label'): {
1422
- if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1423
- acc += (value.toString())
1424
- }
1425
- break
1426
- }
1427
- case('value'): {
1428
- if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1429
- const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1430
- acc += (_value.toString())
1431
- }
1432
- break
1433
- }
1434
1438
  case('array'): {
1435
1439
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1436
1440
  const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || []
@@ -1440,6 +1444,13 @@ function concatStringByArray(arrTemplate, data) {
1440
1444
  }
1441
1445
  break
1442
1446
  }
1447
+ case ('date'): {
1448
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1449
+ const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1450
+ acc += (formatDate(_value, format).toString())
1451
+ }
1452
+ break
1453
+ }
1443
1454
  case('ellipsis'): {
1444
1455
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1445
1456
  const { maxLength } = item
@@ -1452,10 +1463,15 @@ function concatStringByArray(arrTemplate, data) {
1452
1463
  }
1453
1464
  break
1454
1465
  }
1455
- case ('date'): {
1466
+ case('group'): {
1456
1467
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1457
- const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1458
- acc += (formatDate(_value, format).toString())
1468
+ return concatStringByArray(value, data)
1469
+ }
1470
+ break
1471
+ }
1472
+ case('label'): {
1473
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1474
+ acc += (value.toString())
1459
1475
  }
1460
1476
  break
1461
1477
  }
@@ -1466,6 +1482,13 @@ function concatStringByArray(arrTemplate, data) {
1466
1482
  }
1467
1483
  break
1468
1484
  }
1485
+ case('value'): {
1486
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1487
+ const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1488
+ acc += (_value.toString())
1489
+ }
1490
+ break
1491
+ }
1469
1492
  }
1470
1493
  return acc
1471
1494
  }, '')
@@ -1486,7 +1509,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1486
1509
  if (!string) {
1487
1510
  return ''
1488
1511
  }
1489
- let _getValueByKeys = typeof getValueByKeys !== 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1512
+ let _getValueByKeys = typeof getValueByKeys === 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1490
1513
  const reg = new RegExp(patternMatch, 'g')
1491
1514
  return string.replace(reg, (match, key) => {
1492
1515
  const result = _getValueByKeys({ keys: key.split('.'), obj: value })
@@ -1507,7 +1530,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1507
1530
 
1508
1531
  ;// ./lib/helpers/escapeRegex/escapeRegex.js
1509
1532
  function escapeRegex(string) {
1510
- return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1533
+ return String(string).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1511
1534
  }
1512
1535
 
1513
1536
  ;// ./lib/helpers/escapeRegex/index.js
@@ -1943,19 +1966,19 @@ class Repo {
1943
1966
  })
1944
1967
  }
1945
1968
 
1946
- saveAll({ docs, systemLog }) {
1969
+ saveAll({ config = {}, docs, systemLog }) {
1947
1970
  let isNew
1948
1971
  const log = _makeLog({
1949
1972
  systemLog,
1950
1973
  label: 'REPO_WRITE',
1951
1974
  message: `fn ${this._classname}.prototype.saveAll`,
1952
- input: [{ docs: [...docs], systemLog: { ...systemLog } }]
1975
+ input: [{ config, docs: [...docs], systemLog: { ...systemLog } }]
1953
1976
  })
1954
1977
  const promise = typeof this.model.saveAll === 'function'
1955
- ? this.model.saveAll({ docs })
1978
+ ? this.model.saveAll({ config, docs })
1956
1979
  : Promise.all(docs.map(async (doc) => {
1957
1980
  if (doc) {
1958
- const result = await this.saveOne({ doc })
1981
+ const result = await this.saveOne({ config, doc })
1959
1982
  isNew = result.isNew
1960
1983
  const _data = result._data || result.data
1961
1984
  return _data[0]
@@ -1977,15 +2000,19 @@ class Repo {
1977
2000
  })
1978
2001
  }
1979
2002
 
1980
- saveOne({ doc, systemLog }) {
2003
+ saveOne({ config = {}, doc, systemLog }) {
1981
2004
  const log = _makeLog({
1982
2005
  systemLog,
1983
2006
  label: 'REPO_WRITE',
1984
2007
  message: `fn ${this._classname}.prototype.saveOne`,
1985
- input: [{ doc: { ...doc }, systemLog: { ...systemLog } }]
2008
+ input: [{ config, doc: { ...doc }, systemLog: { ...systemLog } }]
1986
2009
  })
2010
+ const saveOptions = {
2011
+ ...this.saveOptions,
2012
+ ...config,
2013
+ }
1987
2014
  return new Promise((resolve, reject) => {
1988
- this.model.saveOne(doc, this.saveOptions, (err, result) => {
2015
+ this.model.saveOne(doc, saveOptions, (err, result) => {
1989
2016
  if (err) {
1990
2017
  log({ level: 'warn', output: err.toString() })
1991
2018
  reject(err)
@@ -2127,11 +2154,11 @@ class Service {
2127
2154
  return this.initFromArray(arr).filter((i) => i)
2128
2155
  }
2129
2156
 
2130
- async saveAll({ docs = [], config = {}, systemLog } = {}) {
2157
+ async saveAll({ config = {}, docs = [], systemLog } = {}) {
2131
2158
  const copies = docs.map((doc) => {
2132
2159
  return config.skipInit ? doc : this.init(doc)
2133
2160
  })
2134
- const result = await this.repo.saveAll({ docs: copies, systemLog })
2161
+ const result = await this.repo.saveAll({ config, docs: copies, systemLog })
2135
2162
  return makeApiResponse({
2136
2163
  repo: this.repo,
2137
2164
  result
@@ -2139,10 +2166,10 @@ class Service {
2139
2166
  }
2140
2167
 
2141
2168
  // set skipInit to true if we want to use POST for query
2142
- async saveOne({ doc = {}, config = {}, systemLog } = {}) {
2169
+ async saveOne({ config = {}, doc = {}, systemLog } = {}) {
2143
2170
  const copy = config.skipInit ? doc : this.init(doc)
2144
2171
  if (copy) {
2145
- const result = await this.repo.saveOne({ doc: copy, systemLog })
2172
+ const result = await this.repo.saveOne({ config, doc: copy, systemLog })
2146
2173
  return makeApiResponse({
2147
2174
  repo: this.repo,
2148
2175
  result
@@ -2415,6 +2442,7 @@ function sanitizeText(input, options = {}) {
2415
2442
  normalizeWhitespace = true,
2416
2443
  removeNewlines = false,
2417
2444
  trim = true,
2445
+ preserveBasicWhitespace = true, // new option to keep tabs, newlines if removeNewlines=false
2418
2446
  } = options
2419
2447
 
2420
2448
  if (typeof input !== 'string') {
@@ -2423,8 +2451,14 @@ function sanitizeText(input, options = {}) {
2423
2451
 
2424
2452
  let result = input
2425
2453
 
2426
- // Phase 1: Remove hidden/control characters
2427
- result = result.replace(/[\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2454
+ // Phase 1: Remove all control characters except basic whitespace if requested
2455
+ if (preserveBasicWhitespace && !removeNewlines) {
2456
+ // Keep tab (\t), newline (\n), carriage return (\r)
2457
+ result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2458
+ } else {
2459
+ // Remove all control characters including basic whitespace
2460
+ result = result.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2461
+ }
2428
2462
 
2429
2463
  // Phase 2: Handle whitespace transformations
2430
2464
  if (removeNewlines) {
@@ -2612,6 +2646,19 @@ function trackingPlugin(schema, options) {
2612
2646
  next()
2613
2647
  })
2614
2648
 
2649
+ // Add core indexes
2650
+ schema.index({
2651
+ 'meta.active': 1,
2652
+ 'meta.deleted': 1
2653
+ }, {
2654
+ name: 'tracking_status_index',
2655
+ background: true,
2656
+ partialFilterExpression: {
2657
+ 'meta.active': true,
2658
+ 'meta.deleted': false
2659
+ }
2660
+ })
2661
+
2615
2662
  // Optional: Add helper methods
2616
2663
  // schema.methods.touch = function(userId) {
2617
2664
  // this.meta.updatedAt = new Date()
@@ -2619,6 +2666,57 @@ function trackingPlugin(schema, options) {
2619
2666
  // }
2620
2667
  }
2621
2668
 
2669
+ ;// ./lib/helpers/tenantPlugin/tenantPlugin.js
2670
+
2671
+
2672
+ function tenantPlugin(schema, options) {
2673
+ // Apply tracking plugin first if not already present
2674
+ if (!schema.path('meta')) {
2675
+ trackingPlugin(schema, options)
2676
+ }
2677
+
2678
+ // Add tenant-specific fields
2679
+ schema.add({
2680
+ metadata: [{ type: Object }], // Instead of Schema.Types.Mixed
2681
+ remarks: [{ type: Object }],
2682
+ tenantCode: { type: String, required: true }
2683
+ })
2684
+
2685
+ // Add core indexes
2686
+ schema.index({
2687
+ 'tenantCode': 1
2688
+ }, {
2689
+ name: 'tenant_core_index',
2690
+ background: true
2691
+ })
2692
+
2693
+ // 1. ENHANCE EXISTING TRACKING INDEXES
2694
+ const existingIndexes = schema.indexes()
2695
+
2696
+ // Check if tracking_status_index exists
2697
+ const hasTenantStatusIndex = existingIndexes.some(idx =>
2698
+ idx.name === 'tenant_status_index' // Check by name for reliability
2699
+ )
2700
+
2701
+ if (!hasTenantStatusIndex) {
2702
+ schema.index({
2703
+ 'tenantCode': 1, // Unique field first
2704
+ _type: 1, // Low-cardinality field last
2705
+ }, {
2706
+ name: 'tenant_status_index',
2707
+ background: true,
2708
+ partialFilterExpression: {
2709
+ '_type': 'Tenant',
2710
+ 'meta.active': true,
2711
+ 'meta.deleted': false
2712
+ }
2713
+ })
2714
+ }
2715
+ }
2716
+
2717
+ ;// ./lib/helpers/tenantPlugin/index.js
2718
+
2719
+
2622
2720
  ;// ./lib/helpers/trackingPlugin/index.js
2623
2721
 
2624
2722
 
@@ -2645,6 +2743,7 @@ function trackingPlugin(schema, options) {
2645
2743
 
2646
2744
 
2647
2745
 
2746
+
2648
2747
 
2649
2748
 
2650
2749
  ;// ./lib/models/apiResponse/index.js
@@ -2986,7 +3085,10 @@ class KeyValueObject {
2986
3085
  }, [])
2987
3086
  }
2988
3087
  static sameKey(item, key) {
2989
- return _isSame(item.key, key)
3088
+ if (item) {
3089
+ return _isSame(item.key, key)
3090
+ }
3091
+ return false
2990
3092
  }
2991
3093
  static toObject(arr = []) {
2992
3094
  if (Array.isArray(arr)) {
@@ -3240,17 +3342,19 @@ class TrackedEntity {
3240
3342
  constructor(options = {}) {
3241
3343
  options = options || {}
3242
3344
  const timestamp = Date.now()
3243
- const _tracking = {
3244
- active: options.active ?? true,
3245
- created: options.created ?? timestamp,
3246
- creator: options.creator ?? '',
3247
- deleted: options.deleted ?? false,
3248
- modified: options.modified ?? timestamp,
3249
- owner: options.owner ?? '',
3345
+ this.meta = {
3346
+ active: options.meta?.active ?? options.active ?? true,
3347
+ created: options.meta?.created ?? (options.created
3348
+ ? new Date(options.created).getTime()
3349
+ : timestamp),
3350
+ creator: options.meta?.creator ?? options.creator ?? '',
3351
+ deleted: options.meta?.deleted ?? options.deleted ?? false,
3352
+ modified: options.meta?.modified ?? (options.modified
3353
+ ? new Date(options.modified).getTime()
3354
+ : timestamp),
3355
+ owner: options.meta?.owner ?? options.owner ?? '',
3250
3356
  }
3251
3357
 
3252
- this.meta = { ..._tracking, ...options.meta }
3253
-
3254
3358
  // if (trackFlat) {
3255
3359
  // Object.assign(this, _tracking)
3256
3360
  // } else {
@@ -1,15 +1,15 @@
1
1
 
2
2
  ;// ./lib/helpers/authorize/authorize.js
3
- function authorize({ allowOwner, query = {}, required, user }) {
3
+ function authorize({ allowCoordinator, allowOwner, query = {}, required, user }) {
4
4
  if (!user) {
5
5
  throw new Error('Require login.')
6
6
  }
7
7
  if (!user.permission) {
8
- throw new Error('You do not have any permission.')
8
+ // throw new Error('You do not have any permission.')
9
9
  }
10
- const scopes = user.permission.getScopes(required || {})
10
+ const scopes = user.permission.getScopes(required || {}) || []
11
11
  if (!scopes || scopes.length === 0) {
12
- throw new Error('You are not allowed in this scope.')
12
+ // throw new Error('You are not allowed in this scope.')
13
13
  }
14
14
  if (!scopes.includes('*')) {
15
15
  query.tenantCode = user.tenantCode
@@ -17,8 +17,21 @@ function authorize({ allowOwner, query = {}, required, user }) {
17
17
  if (!scopes.includes('TENANT')) {
18
18
  query.eventShortCode = user.eventShortCode
19
19
  }
20
- if (!scopes.includes('EVENT')) {
21
- query.eventRegistrationCode = user.eventRegistrationCode
20
+ // if (!scopes.includes('EVENT')) {
21
+ // query.eventRegistrationCode = user.eventRegistrationCode
22
+ // }
23
+ if (allowCoordinator) {
24
+ if (query.registrationGroupCode && user.myManagedRegistrationGroupCodes.includes(query.registrationGroupCode)) {
25
+ query.__ALLOW_COORDINATOR = true
26
+ } else {
27
+ if (!scopes.includes('EVENT')) {
28
+ query.eventRegistrationCode = user.eventRegistrationCode
29
+ }
30
+ }
31
+ } else {
32
+ if (!scopes.includes('EVENT')) {
33
+ query.eventRegistrationCode = user.eventRegistrationCode
34
+ }
22
35
  }
23
36
  if (allowOwner) {
24
37
  query.__ALLOW_OWNER = true
@@ -277,6 +290,9 @@ function getValueByKeys_getValueByKeys(keys, data) {
277
290
  return getValueByKeys_getValueByKeys(_keys, _data[firstKey])
278
291
  }
279
292
  if (_data && firstKey) {
293
+ if (_keys.length > 0) {
294
+ return getValueByKeys_getValueByKeys(_keys, _data[firstKey])
295
+ }
280
296
  return _data[firstKey]
281
297
  }
282
298
  return _data
@@ -1330,19 +1346,6 @@ function concatStringByArray(arrTemplate, data) {
1330
1346
  return arrTemplate.reduce((acc, item) => {
1331
1347
  const { type, value = '', restriction, template, format, showMinutes } = item
1332
1348
  switch (type) {
1333
- case('label'): {
1334
- if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1335
- acc += (value.toString())
1336
- }
1337
- break
1338
- }
1339
- case('value'): {
1340
- if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1341
- const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1342
- acc += (_value.toString())
1343
- }
1344
- break
1345
- }
1346
1349
  case('array'): {
1347
1350
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1348
1351
  const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || []
@@ -1352,6 +1355,13 @@ function concatStringByArray(arrTemplate, data) {
1352
1355
  }
1353
1356
  break
1354
1357
  }
1358
+ case ('date'): {
1359
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1360
+ const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1361
+ acc += (formatDate(_value, format).toString())
1362
+ }
1363
+ break
1364
+ }
1355
1365
  case('ellipsis'): {
1356
1366
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1357
1367
  const { maxLength } = item
@@ -1364,10 +1374,15 @@ function concatStringByArray(arrTemplate, data) {
1364
1374
  }
1365
1375
  break
1366
1376
  }
1367
- case ('date'): {
1377
+ case('group'): {
1368
1378
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1369
- const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1370
- acc += (formatDate(_value, format).toString())
1379
+ return concatStringByArray(value, data)
1380
+ }
1381
+ break
1382
+ }
1383
+ case('label'): {
1384
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1385
+ acc += (value.toString())
1371
1386
  }
1372
1387
  break
1373
1388
  }
@@ -1378,6 +1393,13 @@ function concatStringByArray(arrTemplate, data) {
1378
1393
  }
1379
1394
  break
1380
1395
  }
1396
+ case('value'): {
1397
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1398
+ const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1399
+ acc += (_value.toString())
1400
+ }
1401
+ break
1402
+ }
1381
1403
  }
1382
1404
  return acc
1383
1405
  }, '')
@@ -1398,7 +1420,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1398
1420
  if (!string) {
1399
1421
  return ''
1400
1422
  }
1401
- let _getValueByKeys = typeof getValueByKeys !== 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1423
+ let _getValueByKeys = typeof getValueByKeys === 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1402
1424
  const reg = new RegExp(patternMatch, 'g')
1403
1425
  return string.replace(reg, (match, key) => {
1404
1426
  const result = _getValueByKeys({ keys: key.split('.'), obj: value })
@@ -1419,7 +1441,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1419
1441
 
1420
1442
  ;// ./lib/helpers/escapeRegex/escapeRegex.js
1421
1443
  function escapeRegex(string) {
1422
- return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1444
+ return String(string).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1423
1445
  }
1424
1446
 
1425
1447
  ;// ./lib/helpers/escapeRegex/index.js
@@ -1855,19 +1877,19 @@ class Repo {
1855
1877
  })
1856
1878
  }
1857
1879
 
1858
- saveAll({ docs, systemLog }) {
1880
+ saveAll({ config = {}, docs, systemLog }) {
1859
1881
  let isNew
1860
1882
  const log = _makeLog({
1861
1883
  systemLog,
1862
1884
  label: 'REPO_WRITE',
1863
1885
  message: `fn ${this._classname}.prototype.saveAll`,
1864
- input: [{ docs: [...docs], systemLog: { ...systemLog } }]
1886
+ input: [{ config, docs: [...docs], systemLog: { ...systemLog } }]
1865
1887
  })
1866
1888
  const promise = typeof this.model.saveAll === 'function'
1867
- ? this.model.saveAll({ docs })
1889
+ ? this.model.saveAll({ config, docs })
1868
1890
  : Promise.all(docs.map(async (doc) => {
1869
1891
  if (doc) {
1870
- const result = await this.saveOne({ doc })
1892
+ const result = await this.saveOne({ config, doc })
1871
1893
  isNew = result.isNew
1872
1894
  const _data = result._data || result.data
1873
1895
  return _data[0]
@@ -1889,15 +1911,19 @@ class Repo {
1889
1911
  })
1890
1912
  }
1891
1913
 
1892
- saveOne({ doc, systemLog }) {
1914
+ saveOne({ config = {}, doc, systemLog }) {
1893
1915
  const log = _makeLog({
1894
1916
  systemLog,
1895
1917
  label: 'REPO_WRITE',
1896
1918
  message: `fn ${this._classname}.prototype.saveOne`,
1897
- input: [{ doc: { ...doc }, systemLog: { ...systemLog } }]
1919
+ input: [{ config, doc: { ...doc }, systemLog: { ...systemLog } }]
1898
1920
  })
1921
+ const saveOptions = {
1922
+ ...this.saveOptions,
1923
+ ...config,
1924
+ }
1899
1925
  return new Promise((resolve, reject) => {
1900
- this.model.saveOne(doc, this.saveOptions, (err, result) => {
1926
+ this.model.saveOne(doc, saveOptions, (err, result) => {
1901
1927
  if (err) {
1902
1928
  log({ level: 'warn', output: err.toString() })
1903
1929
  reject(err)
@@ -2039,11 +2065,11 @@ class Service {
2039
2065
  return this.initFromArray(arr).filter((i) => i)
2040
2066
  }
2041
2067
 
2042
- async saveAll({ docs = [], config = {}, systemLog } = {}) {
2068
+ async saveAll({ config = {}, docs = [], systemLog } = {}) {
2043
2069
  const copies = docs.map((doc) => {
2044
2070
  return config.skipInit ? doc : this.init(doc)
2045
2071
  })
2046
- const result = await this.repo.saveAll({ docs: copies, systemLog })
2072
+ const result = await this.repo.saveAll({ config, docs: copies, systemLog })
2047
2073
  return makeApiResponse({
2048
2074
  repo: this.repo,
2049
2075
  result
@@ -2051,10 +2077,10 @@ class Service {
2051
2077
  }
2052
2078
 
2053
2079
  // set skipInit to true if we want to use POST for query
2054
- async saveOne({ doc = {}, config = {}, systemLog } = {}) {
2080
+ async saveOne({ config = {}, doc = {}, systemLog } = {}) {
2055
2081
  const copy = config.skipInit ? doc : this.init(doc)
2056
2082
  if (copy) {
2057
- const result = await this.repo.saveOne({ doc: copy, systemLog })
2083
+ const result = await this.repo.saveOne({ config, doc: copy, systemLog })
2058
2084
  return makeApiResponse({
2059
2085
  repo: this.repo,
2060
2086
  result
@@ -2327,6 +2353,7 @@ function sanitizeText(input, options = {}) {
2327
2353
  normalizeWhitespace = true,
2328
2354
  removeNewlines = false,
2329
2355
  trim = true,
2356
+ preserveBasicWhitespace = true, // new option to keep tabs, newlines if removeNewlines=false
2330
2357
  } = options
2331
2358
 
2332
2359
  if (typeof input !== 'string') {
@@ -2335,8 +2362,14 @@ function sanitizeText(input, options = {}) {
2335
2362
 
2336
2363
  let result = input
2337
2364
 
2338
- // Phase 1: Remove hidden/control characters
2339
- result = result.replace(/[\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2365
+ // Phase 1: Remove all control characters except basic whitespace if requested
2366
+ if (preserveBasicWhitespace && !removeNewlines) {
2367
+ // Keep tab (\t), newline (\n), carriage return (\r)
2368
+ result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2369
+ } else {
2370
+ // Remove all control characters including basic whitespace
2371
+ result = result.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2372
+ }
2340
2373
 
2341
2374
  // Phase 2: Handle whitespace transformations
2342
2375
  if (removeNewlines) {
@@ -2524,6 +2557,19 @@ function trackingPlugin(schema, options) {
2524
2557
  next()
2525
2558
  })
2526
2559
 
2560
+ // Add core indexes
2561
+ schema.index({
2562
+ 'meta.active': 1,
2563
+ 'meta.deleted': 1
2564
+ }, {
2565
+ name: 'tracking_status_index',
2566
+ background: true,
2567
+ partialFilterExpression: {
2568
+ 'meta.active': true,
2569
+ 'meta.deleted': false
2570
+ }
2571
+ })
2572
+
2527
2573
  // Optional: Add helper methods
2528
2574
  // schema.methods.touch = function(userId) {
2529
2575
  // this.meta.updatedAt = new Date()
@@ -2531,6 +2577,57 @@ function trackingPlugin(schema, options) {
2531
2577
  // }
2532
2578
  }
2533
2579
 
2580
+ ;// ./lib/helpers/tenantPlugin/tenantPlugin.js
2581
+
2582
+
2583
+ function tenantPlugin(schema, options) {
2584
+ // Apply tracking plugin first if not already present
2585
+ if (!schema.path('meta')) {
2586
+ trackingPlugin(schema, options)
2587
+ }
2588
+
2589
+ // Add tenant-specific fields
2590
+ schema.add({
2591
+ metadata: [{ type: Object }], // Instead of Schema.Types.Mixed
2592
+ remarks: [{ type: Object }],
2593
+ tenantCode: { type: String, required: true }
2594
+ })
2595
+
2596
+ // Add core indexes
2597
+ schema.index({
2598
+ 'tenantCode': 1
2599
+ }, {
2600
+ name: 'tenant_core_index',
2601
+ background: true
2602
+ })
2603
+
2604
+ // 1. ENHANCE EXISTING TRACKING INDEXES
2605
+ const existingIndexes = schema.indexes()
2606
+
2607
+ // Check if tracking_status_index exists
2608
+ const hasTenantStatusIndex = existingIndexes.some(idx =>
2609
+ idx.name === 'tenant_status_index' // Check by name for reliability
2610
+ )
2611
+
2612
+ if (!hasTenantStatusIndex) {
2613
+ schema.index({
2614
+ 'tenantCode': 1, // Unique field first
2615
+ _type: 1, // Low-cardinality field last
2616
+ }, {
2617
+ name: 'tenant_status_index',
2618
+ background: true,
2619
+ partialFilterExpression: {
2620
+ '_type': 'Tenant',
2621
+ 'meta.active': true,
2622
+ 'meta.deleted': false
2623
+ }
2624
+ })
2625
+ }
2626
+ }
2627
+
2628
+ ;// ./lib/helpers/tenantPlugin/index.js
2629
+
2630
+
2534
2631
  ;// ./lib/helpers/trackingPlugin/index.js
2535
2632
 
2536
2633
 
@@ -2557,6 +2654,7 @@ function trackingPlugin(schema, options) {
2557
2654
 
2558
2655
 
2559
2656
 
2657
+
2560
2658
 
2561
2659
 
2562
2660
  ;// ./lib/models/apiResponse/index.js
@@ -2898,7 +2996,10 @@ class KeyValueObject {
2898
2996
  }, [])
2899
2997
  }
2900
2998
  static sameKey(item, key) {
2901
- return _isSame(item.key, key)
2999
+ if (item) {
3000
+ return _isSame(item.key, key)
3001
+ }
3002
+ return false
2902
3003
  }
2903
3004
  static toObject(arr = []) {
2904
3005
  if (Array.isArray(arr)) {
@@ -3152,17 +3253,19 @@ class TrackedEntity {
3152
3253
  constructor(options = {}) {
3153
3254
  options = options || {}
3154
3255
  const timestamp = Date.now()
3155
- const _tracking = {
3156
- active: options.active ?? true,
3157
- created: options.created ?? timestamp,
3158
- creator: options.creator ?? '',
3159
- deleted: options.deleted ?? false,
3160
- modified: options.modified ?? timestamp,
3161
- owner: options.owner ?? '',
3256
+ this.meta = {
3257
+ active: options.meta?.active ?? options.active ?? true,
3258
+ created: options.meta?.created ?? (options.created
3259
+ ? new Date(options.created).getTime()
3260
+ : timestamp),
3261
+ creator: options.meta?.creator ?? options.creator ?? '',
3262
+ deleted: options.meta?.deleted ?? options.deleted ?? false,
3263
+ modified: options.meta?.modified ?? (options.modified
3264
+ ? new Date(options.modified).getTime()
3265
+ : timestamp),
3266
+ owner: options.meta?.owner ?? options.owner ?? '',
3162
3267
  }
3163
3268
 
3164
- this.meta = { ..._tracking, ...options.meta }
3165
-
3166
3269
  // if (trackFlat) {
3167
3270
  // Object.assign(this, _tracking)
3168
3271
  // } else {
@@ -3431,4 +3534,4 @@ function _makeSetCode(fieldName, options) {
3431
3534
  ;// ./index.js
3432
3535
 
3433
3536
 
3434
- export { ApiResponse, AwsStsS3Client, KeyValueObject, Metadata, QMeta, Repo, Service, TemplateCompiler, TenantAwareEntity, TrackedEntity, UniqueKeyGenerator, authorize, changeCreatorOwner, concatStringByArray, convertString, escapeRegex, expressHelper, extractEmails, formatDate, generalPost, getValidation, getValueByKeys_getValueByKeys as getValueByKeys, init, initFromArray, initOnlyValidFromArray, makeApiResponse, makeService, mergeArraysByKey, objectHelper, pReduce, padZeros, replacePlaceholders, sanitizeText, stringFormatter, stringHelper, trackingPlugin };
3537
+ export { ApiResponse, AwsStsS3Client, KeyValueObject, Metadata, QMeta, Repo, Service, TemplateCompiler, TenantAwareEntity, TrackedEntity, UniqueKeyGenerator, authorize, changeCreatorOwner, concatStringByArray, convertString, escapeRegex, expressHelper, extractEmails, formatDate, generalPost, getValidation, getValueByKeys_getValueByKeys as getValueByKeys, init, initFromArray, initOnlyValidFromArray, makeApiResponse, makeService, mergeArraysByKey, objectHelper, pReduce, padZeros, replacePlaceholders, sanitizeText, stringFormatter, stringHelper, tenantPlugin, trackingPlugin };
@@ -83,20 +83,21 @@ __webpack_require__.d(__webpack_exports__, {
83
83
  sanitizeText: () => (/* reexport */ sanitizeText),
84
84
  stringFormatter: () => (/* reexport */ stringFormatter),
85
85
  stringHelper: () => (/* reexport */ stringHelper),
86
+ tenantPlugin: () => (/* reexport */ tenantPlugin),
86
87
  trackingPlugin: () => (/* reexport */ trackingPlugin)
87
88
  });
88
89
 
89
90
  ;// ./lib/helpers/authorize/authorize.js
90
- function authorize({ allowOwner, query = {}, required, user }) {
91
+ function authorize({ allowCoordinator, allowOwner, query = {}, required, user }) {
91
92
  if (!user) {
92
93
  throw new Error('Require login.')
93
94
  }
94
95
  if (!user.permission) {
95
- throw new Error('You do not have any permission.')
96
+ // throw new Error('You do not have any permission.')
96
97
  }
97
- const scopes = user.permission.getScopes(required || {})
98
+ const scopes = user.permission.getScopes(required || {}) || []
98
99
  if (!scopes || scopes.length === 0) {
99
- throw new Error('You are not allowed in this scope.')
100
+ // throw new Error('You are not allowed in this scope.')
100
101
  }
101
102
  if (!scopes.includes('*')) {
102
103
  query.tenantCode = user.tenantCode
@@ -104,8 +105,21 @@ function authorize({ allowOwner, query = {}, required, user }) {
104
105
  if (!scopes.includes('TENANT')) {
105
106
  query.eventShortCode = user.eventShortCode
106
107
  }
107
- if (!scopes.includes('EVENT')) {
108
- query.eventRegistrationCode = user.eventRegistrationCode
108
+ // if (!scopes.includes('EVENT')) {
109
+ // query.eventRegistrationCode = user.eventRegistrationCode
110
+ // }
111
+ if (allowCoordinator) {
112
+ if (query.registrationGroupCode && user.myManagedRegistrationGroupCodes.includes(query.registrationGroupCode)) {
113
+ query.__ALLOW_COORDINATOR = true
114
+ } else {
115
+ if (!scopes.includes('EVENT')) {
116
+ query.eventRegistrationCode = user.eventRegistrationCode
117
+ }
118
+ }
119
+ } else {
120
+ if (!scopes.includes('EVENT')) {
121
+ query.eventRegistrationCode = user.eventRegistrationCode
122
+ }
109
123
  }
110
124
  if (allowOwner) {
111
125
  query.__ALLOW_OWNER = true
@@ -364,6 +378,9 @@ function getValueByKeys_getValueByKeys(keys, data) {
364
378
  return getValueByKeys_getValueByKeys(_keys, _data[firstKey])
365
379
  }
366
380
  if (_data && firstKey) {
381
+ if (_keys.length > 0) {
382
+ return getValueByKeys_getValueByKeys(_keys, _data[firstKey])
383
+ }
367
384
  return _data[firstKey]
368
385
  }
369
386
  return _data
@@ -1417,19 +1434,6 @@ function concatStringByArray(arrTemplate, data) {
1417
1434
  return arrTemplate.reduce((acc, item) => {
1418
1435
  const { type, value = '', restriction, template, format, showMinutes } = item
1419
1436
  switch (type) {
1420
- case('label'): {
1421
- if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1422
- acc += (value.toString())
1423
- }
1424
- break
1425
- }
1426
- case('value'): {
1427
- if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1428
- const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1429
- acc += (_value.toString())
1430
- }
1431
- break
1432
- }
1433
1437
  case('array'): {
1434
1438
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1435
1439
  const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || []
@@ -1439,6 +1443,13 @@ function concatStringByArray(arrTemplate, data) {
1439
1443
  }
1440
1444
  break
1441
1445
  }
1446
+ case ('date'): {
1447
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1448
+ const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1449
+ acc += (formatDate(_value, format).toString())
1450
+ }
1451
+ break
1452
+ }
1442
1453
  case('ellipsis'): {
1443
1454
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1444
1455
  const { maxLength } = item
@@ -1451,10 +1462,15 @@ function concatStringByArray(arrTemplate, data) {
1451
1462
  }
1452
1463
  break
1453
1464
  }
1454
- case ('date'): {
1465
+ case('group'): {
1455
1466
  if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1456
- const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1457
- acc += (formatDate(_value, format).toString())
1467
+ return concatStringByArray(value, data)
1468
+ }
1469
+ break
1470
+ }
1471
+ case('label'): {
1472
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1473
+ acc += (value.toString())
1458
1474
  }
1459
1475
  break
1460
1476
  }
@@ -1465,6 +1481,13 @@ function concatStringByArray(arrTemplate, data) {
1465
1481
  }
1466
1482
  break
1467
1483
  }
1484
+ case('value'): {
1485
+ if (getValidation(restriction, data, getValueByKeys_getValueByKeys)) {
1486
+ const _value = getValueByKeys_getValueByKeys(value.split('.'), data) || ''
1487
+ acc += (_value.toString())
1488
+ }
1489
+ break
1490
+ }
1468
1491
  }
1469
1492
  return acc
1470
1493
  }, '')
@@ -1485,7 +1508,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1485
1508
  if (!string) {
1486
1509
  return ''
1487
1510
  }
1488
- let _getValueByKeys = typeof getValueByKeys !== 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1511
+ let _getValueByKeys = typeof getValueByKeys === 'function' ? getValueByKeys : getValueByKeys_getValueByKeys
1489
1512
  const reg = new RegExp(patternMatch, 'g')
1490
1513
  return string.replace(reg, (match, key) => {
1491
1514
  const result = _getValueByKeys({ keys: key.split('.'), obj: value })
@@ -1506,7 +1529,7 @@ function convertString(string, patternMatch = /\$\{(.+?)\}/g, value, getValueByK
1506
1529
 
1507
1530
  ;// ./lib/helpers/escapeRegex/escapeRegex.js
1508
1531
  function escapeRegex(string) {
1509
- return string.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1532
+ return String(string).replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
1510
1533
  }
1511
1534
 
1512
1535
  ;// ./lib/helpers/escapeRegex/index.js
@@ -1942,19 +1965,19 @@ class Repo {
1942
1965
  })
1943
1966
  }
1944
1967
 
1945
- saveAll({ docs, systemLog }) {
1968
+ saveAll({ config = {}, docs, systemLog }) {
1946
1969
  let isNew
1947
1970
  const log = _makeLog({
1948
1971
  systemLog,
1949
1972
  label: 'REPO_WRITE',
1950
1973
  message: `fn ${this._classname}.prototype.saveAll`,
1951
- input: [{ docs: [...docs], systemLog: { ...systemLog } }]
1974
+ input: [{ config, docs: [...docs], systemLog: { ...systemLog } }]
1952
1975
  })
1953
1976
  const promise = typeof this.model.saveAll === 'function'
1954
- ? this.model.saveAll({ docs })
1977
+ ? this.model.saveAll({ config, docs })
1955
1978
  : Promise.all(docs.map(async (doc) => {
1956
1979
  if (doc) {
1957
- const result = await this.saveOne({ doc })
1980
+ const result = await this.saveOne({ config, doc })
1958
1981
  isNew = result.isNew
1959
1982
  const _data = result._data || result.data
1960
1983
  return _data[0]
@@ -1976,15 +1999,19 @@ class Repo {
1976
1999
  })
1977
2000
  }
1978
2001
 
1979
- saveOne({ doc, systemLog }) {
2002
+ saveOne({ config = {}, doc, systemLog }) {
1980
2003
  const log = _makeLog({
1981
2004
  systemLog,
1982
2005
  label: 'REPO_WRITE',
1983
2006
  message: `fn ${this._classname}.prototype.saveOne`,
1984
- input: [{ doc: { ...doc }, systemLog: { ...systemLog } }]
2007
+ input: [{ config, doc: { ...doc }, systemLog: { ...systemLog } }]
1985
2008
  })
2009
+ const saveOptions = {
2010
+ ...this.saveOptions,
2011
+ ...config,
2012
+ }
1986
2013
  return new Promise((resolve, reject) => {
1987
- this.model.saveOne(doc, this.saveOptions, (err, result) => {
2014
+ this.model.saveOne(doc, saveOptions, (err, result) => {
1988
2015
  if (err) {
1989
2016
  log({ level: 'warn', output: err.toString() })
1990
2017
  reject(err)
@@ -2126,11 +2153,11 @@ class Service {
2126
2153
  return this.initFromArray(arr).filter((i) => i)
2127
2154
  }
2128
2155
 
2129
- async saveAll({ docs = [], config = {}, systemLog } = {}) {
2156
+ async saveAll({ config = {}, docs = [], systemLog } = {}) {
2130
2157
  const copies = docs.map((doc) => {
2131
2158
  return config.skipInit ? doc : this.init(doc)
2132
2159
  })
2133
- const result = await this.repo.saveAll({ docs: copies, systemLog })
2160
+ const result = await this.repo.saveAll({ config, docs: copies, systemLog })
2134
2161
  return makeApiResponse({
2135
2162
  repo: this.repo,
2136
2163
  result
@@ -2138,10 +2165,10 @@ class Service {
2138
2165
  }
2139
2166
 
2140
2167
  // set skipInit to true if we want to use POST for query
2141
- async saveOne({ doc = {}, config = {}, systemLog } = {}) {
2168
+ async saveOne({ config = {}, doc = {}, systemLog } = {}) {
2142
2169
  const copy = config.skipInit ? doc : this.init(doc)
2143
2170
  if (copy) {
2144
- const result = await this.repo.saveOne({ doc: copy, systemLog })
2171
+ const result = await this.repo.saveOne({ config, doc: copy, systemLog })
2145
2172
  return makeApiResponse({
2146
2173
  repo: this.repo,
2147
2174
  result
@@ -2414,6 +2441,7 @@ function sanitizeText(input, options = {}) {
2414
2441
  normalizeWhitespace = true,
2415
2442
  removeNewlines = false,
2416
2443
  trim = true,
2444
+ preserveBasicWhitespace = true, // new option to keep tabs, newlines if removeNewlines=false
2417
2445
  } = options
2418
2446
 
2419
2447
  if (typeof input !== 'string') {
@@ -2422,8 +2450,14 @@ function sanitizeText(input, options = {}) {
2422
2450
 
2423
2451
  let result = input
2424
2452
 
2425
- // Phase 1: Remove hidden/control characters
2426
- result = result.replace(/[\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2453
+ // Phase 1: Remove all control characters except basic whitespace if requested
2454
+ if (preserveBasicWhitespace && !removeNewlines) {
2455
+ // Keep tab (\t), newline (\n), carriage return (\r)
2456
+ result = result.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2457
+ } else {
2458
+ // Remove all control characters including basic whitespace
2459
+ result = result.replace(/[\x00-\x1F\x7F-\x9F\u200B-\u200D\uFEFF\u202A-\u202E]/g, '')
2460
+ }
2427
2461
 
2428
2462
  // Phase 2: Handle whitespace transformations
2429
2463
  if (removeNewlines) {
@@ -2611,6 +2645,19 @@ function trackingPlugin(schema, options) {
2611
2645
  next()
2612
2646
  })
2613
2647
 
2648
+ // Add core indexes
2649
+ schema.index({
2650
+ 'meta.active': 1,
2651
+ 'meta.deleted': 1
2652
+ }, {
2653
+ name: 'tracking_status_index',
2654
+ background: true,
2655
+ partialFilterExpression: {
2656
+ 'meta.active': true,
2657
+ 'meta.deleted': false
2658
+ }
2659
+ })
2660
+
2614
2661
  // Optional: Add helper methods
2615
2662
  // schema.methods.touch = function(userId) {
2616
2663
  // this.meta.updatedAt = new Date()
@@ -2618,6 +2665,57 @@ function trackingPlugin(schema, options) {
2618
2665
  // }
2619
2666
  }
2620
2667
 
2668
+ ;// ./lib/helpers/tenantPlugin/tenantPlugin.js
2669
+
2670
+
2671
+ function tenantPlugin(schema, options) {
2672
+ // Apply tracking plugin first if not already present
2673
+ if (!schema.path('meta')) {
2674
+ trackingPlugin(schema, options)
2675
+ }
2676
+
2677
+ // Add tenant-specific fields
2678
+ schema.add({
2679
+ metadata: [{ type: Object }], // Instead of Schema.Types.Mixed
2680
+ remarks: [{ type: Object }],
2681
+ tenantCode: { type: String, required: true }
2682
+ })
2683
+
2684
+ // Add core indexes
2685
+ schema.index({
2686
+ 'tenantCode': 1
2687
+ }, {
2688
+ name: 'tenant_core_index',
2689
+ background: true
2690
+ })
2691
+
2692
+ // 1. ENHANCE EXISTING TRACKING INDEXES
2693
+ const existingIndexes = schema.indexes()
2694
+
2695
+ // Check if tracking_status_index exists
2696
+ const hasTenantStatusIndex = existingIndexes.some(idx =>
2697
+ idx.name === 'tenant_status_index' // Check by name for reliability
2698
+ )
2699
+
2700
+ if (!hasTenantStatusIndex) {
2701
+ schema.index({
2702
+ 'tenantCode': 1, // Unique field first
2703
+ _type: 1, // Low-cardinality field last
2704
+ }, {
2705
+ name: 'tenant_status_index',
2706
+ background: true,
2707
+ partialFilterExpression: {
2708
+ '_type': 'Tenant',
2709
+ 'meta.active': true,
2710
+ 'meta.deleted': false
2711
+ }
2712
+ })
2713
+ }
2714
+ }
2715
+
2716
+ ;// ./lib/helpers/tenantPlugin/index.js
2717
+
2718
+
2621
2719
  ;// ./lib/helpers/trackingPlugin/index.js
2622
2720
 
2623
2721
 
@@ -2644,6 +2742,7 @@ function trackingPlugin(schema, options) {
2644
2742
 
2645
2743
 
2646
2744
 
2745
+
2647
2746
 
2648
2747
 
2649
2748
  ;// ./lib/models/apiResponse/index.js
@@ -2985,7 +3084,10 @@ class KeyValueObject {
2985
3084
  }, [])
2986
3085
  }
2987
3086
  static sameKey(item, key) {
2988
- return _isSame(item.key, key)
3087
+ if (item) {
3088
+ return _isSame(item.key, key)
3089
+ }
3090
+ return false
2989
3091
  }
2990
3092
  static toObject(arr = []) {
2991
3093
  if (Array.isArray(arr)) {
@@ -3239,17 +3341,19 @@ class TrackedEntity {
3239
3341
  constructor(options = {}) {
3240
3342
  options = options || {}
3241
3343
  const timestamp = Date.now()
3242
- const _tracking = {
3243
- active: options.active ?? true,
3244
- created: options.created ?? timestamp,
3245
- creator: options.creator ?? '',
3246
- deleted: options.deleted ?? false,
3247
- modified: options.modified ?? timestamp,
3248
- owner: options.owner ?? '',
3344
+ this.meta = {
3345
+ active: options.meta?.active ?? options.active ?? true,
3346
+ created: options.meta?.created ?? (options.created
3347
+ ? new Date(options.created).getTime()
3348
+ : timestamp),
3349
+ creator: options.meta?.creator ?? options.creator ?? '',
3350
+ deleted: options.meta?.deleted ?? options.deleted ?? false,
3351
+ modified: options.meta?.modified ?? (options.modified
3352
+ ? new Date(options.modified).getTime()
3353
+ : timestamp),
3354
+ owner: options.meta?.owner ?? options.owner ?? '',
3249
3355
  }
3250
3356
 
3251
- this.meta = { ..._tracking, ...options.meta }
3252
-
3253
3357
  // if (trackFlat) {
3254
3358
  // Object.assign(this, _tracking)
3255
3359
  // } else {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questwork/q-utilities",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Questwork QUtilities",
5
5
  "type": "module",
6
6
  "exports": {
@@ -10,12 +10,6 @@
10
10
  "default": "./dist/q-utilities.min.js"
11
11
  }
12
12
  },
13
- "scripts": {
14
- "build": "cross-env NODE_ENV=production minimize=false gulp",
15
- "build:wp": "cross-env NODE_ENV=production minimize=false gulp wp",
16
- "lint": "eslint .",
17
- "test:models": "NODE_ENV=test mocha --exit 'lib/models/test.setup.js' 'lib/models/**/*.spec.js'"
18
- },
19
13
  "author": {
20
14
  "name": "Questwork Consulting Limited",
21
15
  "email": "info@questwork.com",
@@ -45,5 +39,11 @@
45
39
  },
46
40
  "engines": {
47
41
  "node": ">=10.0.0"
42
+ },
43
+ "scripts": {
44
+ "build": "cross-env NODE_ENV=production minimize=false gulp",
45
+ "build:wp": "cross-env NODE_ENV=production minimize=false gulp wp",
46
+ "lint": "eslint .",
47
+ "test:models": "NODE_ENV=test mocha --exit 'lib/models/test.setup.js' 'lib/models/**/*.spec.js'"
48
48
  }
49
- }
49
+ }