@tickernelz/paperclip-pro-server 2026.1014.1 → 2026.1015.0

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.
Files changed (40) hide show
  1. package/dist/build-info.json +1 -1
  2. package/dist/services/heartbeat/retries.d.ts.map +1 -1
  3. package/dist/services/heartbeat/retries.js +33 -20
  4. package/dist/services/heartbeat/retries.js.map +1 -1
  5. package/dist/services/heartbeat/workspaces.d.ts +3 -0
  6. package/dist/services/heartbeat/workspaces.d.ts.map +1 -1
  7. package/dist/services/heartbeat/workspaces.js +49 -1
  8. package/dist/services/heartbeat/workspaces.js.map +1 -1
  9. package/dist/services/heartbeat.d.ts.map +1 -1
  10. package/dist/services/heartbeat.js +2 -1
  11. package/dist/services/heartbeat.js.map +1 -1
  12. package/dist/services/model-fallback.d.ts +40 -24
  13. package/dist/services/model-fallback.d.ts.map +1 -1
  14. package/dist/services/model-fallback.js +93 -61
  15. package/dist/services/model-fallback.js.map +1 -1
  16. package/dist/services/model-fallback.test.js +42 -56
  17. package/dist/services/model-fallback.test.js.map +1 -1
  18. package/node_modules/qs/.github/THREAT_MODEL.md +4 -2
  19. package/node_modules/qs/CHANGELOG.md +38 -14
  20. package/node_modules/qs/README.md +31 -3
  21. package/node_modules/qs/dist/qs.js +20 -20
  22. package/node_modules/qs/lib/parse.js +94 -4
  23. package/node_modules/qs/lib/stringify.js +4 -4
  24. package/node_modules/qs/lib/utils.js +130 -6
  25. package/node_modules/qs/package.json +4 -4
  26. package/node_modules/qs/test/parse.js +174 -0
  27. package/node_modules/qs/test/stringify.js +263 -33
  28. package/node_modules/qs/test/utils.js +184 -0
  29. package/package.json +21 -21
  30. package/ui-dist/assets/{Agents.production-B_gOaRfn.js → Agents.production-BHWznw8y.js} +2 -2
  31. package/ui-dist/assets/{CompanyActivity.production-CtbE4gxO.js → CompanyActivity.production-8aUIBuOH.js} +1 -1
  32. package/ui-dist/assets/{CompanyExport-JuOccR7C.js → CompanyExport-wysQLLwi.js} +1 -1
  33. package/ui-dist/assets/{CompanySkills.production-3LEUQ24r.js → CompanySkills.production-CeGKxxeo.js} +1 -1
  34. package/ui-dist/assets/{OrgChart.production-B41A6MKK.js → OrgChart.production-Bv25raI7.js} +1 -1
  35. package/ui-dist/assets/{RoutineDetail.production-rGdQgBL_.js → RoutineDetail.production-BP0s3T7M.js} +1 -1
  36. package/ui-dist/assets/{Routines.production-BCxCN3wv.js → Routines.production-BQL74XW8.js} +1 -1
  37. package/ui-dist/assets/{definition-B8BsU-S8.js → definition-CX59WzhL.js} +1 -1
  38. package/ui-dist/assets/{index-D6WtpzBM.js → index-DvHeLBFO.js} +88 -88
  39. package/ui-dist/index.html +1 -1
  40. package/ui-dist/sw.js +1 -1
@@ -28,6 +28,23 @@ var setMaxIndex = function setMaxIndex(obj, maxIndex) {
28
28
  overflowChannel.set(obj, maxIndex);
29
29
  };
30
30
 
