Skip to content

Commit 951ddc4

Browse files
committed
Require Node.js 20 and split option into two
1 parent 8314f02 commit 951ddc4

File tree

7 files changed

+1169
-340
lines changed

7 files changed

+1169
-340
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
node-version:
13-
- 18
13+
- 24
14+
- 22
15+
- 20
1416
steps:
1517
- uses: actions/checkout@v4
1618
- uses: actions/setup-node@v4

index.d.ts

Lines changed: 132 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,173 @@
1+
/**
2+
Context information passed to filter functions.
3+
*/
4+
export type SortContext = {
5+
/**
6+
The current key being processed.
7+
*/
8+
readonly key: string;
9+
10+
/**
11+
The value associated with the current key
12+
*/
13+
readonly value: unknown;
14+
15+
/**
16+
The full path to this key as an array of path elements (for example, `['user', 'profile', 'name']`).
17+
18+
Array indices are stringified (for example, `['items', '0', 'title']`).
119
2-
export type IgnoreContext = {
3-
/** Key of the current object or array item. */
4-
key: string | number | undefined;
5-
/** Value of the current object or array item. */
6-
value: unknown;
7-
/** Path to the current object or array item. */
8-
path: Array<string | number>;
9-
/** Current depth in the object or array. */
10-
depth: number;
20+
Examples of generated paths (with depths):
21+
- ['user'] (depth: 0)
22+
- ['user', 'profile'] (depth: 1)
23+
- ['user', 'profile', 'name'] (depth: 2)
24+
- ['items'] (depth: 0)
25+
- ['items', '0'] (depth: 1)
26+
- ['items', '0', 'title'] (depth: 2)
27+
- ['items', '1'] (depth: 1)
28+
- ['items', '1', 'title'] (depth: 2)
29+
*/
30+
readonly path: readonly string[];
31+
32+
/**
33+
The current nesting depth (0 for root level).
34+
*/
35+
readonly depth: number;
1136
};
1237

1338
export type Options = {
1439
/**
15-
Recursively sort keys, including keys of objects inside arrays.
40+
Compare function for sorting keys.
1641
17-
@default false
42+
@see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
43+
44+
If omitted, remaining keys are sorted using the platform's default string sort.
1845
*/
19-
readonly deep?: boolean;
46+
readonly compare?: (left: string, right: string) => number;
2047

2148
/**
22-
[Compare function.](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)
49+
Recursively sort keys, including keys of objects inside arrays.
50+
51+
@default false
52+
53+
Only plain objects are sorted; other object types are left as-is. For arrays, deep processing applies to their elements.
54+
55+
When a boolean:
56+
- `true`: Deep process all nested objects and arrays.
57+
- `false`: Only sort keys at the current level.
58+
59+
When a function, it receives a context object and should return `true` to enable deep processing for that specific key-value pair.
60+
The context is `SortContext` with `{ key, value, path, depth }`.
61+
62+
@example
63+
```
64+
// Fine-grained deep control with context
65+
sortKeys(data, {
66+
deep: ({key, value, path, depth}) => {
67+
// Only deep process up to 2 levels
68+
if (depth >= 2) {
69+
return false;
70+
}
71+
72+
// Skip deep processing of large arrays for performance
73+
if (Array.isArray(value) && (value as any[]).length > 100) {
74+
return false;
75+
}
76+
77+
// Skip config objects entirely
78+
if (path.includes('config')) {
79+
return false;
80+
}
81+
82+
return true;
83+
}
84+
});
85+
```
2386
*/
24-
readonly compare?: (left: string, right: string) => number;
87+
readonly deep?: boolean | ((context: SortContext) => boolean);
2588

2689
/**
27-
Decide whether to skip sorting of certain options based on the result returned by ignore function. This only applies when `deep` is set to `true`.
90+
Keys to ignore during sorting. Ignored keys appear first in their original order, followed by the sorted keys. Remaining keys are sorted by `compare`, or by default string sort if `compare` is not provided.
91+
92+
@default []
93+
94+
Only affects the ordering of object keys; it does not control deep processing, and array indices are not sorted or filtered.
95+
96+
Can be an array of key names, or a function that receives context and returns true to ignore the key.
97+
98+
@example
99+
```
100+
// Ignore by name; ignored keys keep original order and appear first
101+
sortKeys({c: 0, _private: 1, a: 0, b: 0}, {ignoreKeys: ['_private']});
102+
//=> {_private: 1, a: 0, b: 0, c: 0}
103+
104+
// Ignore by function with multiple conditions
105+
sortKeys(data, {
106+
ignoreKeys: ({key, value, path, depth}) => {
107+
// Ignore private keys at root level
108+
if (key.startsWith('_') && depth === 0) {
109+
return true;
110+
}
111+
112+
// Ignore metadata keys in user objects
113+
if (path[0] === 'user' && key === 'metadata') {
114+
return true;
115+
}
116+
117+
// Ignore empty objects
118+
if (typeof value === 'object' && Object.keys(value as any).length === 0) {
119+
return true;
120+
}
121+
122+
return false;
123+
}
124+
});
125+
```
28126
*/
29-
readonly ignore?: (context: IgnoreContext) => boolean;
127+
readonly ignoreKeys?: readonly string[] | ((context: SortContext) => boolean);
30128
};
31129

32130
/**
33131
Sort the keys of an object.
34132
133+
@param object - The object or array to sort.
35134
@returns A new object with sorted keys.
36135
136+
Property descriptors are preserved, including accessors (get/set); getters are not invoked or deep-processed. Circular references are supported and preserved.
137+
138+
When it's an object:
139+
- Only plain objects are deeply processed.
140+
- Only enumerable own string keys are considered; symbol and non-enumerable properties are ignored.
141+
142+
When it's an array:
143+
- Array order is unchanged; holes in sparse arrays are preserved.
144+
- Elements may be deep-processed if `deep` enables it.
145+
- Extra enumerable properties on arrays are ignored.
146+
37147
@example
38-
```
39148
import sortKeys from 'sort-keys';
40149
150+
// Basic usage
41151
sortKeys({c: 0, a: 0, b: 0});
42152
//=> {a: 0, b: 0, c: 0}
43153
154+
// Deep sorting of nested objects
44155
sortKeys({b: {b: 0, a: 0}, a: 0}, {deep: true});
45156
//=> {a: 0, b: {a: 0, b: 0}}
46157
158+
// Deep sorting of objects inside arrays
47159
sortKeys({b: [{b: 0, a: 0}], a: 0}, {deep: true});
48160
//=> {a: 0, b: [{a: 0, b: 0}]}
49161
162+
// Custom key compare (reverse alphabetical)
50163
sortKeys({c: 0, a: 0, b: 0}, {
51164
compare: (a, b) => -a.localeCompare(b)
52165
});
53166
//=> {c: 0, b: 0, a: 0}
54167
55-
sortKeys([{b: 0, a:2}], {deep: true});
168+
// Deep processing for a top-level array
169+
sortKeys([{b: 0, a: 2}], {deep: true});
56170
//=> [{a: 2, b: 0}]
57-
```
58171
*/
59172
export default function sortKeys<T extends Record<string, any>>(
60173
object: T,

0 commit comments

Comments
 (0)