@questwork/q-utilities 0.1.24 → 0.1.30

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.
@@ -5,16 +5,12 @@ on:
5
5
  - 'v*'
6
6
  workflow_dispatch:
7
7
 
8
- permissions:
9
- id-token: write # Required for OIDC
10
- contents: read
11
-
12
8
  jobs:
13
9
  publish:
14
10
  runs-on: ubuntu-latest
15
11
  permissions:
16
12
  contents: read
17
- packages: write
13
+ id-token: write # OIDC
18
14
  # environment: production
19
15
  steps:
20
16
  - uses: actions/checkout@v4
@@ -51,7 +47,14 @@ jobs:
51
47
  # NODE_AUTH_TOKEN: ${{ secrets.QW_NPM_TOKEN }}
52
48
 
53
49
  - name: Update npm
54
- run: npm install -g npm@latest
50
+ run: |
51
+ NPM_VERSION=$(npm -v)
52
+ echo "Current npm version: $NPM_VERSION"
53
+ if ! npx semver -r ">=11.5.1" "$NPM_VERSION"; then
54
+ echo "npm version $NPM_VERSION is too old. Installing latest npm..."
55
+ npm install -g npm@latest
56
+ echo "Updated npm version: $(npm -v)"
57
+ fi
55
58
 
56
59
  - name: Publish to npm
