@tamtamchik/json-deep-sort 1.4.0-beta.2 → 1.4.0-beta.4

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.
package/dist/index.js CHANGED
@@ -23,35 +23,88 @@ __export(index_exports, {
23
23
  sort: () => sort
24
24
  });
25
25
  module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/types.ts
26
28
  var NULLISH_VALUES = [null, void 0];
27
- var SORTABLE_PRIMITIVE_TYPES = ["string", "number", "boolean"];
28
- function sort(data, ascending = true, sortPrimitiveArrays = false) {
29
- const options = { ascending, sortPrimitiveArrays };
30
- return sortRecursively(data, options);
29
+ var SORTABLE_PRIMITIVE_TYPES = [
30
+ "string",
31
+ "number",
32
+ "boolean"
33
+ ];
34
+
35
+ // src/type-guards.ts
36
+ function isNullish(value) {
37
+ return NULLISH_VALUES.includes(value);
31
38
  }
32
- function sortRecursively(data, options) {
33
- if (Array.isArray(data)) {
34
- return sortArray(data, options);
39
+ function hasNullishValues(array) {
40
+ return array.some(isNullish);
41
+ }
42
+ function isSortablePrimitive(value) {
43
+ return SORTABLE_PRIMITIVE_TYPES.includes(
44
+ typeof value
45
+ );
46
+ }
47
+ function isPrimitive(data) {
48
+ return isSortablePrimitive(data) || isNullish(data);
49
+ }
50
+ function isObject(data) {
51
+ return typeof data === "object" && data !== null;
52
+ }
53
+ function isNonSortableObject(obj) {
54
+ const nonSortableTypes = [
55
+ Date,
56
+ RegExp,
57
+ Function,
58
+ Error,
59
+ Map,
60
+ Set,
61
+ WeakMap,
62
+ WeakSet,
63
+ Promise
64
+ ];
65
+ return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
66
+ }
67
+
68
+ // src/comparators.ts
69
+ function compareSortablePrimitives(a, b, ascending) {
70
+ if (typeof a === "string" && typeof b === "string") {
71
+ return ascending ? a.localeCompare(b) : b.localeCompare(a);
35
72
  }
36
- if (isPrimitive(data) || isNonSortableObject(data)) {
37
- return data;
73
+ if (typeof a === "number" && typeof b === "number") {
74
+ return compareNumbers(a, b, ascending);
38
75
  }
39
- if (isObject(data)) {
40
- return sortObject(data, options);
76
+ if (typeof a === "boolean" && typeof b === "boolean") {
77
+ return compareBooleans(a, b, ascending);
41
78
  }
42
- return data;
79
+ return 0;
80
+ }
81
+ function compareNumbers(a, b, ascending) {
82
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
83
+ if (Number.isNaN(a)) return 1;
84
+ if (Number.isNaN(b)) return -1;
85
+ return ascending ? a - b : b - a;
43
86
  }
87
+ function compareBooleans(a, b, ascending) {
88
+ if (a === b) return 0;
89
+ if (a) return ascending ? 1 : -1;
90
+ return ascending ? -1 : 1;
91
+ }
92
+ function compareObjectKeys(keyA, keyB, ascending) {
93
+ if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
94
+ if (typeof keyA === "symbol") return 1;
95
+ if (typeof keyB === "symbol") return -1;
96
+ const stringA = keyA;
97
+ const stringB = keyB;
98
+ return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
99
+ }
100
+
101
+ // src/array-sorter.ts
44
102
  function sortArray(array, options) {
45
103
  if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
46
104
  return sortPrimitiveArray(array, options.ascending);
47
105
  }
48
106
  return array.map((item) => sortRecursively(item, options));
49
107
  }
50
- function sortObject(obj, options) {
51
- const entries = collectObjectEntries(obj);
52
- const sortedEntries = sortObjectEntries(entries, options.ascending);
53
- return createSortedObject(sortedEntries, options);
54
- }
55
108
  function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
56
109
  return sortPrimitiveArrays && canSortPrimitiveArray(array);
57
110
  }
@@ -76,28 +129,12 @@ function allItemsHaveSameSortableType(array) {
76
129
  if (allSameType) return true;
77
130
  return array.every(isSortablePrimitive);
78
131
  }
79
- function compareSortablePrimitives(a, b, ascending) {
80
- if (typeof a === "string" && typeof b === "string") {
81
- return ascending ? a.localeCompare(b) : b.localeCompare(a);
82
- }
83
- if (typeof a === "number" && typeof b === "number") {
84
- return compareNumbers(a, b, ascending);
85
- }
86
- if (typeof a === "boolean" && typeof b === "boolean") {
87
- return compareBooleans(a, b, ascending);
88
- }
89
- return 0;
90
- }
91
- function compareNumbers(a, b, ascending) {
92
- if (Number.isNaN(a) && Number.isNaN(b)) return 0;
93
- if (Number.isNaN(a)) return 1;
94
- if (Number.isNaN(b)) return -1;
95
- return ascending ? a - b : b - a;
96
- }
97
- function compareBooleans(a, b, ascending) {
98
- if (a === b) return 0;
99
- if (a) return ascending ? 1 : -1;
100
- return ascending ? -1 : 1;
132
+
133
+ // src/object-sorter.ts
134
+ function sortObject(obj, options) {
135
+ const entries = collectObjectEntries(obj);
136
+ const sortedEntries = sortObjectEntries(entries, options.ascending);
137
+ return createSortedObject(sortedEntries, options);
101
138
  }
102
139
  function collectObjectEntries(obj) {
103
140
  const stringEntries = Object.entries(obj);
@@ -111,14 +148,6 @@ function sortObjectEntries(entries, ascending) {
111
148
  ([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
112
149
  );
113
150
  }
114
- function compareObjectKeys(keyA, keyB, ascending) {
115
- if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
116
- if (typeof keyA === "symbol") return 1;
117
- if (typeof keyB === "symbol") return -1;
118
- const stringA = keyA;
119
- const stringB = keyB;
120
- return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
121
- }
122
151
  function createSortedObject(entries, options) {
123
152
  const sortedEntries = entries.map(([key, value]) => [
124
153
  key,
@@ -126,36 +155,25 @@ function createSortedObject(entries, options) {
126
155
  ]);
127
156
  return Object.fromEntries(sortedEntries);
128
157
  }
129
- function isNullish(value) {
130
- return NULLISH_VALUES.includes(value);
131
- }
132
- function hasNullishValues(array) {
133
- return array.some(isNullish);
134
- }
135
- function isSortablePrimitive(value) {
136
- return SORTABLE_PRIMITIVE_TYPES.includes(
137
- typeof value
138
- );
139
- }
140
- function isPrimitive(data) {
141
- return isSortablePrimitive(data) || isNullish(data);
142
- }
143
- function isObject(data) {
144
- return typeof data === "object" && data !== null;
158
+
159
+ // src/core.ts
160
+ function sortRecursively(data, options) {
161
+ if (Array.isArray(data)) {
162
+ return sortArray(data, options);
163
+ }
164
+ if (isPrimitive(data) || isNonSortableObject(data)) {
165
+ return data;
166
+ }
167
+ if (isObject(data)) {
168
+ return sortObject(data, options);
169
+ }
170
+ return data;
145
171
  }
146
- function isNonSortableObject(obj) {
147
- const nonSortableTypes = [
148
- Date,
149
- RegExp,
150
- Function,
151
- Error,
152
- Map,
153
- Set,
154
- WeakMap,
155
- WeakSet,
156
- Promise
157
- ];
158
- return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
172
+
173
+ // src/index.ts
174
+ function sort(data, ascending = true, sortPrimitiveArrays = false) {
175
+ const options = { ascending, sortPrimitiveArrays };
176
+ return sortRecursively(data, options);
159
177
  }
160
178
  // Annotate the CommonJS export names for ESM import in node:
161
179
  0 && (module.exports = {
package/dist/index.mjs CHANGED
@@ -1,33 +1,84 @@
1
- // src/index.ts
1
+ // src/types.ts
2
2
  var NULLISH_VALUES = [null, void 0];
3
- var SORTABLE_PRIMITIVE_TYPES = ["string", "number", "boolean"];
4
- function sort(data, ascending = true, sortPrimitiveArrays = false) {
5
- const options = { ascending, sortPrimitiveArrays };
6
- return sortRecursively(data, options);
3
+ var SORTABLE_PRIMITIVE_TYPES = [
4
+ "string",
5
+ "number",
6
+ "boolean"
7
+ ];
8
+
9
+ // src/type-guards.ts
10
+ function isNullish(value) {
11
+ return NULLISH_VALUES.includes(value);
7
12
  }
8
- function sortRecursively(data, options) {
9
- if (Array.isArray(data)) {
10
- return sortArray(data, options);
13
+ function hasNullishValues(array) {
14
+ return array.some(isNullish);
15
+ }
16
+ function isSortablePrimitive(value) {
17
+ return SORTABLE_PRIMITIVE_TYPES.includes(
18
+ typeof value
19
+ );
20
+ }
21
+ function isPrimitive(data) {
22
+ return isSortablePrimitive(data) || isNullish(data);
23
+ }
24
+ function isObject(data) {
25
+ return typeof data === "object" && data !== null;
26
+ }
27
+ function isNonSortableObject(obj) {
28
+ const nonSortableTypes = [
29
+ Date,
30
+ RegExp,
31
+ Function,
32
+ Error,
33
+ Map,
34
+ Set,
35
+ WeakMap,
36
+ WeakSet,
37
+ Promise
38
+ ];
39
+ return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
40
+ }
41
+
42
+ // src/comparators.ts
43
+ function compareSortablePrimitives(a, b, ascending) {
44
+ if (typeof a === "string" && typeof b === "string") {
45
+ return ascending ? a.localeCompare(b) : b.localeCompare(a);
11
46
  }
12
- if (isPrimitive(data) || isNonSortableObject(data)) {
13
- return data;
47
+ if (typeof a === "number" && typeof b === "number") {
48
+ return compareNumbers(a, b, ascending);
14
49
  }
15
- if (isObject(data)) {
16
- return sortObject(data, options);
50
+ if (typeof a === "boolean" && typeof b === "boolean") {
51
+ return compareBooleans(a, b, ascending);
17
52
  }
18
- return data;
53
+ return 0;
54
+ }
55
+ function compareNumbers(a, b, ascending) {
56
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
57
+ if (Number.isNaN(a)) return 1;
58
+ if (Number.isNaN(b)) return -1;
59
+ return ascending ? a - b : b - a;
60
+ }
61
+ function compareBooleans(a, b, ascending) {
62
+ if (a === b) return 0;
63
+ if (a) return ascending ? 1 : -1;
64
+ return ascending ? -1 : 1;
19
65
  }
66
+ function compareObjectKeys(keyA, keyB, ascending) {
67
+ if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
68
+ if (typeof keyA === "symbol") return 1;
69
+ if (typeof keyB === "symbol") return -1;
70
+ const stringA = keyA;
71
+ const stringB = keyB;
72
+ return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
73
+ }
74
+
75
+ // src/array-sorter.ts
20
76
  function sortArray(array, options) {
21
77
  if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
22
78
  return sortPrimitiveArray(array, options.ascending);
23
79
  }
24
80
  return array.map((item) => sortRecursively(item, options));
25
81
  }
26
- function sortObject(obj, options) {
27
- const entries = collectObjectEntries(obj);
28
- const sortedEntries = sortObjectEntries(entries, options.ascending);
29
- return createSortedObject(sortedEntries, options);
30
- }
31
82
  function shouldSortPrimitiveArray(array, sortPrimitiveArrays) {
32
83
  return sortPrimitiveArrays && canSortPrimitiveArray(array);
33
84
  }
@@ -52,28 +103,12 @@ function allItemsHaveSameSortableType(array) {
52
103
  if (allSameType) return true;
53
104
  return array.every(isSortablePrimitive);
54
105
  }
55
- function compareSortablePrimitives(a, b, ascending) {
56
- if (typeof a === "string" && typeof b === "string") {
57
- return ascending ? a.localeCompare(b) : b.localeCompare(a);
58
- }
59
- if (typeof a === "number" && typeof b === "number") {
60
- return compareNumbers(a, b, ascending);
61
- }
62
- if (typeof a === "boolean" && typeof b === "boolean") {
63
- return compareBooleans(a, b, ascending);
64
- }
65
- return 0;
66
- }
67
- function compareNumbers(a, b, ascending) {
68
- if (Number.isNaN(a) && Number.isNaN(b)) return 0;
69
- if (Number.isNaN(a)) return 1;
70
- if (Number.isNaN(b)) return -1;
71
- return ascending ? a - b : b - a;
72
- }
73
- function compareBooleans(a, b, ascending) {
74
- if (a === b) return 0;
75
- if (a) return ascending ? 1 : -1;
76
- return ascending ? -1 : 1;
106
+
107
+ // src/object-sorter.ts
108
+ function sortObject(obj, options) {
109
+ const entries = collectObjectEntries(obj);
110
+ const sortedEntries = sortObjectEntries(entries, options.ascending);
111
+ return createSortedObject(sortedEntries, options);
77
112
  }
78
113
  function collectObjectEntries(obj) {
79
114
  const stringEntries = Object.entries(obj);
@@ -87,14 +122,6 @@ function sortObjectEntries(entries, ascending) {
87
122
  ([keyA], [keyB]) => compareObjectKeys(keyA, keyB, ascending)
88
123
  );
89
124
  }
90
- function compareObjectKeys(keyA, keyB, ascending) {
91
- if (typeof keyA === "symbol" && typeof keyB === "symbol") return 0;
92
- if (typeof keyA === "symbol") return 1;
93
- if (typeof keyB === "symbol") return -1;
94
- const stringA = keyA;
95
- const stringB = keyB;
96
- return ascending ? stringA.localeCompare(stringB) : stringB.localeCompare(stringA);
97
- }
98
125
  function createSortedObject(entries, options) {
99
126
  const sortedEntries = entries.map(([key, value]) => [
100
127
  key,
@@ -102,36 +129,25 @@ function createSortedObject(entries, options) {
102
129
  ]);
103
130
  return Object.fromEntries(sortedEntries);
104
131
  }
105
- function isNullish(value) {
106
- return NULLISH_VALUES.includes(value);
107
- }
108
- function hasNullishValues(array) {
109
- return array.some(isNullish);
110
- }
111
- function isSortablePrimitive(value) {
112
- return SORTABLE_PRIMITIVE_TYPES.includes(
113
- typeof value
114
- );
115
- }
116
- function isPrimitive(data) {
117
- return isSortablePrimitive(data) || isNullish(data);
118
- }
119
- function isObject(data) {
120
- return typeof data === "object" && data !== null;
132
+
133
+ // src/core.ts
134
+ function sortRecursively(data, options) {
135
+ if (Array.isArray(data)) {
136
+ return sortArray(data, options);
137
+ }
138
+ if (isPrimitive(data) || isNonSortableObject(data)) {
139
+ return data;
140
+ }
141
+ if (isObject(data)) {
142
+ return sortObject(data, options);
143
+ }
144
+ return data;
121
145
  }
122
- function isNonSortableObject(obj) {
123
- const nonSortableTypes = [
124
- Date,
125
- RegExp,
126
- Function,
127
- Error,
128
- Map,
129
- Set,
130
- WeakMap,
131
- WeakSet,
132
- Promise
133
- ];
134
- return nonSortableTypes.some((type) => obj instanceof type) || Symbol.iterator in Object(obj);
146
+
147
+ // src/index.ts
148
+ function sort(data, ascending = true, sortPrimitiveArrays = false) {
149
+ const options = { ascending, sortPrimitiveArrays };
150
+ return sortRecursively(data, options);
135
151
  }
136
152
  export {
137
153
  sort
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tamtamchik/json-deep-sort",
3
- "version": "1.4.0-beta.2",
3
+ "version": "1.4.0-beta.4",
4
4
  "description": "A comprehensive utility for sorting JSON objects by keys.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -0,0 +1,61 @@
1
+ import { SortOptions } from './types';
2
+ import { isSortablePrimitive, hasNullishValues } from './type-guards';
3
+ import { compareSortablePrimitives } from './comparators';
4
+
5
+ export function sortArray<T>(array: T[], options: SortOptions): T[] {
6
+ if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
7
+ return sortPrimitiveArray(array, options.ascending) as T[];
8
+ }
9
+
10
+ return array.map((item) => sortRecursively(item, options));
11
+ }
12
+
13
+ function shouldSortPrimitiveArray(
14
+ array: unknown[],
15
+ sortPrimitiveArrays: boolean
16
+ ): boolean {
17
+ return sortPrimitiveArrays && canSortPrimitiveArray(array);
18
+ }
19
+
20
+ function canSortPrimitiveArray(array: unknown[]): boolean {
21
+ return (
22
+ allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array)
23
+ );
24
+ }
25
+
26
+ function allItemsAreSortablePrimitives(array: unknown[]): boolean {
27
+ return array.every(isSortablePrimitive);
28
+ }
29
+
30
+ function sortPrimitiveArray(array: unknown[], ascending: boolean): unknown[] {
31
+ // The array has already been validated as sortable in canSortPrimitiveArray
32
+ // No need to check allItemsHaveSameSortableType again
33
+ return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
34
+ }
35
+
36
+ function allItemsHaveSameSortableType(array: unknown[]): boolean {
37
+ if (array.length === 0) return true;
38
+
39
+ // Don't sort arrays that contain null or undefined values
40
+ // as they represent absence of value and don't have a natural ordering
41
+ if (hasNullishValues(array)) {
42
+ return false;
43
+ }
44
+
45
+ // For arrays with only sortable primitives, check if they have the same type
46
+ const firstItem = array[0];
47
+ if (!isSortablePrimitive(firstItem)) return false;
48
+
49
+ const expectedType = typeof firstItem;
50
+ const allSameType = array.every((item) => typeof item === expectedType);
51
+
52
+ // If all items are the same type, we can sort them normally
53
+ if (allSameType) return true;
54
+
55
+ // If we have mixed primitive types, we can still "sort" them
56
+ // (the comparison function will return 0 for different types, maintaining order)
57
+ // This allows us to cover the fallback case in compareSortablePrimitives
58
+ return array.every(isSortablePrimitive);
59
+ }
60
+
61
+ import { sortRecursively } from './core';
@@ -0,0 +1,59 @@
1
+ // Comparison functions for different data types
2
+ export function compareSortablePrimitives(
3
+ a: unknown,
4
+ b: unknown,
5
+ ascending: boolean
6
+ ): number {
7
+ if (typeof a === 'string' && typeof b === 'string') {
8
+ return ascending ? a.localeCompare(b) : b.localeCompare(a);
9
+ }
10
+
11
+ if (typeof a === 'number' && typeof b === 'number') {
12
+ return compareNumbers(a, b, ascending);
13
+ }
14
+
15
+ if (typeof a === 'boolean' && typeof b === 'boolean') {
16
+ return compareBooleans(a, b, ascending);
17
+ }
18
+
19
+ return 0; // Maintain order for other primitives
20
+ }
21
+
22
+ export function compareNumbers(
23
+ a: number,
24
+ b: number,
25
+ ascending: boolean
26
+ ): number {
27
+ if (Number.isNaN(a) && Number.isNaN(b)) return 0;
28
+ if (Number.isNaN(a)) return 1;
29
+ if (Number.isNaN(b)) return -1;
30
+
31
+ return ascending ? a - b : b - a;
32
+ }
33
+
34
+ export function compareBooleans(
35
+ a: boolean,
36
+ b: boolean,
37
+ ascending: boolean
38
+ ): number {
39
+ if (a === b) return 0;
40
+ if (a) return ascending ? 1 : -1;
41
+ return ascending ? -1 : 1;
42
+ }
43
+
44
+ export function compareObjectKeys(
45
+ keyA: string | symbol,
46
+ keyB: string | symbol,
47
+ ascending: boolean
48
+ ): number {
49
+ if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
50
+ if (typeof keyA === 'symbol') return 1;
51
+ if (typeof keyB === 'symbol') return -1;
52
+
53
+ const stringA = keyA as string;
54
+ const stringB = keyB as string;
55
+
56
+ return ascending
57
+ ? stringA.localeCompare(stringB)
58
+ : stringB.localeCompare(stringA);
59
+ }
package/src/core.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { SortOptions } from './types';
2
+ import { isPrimitive, isNonSortableObject, isObject } from './type-guards';
3
+ import { sortArray } from './array-sorter';
4
+ import { sortObject } from './object-sorter';
5
+
6
+ export function sortRecursively<T>(data: T, options: SortOptions): T {
7
+ if (Array.isArray(data)) {
8
+ return sortArray(data, options) as T;
9
+ }
10
+
11
+ if (isPrimitive(data) || isNonSortableObject(data)) {
12
+ return data;
13
+ }
14
+
15
+ if (isObject(data)) {
16
+ return sortObject(data, options) as T;
17
+ }
18
+
19
+ return data;
20
+ }
package/src/index.ts CHANGED
@@ -1,28 +1,5 @@
1
- // Constants - make the code self-documenting
2
- const NULLISH_VALUES = [null, undefined] as const;
3
- const SORTABLE_PRIMITIVE_TYPES = ['string', 'number', 'boolean'] as const;
4
-
5
- // Type definitions that make sense
6
- type NullishValue = (typeof NULLISH_VALUES)[number];
7
- type SortablePrimitiveType = (typeof SORTABLE_PRIMITIVE_TYPES)[number];
8
- type ObjectType = Record<string | symbol, unknown>;
9
- type SortedEntry = [string | symbol, unknown];
10
- type NonSortableType =
11
- | Date
12
- | RegExp
13
- | (() => unknown)
14
- | ((...args: unknown[]) => unknown)
15
- | Error
16
- | Map<unknown, unknown>
17
- | Set<unknown>
18
- | WeakMap<object, unknown>
19
- | WeakSet<object>
20
- | Promise<unknown>;
21
-
22
- type SortOptions = {
23
- ascending: boolean;
24
- sortPrimitiveArrays: boolean;
25
- };
1
+ import { SortOptions } from './types';
2
+ import { sortRecursively } from './core';
26
3
 
27
4
  export function sort<T>(
28
5
  data: T,
@@ -33,206 +10,10 @@ export function sort<T>(
33
10
  return sortRecursively(data, options);
34
11
  }
35
12
 
36
- function sortRecursively<T>(data: T, options: SortOptions): T {
37
- if (Array.isArray(data)) {
38
- return sortArray(data, options) as T;
39
- }
40
-
41
- if (isPrimitive(data) || isNonSortableObject(data)) {
42
- return data;
43
- }
44
-
45
- if (isObject(data)) {
46
- return sortObject(data, options) as T;
47
- }
48
-
49
- return data;
50
- }
51
-
52
- function sortArray<T>(array: T[], options: SortOptions): T[] {
53
- if (shouldSortPrimitiveArray(array, options.sortPrimitiveArrays)) {
54
- return sortPrimitiveArray(array, options.ascending) as T[];
55
- }
56
-
57
- return array.map((item) => sortRecursively(item, options));
58
- }
59
-
60
- function sortObject(obj: ObjectType, options: SortOptions): ObjectType {
61
- const entries = collectObjectEntries(obj);
62
- const sortedEntries = sortObjectEntries(entries, options.ascending);
63
- return createSortedObject(sortedEntries, options);
64
- }
65
-
66
- function shouldSortPrimitiveArray(
67
- array: unknown[],
68
- sortPrimitiveArrays: boolean
69
- ): boolean {
70
- return sortPrimitiveArrays && canSortPrimitiveArray(array);
71
- }
72
-
73
- function canSortPrimitiveArray(array: unknown[]): boolean {
74
- return (
75
- allItemsAreSortablePrimitives(array) && allItemsHaveSameSortableType(array)
76
- );
77
- }
78
-
79
- function allItemsAreSortablePrimitives(array: unknown[]): boolean {
80
- return array.every(isSortablePrimitive);
81
- }
82
-
83
- function sortPrimitiveArray(array: unknown[], ascending: boolean): unknown[] {
84
- // The array has already been validated as sortable in canSortPrimitiveArray
85
- // No need to check allItemsHaveSameSortableType again
86
- return [...array].sort((a, b) => compareSortablePrimitives(a, b, ascending));
87
- }
88
-
89
- function allItemsHaveSameSortableType(array: unknown[]): boolean {
90
- if (array.length === 0) return true;
91
-
92
- // Don't sort arrays that contain null or undefined values
93
- // as they represent absence of value and don't have a natural ordering
94
- if (hasNullishValues(array)) {
95
- return false;
96
- }
97
-
98
- // For arrays with only sortable primitives, check if they have the same type
99
- const firstItem = array[0];
100
- if (!isSortablePrimitive(firstItem)) return false;
101
-
102
- const expectedType = typeof firstItem;
103
- const allSameType = array.every((item) => typeof item === expectedType);
104
-
105
- // If all items are the same type, we can sort them normally
106
- if (allSameType) return true;
107
-
108
- // If we have mixed primitive types, we can still "sort" them
109
- // (the comparison function will return 0 for different types, maintaining order)
110
- // This allows us to cover the fallback case in compareSortablePrimitives
111
- return array.every(isSortablePrimitive);
112
- }
113
-
114
- function compareSortablePrimitives(
115
- a: unknown,
116
- b: unknown,
117
- ascending: boolean
118
- ): number {
119
- if (typeof a === 'string' && typeof b === 'string') {
120
- return ascending ? a.localeCompare(b) : b.localeCompare(a);
121
- }
122
-
123
- if (typeof a === 'number' && typeof b === 'number') {
124
- return compareNumbers(a, b, ascending);
125
- }
126
-
127
- if (typeof a === 'boolean' && typeof b === 'boolean') {
128
- return compareBooleans(a, b, ascending);
129
- }
130
-
131
- return 0; // Maintain order for other primitives
132
- }
133
-
134
- function compareNumbers(a: number, b: number, ascending: boolean): number {
135
- if (Number.isNaN(a) && Number.isNaN(b)) return 0;
136
- if (Number.isNaN(a)) return 1;
137
- if (Number.isNaN(b)) return -1;
138
-
139
- return ascending ? a - b : b - a;
140
- }
141
-
142
- function compareBooleans(a: boolean, b: boolean, ascending: boolean): number {
143
- if (a === b) return 0;
144
- if (a) return ascending ? 1 : -1;
145
- return ascending ? -1 : 1;
146
- }
147
-
148
- // Object entry handling
149
- function collectObjectEntries(obj: ObjectType): SortedEntry[] {
150
- const stringEntries = Object.entries(obj);
151
- const symbolEntries = Object.getOwnPropertySymbols(obj).map(
152
- (symbol) => [symbol, obj[symbol]] as SortedEntry
153
- );
154
-
155
- return [...stringEntries, ...symbolEntries];
156
- }
157
-
158
- function sortObjectEntries(
159
- entries: SortedEntry[],
160
- ascending: boolean
161
- ): SortedEntry[] {
162
- return entries.sort(([keyA], [keyB]) =>
163
- compareObjectKeys(keyA, keyB, ascending)
164
- );
165
- }
166
-
167
- function compareObjectKeys(
168
- keyA: string | symbol,
169
- keyB: string | symbol,
170
- ascending: boolean
171
- ): number {
172
- if (typeof keyA === 'symbol' && typeof keyB === 'symbol') return 0;
173
- if (typeof keyA === 'symbol') return 1;
174
- if (typeof keyB === 'symbol') return -1;
175
-
176
- const stringA = keyA as string;
177
- const stringB = keyB as string;
178
-
179
- return ascending
180
- ? stringA.localeCompare(stringB)
181
- : stringB.localeCompare(stringA);
182
- }
183
-
184
- function createSortedObject(
185
- entries: SortedEntry[],
186
- options: SortOptions
187
- ): ObjectType {
188
- const sortedEntries = entries.map(([key, value]) => [
189
- key,
190
- sortRecursively(value, options),
191
- ]);
192
-
193
- return Object.fromEntries(sortedEntries);
194
- }
195
-
196
- // Type guards - single responsibility, clear naming
197
- function isNullish(value: unknown): value is NullishValue {
198
- return NULLISH_VALUES.includes(value as NullishValue);
199
- }
200
-
201
- function hasNullishValues(array: unknown[]): boolean {
202
- return array.some(isNullish);
203
- }
204
-
205
- function isSortablePrimitive(
206
- value: unknown
207
- ): value is string | number | boolean {
208
- return SORTABLE_PRIMITIVE_TYPES.includes(
209
- typeof value as SortablePrimitiveType
210
- );
211
- }
212
-
213
- function isPrimitive(data: unknown): boolean {
214
- return isSortablePrimitive(data) || isNullish(data);
215
- }
216
-
217
- function isObject(data: unknown): data is ObjectType {
218
- return typeof data === 'object' && data !== null;
219
- }
220
-
221
- function isNonSortableObject(obj: unknown): obj is NonSortableType {
222
- const nonSortableTypes = [
223
- Date,
224
- RegExp,
225
- Function,
226
- Error,
227
- Map,
228
- Set,
229
- WeakMap,
230
- WeakSet,
231
- Promise,
232
- ];
233
-
234
- return (
235
- nonSortableTypes.some((type) => obj instanceof type) ||
236
- Symbol.iterator in Object(obj)
237
- );
238
- }
13
+ // Re-export types for convenience
14
+ export type {
15
+ SortOptions,
16
+ ObjectType,
17
+ SortedEntry,
18
+ NonSortableType,
19
+ } from './types';
@@ -0,0 +1,41 @@
1
+ import { SortOptions, ObjectType, SortedEntry } from './types';
2
+ import { compareObjectKeys } from './comparators';
3
+
4
+ export function sortObject(obj: ObjectType, options: SortOptions): ObjectType {
5
+ const entries = collectObjectEntries(obj);
6
+ const sortedEntries = sortObjectEntries(entries, options.ascending);
7
+ return createSortedObject(sortedEntries, options);
8
+ }
9
+
10
+ // Object entry handling
11
+ function collectObjectEntries(obj: ObjectType): SortedEntry[] {
12
+ const stringEntries = Object.entries(obj);
13
+ const symbolEntries = Object.getOwnPropertySymbols(obj).map(
14
+ (symbol) => [symbol, obj[symbol]] as SortedEntry
15
+ );
16
+
17
+ return [...stringEntries, ...symbolEntries];
18
+ }
19
+
20
+ function sortObjectEntries(
21
+ entries: SortedEntry[],
22
+ ascending: boolean
23
+ ): SortedEntry[] {
24
+ return entries.sort(([keyA], [keyB]) =>
25
+ compareObjectKeys(keyA, keyB, ascending)
26
+ );
27
+ }
28
+
29
+ function createSortedObject(
30
+ entries: SortedEntry[],
31
+ options: SortOptions
32
+ ): ObjectType {
33
+ const sortedEntries = entries.map(([key, value]) => [
34
+ key,
35
+ sortRecursively(value, options),
36
+ ]);
37
+
38
+ return Object.fromEntries(sortedEntries);
39
+ }
40
+
41
+ import { sortRecursively } from './core';
@@ -0,0 +1,51 @@
1
+ import {
2
+ NULLISH_VALUES,
3
+ SORTABLE_PRIMITIVE_TYPES,
4
+ NullishValue,
5
+ NonSortableType,
6
+ ObjectType,
7
+ } from './types';
8
+
9
+ // Type guards - single responsibility, clear naming
10
+ export function isNullish(value: unknown): value is NullishValue {
11
+ return NULLISH_VALUES.includes(value as NullishValue);
12
+ }
13
+
14
+ export function hasNullishValues(array: unknown[]): boolean {
15
+ return array.some(isNullish);
16
+ }
17
+
18
+ export function isSortablePrimitive(
19
+ value: unknown
20
+ ): value is string | number | boolean {
21
+ return SORTABLE_PRIMITIVE_TYPES.includes(
22
+ typeof value as 'string' | 'number' | 'boolean'
23
+ );
24
+ }
25
+
26
+ export function isPrimitive(data: unknown): boolean {
27
+ return isSortablePrimitive(data) || isNullish(data);
28
+ }
29
+
30
+ export function isObject(data: unknown): data is ObjectType {
31
+ return typeof data === 'object' && data !== null;
32
+ }
33
+
34
+ export function isNonSortableObject(obj: unknown): obj is NonSortableType {
35
+ const nonSortableTypes = [
36
+ Date,
37
+ RegExp,
38
+ Function,
39
+ Error,
40
+ Map,
41
+ Set,
42
+ WeakMap,
43
+ WeakSet,
44
+ Promise,
45
+ ];
46
+
47
+ return (
48
+ nonSortableTypes.some((type) => obj instanceof type) ||
49
+ Symbol.iterator in Object(obj)
50
+ );
51
+ }
package/src/types.ts ADDED
@@ -0,0 +1,29 @@
1
+ // Constants - make the code self-documenting
2
+ export const NULLISH_VALUES = [null, undefined] as const;
3
+ export const SORTABLE_PRIMITIVE_TYPES = [
4
+ 'string',
5
+ 'number',
6
+ 'boolean',
7
+ ] as const;
8
+
9
+ // Type definitions that make sense
10
+ export type NullishValue = (typeof NULLISH_VALUES)[number];
11
+ export type SortablePrimitiveType = (typeof SORTABLE_PRIMITIVE_TYPES)[number];
12
+ export type ObjectType = Record<string | symbol, unknown>;
13
+ export type SortedEntry = [string | symbol, unknown];
14
+ export type NonSortableType =
15
+ | Date
16
+ | RegExp
17
+ | (() => unknown)
18
+ | ((...args: unknown[]) => unknown)
19
+ | Error
20
+ | Map<unknown, unknown>
21
+ | Set<unknown>
22
+ | WeakMap<object, unknown>
23
+ | WeakSet<object>
24
+ | Promise<unknown>;
25
+
26
+ export type SortOptions = {
27
+ ascending: boolean;
28
+ sortPrimitiveArrays: boolean;
29
+ };