@synnaxlabs/x 0.32.0 → 0.33.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 (79) hide show
  1. package/.turbo/turbo-build.log +23 -23
  2. package/dist/binary.cjs +1 -1
  3. package/dist/binary.js +1 -1
  4. package/dist/bounds-CCueigU3.js +171 -0
  5. package/dist/bounds-Cudf5M4H.cjs +1 -0
  6. package/dist/bounds.cjs +1 -1
  7. package/dist/bounds.js +1 -1
  8. package/dist/box-CZVdKCOc.cjs +1 -0
  9. package/dist/box-hAkmDC3K.js +201 -0
  10. package/dist/box.cjs +1 -1
  11. package/dist/box.js +1 -1
  12. package/dist/caseconv.cjs +1 -1
  13. package/dist/caseconv.js +1 -1
  14. package/dist/index-By0n2R_b.cjs +1 -0
  15. package/dist/{index-DgaYJC35.cjs → index-DQZfhLnw.cjs} +1 -1
  16. package/dist/{index-Duv1uH08.js → index-q_1Jz5rY.js} +5 -5
  17. package/dist/{index-B5THJ1eb.js → index-zsix_qnl.js} +1 -1
  18. package/dist/index.cjs +2 -2
  19. package/dist/index.js +163 -146
  20. package/dist/{location-DjcaXEps.js → location-B5rSnQP3.js} +1 -1
  21. package/dist/{location-gPB1RtfA.cjs → location-YGxhLPDy.cjs} +1 -1
  22. package/dist/location.cjs +1 -1
  23. package/dist/location.js +1 -1
  24. package/dist/{position-DkON65EZ.js → position-BZOTg74V.js} +2 -2
  25. package/dist/{position-C71OiHiw.cjs → position-CjNCcq8X.cjs} +1 -1
  26. package/dist/position.cjs +1 -1
  27. package/dist/position.js +1 -1
  28. package/dist/{scale-COPgp55a.cjs → scale-BvbW9p2C.cjs} +1 -1
  29. package/dist/{scale-qw6vRO4s.js → scale-C7_4I3pa.js} +3 -3
  30. package/dist/scale.cjs +1 -1
  31. package/dist/scale.js +1 -1
  32. package/dist/series-BId9slhU.cjs +11 -0
  33. package/dist/{series-B5eA90Ci.js → series-CZw97Bq2.js} +619 -456
  34. package/dist/spatial.cjs +1 -1
  35. package/dist/spatial.js +5 -5
  36. package/dist/src/caseconv/caseconv.d.ts.map +1 -1
  37. package/dist/src/deep/path.d.ts +1 -1
  38. package/dist/src/deep/path.d.ts.map +1 -1
  39. package/dist/src/math/math.d.ts +26 -6
  40. package/dist/src/math/math.d.ts.map +1 -1
  41. package/dist/src/math/math.spec.d.ts +2 -0
  42. package/dist/src/math/math.spec.d.ts.map +1 -0
  43. package/dist/src/record.d.ts +4 -0
  44. package/dist/src/record.d.ts.map +1 -1
  45. package/dist/src/spatial/bounds/bounds.d.ts +204 -2
  46. package/dist/src/spatial/bounds/bounds.d.ts.map +1 -1
  47. package/dist/src/spatial/box/box.d.ts +4 -4
  48. package/dist/src/spatial/box/box.d.ts.map +1 -1
  49. package/dist/src/strings/strings.d.ts +14 -0
  50. package/dist/src/strings/strings.d.ts.map +1 -1
  51. package/dist/src/telem/series.d.ts +35 -10
  52. package/dist/src/telem/series.d.ts.map +1 -1
  53. package/dist/src/telem/telem.d.ts +12 -10
  54. package/dist/src/telem/telem.d.ts.map +1 -1
  55. package/dist/telem.cjs +1 -1
  56. package/dist/telem.js +1 -1
  57. package/package.json +9 -9
  58. package/src/caseconv/caseconv.ts +1 -0
  59. package/src/deep/path.ts +1 -1
  60. package/src/math/math.spec.ts +149 -0
  61. package/src/math/math.ts +60 -9
  62. package/src/record.ts +5 -0
  63. package/src/spatial/bounds/bounds.spec.ts +135 -270
  64. package/src/spatial/bounds/bounds.ts +290 -25
  65. package/src/spatial/box/box.ts +9 -5
  66. package/src/strings/strings.spec.ts +33 -1
  67. package/src/strings/strings.ts +52 -0
  68. package/src/telem/series.spec.ts +235 -0
  69. package/src/telem/series.ts +271 -52
  70. package/src/telem/telem.spec.ts +22 -0
  71. package/src/telem/telem.ts +44 -20
  72. package/src/zodutil/zodutil.spec.ts +5 -7
  73. package/tsconfig.tsbuildinfo +1 -1
  74. package/dist/bounds-CpboA0q6.js +0 -127
  75. package/dist/bounds-ZZc1c-_Z.cjs +0 -1
  76. package/dist/box-BQID-0jO.cjs +0 -1
  77. package/dist/box-xRqO6NvI.js +0 -202
  78. package/dist/index-xk130iQA.cjs +0 -1
  79. package/dist/series-CJ65b1Uz.cjs +0 -11