57
- run: npm publish --access public
60
+ run: npm publish --no-git-checks --access public
@@ -89,6 +89,9 @@ __webpack_require__.d(__webpack_exports__, {
89
89
  makeService: () => (/* reexport */ makeService),
90
90
  mergeArraysByKey: () => (/* reexport */ mergeArraysByKey),
91
91
  objectHelper: () => (/* reexport */ objectHelper),
92
+ pMap: () => (/* reexport */ pMap),
93
+ pMapIterable: () => (/* reexport */ pMapIterable),
94
+ pMapSkip: () => (/* reexport */ pMapSkip),
92
95
  pReduce: () => (/* reexport */ pReduce),
93
96
  padZeros: () => (/* reexport */ padZeros),
94
97
  printControlCharReport: () => (/* reexport */ printControlCharReport),
@@ -1349,15 +1352,19 @@ class Repo {
1349
1352
  }
1350
1353
 
1351
1354
  // systemLog is optional
1352
- findAll({ query, systemLog }) {
1355
+ findAll({ config = {}, query, systemLog }) {
1353
1356
  const log = _makeLog({
1354
1357
  systemLog,
1355
1358
  label: 'REPO_READ',
1356
1359
  message: `fn ${this._classname}.prototype.findAll`,
1357
1360
  input: [{ query: { ...query }, systemLog: { ...systemLog } }]
1358
1361
  })
1362
+ const queryOptions = {
1363
+ ...this.queryOptions,
1364
+ ...config,
1365
+ }
1359
1366
  return new Promise((resolve, reject) => {
1360
- this.model.findAll(query, this.queryOptions, (err, data, total) => {
1367
+ this.model.findAll(query, queryOptions, (err, data, total) => {
1361
1368
  if (err) {
1362
1369
  log({ level: 'warn', output: err.toString() })
1363
1370
  reject(err)
@@ -1374,15 +1381,19 @@ class Repo {
1374
1381
  })
1375
1382
  }
1376
1383
 
1377
- findOne({ query, systemLog }) {
1384
+ findOne({ config = {}, query, systemLog }) {
1378
1385
  const log = _makeLog({
1379
1386
  systemLog,
1380
1387
  label: 'REPO_READ',
1381
1388
  message: `fn ${this._classname}.prototype.findOne`,
1382
1389
  input: [{ query: { ...query }, systemLog: { ...systemLog } }]
1383
1390
  })
1391
+ const queryOptions = {
1392
+ ...this.queryOptions,
1393
+ ...config,
1394
+ }
1384
1395
  return new Promise((resolve, reject) => {
1385
- this.model.findAll(query, this.queryOptions, (err, data) => {
1396
+ this.model.findAll(query, queryOptions, (err, data) => {
1386
1397
  if (err) {
1387
1398
  reject(err)
1388
1399
  } else if (data.length === 1) {
@@ -1416,15 +1427,16 @@ class Repo {
1416
1427
  })
1417
1428
  const promise = typeof this.model.saveAll === 'function'
1418
1429
  ? this.model.saveAll({ config, docs })
1419
- : Promise.all(docs.map(async (doc) => {
1420
- if (doc) {
1421
- const result = await this.saveOne({ config, doc })
1422
- isNew = result.isNew
1423
- const _data = result._data || result.data
1424
- return _data[0]
1425
- }
1426
- return null
1427
- }))
1430
+ : _saveAll({ config, docs, service: this })
1431
+ // : Promise.all(docs.map(async (doc) => {
1432
+ // if (doc) {
1433
+ // const result = await this.saveOne({ config, doc })
1434
+ // isNew = result.isNew
1435
+ // const _data = result._data || result.data
1436
+ // return _data[0]
1437
+ // }
1438
+ // return null
1439
+ // }))
1428
1440
  return promise.then((savedData) => {
1429
1441
  if (savedData.length !== 1)
1430
1442
  isNew = null
@@ -1485,6 +1497,32 @@ function _makeLog({ systemLog, label, message: message1, input } = {}) {
1485
1497
  }
1486
1498
  }
1487
1499
 
1500
+ async function _saveAll({ config = {}, docs, service }) {
1501
+ let _result = null
1502
+ if (config.session || service.saveOptions.session) {
1503
+ _result = await pMap(docs, async (doc) => {
1504
+ if (doc) {
1505
+ const result = await service.saveOne({ config, doc })
1506
+ // isNew = result.isNew
1507
+ const _data = result._data || result.data
1508
+ return _data[0]
1509
+ }
1510
+ return null
1511
+ }, { concurrency: 1 })
1512
+ } else {
1513
+ _result = await Promise.all(docs.map(async (doc) => {
1514
+ if (doc) {
1515
+ const result = await service.saveOne({ config, doc })
1516
+ // isNew = result.isNew
1517
+ const _data = result._data || result.data
1518
+ return _data[0]
1519
+ }
1520
+ return null
1521
+ }))
1522
+ }
1523
+ return _result
1524
+ }
1525
+
1488
1526
 
1489
1527
 
1490
1528
  ;// ./lib/models/repo/index.js
@@ -1520,16 +1558,16 @@ class Service {
1520
1558
  // })
1521
1559
  }
1522
1560
 
1523
- async findAll({ query = {}, systemLog } = {}) {
1524
- const result = await this.repo.findAll({ query, systemLog })
1561
+ async findAll({ config = {}, query = {}, systemLog } = {}) {
1562
+ const result = await this.repo.findAll({ config, query, systemLog })
1525
1563
  return makeApiResponse({
1526
1564
  repo: this.repo,
1527
1565
  result
1528
1566
  })
1529
1567
  }
1530
1568
 
1531
- async findOne({ query = {}, systemLog } = {}) {
1532
- const result = await this.repo.findOne({ query, systemLog })
1569
+ async findOne({ config = {}, query = {}, systemLog } = {}) {
1570
+ const result = await this.repo.findOne({ config, query, systemLog })
1533
1571
  return makeApiResponse({
1534
1572
  repo: this.repo,
1535
1573
  result
@@ -3899,6 +3937,293 @@ function padZeros(num, minLength = 6) {
3899
3937
 
3900
3938
 
3901
3939
 
3940
+ ;// ./lib/helpers/pMap/pMap.js
3941
+ async function pMap(
3942
+ iterable,
3943
+ mapper,
3944
+ {
3945
+ concurrency = Number.POSITIVE_INFINITY,
3946
+ stopOnError = true,
3947
+ signal,
3948
+ } = {},
3949
+ ) {
3950
+ return new Promise((resolve_, reject_) => {
3951
+ if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
3952
+ throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`)
3953
+ }
3954
+
3955
+ if (typeof mapper !== 'function') {
3956
+ throw new TypeError('Mapper function is required')
3957
+ }
3958
+
3959
+ if (!((Number.isSafeInteger(concurrency) && concurrency >= 1) || concurrency === Number.POSITIVE_INFINITY)) {
3960
+ throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`)
3961
+ }
3962
+
3963
+ const result = []
3964
+ const errors = []
3965
+ const skippedIndexesMap = new Map()
3966
+ let isRejected = false
3967
+ let isResolved = false
3968
+ let isIterableDone = false
3969
+ let resolvingCount = 0
3970
+ let currentIndex = 0
3971
+ const iterator = iterable[Symbol.iterator] === undefined ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]()
3972
+
3973
+ const signalListener = () => {
3974
+ reject(signal.reason)
3975
+ }
3976
+
3977
+ const cleanup = () => {
3978
+ signal?.removeEventListener('abort', signalListener)
3979
+ }
3980
+
3981
+ const resolve = (value) => {
3982
+ resolve_(value)
3983
+ cleanup()
3984
+ }
3985
+
3986
+ const reject = (reason) => {
3987
+ isRejected = true
3988
+ isResolved = true
3989
+ reject_(reason)
3990
+ cleanup()
3991
+ }
3992
+
3993
+ if (signal) {
3994
+ if (signal.aborted) {
3995
+ reject(signal.reason)
3996
+ }
3997
+
3998
+ signal.addEventListener('abort', signalListener, { once: true })
3999
+ }
4000
+
4001
+ const next = async () => {
4002
+ if (isResolved) {
4003
+ return
4004
+ }
4005
+
4006
+ const nextItem = await iterator.next()
4007
+
4008
+ const index = currentIndex
4009
+ currentIndex++
4010
+
4011
+ // Note: `iterator.next()` can be called many times in parallel.
4012
+ // This can cause multiple calls to this `next()` function to
4013
+ // receive a `nextItem` with `done === true`.
4014
+ // The shutdown logic that rejects/resolves must be protected
4015
+ // so it runs only one time as the `skippedIndex` logic is
4016
+ // non-idempotent.
4017
+ if (nextItem.done) {
4018
+ isIterableDone = true
4019
+
4020
+ if (resolvingCount === 0 && !isResolved) {
4021
+ if (!stopOnError && errors.length > 0) {
4022
+ reject(new AggregateError(errors)) // eslint-disable-line unicorn/error-message
4023
+ return
4024
+ }
4025
+
4026
+ isResolved = true
4027
+
4028
+ if (skippedIndexesMap.size === 0) {
4029
+ resolve(result)
4030
+ return
4031
+ }
4032
+
4033
+ const pureResult = []
4034
+
4035
+ // Support multiple `pMapSkip`'s.
4036
+ for (const [index, value] of result.entries()) {
4037
+ if (skippedIndexesMap.get(index) === pMapSkip) {
4038
+ continue
4039
+ }
4040
+
4041
+ pureResult.push(value)
4042
+ }
4043
+
4044
+ resolve(pureResult)
4045
+ }
4046
+
4047
+ return
4048
+ }
4049
+
4050
+ resolvingCount++;
4051
+
4052
+ // Intentionally detached
4053
+ (async () => {
4054
+ try {
4055
+ const element = await nextItem.value
4056
+
4057
+ if (isResolved) {
4058
+ return
4059
+ }
4060
+
4061
+ const value = await mapper(element, index)
4062
+
4063
+ // Use Map to stage the index of the element.
4064
+ if (value === pMapSkip) {
4065
+ skippedIndexesMap.set(index, value)
4066
+ }
4067
+
4068
+ result[index] = value
4069
+
4070
+ resolvingCount--
4071
+ await next()
4072
+ } catch (error) {
4073
+ if (stopOnError) {
4074
+ reject(error)
4075
+ } else {
4076
+ errors.push(error)
4077
+ resolvingCount--
4078
+
4079
+ // In that case we can't really continue regardless of `stopOnError` state
4080
+ // since an iterable is likely to continue throwing after it throws once.
4081
+ // If we continue calling `next()` indefinitely we will likely end up
4082
+ // in an infinite loop of failed iteration.
4083
+ try {
4084
+ await next()
4085
+ } catch (error) {
4086
+ reject(error)
4087
+ }
4088
+ }
4089
+ }
4090
+ })()
4091
+ };
4092
+
4093
+ // Create the concurrent runners in a detached (non-awaited)
4094
+ // promise. We need this so we can await the `next()` calls
4095
+ // to stop creating runners before hitting the concurrency limit
4096
+ // if the iterable has already been marked as done.
4097
+ // NOTE: We *must* do this for async iterators otherwise we'll spin up
4098
+ // infinite `next()` calls by default and never start the event loop.
4099
+ (async () => {
4100
+ for (let index = 0; index < concurrency; index++) {
4101
+ try {
4102
+ await next()
4103
+ } catch (error) {
4104
+ reject(error)
4105
+ break
4106
+ }
4107
+
4108
+ if (isIterableDone || isRejected) {
4109
+ break
4110
+ }
4111
+ }
4112
+ })()
4113
+ })
4114
+ }
4115
+
4116
+ function pMapIterable(
4117
+ iterable,
4118
+ mapper,
4119
+ {
4120
+ concurrency = Number.POSITIVE_INFINITY,
4121
+ backpressure = concurrency,
4122
+ } = {},
4123
+ ) {
4124
+ if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
4125
+ throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`)
4126
+ }
4127
+
4128
+ if (typeof mapper !== 'function') {
4129
+ throw new TypeError('Mapper function is required')
4130
+ }
4131
+
4132
+ if (!((Number.isSafeInteger(concurrency) && concurrency >= 1) || concurrency === Number.POSITIVE_INFINITY)) {
4133
+ throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`)
4134
+ }
4135
+
4136
+ if (!((Number.isSafeInteger(backpressure) && backpressure >= concurrency) || backpressure === Number.POSITIVE_INFINITY)) {
4137
+ throw new TypeError(`Expected \`backpressure\` to be an integer from \`concurrency\` (${concurrency}) and up or \`Infinity\`, got \`${backpressure}\` (${typeof backpressure})`)
4138
+ }
4139
+
4140
+ return {
4141
+ async* [Symbol.asyncIterator]() {
4142
+ const iterator = iterable[Symbol.asyncIterator] === undefined ? iterable[Symbol.iterator]() : iterable[Symbol.asyncIterator]()
4143
+
4144
+ const promises = []
4145
+ let pendingPromisesCount = 0
4146
+ let isDone = false
4147
+ let index = 0
4148
+
4149
+ function trySpawn() {
4150
+ if (isDone || !(pendingPromisesCount < concurrency && promises.length < backpressure)) {
4151
+ return
4152
+ }
4153
+
4154
+ pendingPromisesCount++
4155
+
4156
+ const promise = (async () => {
4157
+ const { done, value } = await iterator.next()
4158
+
4159
+ if (done) {
4160
+ pendingPromisesCount--
4161
+ return { done: true }
4162
+ }
4163
+
4164
+ // Spawn if still below concurrency and backpressure limit
4165
+ trySpawn()
4166
+
4167
+ try {
4168
+ const returnValue = await mapper(await value, index++)
4169
+
4170
+ pendingPromisesCount--
4171
+
4172
+ if (returnValue === pMapSkip) {
4173
+ const index = promises.indexOf(promise)
4174
+
4175
+ if (index > 0) {
4176
+ promises.splice(index, 1)
4177
+ }
4178
+ }
4179
+
4180
+ // Spawn if still below backpressure limit and just dropped below concurrency limit
4181
+ trySpawn()
4182
+
4183
+ return { done: false, value: returnValue }
4184
+ } catch (error) {
4185
+ pendingPromisesCount--
4186
+ isDone = true
4187
+ return { error }
4188
+ }
4189
+ })()
4190
+
4191
+ promises.push(promise)
4192
+ }
4193
+
4194
+ trySpawn()
4195
+
4196
+ while (promises.length > 0) {
4197
+ const { error, done, value } = await promises[0]
4198
+
4199
+ promises.shift()
4200
+
4201
+ if (error) {
4202
+ throw error
4203
+ }
4204
+
4205
+ if (done) {
4206
+ return
4207
+ }
4208
+
4209
+ // Spawn if just dropped below backpressure limit and below the concurrency limit
4210
+ trySpawn()
4211
+
4212
+ if (value === pMapSkip) {
4213
+ continue
4214
+ }
4215
+
4216
+ yield value
4217
+ }
4218
+ },
4219
+ }
4220
+ }
4221
+
4222
+ const pMapSkip = Symbol('skip')
4223
+
4224
+ ;// ./lib/helpers/pMap/index.js
4225
+
4226
+
3902
4227
  ;// ./lib/helpers/pReduce/index.js
3903
4228
 
3904
4229
 
@@ -4255,11 +4580,19 @@ function toLowerCase(str) {
4255
4580
  .toLowerCase()
4256
4581
  }
4257
4582
 
4583
+ function toScreamingSnakeCase(str) {
4584
+ return str
4585
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
4586
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
4587
+ .toUpperCase()
4588
+ }
4589
+
4258
4590
  const stringHelper = {
4259
4591
  isSame,
4260
4592
  setCode,
4261
4593
  toCamelCase,
4262
4594
  toLowerCase,
4595
+ toScreamingSnakeCase,
4263
4596
  }
4264
4597
 
4265
4598
 
@@ -4395,6 +4728,7 @@ function tenantPlugin(schema, options) {
4395
4728
 
4396
4729
 
4397
4730
 
4731
+
4398
4732
 
4399
4733
 
4400
4734
  ;// ./lib/index.js
@@ -1274,15 +1274,19 @@ class Repo {
1274
1274
  }
1275
1275
 
1276
1276
  // systemLog is optional
1277
- findAll({ query, systemLog }) {
1277
+ findAll({ config = {}, query, systemLog }) {
1278
1278
  const log = _makeLog({
1279
1279
  systemLog,
1280
1280
  label: 'REPO_READ',
1281
1281
  message: `fn ${this._classname}.prototype.findAll`,
1282
1282
  input: [{ query: { ...query }, systemLog: { ...systemLog } }]
1283
1283
  })
1284
+ const queryOptions = {
1285
+ ...this.queryOptions,
1286
+ ...config,
1287
+ }
1284
1288
  return new Promise((resolve, reject) => {
1285
- this.model.findAll(query, this.queryOptions, (err, data, total) => {
1289
+ this.model.findAll(query, queryOptions, (err, data, total) => {
1286
1290
  if (err) {
1287
1291
  log({ level: 'warn', output: err.toString() })
1288
1292
  reject(err)
@@ -1299,15 +1303,19 @@ class Repo {
1299
1303
  })
1300
1304
  }
1301
1305
 
1302
- findOne({ query, systemLog }) {
1306
+ findOne({ config = {}, query, systemLog }) {
1303
1307
  const log = _makeLog({
1304
1308
  systemLog,
1305
1309
  label: 'REPO_READ',
1306
1310
  message: `fn ${this._classname}.prototype.findOne`,
1307
1311
  input: [{ query: { ...query }, systemLog: { ...systemLog } }]
1308
1312
  })
1313
+ const queryOptions = {
1314
+ ...this.queryOptions,
1315
+ ...config,
1316
+ }
1309
1317
  return new Promise((resolve, reject) => {
1310
- this.model.findAll(query, this.queryOptions, (err, data) => {
1318
+ this.model.findAll(query, queryOptions, (err, data) => {
1311
1319
  if (err) {
1312
1320
  reject(err)
1313
1321
  } else if (data.length === 1) {
@@ -1341,15 +1349,16 @@ class Repo {
1341
1349
  })
1342
1350
  const promise = typeof this.model.saveAll === 'function'
1343
1351
  ? this.model.saveAll({ config, docs })
1344
- : Promise.all(docs.map(async (doc) => {
1345
- if (doc) {
1346
- const result = await this.saveOne({ config, doc })
1347
- isNew = result.isNew
1348
- const _data = result._data || result.data
1349
- return _data[0]
1350
- }
1351
- return null
1352
- }))
1352
+ : _saveAll({ config, docs, service: this })
1353
+ // : Promise.all(docs.map(async (doc) => {
1354
+ // if (doc) {
1355
+ // const result = await this.saveOne({ config, doc })
1356
+ // isNew = result.isNew
1357
+ // const _data = result._data || result.data
1358
+ // return _data[0]
1359
+ // }
1360
+ // return null
1361
+ // }))
1353
1362
  return promise.then((savedData) => {
1354
1363
  if (savedData.length !== 1)
1355
1364
  isNew = null
@@ -1410,6 +1419,32 @@ function _makeLog({ systemLog, label, message: message1, input } = {}) {
1410
1419
  }
1411
1420
  }
1412
1421
 
1422
+ async function _saveAll({ config = {}, docs, service }) {
1423
+ let _result = null
1424
+ if (config.session || service.saveOptions.session) {
1425
+ _result = await pMap(docs, async (doc) => {
1426
+ if (doc) {
1427
+ const result = await service.saveOne({ config, doc })
1428
+ // isNew = result.isNew
1429
+ const _data = result._data || result.data
1430
+ return _data[0]
1431
+ }
1432
+ return null
1433
+ }, { concurrency: 1 })
1434
+ } else {
1435
+ _result = await Promise.all(docs.map(async (doc) => {
1436
+ if (doc) {
1437
+ const result = await service.saveOne({ config, doc })
1438
+ // isNew = result.isNew
1439
+ const _data = result._data || result.data
1440
+ return _data[0]
1441
+ }
1442
+ return null
1443
+ }))
1444
+ }
1445
+ return _result
1446
+ }
1447
+
1413
1448
 
1414
1449
 
1415
1450
  ;// ./lib/models/repo/index.js
@@ -1445,16 +1480,16 @@ class Service {
1445
1480
  // })
1446
1481
  }
1447
1482
 
1448
- async findAll({ query = {}, systemLog } = {}) {
1449
- const result = await this.repo.findAll({ query, systemLog })
1483
+ async findAll({ config = {}, query = {}, systemLog } = {}) {
1484
+ const result = await this.repo.findAll({ config, query, systemLog })
1450
1485
  return makeApiResponse({
1451
1486
  repo: this.repo,
1452
1487
  result
1453
1488
  })
1454
1489
  }
1455
1490
 
1456
- async findOne({ query = {}, systemLog } = {}) {
1457
- const result = await this.repo.findOne({ query, systemLog })
1491
+ async findOne({ config = {}, query = {}, systemLog } = {}) {
1492
+ const result = await this.repo.findOne({ config, query, systemLog })
1458
1493
  return makeApiResponse({
1459
1494
  repo: this.repo,
1460
1495
  result
@@ -3824,6 +3859,293 @@ function padZeros(num, minLength = 6) {
3824
3859
 
3825
3860
 
3826
3861
 
3862
+ ;// ./lib/helpers/pMap/pMap.js
3863
+ async function pMap(
3864
+ iterable,
3865
+ mapper,
3866
+ {
3867
+ concurrency = Number.POSITIVE_INFINITY,
3868
+ stopOnError = true,
3869
+ signal,
3870
+ } = {},
3871
+ ) {
3872
+ return new Promise((resolve_, reject_) => {
3873
+ if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
3874
+ throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`)
3875
+ }
3876
+
3877
+ if (typeof mapper !== 'function') {
3878
+ throw new TypeError('Mapper function is required')
3879
+ }
3880
+
3881
+ if (!((Number.isSafeInteger(concurrency) && concurrency >= 1) || concurrency === Number.POSITIVE_INFINITY)) {
3882
+ throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`)
3883
+ }
3884
+
3885
+ const result = []
3886
+ const errors = []
3887
+ const skippedIndexesMap = new Map()
3888
+ let isRejected = false
3889
+ let isResolved = false
3890
+ let isIterableDone = false
3891
+ let resolvingCount = 0
3892
+ let currentIndex = 0
3893
+ const iterator = iterable[Symbol.iterator] === undefined ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]()
3894
+
3895
+ const signalListener = () => {
3896
+ reject(signal.reason)
3897
+ }
3898
+
3899
+ const cleanup = () => {
3900
+ signal?.removeEventListener('abort', signalListener)
3901
+ }
3902
+
3903
+ const resolve = (value) => {
3904
+ resolve_(value)
3905
+ cleanup()
3906
+ }
3907
+
3908
+ const reject = (reason) => {
3909
+ isRejected = true
3910
+ isResolved = true
3911
+ reject_(reason)
3912
+ cleanup()
3913
+ }
3914
+
3915
+ if (signal) {
3916
+ if (signal.aborted) {
3917
+ reject(signal.reason)
3918
+ }
3919
+
3920
+ signal.addEventListener('abort', signalListener, { once: true })
3921
+ }
3922
+
3923
+ const next = async () => {
3924
+ if (isResolved) {
3925
+ return
3926
+ }
3927
+
3928
+ const nextItem = await iterator.next()
3929
+
3930
+ const index = currentIndex
3931
+ currentIndex++
3932
+
3933
+ // Note: `iterator.next()` can be called many times in parallel.
3934
+ // This can cause multiple calls to this `next()` function to
3935
+ // receive a `nextItem` with `done === true`.
3936
+ // The shutdown logic that rejects/resolves must be protected
3937
+ // so it runs only one time as the `skippedIndex` logic is
3938
+ // non-idempotent.
3939
+ if (nextItem.done) {
3940
+ isIterableDone = true
3941
+
3942
+ if (resolvingCount === 0 && !isResolved) {
3943
+ if (!stopOnError && errors.length > 0) {
3944
+ reject(new AggregateError(errors)) // eslint-disable-line unicorn/error-message
3945
+ return
3946
+ }
3947
+
3948
+ isResolved = true
3949
+
3950
+ if (skippedIndexesMap.size === 0) {
3951
+ resolve(result)
3952
+ return
3953
+ }
3954
+
3955
+ const pureResult = []
3956
+
3957
+ // Support multiple `pMapSkip`'s.
3958
+ for (const [index, value] of result.entries()) {
3959
+ if (skippedIndexesMap.get(index) === pMapSkip) {
3960
+ continue
3961
+ }
3962
+
3963
+ pureResult.push(value)
3964
+ }
3965
+
3966
+ resolve(pureResult)
3967
+ }
3968
+
3969
+ return
3970
+ }
3971
+
3972
+ resolvingCount++;
3973
+
3974
+ // Intentionally detached
3975
+ (async () => {
3976
+ try {
3977
+ const element = await nextItem.value
3978
+
3979
+ if (isResolved) {
3980
+ return
3981
+ }
3982
+
3983
+ const value = await mapper(element, index)
3984
+
3985
+ // Use Map to stage the index of the element.
3986
+ if (value === pMapSkip) {
3987
+ skippedIndexesMap.set(index, value)
3988
+ }
3989
+
3990
+ result[index] = value
3991
+
3992
+ resolvingCount--
3993
+ await next()
3994
+ } catch (error) {
3995
+ if (stopOnError) {
3996
+ reject(error)
3997
+ } else {
3998
+ errors.push(error)
3999
+ resolvingCount--
4000
+
4001
+ // In that case we can't really continue regardless of `stopOnError` state
4002
+ // since an iterable is likely to continue throwing after it throws once.
4003
+ // If we continue calling `next()` indefinitely we will likely end up
4004
+ // in an infinite loop of failed iteration.
4005
+ try {
4006
+ await next()
4007
+ } catch (error) {
4008
+ reject(error)
4009
+ }
4010
+ }
4011
+ }
4012
+ })()
4013
+ };
4014
+
4015
+ // Create the concurrent runners in a detached (non-awaited)
4016
+ // promise. We need this so we can await the `next()` calls
4017
+ // to stop creating runners before hitting the concurrency limit
4018
+ // if the iterable has already been marked as done.
4019
+ // NOTE: We *must* do this for async iterators otherwise we'll spin up
4020
+ // infinite `next()` calls by default and never start the event loop.
4021
+ (async () => {
4022
+ for (let index = 0; index < concurrency; index++) {
4023
+ try {
4024
+ await next()
4025
+ } catch (error) {
4026
+ reject(error)
4027
+ break
4028
+ }
4029
+
4030
+ if (isIterableDone || isRejected) {
4031
+ break
4032
+ }
4033
+ }
4034
+ })()
4035
+ })
4036
+ }
4037
+
4038
+ function pMapIterable(
4039
+ iterable,
4040
+ mapper,
4041
+ {
4042
+ concurrency = Number.POSITIVE_INFINITY,
4043
+ backpressure = concurrency,
4044
+ } = {},
4045
+ ) {
4046
+ if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
4047
+ throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`)
4048
+ }
4049
+
4050
+ if (typeof mapper !== 'function') {
4051
+ throw new TypeError('Mapper function is required')
4052
+ }
4053
+
4054
+ if (!((Number.isSafeInteger(concurrency) && concurrency >= 1) || concurrency === Number.POSITIVE_INFINITY)) {
4055
+ throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`)
4056
+ }
4057
+
4058
+ if (!((Number.isSafeInteger(backpressure) && backpressure >= concurrency) || backpressure === Number.POSITIVE_INFINITY)) {
4059
+ throw new TypeError(`Expected \`backpressure\` to be an integer from \`concurrency\` (${concurrency}) and up or \`Infinity\`, got \`${backpressure}\` (${typeof backpressure})`)
4060
+ }
4061
+
4062
+ return {
4063
+ async* [Symbol.asyncIterator]() {
4064
+ const iterator = iterable[Symbol.asyncIterator] === undefined ? iterable[Symbol.iterator]() : iterable[Symbol.asyncIterator]()
4065
+
4066
+ const promises = []
4067
+ let pendingPromisesCount = 0
4068
+ let isDone = false
4069
+ let index = 0
4070
+
4071
+ function trySpawn() {
4072
+ if (isDone || !(pendingPromisesCount < concurrency && promises.length < backpressure)) {
4073
+ return
4074
+ }
4075
+
4076
+ pendingPromisesCount++
4077
+
4078
+ const promise = (async () => {
4079
+ const { done, value } = await iterator.next()
4080
+
4081
+ if (done) {
4082
+ pendingPromisesCount--
4083
+ return { done: true }
4084
+ }
4085
+
4086
+ // Spawn if still below concurrency and backpressure limit
4087
+ trySpawn()
4088
+
4089
+ try {
4090
+ const returnValue = await mapper(await value, index++)
4091
+
4092
+ pendingPromisesCount--
4093
+
4094
+ if (returnValue === pMapSkip) {
4095
+ const index = promises.indexOf(promise)
4096
+
4097
+ if (index > 0) {
4098
+ promises.splice(index, 1)
4099
+ }
4100
+ }
4101
+
4102
+ // Spawn if still below backpressure limit and just dropped below concurrency limit
4103
+ trySpawn()
4104
+
4105
+ return { done: false, value: returnValue }
4106
+ } catch (error) {
4107
+ pendingPromisesCount--
4108
+ isDone = true
4109
+ return { error }
4110
+ }
4111
+ })()
4112
+
4113
+ promises.push(promise)
4114
+ }
4115
+
4116
+ trySpawn()
4117
+
4118
+ while (promises.length > 0) {
4119
+ const { error, done, value } = await promises[0]
4120
+
4121
+ promises.shift()
4122
+
4123
+ if (error) {
4124
+ throw error
4125
+ }
4126
+
4127
+ if (done) {
4128
+ return
4129
+ }
4130
+
4131
+ // Spawn if just dropped below backpressure limit and below the concurrency limit
4132
+ trySpawn()
4133
+
4134
+ if (value === pMapSkip) {
4135
+ continue
4136
+ }
4137
+
4138
+ yield value
4139
+ }
4140
+ },
4141
+ }
4142
+ }
4143
+
4144
+ const pMapSkip = Symbol('skip')
4145
+
4146
+ ;// ./lib/helpers/pMap/index.js
4147
+
4148
+
3827
4149
  ;// ./lib/helpers/pReduce/index.js
3828
4150
 
3829
4151
 
@@ -4180,11 +4502,19 @@ function toLowerCase(str) {
4180
4502
  .toLowerCase()
4181
4503
  }
4182
4504
 
4505
+ function toScreamingSnakeCase(str) {
4506
+ return str
4507
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
4508
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
4509
+ .toUpperCase()
4510
+ }
4511
+
4183
4512
  const stringHelper = {
4184
4513
  isSame,
4185
4514
  setCode,
4186
4515
  toCamelCase,
4187
4516
  toLowerCase,
4517
+ toScreamingSnakeCase,
4188
4518
  }
4189
4519
 
4190
4520
 
@@ -4320,6 +4650,7 @@ function tenantPlugin(schema, options) {
4320
4650
 
4321
4651
 
4322
4652
 
4653
+
4323
4654
 
4324
4655
 
4325
4656
  ;// ./lib/index.js
@@ -4329,4 +4660,4 @@ function tenantPlugin(schema, options) {
4329
4660
  ;// ./index.js
4330
4661
 
4331
4662
 
4332
- export { ActionRecord, ApiResponse, AwsStsS3Client, keyValueObject_KeyValueObject as KeyValueObject, Metadata, PushEnvelope, QMeta, Repo, Service, Status, StatusDocument, TemplateCompiler, TenantAwareEntity, TrackedEntity, UniqueKeyGenerator, authorize, bwipJsHelper, calculateAge, changeCreatorOwner, concatStringByArray, convertString, detectControlCharacters, downloadFileByUrl, escapeRegex, expressHelper, extractEmails, formatDate, generalPost, getObjectByArr, getValidation, getValueByKeys_getValueByKeys as getValueByKeys, groupArrayByKey, init, initFromArray, initOnlyValidFromArray, isConvertibleToNumber, makeApiResponse, makeService, mergeArraysByKey, objectHelper, pReduce, padZeros, printControlCharReport, replacePlaceholders, sanitizeText, shuffleArray, stringFormatter, stringHelper, tenantPlugin, trackingPlugin };
4663
+ export { ActionRecord, ApiResponse, AwsStsS3Client, keyValueObject_KeyValueObject as KeyValueObject, Metadata, PushEnvelope, QMeta, Repo, Service, Status, StatusDocument, TemplateCompiler, TenantAwareEntity, TrackedEntity, UniqueKeyGenerator, authorize, bwipJsHelper, calculateAge, changeCreatorOwner, concatStringByArray, convertString, detectControlCharacters, downloadFileByUrl, escapeRegex, expressHelper, extractEmails, formatDate, generalPost, getObjectByArr, getValidation, getValueByKeys_getValueByKeys as getValueByKeys, groupArrayByKey, init, initFromArray, initOnlyValidFromArray, isConvertibleToNumber, makeApiResponse, makeService, mergeArraysByKey, objectHelper, pMap, pMapIterable, pMapSkip, pReduce, padZeros, printControlCharReport, replacePlaceholders, sanitizeText, shuffleArray, stringFormatter, stringHelper, tenantPlugin, trackingPlugin };
@@ -120,6 +120,9 @@ __webpack_require__.d(__webpack_exports__, {
120
120
  makeService: () => (/* reexport */ makeService),
121
121
  mergeArraysByKey: () => (/* reexport */ mergeArraysByKey),
122
122
  objectHelper: () => (/* reexport */ objectHelper),
123
+ pMap: () => (/* reexport */ pMap),
124
+ pMapIterable: () => (/* reexport */ pMapIterable),
125
+ pMapSkip: () => (/* reexport */ pMapSkip),
123
126
  pReduce: () => (/* reexport */ pReduce),
124
127
  padZeros: () => (/* reexport */ padZeros),
125
128
  printControlCharReport: () => (/* reexport */ printControlCharReport),
@@ -1380,15 +1383,19 @@ class Repo {
1380
1383
  }
1381
1384
 
1382
1385
  // systemLog is optional
1383
- findAll({ query, systemLog }) {
1386
+ findAll({ config = {}, query, systemLog }) {
1384
1387
  const log = _makeLog({
1385
1388
  systemLog,
1386
1389
  label: 'REPO_READ',
1387
1390
  message: `fn ${this._classname}.prototype.findAll`,
1388
1391
  input: [{ query: { ...query }, systemLog: { ...systemLog } }]
1389
1392
  })
1393
+ const queryOptions = {
1394
+ ...this.queryOptions,
1395
+ ...config,
1396
+ }
1390
1397
  return new Promise((resolve, reject) => {
1391
- this.model.findAll(query, this.queryOptions, (err, data, total) => {
1398
+ this.model.findAll(query, queryOptions, (err, data, total) => {
1392
1399
  if (err) {
1393
1400
  log({ level: 'warn', output: err.toString() })
1394
1401
  reject(err)
@@ -1405,15 +1412,19 @@ class Repo {
1405
1412
  })
1406
1413
  }
1407
1414
 
1408
- findOne({ query, systemLog }) {
1415
+ findOne({ config = {}, query, systemLog }) {
1409
1416
  const log = _makeLog({
1410
1417
  systemLog,
1411
1418
  label: 'REPO_READ',
1412
1419
  message: `fn ${this._classname}.prototype.findOne`,
1413
1420
  input: [{ query: { ...query }, systemLog: { ...systemLog } }]
1414
1421
  })
1422
+ const queryOptions = {
1423
+ ...this.queryOptions,
1424
+ ...config,
1425
+ }
1415
1426
  return new Promise((resolve, reject) => {
1416
- this.model.findAll(query, this.queryOptions, (err, data) => {
1427
+ this.model.findAll(query, queryOptions, (err, data) => {
1417
1428
  if (err) {
1418
1429
  reject(err)
1419
1430
  } else if (data.length === 1) {
@@ -1447,15 +1458,16 @@ class Repo {
1447
1458
  })
1448
1459
  const promise = typeof this.model.saveAll === 'function'
1449
1460
  ? this.model.saveAll({ config, docs })
1450
- : Promise.all(docs.map(async (doc) => {
1451
- if (doc) {
1452
- const result = await this.saveOne({ config, doc })
1453
- isNew = result.isNew
1454
- const _data = result._data || result.data
1455
- return _data[0]
1456
- }
1457
- return null
1458
- }))
1461
+ : _saveAll({ config, docs, service: this })
1462
+ // : Promise.all(docs.map(async (doc) => {
1463
+ // if (doc) {
1464
+ // const result = await this.saveOne({ config, doc })
1465
+ // isNew = result.isNew
1466
+ // const _data = result._data || result.data
1467
+ // return _data[0]
1468
+ // }
1469
+ // return null
1470
+ // }))
1459
1471
  return promise.then((savedData) => {
1460
1472
  if (savedData.length !== 1)
1461
1473
  isNew = null
@@ -1516,6 +1528,32 @@ function _makeLog({ systemLog, label, message: message1, input } = {}) {
1516
1528
  }
1517
1529
  }
1518
1530
 
1531
+ async function _saveAll({ config = {}, docs, service }) {
1532
+ let _result = null
1533
+ if (config.session || service.saveOptions.session) {
1534
+ _result = await pMap(docs, async (doc) => {
1535
+ if (doc) {
1536
+ const result = await service.saveOne({ config, doc })
1537
+ // isNew = result.isNew
1538
+ const _data = result._data || result.data
1539
+ return _data[0]
1540
+ }
1541
+ return null
1542
+ }, { concurrency: 1 })
1543
+ } else {
1544
+ _result = await Promise.all(docs.map(async (doc) => {
1545
+ if (doc) {
1546
+ const result = await service.saveOne({ config, doc })
1547
+ // isNew = result.isNew
1548
+ const _data = result._data || result.data
1549
+ return _data[0]
1550
+ }
1551
+ return null
1552
+ }))
1553
+ }
1554
+ return _result
1555
+ }
1556
+
1519
1557
 
1520
1558
 
1521
1559
  ;// ./lib/models/repo/index.js
@@ -1551,16 +1589,16 @@ class Service {
1551
1589
  // })
1552
1590
  }
1553
1591
 
1554
- async findAll({ query = {}, systemLog } = {}) {
1555
- const result = await this.repo.findAll({ query, systemLog })
1592
+ async findAll({ config = {}, query = {}, systemLog } = {}) {
1593
+ const result = await this.repo.findAll({ config, query, systemLog })
1556
1594
  return makeApiResponse({
1557
1595
  repo: this.repo,
1558
1596
  result
1559
1597
  })
1560
1598
  }
1561
1599
 
1562
- async findOne({ query = {}, systemLog } = {}) {
1563
- const result = await this.repo.findOne({ query, systemLog })
1600
+ async findOne({ config = {}, query = {}, systemLog } = {}) {
1601
+ const result = await this.repo.findOne({ config, query, systemLog })
1564
1602
  return makeApiResponse({
1565
1603
  repo: this.repo,
1566
1604
  result
@@ -3930,6 +3968,293 @@ function padZeros(num, minLength = 6) {
3930
3968
 
3931
3969
 
3932
3970
 
3971
+ ;// ./lib/helpers/pMap/pMap.js
3972
+ async function pMap(
3973
+ iterable,
3974
+ mapper,
3975
+ {
3976
+ concurrency = Number.POSITIVE_INFINITY,
3977
+ stopOnError = true,
3978
+ signal,
3979
+ } = {},
3980
+ ) {
3981
+ return new Promise((resolve_, reject_) => {
3982
+ if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
3983
+ throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`)
3984
+ }
3985
+
3986
+ if (typeof mapper !== 'function') {
3987
+ throw new TypeError('Mapper function is required')
3988
+ }
3989
+
3990
+ if (!((Number.isSafeInteger(concurrency) && concurrency >= 1) || concurrency === Number.POSITIVE_INFINITY)) {
3991
+ throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`)
3992
+ }
3993
+
3994
+ const result = []
3995
+ const errors = []
3996
+ const skippedIndexesMap = new Map()
3997
+ let isRejected = false
3998
+ let isResolved = false
3999
+ let isIterableDone = false
4000
+ let resolvingCount = 0
4001
+ let currentIndex = 0
4002
+ const iterator = iterable[Symbol.iterator] === undefined ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]()
4003
+
4004
+ const signalListener = () => {
4005
+ reject(signal.reason)
4006
+ }
4007
+
4008
+ const cleanup = () => {
4009
+ signal?.removeEventListener('abort', signalListener)
4010
+ }
4011
+
4012
+ const resolve = (value) => {
4013
+ resolve_(value)
4014
+ cleanup()
4015
+ }
4016
+
4017
+ const reject = (reason) => {
4018
+ isRejected = true
4019
+ isResolved = true
4020
+ reject_(reason)
4021
+ cleanup()
4022
+ }
4023
+
4024
+ if (signal) {
4025
+ if (signal.aborted) {
4026
+ reject(signal.reason)
4027
+ }
4028
+
4029
+ signal.addEventListener('abort', signalListener, { once: true })
4030
+ }
4031
+
4032
+ const next = async () => {
4033
+ if (isResolved) {
4034
+ return
4035
+ }
4036
+
4037
+ const nextItem = await iterator.next()
4038
+
4039
+ const index = currentIndex
4040
+ currentIndex++
4041
+
4042
+ // Note: `iterator.next()` can be called many times in parallel.
4043
+ // This can cause multiple calls to this `next()` function to
4044
+ // receive a `nextItem` with `done === true`.
4045
+ // The shutdown logic that rejects/resolves must be protected
4046
+ // so it runs only one time as the `skippedIndex` logic is
4047
+ // non-idempotent.
4048
+ if (nextItem.done) {
4049
+ isIterableDone = true
4050
+
4051
+ if (resolvingCount === 0 && !isResolved) {
4052
+ if (!stopOnError && errors.length > 0) {
4053
+ reject(new AggregateError(errors)) // eslint-disable-line unicorn/error-message
4054
+ return
4055
+ }
4056
+
4057
+ isResolved = true
4058
+
4059
+ if (skippedIndexesMap.size === 0) {
4060
+ resolve(result)
4061
+ return
4062
+ }
4063
+
4064
+ const pureResult = []
4065
+
4066
+ // Support multiple `pMapSkip`'s.
4067
+ for (const [index, value] of result.entries()) {
4068
+ if (skippedIndexesMap.get(index) === pMapSkip) {
4069
+ continue
4070
+ }
4071
+
4072
+ pureResult.push(value)
4073
+ }
4074
+
4075
+ resolve(pureResult)
4076
+ }
4077
+
4078
+ return
4079
+ }
4080
+
4081
+ resolvingCount++;
4082
+
4083
+ // Intentionally detached
4084
+ (async () => {
4085
+ try {
4086
+ const element = await nextItem.value
4087
+
4088
+ if (isResolved) {
4089
+ return
4090
+ }
4091
+
4092
+ const value = await mapper(element, index)
4093
+
4094
+ // Use Map to stage the index of the element.
4095
+ if (value === pMapSkip) {
4096
+ skippedIndexesMap.set(index, value)
4097
+ }
4098
+
4099
+ result[index] = value
4100
+
4101
+ resolvingCount--
4102
+ await next()
4103
+ } catch (error) {
4104
+ if (stopOnError) {
4105
+ reject(error)
4106
+ } else {
4107
+ errors.push(error)
4108
+ resolvingCount--
4109
+
4110
+ // In that case we can't really continue regardless of `stopOnError` state
4111
+ // since an iterable is likely to continue throwing after it throws once.
4112
+ // If we continue calling `next()` indefinitely we will likely end up
4113
+ // in an infinite loop of failed iteration.
4114
+ try {
4115
+ await next()
4116
+ } catch (error) {
4117
+ reject(error)
4118
+ }
4119
+ }
4120
+ }
4121
+ })()
4122
+ };
4123
+
4124
+ // Create the concurrent runners in a detached (non-awaited)
4125
+ // promise. We need this so we can await the `next()` calls
4126
+ // to stop creating runners before hitting the concurrency limit
4127
+ // if the iterable has already been marked as done.
4128
+ // NOTE: We *must* do this for async iterators otherwise we'll spin up
4129
+ // infinite `next()` calls by default and never start the event loop.
4130
+ (async () => {
4131
+ for (let index = 0; index < concurrency; index++) {
4132
+ try {
4133
+ await next()
4134
+ } catch (error) {
4135
+ reject(error)
4136
+ break
4137
+ }
4138
+
4139
+ if (isIterableDone || isRejected) {
4140
+ break
4141
+ }
4142
+ }
4143
+ })()
4144
+ })
4145
+ }
4146
+
4147
+ function pMapIterable(
4148
+ iterable,
4149
+ mapper,
4150
+ {
4151
+ concurrency = Number.POSITIVE_INFINITY,
4152
+ backpressure = concurrency,
4153
+ } = {},
4154
+ ) {
4155
+ if (iterable[Symbol.iterator] === undefined && iterable[Symbol.asyncIterator] === undefined) {
4156
+ throw new TypeError(`Expected \`input\` to be either an \`Iterable\` or \`AsyncIterable\`, got (${typeof iterable})`)
4157
+ }
4158
+
4159
+ if (typeof mapper !== 'function') {
4160
+ throw new TypeError('Mapper function is required')
4161
+ }
4162
+
4163
+ if (!((Number.isSafeInteger(concurrency) && concurrency >= 1) || concurrency === Number.POSITIVE_INFINITY)) {
4164
+ throw new TypeError(`Expected \`concurrency\` to be an integer from 1 and up or \`Infinity\`, got \`${concurrency}\` (${typeof concurrency})`)
4165
+ }
4166
+
4167
+ if (!((Number.isSafeInteger(backpressure) && backpressure >= concurrency) || backpressure === Number.POSITIVE_INFINITY)) {
4168
+ throw new TypeError(`Expected \`backpressure\` to be an integer from \`concurrency\` (${concurrency}) and up or \`Infinity\`, got \`${backpressure}\` (${typeof backpressure})`)
4169
+ }
4170
+
4171
+ return {
4172
+ async* [Symbol.asyncIterator]() {
4173
+ const iterator = iterable[Symbol.asyncIterator] === undefined ? iterable[Symbol.iterator]() : iterable[Symbol.asyncIterator]()
4174
+
4175
+ const promises = []
4176
+ let pendingPromisesCount = 0
4177
+ let isDone = false
4178
+ let index = 0
4179
+
4180
+ function trySpawn() {
4181
+ if (isDone || !(pendingPromisesCount < concurrency && promises.length < backpressure)) {
4182
+ return
4183
+ }
4184
+
4185
+ pendingPromisesCount++
4186
+
4187
+ const promise = (async () => {
4188
+ const { done, value } = await iterator.next()
4189
+
4190
+ if (done) {
4191
+ pendingPromisesCount--
4192
+ return { done: true }
4193
+ }
4194
+
4195
+ // Spawn if still below concurrency and backpressure limit
4196
+ trySpawn()
4197
+
4198
+ try {
4199
+ const returnValue = await mapper(await value, index++)
4200
+
4201
+ pendingPromisesCount--
4202
+
4203
+ if (returnValue === pMapSkip) {
4204
+ const index = promises.indexOf(promise)
4205
+
4206
+ if (index > 0) {
4207
+ promises.splice(index, 1)
4208
+ }
4209
+ }
4210
+
4211
+ // Spawn if still below backpressure limit and just dropped below concurrency limit
4212
+ trySpawn()
4213
+
4214
+ return { done: false, value: returnValue }
4215
+ } catch (error) {
4216
+ pendingPromisesCount--
4217
+ isDone = true
4218
+ return { error }
4219
+ }
4220
+ })()
4221
+
4222
+ promises.push(promise)
4223
+ }
4224
+
4225
+ trySpawn()
4226
+
4227
+ while (promises.length > 0) {
4228
+ const { error, done, value } = await promises[0]
4229
+
4230
+ promises.shift()
4231
+
4232
+ if (error) {
4233
+ throw error
4234
+ }
4235
+
4236
+ if (done) {
4237
+ return
4238
+ }
4239
+
4240
+ // Spawn if just dropped below backpressure limit and below the concurrency limit
4241
+ trySpawn()
4242
+
4243
+ if (value === pMapSkip) {
4244
+ continue
4245
+ }
4246
+
4247
+ yield value
4248
+ }
4249
+ },
4250
+ }
4251
+ }
4252
+
4253
+ const pMapSkip = Symbol('skip')
4254
+
4255
+ ;// ./lib/helpers/pMap/index.js
4256
+
4257
+
3933
4258
  ;// ./lib/helpers/pReduce/index.js
3934
4259
 
3935
4260
 
@@ -4286,11 +4611,19 @@ function toLowerCase(str) {
4286
4611
  .toLowerCase()
4287
4612
  }
4288
4613
 
4614
+ function toScreamingSnakeCase(str) {
4615
+ return str
4616
+ .replace(/([a-z])([A-Z])/g, '$1_$2')
4617
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
4618
+ .toUpperCase()
4619
+ }
4620
+
4289
4621
  const stringHelper = {
4290
4622
  isSame,
4291
4623
  setCode,
4292
4624
  toCamelCase,
4293
4625
  toLowerCase,
4626
+ toScreamingSnakeCase,
4294
4627
  }
4295
4628
 
4296
4629
 
@@ -4426,6 +4759,7 @@ function tenantPlugin(schema, options) {
4426
4759
 
4427
4760
 
4428
4761
 
4762
+
4429
4763
 
4430
4764
 
4431
4765
  ;// ./lib/index.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@questwork/q-utilities",
3
- "version": "0.1.24",
3
+ "version": "0.1.30",
4
4
  "description": "Questwork QUtilities",
5
5
  "author": {
6
6
  "name": "Questwork Consulting Limited",
@@ -31,6 +31,7 @@
31
31
  "@aws-sdk/client-sts": "^3.812.0",
32
32
  "@babel/core": "^7.27.1",
33
33
  "@babel/eslint-parser": "^7.27.1",
34
+ "@jmespath-community/jmespath": "^1.3.0",
34
35
  "@questwork/q-eslint-config": "^0.1.8",
35
36
  "babel-loader": "^8.2.4",
36
37
  "babel-plugin-component": "^1.1.1",