@revolugo/common 6.10.8 → 6.10.9-beta.1

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 (54) hide show
  1. package/package.json +2 -6
  2. package/src/cancellation-policies.ts +27 -19
  3. package/src/constants/index.ts +0 -1
  4. package/src/countries/{constants.ts → countries.ts} +2 -36
  5. package/src/{constants/countries.ts → countries/country.ts} +0 -1
  6. package/src/countries/europe.ts +35 -0
  7. package/src/countries/index.ts +4 -7
  8. package/src/{types/country.ts → countries/types.ts} +1 -1
  9. package/src/currencies/index.ts +1 -1
  10. package/src/types/elements/contact-person.ts +2 -1
  11. package/src/types/elements/currency.ts +2 -1
  12. package/src/types/elements/hotel-offer.ts +2 -1
  13. package/src/types/elements/hotel-room-offer.ts +2 -1
  14. package/src/types/elements/index.ts +1 -0
  15. package/src/types/geo-coordinates.ts +4 -0
  16. package/src/types/index.ts +1 -1
  17. package/src/utils/{math.ts → amount-from-percentage.ts} +0 -10
  18. package/src/utils/case-transformers.ts +0 -1
  19. package/src/utils/chunk.ts +7 -0
  20. package/src/utils/colors.ts +4 -1
  21. package/src/utils/compact-object.ts +7 -0
  22. package/src/utils/compact.ts +28 -0
  23. package/src/utils/compute-margin-rate.ts +13 -0
  24. package/src/utils/compute-selling-price.ts +12 -0
  25. package/src/utils/dates.ts +10 -2
  26. package/src/utils/dayjs.ts +2 -0
  27. package/src/utils/defaults-deep.ts +85 -0
  28. package/src/utils/delay.ts +5 -0
  29. package/src/utils/generate-numbers-from-str.ts +15 -0
  30. package/src/utils/generate-pseudo-random-string.ts +3 -0
  31. package/src/utils/get-guest-count.ts +1 -2
  32. package/src/utils/get-random-element-from-array.ts +10 -0
  33. package/src/utils/get-random-hex-color.ts +5 -0
  34. package/src/utils/{random.ts → get-random-int.ts} +0 -6
  35. package/src/utils/index.ts +31 -12
  36. package/src/utils/key-by.ts +25 -0
  37. package/src/utils/merge.ts +140 -0
  38. package/src/utils/omit-by.ts +37 -0
  39. package/src/utils/omit.ts +39 -0
  40. package/src/utils/pick.ts +12 -0
  41. package/src/utils/poller.ts +11 -13
  42. package/src/utils/{strings.ts → prepare-ts-query.ts} +0 -6
  43. package/src/utils/{promise-tools.ts → promise-timeout.ts} +0 -6
  44. package/src/utils/random-int.ts +3 -0
  45. package/src/utils/shuffle-array.ts +13 -0
  46. package/src/utils/{array-tools.ts → sort-by.ts} +2 -93
  47. package/src/utils/sum-by.ts +23 -0
  48. package/src/utils/uniq-with.ts +16 -0
  49. package/src/utils/validators.ts +0 -1
  50. package/src/utils/weighted-mean.ts +9 -0
  51. package/src/utils/numbers.ts +0 -46
  52. package/src/utils/object-tools.ts +0 -126
  53. /package/src/utils/{value-tools.ts → is-nil.ts} +0 -0
  54. /package/src/utils/{children-tools.ts → parse-children.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revolugo/common",
3
- "version": "6.10.8",
3
+ "version": "6.10.9-beta.1",
4
4
  "private": false,
5
5
  "description": "Revolugo common",
6
6
  "author": "Revolugo",
@@ -8,9 +8,6 @@
8
8
  "files": [
9
9
  "src"
10
10
  ],
11
- "imports": {
12
- "#constants": "./src/constants/index.ts"
13
- },
14
11
  "exports": {
15
12
  "./cancellation-policies": "./src/cancellation-policies.ts",
16
13
  "./countries": "./src/countries/index.ts",
@@ -25,8 +22,7 @@
25
22
  "dependencies": {
26
23
  "change-case": "5.4.4",
27
24
  "dayjs": "1.11.18",
28
- "ky": "1.10.0",
29
- "lodash-es": "4.17.21",
25
+ "ky": "1.11.0",
30
26
  "slugify": "1.6.6",
31
27
  "uuid": "13.0.0"
32
28
  },
@@ -1,4 +1,4 @@
1
- import { compact } from './utils/array-tools.ts'
1
+ import { compact } from './utils/compact.ts'
2
2
  import { type Dayjs, dayjs } from './utils/dayjs.ts'
3
3
  import { isEmpty } from './utils/is-empty.ts'
4
4
 
@@ -169,6 +169,7 @@ export function sanitizeCancellationPolicies({
169
169
 
170
170
  if (
171
171
  releaseDatetimeUTC &&
172
+ compactedCancellationPolicies[0] &&
172
173
  dayjs(releaseDatetimeUTC).isBetween(
173
174
  dayjs(bookingDatetimeUTC),
174
175
  dayjs(compactedCancellationPolicies[0].dateFrom),
@@ -176,6 +177,7 @@ export function sanitizeCancellationPolicies({
176
177
  '[)',
177
178
  )
178
179
  ) {
180
+ const firstPolicyDateFrom = compactedCancellationPolicies[0].dateFrom
179
181
  compactedCancellationPolicies.push(
180
182
  {
181
183
  dateFrom: bookingDatetimeUTC,
@@ -184,7 +186,7 @@ export function sanitizeCancellationPolicies({
184
186
  },
185
187
  {
186
188
  dateFrom: releaseDatetimeUTC,
187
- dateTo: compactedCancellationPolicies[0].dateFrom,
189
+ dateTo: firstPolicyDateFrom,
188
190
  penaltyPercentage: 100,
189
191
  },
190
192
  )
@@ -268,11 +270,14 @@ export function sanitizeCancellationPolicies({
268
270
  !dayjs(cp.dateFrom).isSameOrAfter(nextDayCheckInDateUTC),
269
271
  )
270
272
 
271
- sanitizedCancellationPolicies[0].dateFrom = bookingDatetimeUTC
273
+ if (sanitizedCancellationPolicies[0]) {
274
+ sanitizedCancellationPolicies[0].dateFrom = bookingDatetimeUTC
275
+ }
272
276
 
273
- sanitizedCancellationPolicies[
274
- sanitizedCancellationPolicies.length - 1
275
- ]!.dateTo = nextDayCheckInDateUTC
277
+ const lastIndex = sanitizedCancellationPolicies.length - 1
278
+ if (sanitizedCancellationPolicies[lastIndex]) {
279
+ sanitizedCancellationPolicies[lastIndex].dateTo = nextDayCheckInDateUTC
280
+ }
276
281
 
277
282
  return sanitizedCancellationPolicies
278
283
  }
@@ -340,10 +345,7 @@ export function getPenaltyPercentage({
340
345
  date: datetime,
341
346
  })
342
347
 
343
- return (
344
- matchingCancellationPolicies[0] &&
345
- matchingCancellationPolicies[0].penaltyPercentage
346
- )
348
+ return matchingCancellationPolicies[0]?.penaltyPercentage ?? 0
347
349
  }
348
350
 
349
351
  export function getCurrentPenaltyPercentage({
@@ -383,7 +385,7 @@ export function isBetterCancellationPolicies(
383
385
  return false
384
386
  }
385
387
 
386
- if (!newVal[0].dateTo) {
388
+ if (!newVal[0]?.dateTo) {
387
389
  return oldVal.every(oldCancellationPolicy => {
388
390
  const relatedNewCancellationPolicy = newVal.find(newCancellationPolicy =>
389
391
  dayjs(oldCancellationPolicy.dateTo).isBetween(
@@ -399,8 +401,8 @@ export function isBetterCancellationPolicies(
399
401
  }
400
402
 
401
403
  return (
402
- oldCancellationPolicy.penaltyPercentage! >=
403
- relatedNewCancellationPolicy.penaltyPercentage!
404
+ oldCancellationPolicy.penaltyPercentage >=
405
+ relatedNewCancellationPolicy.penaltyPercentage
404
406
  )
405
407
  })
406
408
  }
@@ -420,8 +422,8 @@ export function isBetterCancellationPolicies(
420
422
  }
421
423
 
422
424
  return (
423
- oldCancellationPolicy.penaltyPercentage! >=
424
- relatedNewCancellationPolicy.penaltyPercentage!
425
+ oldCancellationPolicy.penaltyPercentage >=
426
+ relatedNewCancellationPolicy.penaltyPercentage
425
427
  )
426
428
  })
427
429
  }
@@ -470,11 +472,17 @@ export function getSanitizedCancellationPolicies({
470
472
  export function getCurrentCancellationPolicy(
471
473
  sanitizedCancellationPolicies: ICancellationPolicy[],
472
474
  ): ICancellationPolicy {
473
- return (
474
- sanitizedCancellationPolicies.find(policy =>
475
- dayjs().isBetween(dayjs(policy.dateFrom), dayjs(policy.dateTo)),
476
- ) || sanitizedCancellationPolicies[0]
475
+ const found = sanitizedCancellationPolicies.find(policy =>
476
+ dayjs().isBetween(dayjs(policy.dateFrom), dayjs(policy.dateTo)),
477
477
  )
478
+ if (found) {
479
+ return found
480
+ }
481
+ const first = sanitizedCancellationPolicies[0]
482
+ if (!first) {
483
+ throw new Error('No cancellation policies available')
484
+ }
485
+ return first
478
486
  }
479
487
 
480
488
  function adjustIfFullHour(date: string): Dayjs {
@@ -1,4 +1,3 @@
1
- export * from './countries.ts'
2
1
  export * from './currencies.ts'
3
2
  export * from './environment.ts'
4
3
  export * from './hotel.ts'
@@ -1,41 +1,8 @@
1
- // oxlint-disable max-lines
2
- import { CountryIso2Code } from '../constants/countries.ts'
1
+ /* eslint-disable max-lines */
3
2
  import { Currency } from '../constants/currencies.ts'
4
3
 
5
- import type { Country } from '../types/country.ts'
4
+ import type { Country } from './types.ts'
6
5
 
7
- /* @__PURE__ */
8
- export const EU_COUNTRIES_ISO2_CODE = new Set<string>([
9
- CountryIso2Code.AT,
10
- CountryIso2Code.BE,
11
- CountryIso2Code.BG,
12
- CountryIso2Code.CY,
13
- CountryIso2Code.CZ,
14
- CountryIso2Code.DE,
15
- CountryIso2Code.DK,
16
- CountryIso2Code.EE,
17
- CountryIso2Code.ES,
18
- CountryIso2Code.FI,
19
- CountryIso2Code.FR,
20
- CountryIso2Code.GR,
21
- CountryIso2Code.HR,
22
- CountryIso2Code.HU,
23
- CountryIso2Code.IE,
24
- CountryIso2Code.IT,
25
- CountryIso2Code.LT,
26
- CountryIso2Code.LU,
27
- CountryIso2Code.LV,
28
- CountryIso2Code.MT,
29
- CountryIso2Code.NL,
30
- CountryIso2Code.PL,
31
- CountryIso2Code.PT,
32
- CountryIso2Code.RO,
33
- CountryIso2Code.SE,
34
- CountryIso2Code.SI,
35
- CountryIso2Code.SK,
36
- ])
37
-
38
- /* @__PURE__ */
39
6
  export const COUNTRIES: Record<string, Country> = {
40
7
  AD: {
41
8
  areaCodes: null,
@@ -2512,7 +2479,6 @@ export const COUNTRIES: Record<string, Country> = {
2512
2479
  },
2513
2480
  }
2514
2481
 
2515
- /* @__PURE__ */
2516
2482
  export const ISO_COUNTRIES = Object.values(COUNTRIES).map(
2517
2483
  country => country.iso2,
2518
2484
  )
@@ -1,4 +1,3 @@
1
- /* eslint-disable max-lines */
2
1
  export enum CountryIso2Code {
3
2
  AD = 'AD',
4
3
  AE = 'AE',
@@ -0,0 +1,35 @@
1
+ import { CountryIso2Code } from './country.ts'
2
+
3
+ export const EU_COUNTRIES_ISO2_CODE = new Set<string>([
4
+ CountryIso2Code.AT,
5
+ CountryIso2Code.BE,
6
+ CountryIso2Code.BG,
7
+ CountryIso2Code.CY,
8
+ CountryIso2Code.CZ,
9
+ CountryIso2Code.DE,
10
+ CountryIso2Code.DK,
11
+ CountryIso2Code.EE,
12
+ CountryIso2Code.ES,
13
+ CountryIso2Code.FI,
14
+ CountryIso2Code.FR,
15
+ CountryIso2Code.GR,
16
+ CountryIso2Code.HR,
17
+ CountryIso2Code.HU,
18
+ CountryIso2Code.IE,
19
+ CountryIso2Code.IT,
20
+ CountryIso2Code.LT,
21
+ CountryIso2Code.LU,
22
+ CountryIso2Code.LV,
23
+ CountryIso2Code.MT,
24
+ CountryIso2Code.NL,
25
+ CountryIso2Code.PL,
26
+ CountryIso2Code.PT,
27
+ CountryIso2Code.RO,
28
+ CountryIso2Code.SE,
29
+ CountryIso2Code.SI,
30
+ CountryIso2Code.SK,
31
+ ])
32
+
33
+ export function isInEu(countryCode: string): boolean {
34
+ return EU_COUNTRIES_ISO2_CODE.has(countryCode)
35
+ }
@@ -1,7 +1,4 @@
1
- import { EU_COUNTRIES_ISO2_CODE } from './constants.ts'
2
-
3
- export * from './constants.ts'
4
-
5
- export function isInEu(countryCode: string): boolean {
6
- return EU_COUNTRIES_ISO2_CODE.has(countryCode)
7
- }
1
+ export * from './countries.ts'
2
+ export * from './country.ts'
3
+ export * from './europe.ts'
4
+ export type * from './types.ts'
@@ -1,5 +1,5 @@
1
+ import type { ISO_COUNTRIES } from './index.ts'
1
2
  import type { Currency } from '../constants/currencies.ts'
2
- import type { ISO_COUNTRIES } from '../countries/constants.ts'
3
3
 
4
4
  export type CountryIsoCode = (typeof ISO_COUNTRIES)[number]
5
5
 
@@ -1,6 +1,6 @@
1
1
  /* eslint-disable max-lines */
2
2
  import { Currency } from '../constants/currencies.ts'
3
- import { pick } from '../utils/object-tools.ts'
3
+ import { pick } from '../utils/pick.ts'
4
4
 
5
5
  import type { ICurrency } from '../types/currency.ts'
6
6
 
@@ -1,4 +1,5 @@
1
- import type { CountryIso2Code } from '#constants'
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import type { CountryIso2Code } from '../../countries/index.ts'
2
3
 
3
4
  type CountryIso2CodeType = `${CountryIso2Code}`
4
5
 
@@ -1,3 +1,4 @@
1
- import type { Currency } from '#constants'
1
+ // eslint-disable-next-line no-restricted-imports
2
+ import type { Currency } from '../../currencies/index.ts'
2
3
 
3
4
  export type CurrencyType = `${Currency}`
@@ -1,4 +1,3 @@
1
- import type { Currency } from '#constants'
2
1
  import type { Amenities } from './amenity.ts'
3
2
  import type { HotelImage } from './hotel-image.ts'
4
3
  import type { HotelImages } from './hotel-images.ts'
@@ -6,6 +5,8 @@ import type { HotelReviewRating } from './hotel-review-rating.ts'
6
5
  import type { HotelRoomOffer } from './hotel-room-offer.ts'
7
6
  import type { Tag } from './tag.ts'
8
7
  import type { TravelTimeItem } from './travel-times.ts'
8
+ // eslint-disable-next-line no-restricted-imports
9
+ import type { Currency } from '../../currencies/index.ts'
9
10
 
10
11
  export interface HotelOffer {
11
12
  /**
@@ -1,4 +1,3 @@
1
- import type { BreakfastOptionType } from '#constants'
2
1
  import type { CancellationPolicy } from './cancellation-policy.ts'
3
2
  import type { CurrencyType } from './currency.ts'
4
3
  import type { HotelRoomOfferPackageType } from './hotel-room-offer-package-type.ts'
@@ -7,6 +6,8 @@ import type { HotelRoom } from './hotel-room.ts'
7
6
  import type { SourceMarket } from './source-market.ts'
8
7
  import type { Tag } from './tag.ts'
9
8
  import type { Tax } from './tax.ts'
9
+ // eslint-disable-next-line no-restricted-imports
10
+ import type { BreakfastOptionType } from '../../constants/index.ts'
10
11
 
11
12
  /**
12
13
  * Description of the Hotel Room Offer.
@@ -20,3 +20,4 @@ export type * from './hotel.ts'
20
20
  export * from './hotel-offer.ts'
21
21
  export * from './contact-person.ts'
22
22
  export type * from './hotel-review-rating.ts'
23
+ export type * from './travel-times.ts'
@@ -0,0 +1,4 @@
1
+ export interface GeoCoordinates {
2
+ latitude: number
3
+ longitude: number
4
+ }
@@ -2,10 +2,10 @@ export type * from './api.ts'
2
2
  export * from './booking.ts'
3
3
  export type * from './calendar.ts'
4
4
  export type * from './cancellation-policy.ts'
5
- export type * from './country.ts'
6
5
  export type * from './currency.ts'
7
6
  export type * from './date.ts'
8
7
  export type * from './event.ts'
8
+ export type * from './geo-coordinates.ts'
9
9
  export type * from './hotel-contract.ts'
10
10
  export type * from './hotel-room-stock.ts'
11
11
  export type * from './money-object.ts'
@@ -1,13 +1,3 @@
1
- export function weightedMean(values: number[], weights: number[]): number {
2
- const sum = values.reduce(
3
- (acc: number, val: number, i: number) => acc + val * weights[i],
4
- 0,
5
- )
6
- const sumWeights = weights.reduce((acc: number, val: number) => acc + val, 0)
7
-
8
- return sum / sumWeights
9
- }
10
-
11
1
  export function amountFromPercentage(
12
2
  percentage: number,
13
3
  amount: number,
@@ -23,7 +23,6 @@ export enum CaseTransformer {
23
23
  Snake = 'snakeCase',
24
24
  }
25
25
 
26
- /* @__PURE__ */
27
26
  const CASE_TRANSORMERS_MAPPING = {
28
27
  [CaseTransformer.Camel]: camelCase,
29
28
  [CaseTransformer.Capital]: capitalCase,
@@ -0,0 +1,7 @@
1
+ export function chunk<T>(array: T[], size: number): T[][] {
2
+ const chunks: T[][] = []
3
+ for (let i = 0; i < array.length; i += size) {
4
+ chunks.push(array.slice(i, i + size))
5
+ }
6
+ return chunks
7
+ }
@@ -1,4 +1,4 @@
1
- import { generateNumbersFromStr } from './numbers.ts'
1
+ import { generateNumbersFromStr } from './generate-numbers-from-str.ts'
2
2
 
3
3
  export function generateRandomColorFromString(
4
4
  str: string,
@@ -9,6 +9,9 @@ export function generateRandomColorFromString(
9
9
  const hueRange = 5
10
10
  const randomIndex = Math.floor(num1 * existingColors.length)
11
11
  const baseColor = existingColors[randomIndex]
12
+ if (!baseColor) {
13
+ throw new Error('Base color not found')
14
+ }
12
15
  // extract the hue value from the base color
13
16
  const baseHue = Number.parseInt(baseColor.slice(1, 3), 16)
14
17
  const minHue = Math.max(0, baseHue - hueRange)
@@ -0,0 +1,7 @@
1
+ import { isNil } from './is-nil.ts'
2
+
3
+ export function compactObject<T extends object>(obj: T): Partial<T> {
4
+ return Object.fromEntries(
5
+ Object.entries(obj).filter(([, value]) => !isNil(value)),
6
+ ) as Partial<T>
7
+ }
@@ -0,0 +1,28 @@
1
+ export function compact<T>(
2
+ array: (T | null | undefined)[] | null | undefined,
3
+ ): Exclude<T, null | undefined>[] {
4
+ const length = array === null || array === undefined ? 0 : array.length
5
+ let index = -1
6
+ let resIndex = 0
7
+ const result: Exclude<T, null | undefined>[] = []
8
+
9
+ while (index < length) {
10
+ index += 1
11
+ if (array !== null && array !== undefined) {
12
+ const value = array[index]
13
+
14
+ if (
15
+ value !== null &&
16
+ value !== undefined &&
17
+ value !== false &&
18
+ value !== 0 &&
19
+ value !== ''
20
+ ) {
21
+ result[resIndex] = value as Exclude<T, null | undefined>
22
+ resIndex += 1
23
+ }
24
+ }
25
+ }
26
+
27
+ return result
28
+ }
@@ -0,0 +1,13 @@
1
+ export function computeMarginRate(
2
+ buyingPrice: number | string,
3
+ sellingPrice: number | string,
4
+ ): number {
5
+ const buyingPriceNum = Number(buyingPrice)
6
+ const sellingPriceNum = Number(sellingPrice)
7
+
8
+ if (Number.isNaN(sellingPriceNum) || Number.isNaN(buyingPriceNum)) {
9
+ throw new TypeError('sellingPrice or buyingPrice is NaN')
10
+ }
11
+
12
+ return Number((1 - buyingPriceNum / sellingPriceNum).toPrecision(10))
13
+ }
@@ -0,0 +1,12 @@
1
+ export function computeSellingPrice(
2
+ buyingPrice: number | string | null = 0,
3
+ rate: number | string | null = 0,
4
+ ): number {
5
+ if (Number.isNaN(Number(buyingPrice)) || Number.isNaN(Number(rate))) {
6
+ throw new TypeError('price or rate is NaN')
7
+ }
8
+
9
+ const sellingPrice = Number(buyingPrice) / (1 - Number(rate))
10
+
11
+ return Math.ceil(Math.floor(sellingPrice * 100) / 100)
12
+ }
@@ -44,14 +44,22 @@ export function sanitizeDateRange(
44
44
  if (sortedDates.length === 2) {
45
45
  return sortedDates as [string, string]
46
46
  } else if (sortedDates.length === 1) {
47
- if (sortedDates[0] === dayjs().format('YYYY-MM-DD')) {
47
+ const firstDate = sortedDates[0]
48
+ if (!firstDate) {
48
49
  return [
49
50
  dayjs().format('YYYY-MM-DD'),
50
51
  dayjs().add(1, 'day').format('YYYY-MM-DD'),
51
52
  ]
52
53
  }
53
54
 
54
- return [dayjs().format('YYYY-MM-DD'), sortedDates[0]]
55
+ if (firstDate === dayjs().format('YYYY-MM-DD')) {
56
+ return [
57
+ dayjs().format('YYYY-MM-DD'),
58
+ dayjs().add(1, 'day').format('YYYY-MM-DD'),
59
+ ]
60
+ }
61
+
62
+ return [dayjs().format('YYYY-MM-DD'), firstDate]
55
63
  }
56
64
 
57
65
  return [
@@ -4,6 +4,7 @@ import advancedFormat from 'dayjs/plugin/advancedFormat.js'
4
4
  import isBetween from 'dayjs/plugin/isBetween.js'
5
5
  import isSameOrAfter from 'dayjs/plugin/isSameOrAfter.js'
6
6
  import isSameOrBefore from 'dayjs/plugin/isSameOrBefore.js'
7
+ import localeData from 'dayjs/plugin/localeData.js'
7
8
  import localizedFormat from 'dayjs/plugin/localizedFormat.js'
8
9
  import minMax from 'dayjs/plugin/minMax.js'
9
10
  import relativeTime from 'dayjs/plugin/relativeTime.js'
@@ -22,6 +23,7 @@ dayjs.extend(isBetween)
22
23
  dayjs.extend(isSameOrAfter)
23
24
  dayjs.extend(isSameOrBefore)
24
25
  dayjs.extend(localizedFormat)
26
+ dayjs.extend(localeData)
25
27
  dayjs.extend(minMax)
26
28
  dayjs.extend(relativeTime)
27
29
  dayjs.extend(timezone)
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Drop-in native TypeScript replacement for `lodash-es` `defaultsDeep`.
3
+ *
4
+ * Behaviour matched (carefully):
5
+ * - Mutates and returns the first argument (target).
6
+ * - Assigns own and *inherited* enumerable **string-keyed** properties from sources
7
+ * to the target only when the target's property is `undefined`.
8
+ * - Recursively assigns (deep) when both target and source values are objects/arrays.
9
+ * - Does not copy symbol-keyed properties (matches the "string keyed" behaviour).
10
+ * - Preserves `null` (i.e. `null` is NOT considered `undefined` so it won't be overwritten).
11
+ * - Avoids infinite recursion for circular source structures.
12
+ */
13
+
14
+ /* eslint-disable @typescript-eslint/no-explicit-any */
15
+ type AnyObject = Record<string, any>
16
+
17
+ function isObject(value: any): value is AnyObject {
18
+ return value !== null && typeof value === 'object'
19
+ }
20
+
21
+ export function defaultsDeep<T>(
22
+ object: T,
23
+ ...sources: any[]
24
+ ): T extends AnyObject ? T : any {
25
+ // Ensure we always return an object reference we can mutate (lodash creates/uses {} when target is falsy)
26
+ const target: AnyObject = (
27
+ object === null || object === undefined ? {} : object
28
+ ) as AnyObject
29
+
30
+ // WeakMap to track already-cloned source objects so circular refs won't blow the stack
31
+ const seen = new WeakMap<object, object>()
32
+
33
+ function applyDefaults(dst: AnyObject, src: any) {
34
+ // Only iterate string-keyed enumerable properties (own + inherited) => for...in
35
+ for (const key in src) {
36
+ if (Object.hasOwn(src, key)) {
37
+ const srcVal = src[key]
38
+ const dstVal = dst[key]
39
+
40
+ // only apply when destination is strictly `undefined` (lodash semantics)
41
+ if (dstVal === undefined) {
42
+ // eslint-disable-next-line max-depth
43
+ if (isObject(srcVal)) {
44
+ // If we've already cloned this source object (circular), reuse it
45
+ // eslint-disable-next-line max-depth
46
+ if (seen.has(srcVal)) {
47
+ dst[key] = seen.get(srcVal)
48
+ } else {
49
+ // Prepare new container (array vs object)
50
+ const created = Array.isArray(srcVal)
51
+ ? ([] as unknown[])
52
+ : ({} as AnyObject)
53
+ // remember mapping to handle circular refs
54
+ seen.set(srcVal, created)
55
+
56
+ // Recursively fill created from srcVal
57
+ applyDefaults(created, srcVal)
58
+
59
+ // assign the created clone as the default
60
+ dst[key] = created
61
+ }
62
+ } else {
63
+ // Primitive or function: assign directly
64
+ dst[key] = srcVal
65
+ }
66
+ } else if (isObject(dstVal) && isObject(srcVal)) {
67
+ // destination already has a value (not undefined) and both are objects => recurse
68
+ // (do not overwrite existing non-undefined values)
69
+ // Note: do not treat arrays specially here; Array.isArray check only used when creating new value
70
+ applyDefaults(dstVal, srcVal)
71
+ }
72
+ // else: destination has a non-undefined primitive or non-object - skip (do not overwrite)
73
+ }
74
+ }
75
+ }
76
+
77
+ for (const src of sources) {
78
+ if (src !== null && src !== undefined) {
79
+ // If source itself has been seen (unlikely across top-level sources), we still traverse it
80
+ applyDefaults(target, src)
81
+ }
82
+ }
83
+
84
+ return target as any
85
+ }
@@ -0,0 +1,5 @@
1
+ export function delay(duration: number): Promise<void> {
2
+ return new Promise(resolve => {
3
+ setTimeout(resolve, duration)
4
+ })
5
+ }
@@ -0,0 +1,15 @@
1
+ export function generateNumbersFromStr(str: string): [number, number] {
2
+ let hash = 5381
3
+ for (let i = 0; i < str.length; i++) {
4
+ // eslint-disable-next-line no-bitwise
5
+ hash = (hash << 5) + hash + str.charCodeAt(i)
6
+ }
7
+
8
+ // Generate two numbers using the hash
9
+ // eslint-disable-next-line no-bitwise
10
+ const num1 = (hash & 0xff_ff) / 0xff_ff
11
+ // eslint-disable-next-line no-bitwise
12
+ const num2 = ((hash >> 16) & 0xff_ff) / 0xff_ff
13
+
14
+ return [num1, num2]
15
+ }
@@ -0,0 +1,3 @@
1
+ export function generatePseudoRandomString(length: number): string {
2
+ return Array.from({ length }, () => Math.random().toString(36)[2]).join('')
3
+ }
@@ -1,6 +1,5 @@
1
- import { parseChildren } from './children-tools.ts'
1
+ import { parseChildren } from './parse-children.ts'
2
2
 
3
- /* @__PURE__ */
4
3
  export const CHILDREN_FREE_BREAKFAST_AGE_LIMIT = 4
5
4
 
6
5
  export function getGuestCount(
@@ -0,0 +1,10 @@
1
+ export function getRandomElementFromArray<T>(array: readonly T[]): T {
2
+ if (array.length === 0) {
3
+ throw new Error('Cannot get random element from empty array')
4
+ }
5
+ const element = array[Math.floor(Math.random() * array.length)]
6
+ if (element === undefined) {
7
+ throw new Error('Array element is undefined')
8
+ }
9
+ return element
10
+ }
@@ -0,0 +1,5 @@
1
+ export function getRandomHexColor(): string {
2
+ const hex = Math.floor(Math.random() * 0xffffff).toString(16)
3
+
4
+ return hex.padStart(6, '0').toUpperCase()
5
+ }
@@ -4,9 +4,3 @@ export function getRandomInt(min: number, max: number): number {
4
4
 
5
5
  return Math.floor(Math.random() * (roundedMax - roundedMin + 1)) + roundedMin
6
6
  }
7
-
8
- export function getRandomHexColor(): string {
9
- const hex = Math.floor(Math.random() * 0xffffff).toString(16)
10
-
11
- return hex.padStart(6, '0').toUpperCase()
12
- }