31
+ // Track containers produced by a `strictMerge` conflict wrap, and their current max numeric index,
32
+ // so repeated conflicts on one path append instead of nesting a new wrapper each time
33
+ var conflictChannel = getSideChannel();
34
+
35
+ var markConflict = function markConflict(obj, maxIndex) {
36
+ conflictChannel.set(obj, maxIndex);
37
+ return obj;
38
+ };
39
+
40
+ var isConflict = function isConflict(obj) {
41
+ return conflictChannel.has(obj);
42
+ };
43
+
44
+ var getConflictIndex = function getConflictIndex(obj) {
45
+ return conflictChannel.get(obj);
46
+ };
47
+
31
48
  var hexTable = (function () {
32
49
  var array = [];
33
50
  for (var i = 0; i < 256; ++i) {
@@ -80,6 +97,30 @@ var setProperty = function setProperty(obj, key, value) {
80
97
  }
81
98
  };
82
99
 
100
+ var maxNumericIndex = function maxNumericIndex(obj, min) {
101
+ var max = min;
102
+ var keys = Object.keys(obj);
103
+ for (var i = 0; i < keys.length; ++i) {
104
+ var num = parseInt(keys[i], 10);
105
+ if (String(num) === keys[i] && num > max) {
106
+ max = num;
107
+ }
108
+ }
109
+
110
+ return max;
111
+ };
112
+
113
+ var limitedPair = function limitedPair(first, second, options) {
114
+ var pair = [first, second];
115
+ if (options && typeof options.arrayLimit === 'number' && pair.length > options.arrayLimit) {
116
+ if (options.throwOnLimitExceeded) {
117
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
118
+ }
119
+ return markOverflow(arrayToObject(pair, options), 1);
120
+ }
121
+ return pair;
122
+ };
123
+
83
124
  var merge = function merge(target, source, options) {
84
125
  /* eslint no-param-reassign: 0 */
85
126
  if (!source) {
@@ -102,8 +143,20 @@ var merge = function merge(target, source, options) {
102
143
  var newIndex = getMaxIndex(target) + 1;
103
144
  target[newIndex] = source;
104
145
  setMaxIndex(target, newIndex);
146
+ } else if (options && options.strictMerge && isConflict(target)) {
147
+ var nextConflict = getConflictIndex(target) + 1;
148
+ if (
149
+ options.throwOnLimitExceeded
150
+ && typeof options.arrayLimit === 'number'
151
+ && nextConflict >= options.arrayLimit
152
+ ) {
153
+ throw new RangeError('Array limit exceeded. Only ' + options.arrayLimit + ' element' + (options.arrayLimit === 1 ? '' : 's') + ' allowed in an array.');
154
+ }
155
+ target[nextConflict] = source;
156
+ markConflict(target, nextConflict);
105
157
  } else if (options && options.strictMerge) {
106
- return [target, source];
158
+ var conflict = limitedPair(target, source, options);
159
+ return isArray(conflict) ? markConflict(conflict, 1) : conflict;
107
160
  } else if (
108
161
  (options && (options.plainObjects || options.allowPrototypes))
109
162
  || !has.call(Object.prototype, source)
@@ -111,7 +164,7 @@ var merge = function merge(target, source, options) {
111
164
  target[source] = true;
112
165
  }
113
166
  } else {
114
- return [target, source];
167
+ return limitedPair(target, source, options);
115
168
  }
116
169
 
117
170
  return target;
@@ -143,6 +196,10 @@ var merge = function merge(target, source, options) {
143
196
  var mergeTarget = target;
144
197
  if (isArray(target) && !isArray(source)) {
145
198
  mergeTarget = arrayToObject(target, options);
199
+ if (isConflict(target)) {
200
+ // the array may have grown past its last conflict, so continue after its last index
201
+ markConflict(mergeTarget, target.length - 1);
202
+ }
146
203
  }
147
204
 
148
205
  if (isArray(target) && isArray(source)) {
@@ -177,7 +234,7 @@ var merge = function merge(target, source, options) {
177
234
  }
178
235
 
179
236
  if (isOverflow(source) && !isOverflow(acc)) {
180
- markOverflow(acc, getMaxIndex(source));
237
+ markOverflow(acc, maxNumericIndex(acc, getMaxIndex(source)));
181
238
  }
182
239
  if (isOverflow(acc)) {
183
240
  var keyNum = parseInt(key, 10);
@@ -185,6 +242,12 @@ var merge = function merge(target, source, options) {
185
242
  setMaxIndex(acc, keyNum);
186
243
  }
187
244
  }
245
+ if (isConflict(acc)) {
246
+ var conflictKey = parseInt(key, 10);
247
+ if (String(conflictKey) === key && conflictKey > getConflictIndex(acc)) {
248
+ markConflict(acc, conflictKey);
249
+ }
250
+ }
188
251
 
189
252
  return acc;
190
253
  }, mergeTarget);
@@ -198,6 +261,13 @@ var assign = function assignSingleSource(target, source) {
198
261
  };
199
262
 
200
263
  var decode = function (str, defaultDecoder, charset) {
264
+ // With no `%` there is no escape to expand, in either charset: `unescape` has
265
+ // nothing to match, and `decodeURIComponent` is the identity on such a string
266
+ // (and cannot throw). So only `+` -> ' ' remains, and often not even that.
267
+ if (typeof str === 'string' && str.indexOf('%') === -1) {
268
+ return str.indexOf('+') === -1 ? str : str.replace(/\+/g, ' ');
269
+ }
270
+
201
271
  var strWithoutPlus = str.replace(/\+/g, ' ');
202
272
  if (charset === 'iso-8859-1') {
203
273
  // unescape never throws, no try...catch needed:
@@ -211,6 +281,57 @@ var decode = function (str, defaultDecoder, charset) {
211
281
  }
212
282
  };
213
283
 
284
+ var hasToPrimitive = typeof Symbol === 'function' && typeof Symbol.toPrimitive === 'symbol';
285
+ var arrayJoin = Array.prototype.join;
286
+ var arrayToString = Array.prototype.toString;
287
+ var objectToString = Object.prototype.toString;
288
+
289
+ // an object with no callable toString (eg, from JSON, or null-prototype) would make String() or join() throw,
290
+ // so it becomes its primitive valueOf or else Object.prototype.toString; arrays are walked like join walks them,
291
+ // copied only once an element changes, with an array already being joined becoming '' as engines do for cycles
292
+ var toCoercible = function toCoercible(value, joining) {
293
+ if (!value || (typeof value !== 'object' && typeof value !== 'function')) {
294
+ return value;
295
+ }
296
+ if (hasToPrimitive && value[Symbol.toPrimitive] != null) {
297
+ return value;
298
+ }
299
+ if (isArray(value) && value.toString === arrayToString && value.join === arrayJoin) {
300
+ var ancestors = joining || getSideChannel();
301
+ if (ancestors.has(value)) {
302
+ return '';
303
+ }
304
+ ancestors.set(value, true);
305
+ var copy;
306
+ for (var i = 0; i < value.length; i += 1) {
307
+ var item = value[i];
308
+ var coerced = toCoercible(item, ancestors);
309
+ if (!copy && coerced !== item) {
310
+ copy = [];
311
+ for (var j = 0; j < i; j += 1) {
312
+ copy[j] = value[j];
313
+ }
314
+ }
315
+ if (copy) {
316
+ copy[i] = coerced;
317
+ }
318
+ }
319
+ ancestors['delete'](value);
320
+ return copy || value;
321
+ }
322
+ if (typeof value.toString === 'function') {
323
+ return value;
324
+ }
325
+ var valueOf = value.valueOf;
326
+ if (typeof valueOf === 'function') {
327
+ var primitive = valueOf.call(value);
328
+ if (!primitive || (typeof primitive !== 'object' && typeof primitive !== 'function')) {
329
+ return String(primitive);
330
+ }
331
+ }
332
+ return objectToString.call(value);
333
+ };
334
+
214
335
  var limit = 1024;
215
336
 
216
337
  /* eslint operator-linebreak: [2, "before"] */
@@ -226,13 +347,15 @@ var encode = function encode(str, defaultEncoder, charset, kind, format) {
226
347
  if (typeof str === 'symbol') {
227
348
  string = Symbol.prototype.toString.call(str);
228
349
  } else if (typeof str !== 'string') {
229
- string = String(str);
350
+ string = String(toCoercible(str));
230
351
  }
231
352
 
232
353
  if (charset === 'iso-8859-1') {
354
+ // `escape` leaves `+` untouched, but a literal `+` is decoded back to a space,
355
+ // so it must be percent-encoded to round-trip (the utf-8 path already does this).
233
356
  return escape(string).replace(/%u[0-9a-f]{4}/gi, function ($0) {
234
357
  return '%26%23' + parseInt($0.slice(2), 16) + '%3B';
235
- });
358
+ }).replace(/\+/g, '%2B');
236
359
  }
237
360
 
238
361
  var out = '';
@@ -384,5 +507,6 @@ module.exports = {
384
507
  isRegExp: isRegExp,
385
508
  markOverflow: markOverflow,
386
509
  maybeMap: maybeMap,
387
- merge: merge
510
+ merge: merge,
511
+ toCoercible: toCoercible
388
512
  };
@@ -2,7 +2,7 @@
2
2
  "name": "qs",
3
3
  "description": "A querystring parser that supports nesting and arrays, with a depth limit",
4
4
  "homepage": "https://github.com/ljharb/qs",
5
- "version": "6.16.0",
5
+ "version": "6.16.1",
6
6
  "repository": {
7
7
  "type": "git",
8
8
  "url": "https://github.com/ljharb/qs.git"
@@ -37,13 +37,13 @@
37
37
  "devDependencies": {
38
38
  "@browserify/envify": "^6.0.0",
39
39
  "@browserify/uglifyify": "^6.0.0",
40
- "@ljharb/eslint-config": "^22.2.4",
40
+ "@ljharb/eslint-config": "^22.2.5",
41
41
  "browserify": "^16.5.2",
42
42
  "bundle-collapser": "^1.4.0",
43
43
  "common-shakeify": "~1.0.0",
44
44
  "eclint": "^2.8.1",
45
45
  "es-value-fixtures": "^1.7.1",
46
- "eslint": "^10.9.0",
46
+ "eslint": "^10.12.0",
47
47
  "evalmd": "^0.0.22",
48
48
  "for-each": "^0.3.5",
49
49
  "glob": "=10.3.7",
@@ -65,7 +65,7 @@
65
65
  "qs-iconv": "^1.0.4",
66
66
  "safe-publish-latest": "^2.0.0",
67
67
  "safer-buffer": "^2.1.2",
68
- "tape": "^5.10.2",
68
+ "tape": "^5.10.3",
69
69
  "unassertify": "^3.0.1"
70
70
  },
71
71
  "scripts": {
@@ -62,10 +62,39 @@ test('parse()', function (t) {
62
62
  st.end();
63
63
  });
64
64
 
65
+ t.test('comma: `[]=` in a value does not wrap the value in an extra array', function (st) {
66
+ st.deepEqual(qs.parse('a[b]=1,2,3[]=x', { comma: true }), { a: { b: ['1', '2', '3[]=x'] } });
67
+ st.deepEqual(qs.parse('a[b]=1,2[]=3', { comma: true }), { a: { b: ['1', '2[]=3'] } });
68
+ st.deepEqual(qs.parse('a[]=1,2,3', { comma: true }), { a: [['1', '2', '3']] });
69
+ st.end();
70
+ });
71
+
65
72
  t.test('allows enabling dot notation', function (st) {
66
73
  st.deepEqual(qs.parse('a.b=c'), { 'a.b': 'c' });
67
74
  st.deepEqual(qs.parse('a.b=c', { allowDots: true }), { a: { b: 'c' } });
68
75
 
76
+ // a literal dot inside a bracket segment must not be rewritten by allowDots (#617)
77
+ st.deepEqual(
78
+ qs.parse('outer[a.b]=1', { allowDots: true }),
79
+ { outer: { 'a.b': '1' } },
80
+ 'a dot inside brackets stays part of the key when allowDots is true'
81
+ );
82
+ st.deepEqual(
83
+ qs.parse('outer[a.b]=1', { allowDots: true }),
84
+ qs.parse('outer[a.b]=1', { allowDots: false }),
85
+ 'allowDots does not change how a bracket segment containing a dot is parsed'
86
+ );
87
+ st.deepEqual(
88
+ qs.parse('a].b=c', { allowDots: true }),
89
+ { 'a]': { b: 'c' } },
90
+ 'a stray close bracket does not stop a later dot from being converted'
91
+ );
92
+ st.deepEqual(
93
+ qs.parse('a]].b[c.d]=e', { allowDots: true }),
94
+ { 'a]]': { b: { 'c.d': 'e' } } },
95
+ 'stray close brackets do not unbalance a later bracket segment'
96
+ );
97
+
69
98
  st.end();
70
99
  });
71
100
 
@@ -360,6 +389,14 @@ test('parse()', function (t) {
360
389
  st.end();
361
390
  });
362
391
 
392
+ t.test('currently moves the key/value split to a later `]=` when the first `=` comes before any bracket group', function (st) {
393
+ characterizeParse(st, 'a=b[c]=d', undefined, { 'a=b': { c: 'd' } }, 'literal brackets after the first `=`');
394
+ characterizeParse(st, 'q=%5B1%5D=2', undefined, { 'q=': ['2'] }, 'encoded brackets collapse to an array');
395
+ characterizeParse(st, 'role=guest%5Bx%5Dy%5D=z', undefined, { 'role=guest': { x: 'z' } }, 'group opens after the first `=`, and the text after it is dropped');
396
+ characterizeParse(st, 'role=guest%5D=admin', undefined, { 'role=guest]': 'admin' }, 'encoded `]=` with no bracket group at all');
397
+ st.end();
398
+ });
399
+
363
400
  t.test('allows to specify array indices', function (st) {
364
401
  st.deepEqual(qs.parse('a[1]=c&a[0]=b&a[2]=d'), { a: ['b', 'c', 'd'] });
365
402
  st.deepEqual(qs.parse('a[1]=c&a[0]=b'), { a: ['b', 'c'] });
@@ -745,6 +782,38 @@ test('parse()', function (t) {
745
782
  st.end();
746
783
  });
747
784
 
785
+ t.test('does not mutate an object input', function (st) {
786
+ var arr = ['x'];
787
+ st.deepEqual(qs.parse({ a: arr, 'a[]': 'y' }), { a: ['x', 'y'] }, 'appends to a copy of an input array');
788
+ st.deepEqual(arr, ['x'], 'the input array is unchanged');
789
+
790
+ var nested = { b: ['x'] };
791
+ st.deepEqual(qs.parse({ a: nested, 'a[b][]': 'y' }), { a: { b: ['x', 'y'] } }, 'appends to a copy of a nested input array');
792
+ st.deepEqual(nested, { b: ['x'] }, 'the nested input array is unchanged');
793
+
794
+ var obj = { b: 'c' };
795
+ st.deepEqual(qs.parse({ a: obj, 'a[d]': 'e' }), { a: { b: 'c', d: 'e' } }, 'merges into a copy of an input object');
796
+ st.deepEqual(obj, { b: 'c' }, 'the input object is unchanged');
797
+
798
+ var sparse = { b: [1, undefined, 2] };
799
+ st.deepEqual(qs.parse({ a: sparse }), { a: { b: [1, 2] } }, 'compacts a copy of an input array');
800
+ st.deepEqual(sparse, { b: [1, undefined, 2] }, 'the input array is not compacted');
801
+
802
+ var nullProto = { __proto__: null, b: 'c' };
803
+ st.equal(Object.getPrototypeOf(qs.parse({ a: nullProto }).a), null, 'a null-prototype input object stays null-prototype');
804
+
805
+ var date = new Date(0);
806
+ st.equal(qs.parse({ a: date }).a, date, 'a non-plain object is not copied');
807
+
808
+ var cyclic = { b: 'c' };
809
+ cyclic.self = cyclic;
810
+ var result = qs.parse({ a: cyclic });
811
+ st.notEqual(result.a, cyclic, 'a cyclic input object is copied');
812
+ st.equal(result.a.self, result.a, 'the copy keeps the cycle');
813
+
814
+ st.end();
815
+ });
816
+
748
817
  t.test('does not blow up when Buffer global is missing', function (st) {
749
818
  var restore = mockProperty(global, 'Buffer', { 'delete': true });
750
819
 
@@ -962,6 +1031,38 @@ test('parse()', function (t) {
962
1031
  st.end();
963
1032
  });
964
1033
 
1034
+ t.test('strictMerge conflicts do not overwrite indexed values already at that path', function (st) {
1035
+ st.deepEqual(
1036
+ qs.parse('a[z]=o&a=p&a[y]=q&a[2]=keep&[a]=r'),
1037
+ { a: { 0: { z: 'o' }, 1: 'p', 2: 'keep', 3: 'r', y: 'q' } },
1038
+ 'an indexed key merged into the container is kept'
1039
+ );
1040
+
1041
+ st.deepEqual(
1042
+ qs.parse('a[z]=o&a=p&a[2]=keep&a[y]=q&[a]=r'),
1043
+ { a: { 0: { z: 'o' }, 1: 'p', 2: 'keep', 3: 'r', y: 'q' } },
1044
+ 'an indexed key that extended the container while it was an array is kept'
1045
+ );
1046
+
1047
+ st.end();
1048
+ });
1049
+
1050
+ t.test('strictMerge conflicts do not overwrite values once the container overflows', function (st) {
1051
+ st.deepEqual(
1052
+ qs.parse('a[z]=v0&a=v1&a[y]=v2&[a]=v3&[a]x=v4&a[2]=v5&[a]x1=v6', { arrayLimit: 2 }),
1053
+ { a: { 0: { z: 'v0' }, 1: 'v1', 2: ['v3', 'v5'], 3: 'v4', 4: 'v6', y: 'v2' } },
1054
+ 'a conflict appended after the container overflowed keeps every value'
1055
+ );
1056
+
1057
+ st.deepEqual(
1058
+ qs.parse('a[2]=v1&a=v5&a=v7&[a]x=v9', { parseArrays: false, arrayLimit: 1 }),
1059
+ { a: { 0: 'v5', 1: 'v7', 2: 'v1', 3: 'v9' } },
1060
+ 'an index the container already holds is not reused'
1061
+ );
1062
+
1063
+ st.end();
1064
+ });
1065
+
965
1066
  t.test('dunder proto is ignored', function (st) {
966
1067
  var payload = 'categories[__proto__]=login&categories[__proto__]&categories[length]=42';
967
1068
  var result = qs.parse(payload, { allowPrototypes: true });
@@ -1463,6 +1564,57 @@ test('parse()', function (t) {
1463
1564
  sst.end();
1464
1565
  });
1465
1566
 
1567
+ st.test('enforces arrayLimit when a strictMerge conflict appends to its container', function (sst) {
1568
+ var aliased = 'a[z]=o&a=p&a[y]=q&[a]=r';
1569
+ sst['throws'](
1570
+ function () { qs.parse(aliased, { arrayLimit: 2, throwOnLimitExceeded: true }); },
1571
+ new RangeError('Array limit exceeded. Only 2 elements allowed in an array.'),
1572
+ 'a third conflicting value throws once the container would exceed arrayLimit'
1573
+ );
1574
+ sst.deepEqual(
1575
+ qs.parse(aliased, { arrayLimit: 3, throwOnLimitExceeded: true }),
1576
+ { a: { 0: { z: 'o' }, 1: 'p', 2: 'r', y: 'q' } },
1577
+ 'appends while the container stays within arrayLimit'
1578
+ );
1579
+ sst.deepEqual(
1580
+ qs.parse(aliased, { arrayLimit: 2 }),
1581
+ { a: { 0: { z: 'o' }, 1: 'p', 2: 'r', y: 'q' } },
1582
+ 'appends without throwing when throwOnLimitExceeded is not set'
1583
+ );
1584
+
1585
+ sst.end();
1586
+ });
1587
+
1588
+ st.test('enforces arrayLimit when differently spelled keys collide', function (sst) {
1589
+ sst['throws'](
1590
+ function () { qs.parse('[a]=x&a=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
1591
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
1592
+ 'two primitives throw, as `a=x&a=y` does'
1593
+ );
1594
+ sst['throws'](
1595
+ function () { qs.parse('a[b]=x&a=y', { arrayLimit: 1, throwOnLimitExceeded: true }); },
1596
+ new RangeError('Array limit exceeded. Only 1 element allowed in an array.'),
1597
+ 'an object and a primitive throw under strictMerge'
1598
+ );
1599
+ sst.deepEqual(
1600
+ qs.parse('[a]=x&a=y', { arrayLimit: 1 }),
1601
+ { a: { 0: 'x', 1: 'y' } },
1602
+ 'without throwOnLimitExceeded, two primitives convert to an object, as `a=x&a=y` does'
1603
+ );
1604
+ sst.deepEqual(
1605
+ qs.parse('a[b]=x&a=y', { arrayLimit: 1 }),
1606
+ { a: { 0: { b: 'x' }, 1: 'y' } },
1607
+ 'and so do an object and a primitive'
1608
+ );
1609
+ sst.deepEqual(
1610
+ qs.parse('a[b]=x&a=y', { arrayLimit: 2, throwOnLimitExceeded: true }),
1611
+ { a: [{ b: 'x' }, 'y'] },
1612
+ 'a collision within arrayLimit is still an array'
1613
+ );
1614
+
1615
+ sst.end();
1616
+ });
1617
+
1466
1618
  st.test('does not throw when cumulative comma combine stays within arrayLimit', function (sst) {
1467
1619
  var result = qs.parse('a=1,2,3&a=4', { comma: true, arrayLimit: 5, throwOnLimitExceeded: true });
1468
1620
  sst.deepEqual(result, { a: ['1', '2', '3', '4'] }, 'combined array within limit is preserved');
@@ -1807,6 +1959,28 @@ test('`duplicates` option', function (t) {
1807
1959
  st.end();
1808
1960
  });
1809
1961
 
1962
+ t.test('`[]=` in a value does not force combining', function (st) {
1963
+ st.deepEqual(
1964
+ qs.parse('a[b]=1&a[b]=2[]=3', { duplicates: 'last' }),
1965
+ { a: { b: '2[]=3' } },
1966
+ 'duplicates last: `[]=` in the value is not a bracketed key'
1967
+ );
1968
+
1969
+ st.deepEqual(
1970
+ qs.parse('a[b]=1&a[b]=2[]=3', { duplicates: 'first' }),
1971
+ { a: { b: '1' } },
1972
+ 'duplicates first: `[]=` in the value is not a bracketed key'
1973
+ );
1974
+
1975
+ st.deepEqual(
1976
+ qs.parse('a[b]=1&a[b]=2[]=3', { duplicates: 'combine' }),
1977
+ { a: { b: ['1', '2[]=3'] } },
1978
+ 'duplicates combine: still combines'
1979
+ );
1980
+
1981
+ st.end();
1982
+ });
1983
+
1810
1984
  t.end();
1811
1985
  });
1812
1986