@@ -222,14 +222,89 @@ export const linspace = <T extends numeric.Value = number>(bounds: Crude<T>): T[
222
222
  }) as T[];
223
223
  };
224
224
 
225
+ /**
226
+ * Finds the index and position where a target value should be inserted into an array
227
+ * of bounds.
228
+ *
229
+ * Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
230
+ *
231
+ * @template T
232
+ * @param {Array<Crude<T>>} bounds - An array of crude bounds. Each bound can either be
233
+ * an array of length 2 or an object with `lower` and `upper` properties.
234
+ * @param {T} target - The target value to insert.
235
+ *
236
+ * @returns {{ index: number, position: number }} An object containing:
237
+ * - `index`: The index in the bounds array where the target belongs.
238
+ * - `position`: The position within the bound where the target fits. If the target is
239
+ * outside all bounds, the index will be where a new bound can be inserted.
240
+ *
241
+ * @example
242
+ * // Target within an existing bound
243
+ * const bounds = [[0, 10], [20, 30]];
244
+ * const target = 5;
245
+ * const result = findInsertPosition(bounds, target);
246
+ * // { index: 0, position: 5 }
247
+ *
248
+ * @example
249
+ * // Target greater than all bounds
250
+ * const bounds = [[0, 10], [20, 30]];
251
+ * const target = 35;
252
+ * const result = findInsertPosition(bounds, target);
253
+ * // { index: 2, position: 0 }
254
+ *
255
+ * @example
256
+ * // Target less than all bounds
257
+ * const bounds = [[10, 20], [30, 40]];
258
+ * const target = 5;
259
+ * const result = findInsertPosition(bounds, target);
260
+ * // { index: 0, position: 0 }
261
+ *
262
+ * @example
263
+ * // Target overlaps between bounds
264
+ * const bounds = [[0, 10], [20, 30]];
265
+ * const target = 15;
266
+ * const result = findInsertPosition(bounds, target);
267
+ * // { index: 1, position: 0 }
268
+ *
269
+ * @example
270
+ * // Empty bounds array
271
+ * const bounds = [];
272
+ * const target = 5;
273
+ * const result = findInsertPosition(bounds, target);
274
+ * // { index: 0, position: 0 }
275
+ *
276
+ * @example
277
+ * // Target exactly at lower bound
278
+ * const bounds = [[0, 10], [20, 30]];
279
+ * const target = 10;
280
+ * const result = findInsertPosition(bounds, target);
281
+ * // { index: 1, position: 0 }
282
+ *
283
+ * @example
284
+ * // Target exactly at upper bound
285
+ * const bounds = [[0, 10], [20, 30]];
286
+ * const target = 30;
287
+ * const result = findInsertPosition(bounds, target);
288
+ * // { index: 2, position: 0 }
289
+ *
290
+ * @example
291
+ * // Target inside bounds with exact fit
292
+ * const bounds = [[0, 5], [5, 10]];
293
+ * const target = 5;
294
+ * const result = findInsertPosition(bounds, target);
295
+ * // { index: 1, position: 0 }
296
+ *
297
+ * @throws {Error} If invalid bounds are provided, such as bounds arrays not being of
298
+ * length 2.
299
+ *
300
+ * See {@link construct} for constructing valid bounds.
301
+ */
225
302
  export const findInsertPosition = <T extends numeric.Value>(
226
303
  bounds: Array<Crude<T>>,
227
304
  target: T,
228
305
  ): { index: number; position: number } => {
229
306
  const _bounds = bounds.map((b) => construct<T>(b));
230
- const index = _bounds.findIndex(
231
- (b, i) => contains<T>(b, target) || target < _bounds[i].lower,
232
- );
307
+ const index = _bounds.findIndex((b) => contains<T>(b, target) || target < b.lower);
233
308
  if (index === -1) return { index: bounds.length, position: 0 };
234
309
  const b = _bounds[index];
235
310
  if (contains(b, target)) return { index, position: Number(target - b.lower) };
@@ -265,6 +340,7 @@ const ZERO_PLAN: InsertionPlan = {
265
340
  * that may overlap. The plan is used to determine how to splice the new array into the
266
341
  * existing array. The following are important constraints:
267
342
  *
343
+ * Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
268
344
  *
269
345
  * 1. If the new bound is entirely contained within an existing bound, the new bound
270
346
  * is not inserted and the plan is null.
@@ -318,29 +394,218 @@ export const buildInsertionPlan = <T extends numeric.Value>(
318
394
  };
319
395
  };
320
396
 
321
- export const insert = <T extends numeric.Value = number>(
397
+ /**
398
+ * Traverse the given bounds by the specified distance, starting from a given point, and
399
+ * return the end point of the traversal. The traversal 'skips' over integers that are
400
+ * not within the array of bounds, moving only within the defined bounds. Traversing
401
+ * across multiple bounds is handled smoothly, with direction determined by the sign of
402
+ * the distance.
403
+ *
404
+ * Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
405
+ *
406
+ * If the distance takes the traversal beyond the bounds, it returns the last valid point
407
+ * within the bounds or the first valid point depending on direction.
408
+ *
409
+ * @template T
410
+ * @param {Array<Crude<T>>} bounds - An array of crude bounds (array of length 2 or
411
+ * objects with `lower` and `upper` properties).
412
+ * @param {T} start - The starting point of the traversal.
413
+ * @param {T} dist - The distance to traverse. Positive values move forwards, and
414
+ * negative values move backwards.
415
+ *
416
+ * Edge Cases:
417
+ *
418
+ * 1. **Traversal beyond the last bound**: If the traversal moves beyond the last
419
+ * bound (in either direction), the traversal ends at the last valid position within
420
+ * the bounds.
421
+ * - Example: `traverse([[0, 10], [20, 30]], 25, 10); // => 30`
422
+ * (stops at the upper limit of the last bound)
423
+ *
424
+ * 2. **Traversal from a point outside the bounds**: If the starting point is outside
425
+ * the bounds and the traversal distance would move within bounds, it finds the
426
+ * closest bound and continues traversal from there.
427
+ * - Example: `traverse([[0, 10], [20, 30]], 15, 5); // => 25` (enters the second bound)
428
+ *
429
+ * 3. **Distance of 0**: If the distance is `0`, the traversal will return the starting
430
+ * point without moving.
431
+ * - Example: `traverse([[0, 10], [20, 30]], 5, 0); // => 5`
432
+ *
433
+ * @returns {T} The end point of the traversal within the bounds.
434
+ *
435
+ * @example
436
+ * // Traversing 5 units forward from 5, ending exactly at the upper bound of the first
437
+ * range.
438
+ * traverse([[0, 10], [20, 30]], 5, 5);
439
+ * // => 10
440
+ *
441
+ * @example
442
+ * // Traversing 10 units forward from 5, crossing from the first range to the second.
443
+ * traverse([[0, 10], [20, 30]], 5, 10);
444
+ * // => 25
445
+ *
446
+ * @example
447
+ * // Traversing 5 units forward starting outside the bounds, the traversal enters the
448
+ * // second bound.
449
+ * traverse([[0, 10], [20, 30]], 15, 5);
450
+ * // => 25
451
+ *
452
+ * @example
453
+ * // Traversing 30 units forward, stopping at the upper end of the second bound.
454
+ * traverse([[0, 10], [20, 30]], 15, 30);
455
+ * // => 30
456
+ *
457
+ * @example
458
+ * // Traversing 7 units backward starting from 17, moving into the first bound.
459
+ * traverse([[0, 5], [5, 10], [15, 20]], 17, -7);
460
+ * // => 5
461
+ *
462
+ * @example
463
+ * // Traversing beyond the last bound in a positive direction.
464
+ * traverse([[0, 10], [20, 30]], 25, 10);
465
+ * // => 30 (stops at the upper limit of the last bound)
466
+ *
467
+ * @example
468
+ * // Traversing backward from a point not within any bound.
469
+ * traverse([[0, 5], [10, 15]], 20, -10);
470
+ * // => 15 (stops at the upper limit of the nearest previous bound)
471
+ *
472
+ * @example
473
+ * // Traversing a distance of 0 from a point returns the starting point.
474
+ * traverse([[0, 10], [20, 30]], 5, 0);
475
+ * // => 5
476
+ *
477
+ * @throws {Error} If invalid bounds are provided, such as bounds arrays not being of
478
+ * length 2.
479
+ *
480
+ * See {@link construct} for constructing valid bounds.
481
+ *
482
+ */
483
+ export const traverse = <T extends numeric.Value = number>(
322
484
  bounds: Array<Crude<T>>,
323
- value: Crude<T>,
324
- ): Array<Bounds<T>> => {
325
- const plan = buildInsertionPlan(bounds, value);
326
- const out = bounds.map((b) => construct(b));
327
- if (plan == null) return out;
328
- const _target = construct(value);
329
- _target.lower = math.add(_target.lower, plan.removeBefore);
330
- _target.upper = math.sub(_target.upper, plan.removeAfter);
331
- out.splice(plan.insertInto, plan.deleteInBetween, _target);
332
- return out;
485
+ start: T,
486
+ dist: T,
487
+ ): T => {
488
+ const _bounds = bounds.map((b) => construct(b));
489
+
490
+ const dir = dist > 0 ? 1 : dist < 0 ? -1 : 0;
491
+
492
+ // If there's no distance to traverse, return the starting point
493
+ if (dir === 0) return start;
494
+
495
+ let remainingDist = dist;
496
+ let currentPosition = start as number | bigint;
497
+
498
+ while (math.equal(remainingDist, 0) === false) {
499
+ // Find the bound we're currently in or adjacent to
500
+ const index = _bounds.findIndex((b) => {
501
+ if (dir > 0) return currentPosition >= b.lower && currentPosition < b.upper;
502
+ return currentPosition > b.lower && currentPosition <= b.upper;
503
+ });
504
+
505
+ if (index !== -1) {
506
+ const b = _bounds[index];
507
+ let distanceInBound: T;
508
+ if (dir > 0) distanceInBound = math.sub(b.upper, currentPosition) as T;
509
+ else distanceInBound = math.sub(currentPosition, b.lower) as T;
510
+
511
+ if (distanceInBound > (0 as T)) {
512
+ const moveDist = math.min(math.abs(remainingDist), distanceInBound) as T;
513
+ currentPosition = math.add(
514
+ currentPosition,
515
+ dir > 0 ? moveDist : -moveDist,
516
+ ) as T;
517
+ remainingDist = math.sub(remainingDist, dir > 0 ? moveDist : -moveDist) as T;
518
+
519
+ // If we've exhausted the distance, return the current position
520
+ if (math.equal(remainingDist, 0)) return currentPosition as T;
521
+ continue;
522
+ }
523
+ }
524
+
525
+ // If we're not inside any bound, or we've reached the boundary
526
+ if (dir > 0) {
527
+ // Move to the next bound's lower value
528
+ const nextBounds = _bounds.filter((b) => b.lower > currentPosition);
529
+ if (nextBounds.length > 0) currentPosition = nextBounds[0].lower;
530
+ // No more bounds in this direction
531
+ else return currentPosition as T;
532
+ } else {
533
+ // Move to the previous bound's upper value
534
+ const prevBounds = _bounds.filter((b) => b.upper < currentPosition);
535
+ if (prevBounds.length > 0)
536
+ currentPosition = prevBounds[prevBounds.length - 1].upper;
537
+ // No more bounds in this direction
538
+ else return currentPosition as T;
539
+ }
540
+ }
541
+ return currentPosition as T;
333
542
  };
334
543
 
335
- export const exposure = <T extends numeric.Value = number>(
336
- background_: Crude<T>,
337
- filter_: Crude<T>,
338
- ): number => {
339
- const bg = construct(background_);
340
- const f = construct(filter_);
341
- if (bg.upper <= f.lower || bg.lower >= f.upper) return 0;
342
- if (bg.lower >= f.lower && bg.upper <= f.upper) return 1;
343
- if (bg.lower <= f.lower && bg.upper >= f.upper) return span(f) / span(bg);
344
- if (bg.lower <= f.lower) return (bg.upper - f.lower) / span(bg);
345
- return (f.upper - bg.lower) / span(bg);
544
+ /**
545
+ * Returns the number of values within the given bounds, 'skip'ing over values that are
546
+ * not within the bounds.
547
+ *
548
+ * Crucially, this function assumes that the bounds are ORDERED and NON-OVERLAPPING.
549
+ *
550
+ * @example
551
+ * bounds.distance(
552
+ * [[0, 10], [20, 30]]
553
+ * 5,
554
+ * 5,
555
+ * ) // => 0
556
+ *
557
+ * @example
558
+ * bounds.distance(
559
+ * [[0, 10], [20, 30]]
560
+ * 5,
561
+ * 25,
562
+ * ) // => 10
563
+ *
564
+ * @example
565
+ * bounds.distance(
566
+ * [[0, 10], [20, 30]]
567
+ * 15,
568
+ * 25,
569
+ * ) // => 5
570
+ *
571
+ * @example
572
+ * bounds.distance(
573
+ * [[0, 10], [20, 30]]
574
+ * 15,
575
+ * 5,
576
+ * ) // => 5
577
+ *
578
+ * @param bounds
579
+ * @param a - The start value.
580
+ * @param b - The end value.
581
+ */
582
+ export const distance = <T extends numeric.Value = number>(
583
+ bounds: Array<Crude<T>>,
584
+ a: T,
585
+ b: T,
586
+ ): T => {
587
+ const _bounds = bounds.map((b) => construct<T>(b));
588
+
589
+ // If start and end are the same, the distance is zero
590
+ if (a === b) return (typeof a === "bigint" ? 0n : 0) as T;
591
+
592
+ // Determine the interval between a and b
593
+ const interval = a < b ? construct([a, b]) : construct([b, a]);
594
+
595
+ let totalDistance: T = (typeof a === "bigint" ? 0n : 0) as T;
596
+
597
+ for (const bound of _bounds) {
598
+ // Find the overlap between the interval and the current bound
599
+ const overlapLower = bound.lower > interval.lower ? bound.lower : interval.lower;
600
+ const overlapUpper = bound.upper < interval.upper ? bound.upper : interval.upper;
601
+
602
+ // If there is an overlap, add its span to the total distance
603
+ if (overlapLower < overlapUpper) {
604
+ const overlapSpan = (overlapUpper - overlapLower) as T;
605
+ // @ts-expect-error - typescript doesn't recognize that totalDistance is a number
606
+ totalDistance = (totalDistance + overlapSpan) as T;
607
+ }
608
+ }
609
+
610
+ return totalDistance;
346
611
  };
@@ -17,13 +17,13 @@ import * as xy from "@/spatial/xy/xy";
17
17
 
18
18
  const cssPos = z.union([z.number(), z.string()]);
19
19
 
20
- const cssBox = z.object({
20
+ export const cssBox = z.object({
21
21
  top: cssPos,
22
22
  left: cssPos,
23
23
  width: cssPos,
24
24
  height: cssPos,
25
25
  });
26
- const domRect = z.object({
26
+ export const domRect = z.object({
27
27
  left: z.number(),
28
28
  top: z.number(),
29
29
  right: z.number(),
@@ -60,7 +60,7 @@ export const copy = (b: Box, root?: location.CornerXY): Box => ({
60
60
  * Box represents a general box in 2D space. It typically represents a bounding box
61
61
  * for a DOM element, but can also represent a box in clip space or decimal space.
62
62
  *
63
- * It'simportant to note that the behavior of a Box varies depending on its coordinate
63
+ * It's important to note that the behavior of a Box varies depending on its coordinate
64
64
  * system.Make sure you're aware of which coordinate system you're using.
65
65
  *
66
66
  * Many of the properties and methods on a Box access the same semantic value. The
@@ -157,7 +157,11 @@ export const resize: Resize = (
157
157
  * @param inclusive - Whether the edges of the box are inclusive or exclusive.
158
158
  * @returns true if the box inclusively contains the point or box and false otherwise.
159
159
  */
160
- export const contains = (container: Crude, value: Box | xy.XY, inclusive: boolean = true): boolean => {
160
+ export const contains = (
161
+ container: Crude,
162
+ value: Box | xy.XY,
163
+ inclusive: boolean = true,
164
+ ): boolean => {
161
165
  const b_ = construct(container);
162
166
  let comp = (a: number, b: number) => a < b;
163
167
  if (inclusive) comp = (a: number, b: number) => a <= b;
@@ -352,7 +356,7 @@ export const edgePoints = (b: Crude, loc: location.Location): [xy.XY, xy.XY] =>
352
356
  *
353
357
  * @param target The box to reposition - Only works if the root is topLeft
354
358
  * @param bound The box to reposition within - Only works if the root is topLeft
355
- * @returns the repsoitioned box
359
+ * @returns the repositioned box
356
360
  */
357
361
  export const positionInCenter = (target_: Crude, bound_: Crude): Box => {
358
362
  const target = construct(target_);
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { describe, expect, it } from "vitest";
11
11
 
12
- import { naturalLanguageJoin } from "@/strings/strings";
12
+ import { generateShortIdentifiers, naturalLanguageJoin } from "@/strings/strings";
13
13
 
14
14
  describe("naturalLanguageJoin", () => {
15
15
  it("should return an empty string for an empty array", () =>
@@ -34,3 +34,35 @@ describe("naturalLanguageJoin", () => {
34
34
  "apple, banana, cherry, and date",
35
35
  ));
36
36
  });
37
+
38
+ describe("generateShortIdentifiers", () => {
39
+ it("should generate identifiers for a single word", () =>
40
+ expect(generateShortIdentifiers("Bob")).toEqual(expect.arrayContaining(["bob"])));
41
+
42
+ it("should generate identifiers for multiple words", () =>
43
+ expect(generateShortIdentifiers("John Doe")).toEqual(
44
+ expect.arrayContaining(["jd", "j_d", "johdoe", "joh_doe"]),
45
+ ));
46
+
47
+ it("should generate identifiers for words containing numbers", () =>
48
+ expect(generateShortIdentifiers("Alice 123")).toEqual(
49
+ expect.arrayContaining(["a1", "a_1", "ali123", "ali_123"]),
50
+ ));
51
+
52
+ it("should generate identifiers for words longer than three characters", () =>
53
+ expect(generateShortIdentifiers("Jonathan")).toEqual(
54
+ expect.arrayContaining(["jon"]),
55
+ ));
56
+
57
+ it("should generate identifiers for words shorter than three characters", () =>
58
+ expect(generateShortIdentifiers("Al")).toEqual(expect.arrayContaining(["al"])));
59
+
60
+ it("should generate identifiers for mixed cases", () =>
61
+ expect(generateShortIdentifiers("Alice Bob")).toEqual(
62
+ expect.arrayContaining(["ab", "a_b", "alibob", "ali_bob"]),
63
+ ));
64
+ });
65
+
66
+ console.log(generateShortIdentifiers("John Doe")); // ["jd", "j_d", "johdoe", "joh_doe"]
67
+ console.log(generateShortIdentifiers("Alice 123")); // ["a1", "a_1", "ali123", "ali_123"]
68
+ console.log(generateShortIdentifiers("Bob")); // ["bob"]
@@ -33,3 +33,55 @@ export const naturalLanguageJoin = (
33
33
  if (length === 2) return `${strings[0]} and ${strings[1]}`;
34
34
  return `${strings.slice(0, -1).join(", ")}, and ${strings[length - 1]}`;
35
35
  };
36
+
37
+ /**
38
+ * Generates a list of short identifiers from a given name.
39
+ *
40
+ * @param name - The name to generate identifiers from.
41
+ * @returns An array of unique short identifiers.
42
+ *
43
+ * @example
44
+ * ```typescript
45
+ * generateShortIdentifiers("John Doe"); // ["jd", "j_d", "johdoe", "joh_doe"]
46
+ * generateShortIdentifiers("Alice 123"); // ["a1", "a_1", "a123", "a_12_3", "ali123", "ali_123"]
47
+ * generateShortIdentifiers("Bob"); // ["bob"]
48
+ * ```
49
+ */
50
+ export const generateShortIdentifiers = (name: string): string[] => {
51
+ const words = name.split(" ");
52
+ const identifiers = new Set<string>();
53
+
54
+ // Generate initials
55
+ const initials = words.map((word) => word.charAt(0).toLowerCase()).join("");
56
+ identifiers.add(initials);
57
+ identifiers.add(initials.replace(/(.)(.)/g, "$1_$2")); // Insert underscores
58
+
59
+ // Generate combinations with numbers
60
+ const regex = /\d+/g;
61
+ const hasNumbers = name.match(regex);
62
+
63
+ if (hasNumbers)
64
+ words.forEach((word, index) => {
65
+ if (regex.test(word)) {
66
+ const abbreviatedWords = words
67
+ .map((w, i) => (i !== index ? w.charAt(0).toLowerCase() : w))
68
+ .join("");
69
+ identifiers.add(abbreviatedWords);
70
+ identifiers.add(abbreviatedWords.replace(/(.)(.)/g, "$1_$2")); // Insert underscores
71
+ }
72
+ });
73
+
74
+ // Generate other potential combinations
75
+ const wordAbbreviations = words.map((word) =>
76
+ (word.length > 3 ? word.substring(0, 3) : word).toLowerCase(),
77
+ );
78
+ identifiers.add(wordAbbreviations.join(""));
79
+ identifiers.add(wordAbbreviations.join("_"));
80
+
81
+ // Limit length of identifiers
82
+ const filteredIdentifiers = Array.from(identifiers).filter(
83
+ (id) => id.length >= 2 && id.length <= 12,
84
+ );
85
+
86
+ return filteredIdentifiers;
87
+ };