@stack-dev/core 0.1.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.
- package/.turbo/turbo-build.log +23 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/dist/index.d.mts +19 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +108 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +76 -0
- package/dist/index.mjs.map +1 -0
- package/eslint.config.mjs +3 -0
- package/package.json +35 -0
- package/prettier.config.mjs +3 -0
- package/src/are-objects-equal.ts +26 -0
- package/src/have-same-items.ts +84 -0
- package/src/index.ts +6 -0
- package/src/snapshot.ts +11 -0
- package/src/sort-keys.ts +7 -0
- package/src/spec/are-objects-equal.spec.ts +30 -0
- package/src/spec/have-same-items.spec.ts +29 -0
- package/src/spec/sort-keys.spec.ts +57 -0
- package/tsconfig.json +7 -0
- package/tsup.config.ts +15 -0
- package/vitest.config.mts +11 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
> @stack-dev/core@0.1.0 build /home/benmclean/projects/stack-dev/packages/core
|
|
4
|
+
> tsup
|
|
5
|
+
|
|
6
|
+
[34mCLI[39m Building entry: src/index.ts
|
|
7
|
+
[34mCLI[39m Using tsconfig: tsconfig.json
|
|
8
|
+
[34mCLI[39m tsup v7.3.0
|
|
9
|
+
[34mCLI[39m Using tsup config: /home/benmclean/projects/stack-dev/packages/core/tsup.config.ts
|
|
10
|
+
[34mCLI[39m Target: esnext
|
|
11
|
+
[34mCLI[39m Cleaning output folder
|
|
12
|
+
[34mESM[39m Build start
|
|
13
|
+
[34mCJS[39m Build start
|
|
14
|
+
[32mESM[39m [1mdist/index.mjs [22m[32m1.88 KB[39m
|
|
15
|
+
[32mESM[39m [1mdist/index.mjs.map [22m[32m4.31 KB[39m
|
|
16
|
+
[32mESM[39m ⚡️ Build success in 12ms
|
|
17
|
+
[32mCJS[39m [1mdist/index.js [22m[32m3.06 KB[39m
|
|
18
|
+
[32mCJS[39m [1mdist/index.js.map [22m[32m4.53 KB[39m
|
|
19
|
+
[32mCJS[39m ⚡️ Build success in 12ms
|
|
20
|
+
[34mDTS[39m Build start
|
|
21
|
+
[32mDTS[39m ⚡️ Build success in 490ms
|
|
22
|
+
[32mDTS[39m [1mdist/index.d.mts [22m[32m987.00 B[39m
|
|
23
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m987.00 B[39m
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare const TOL = 1e-8;
|
|
2
|
+
type EqualityChecker<T> = (t1: T, t2: T, tol?: number) => boolean;
|
|
3
|
+
interface Equalable {
|
|
4
|
+
equals(other: unknown, tol?: number): boolean;
|
|
5
|
+
}
|
|
6
|
+
declare function isEqualable(t: unknown): t is Equalable;
|
|
7
|
+
declare const defaultEqualityChecker: EqualityChecker<unknown>;
|
|
8
|
+
declare function haveSameItems<T>(it1: Iterable<T>, it2: Iterable<T>, check?: EqualityChecker<T>): boolean;
|
|
9
|
+
|
|
10
|
+
declare function areObjectsEqual<T>(a: Record<string, T>, b: Record<string, T>, areEqual?: EqualityChecker<T>): boolean;
|
|
11
|
+
|
|
12
|
+
declare function sortKeys<T extends Record<string, unknown>>(obj: T, comparer: (a: string, b: string) => number): Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
type Snapshot = {
|
|
15
|
+
[key: string]: SnapshotValue;
|
|
16
|
+
};
|
|
17
|
+
type SnapshotValue = string | number | boolean | undefined | Snapshot | ReadonlyArray<SnapshotValue>;
|
|
18
|
+
|
|
19
|
+
export { type Equalable, type EqualityChecker, type Snapshot, type SnapshotValue, TOL, areObjectsEqual, defaultEqualityChecker, haveSameItems, isEqualable, sortKeys };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
declare const TOL = 1e-8;
|
|
2
|
+
type EqualityChecker<T> = (t1: T, t2: T, tol?: number) => boolean;
|
|
3
|
+
interface Equalable {
|
|
4
|
+
equals(other: unknown, tol?: number): boolean;
|
|
5
|
+
}
|
|
6
|
+
declare function isEqualable(t: unknown): t is Equalable;
|
|
7
|
+
declare const defaultEqualityChecker: EqualityChecker<unknown>;
|
|
8
|
+
declare function haveSameItems<T>(it1: Iterable<T>, it2: Iterable<T>, check?: EqualityChecker<T>): boolean;
|
|
9
|
+
|
|
10
|
+
declare function areObjectsEqual<T>(a: Record<string, T>, b: Record<string, T>, areEqual?: EqualityChecker<T>): boolean;
|
|
11
|
+
|
|
12
|
+
declare function sortKeys<T extends Record<string, unknown>>(obj: T, comparer: (a: string, b: string) => number): Record<string, unknown>;
|
|
13
|
+
|
|
14
|
+
type Snapshot = {
|
|
15
|
+
[key: string]: SnapshotValue;
|
|
16
|
+
};
|
|
17
|
+
type SnapshotValue = string | number | boolean | undefined | Snapshot | ReadonlyArray<SnapshotValue>;
|
|
18
|
+
|
|
19
|
+
export { type Equalable, type EqualityChecker, type Snapshot, type SnapshotValue, TOL, areObjectsEqual, defaultEqualityChecker, haveSameItems, isEqualable, sortKeys };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
TOL: () => TOL,
|
|
24
|
+
areObjectsEqual: () => areObjectsEqual,
|
|
25
|
+
defaultEqualityChecker: () => defaultEqualityChecker,
|
|
26
|
+
haveSameItems: () => haveSameItems,
|
|
27
|
+
isEqualable: () => isEqualable,
|
|
28
|
+
sortKeys: () => sortKeys
|
|
29
|
+
});
|
|
30
|
+
module.exports = __toCommonJS(src_exports);
|
|
31
|
+
|
|
32
|
+
// src/have-same-items.ts
|
|
33
|
+
var TOL = 1e-8;
|
|
34
|
+
function isEqualable(t) {
|
|
35
|
+
return typeof t === "object" && t !== null && "equals" in t && typeof t.equals === "function";
|
|
36
|
+
}
|
|
37
|
+
var defaultEqualityChecker = (t1, t2, tol = TOL) => {
|
|
38
|
+
if (isEqualable(t1)) {
|
|
39
|
+
return t1.equals(t2, tol);
|
|
40
|
+
} else {
|
|
41
|
+
return t1 === t2;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
function haveSameItems(it1, it2, check = defaultEqualityChecker) {
|
|
45
|
+
if (bothZeroLength(it1, it2)) {
|
|
46
|
+
return true;
|
|
47
|
+
}
|
|
48
|
+
if (diffLength(it1, it2)) {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
return haveSameItems_sameLength(it1, it2, check);
|
|
52
|
+
}
|
|
53
|
+
function haveSameItems_sameLength(it1, it2, check) {
|
|
54
|
+
const arr1 = [...it1];
|
|
55
|
+
let arr2 = [...it2];
|
|
56
|
+
for (const item1 of arr1) {
|
|
57
|
+
const i2 = arr2.findIndex((item2) => check(item1, item2));
|
|
58
|
+
if (i2 === -1) {
|
|
59
|
+
return false;
|
|
60
|
+
} else {
|
|
61
|
+
arr2 = remoteAtIndex(arr2, i2);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
function remoteAtIndex(arr, i2) {
|
|
67
|
+
const before = arr.slice(0, i2);
|
|
68
|
+
const after = arr.slice(i2 + 1);
|
|
69
|
+
return [...before, ...after];
|
|
70
|
+
}
|
|
71
|
+
function diffLength(it1, it2) {
|
|
72
|
+
return getSize(it1) !== getSize(it2);
|
|
73
|
+
}
|
|
74
|
+
function bothZeroLength(it1, it2) {
|
|
75
|
+
return getSize(it1) === 0 && getSize(it2) === 0;
|
|
76
|
+
}
|
|
77
|
+
function getSize(it) {
|
|
78
|
+
return [...it].length;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// src/are-objects-equal.ts
|
|
82
|
+
function areObjectsEqual(a, b, areEqual = defaultEqualityChecker) {
|
|
83
|
+
return areObjectsEqualInOneDirection(a, b, areEqual) || areObjectsEqualInOneDirection(b, a, areEqual);
|
|
84
|
+
}
|
|
85
|
+
function areObjectsEqualInOneDirection(a, b, areEqual) {
|
|
86
|
+
for (const key of Object.keys(a)) {
|
|
87
|
+
if (!(key in b) || !areEqual(a[key], b[key])) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// src/sort-keys.ts
|
|
95
|
+
function sortKeys(obj, comparer) {
|
|
96
|
+
const sortedEntries = Object.entries(obj).sort(([a], [b]) => comparer(a, b));
|
|
97
|
+
return Object.fromEntries(sortedEntries);
|
|
98
|
+
}
|
|
99
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
100
|
+
0 && (module.exports = {
|
|
101
|
+
TOL,
|
|
102
|
+
areObjectsEqual,
|
|
103
|
+
defaultEqualityChecker,
|
|
104
|
+
haveSameItems,
|
|
105
|
+
isEqualable,
|
|
106
|
+
sortKeys
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/have-same-items.ts","../src/are-objects-equal.ts","../src/sort-keys.ts"],"sourcesContent":["export * from './are-objects-equal';\nexport * from './have-same-items';\n\nexport * from './sort-keys';\n\nexport * from './snapshot';\n","// TODO: Break this up\nexport const TOL = 1e-8;\n\nexport type EqualityChecker<T> = (t1: T, t2: T, tol?: number) => boolean;\n\nexport interface Equalable {\n equals(other: unknown, tol?: number): boolean;\n}\n\nexport function isEqualable(t: unknown): t is Equalable {\n return (\n typeof t === 'object' &&\n t !== null &&\n 'equals' in t &&\n typeof t.equals === 'function'\n );\n}\n\nexport const defaultEqualityChecker: EqualityChecker<unknown> = (\n t1,\n t2,\n tol = TOL,\n) => {\n if (isEqualable(t1)) {\n return t1.equals(t2, tol);\n } else {\n return t1 === t2;\n }\n};\n\nexport function haveSameItems<T>(\n it1: Iterable<T>,\n it2: Iterable<T>,\n check: EqualityChecker<T> = defaultEqualityChecker,\n): boolean {\n if (bothZeroLength<T>(it1, it2)) {\n return true;\n }\n\n if (diffLength<T>(it1, it2)) {\n return false;\n }\n\n return haveSameItems_sameLength(it1, it2, check);\n}\n\nfunction haveSameItems_sameLength<T>(\n it1: Iterable<T>,\n it2: Iterable<T>,\n check: EqualityChecker<T>,\n) {\n const arr1 = [...it1];\n let arr2 = [...it2];\n\n for (const item1 of arr1) {\n const i2 = arr2.findIndex((item2) => check(item1, item2));\n if (i2 === -1) {\n return false;\n } else {\n arr2 = remoteAtIndex(arr2, i2);\n }\n }\n\n return true;\n}\n\nfunction remoteAtIndex<T>(arr: Array<T>, i2: number): Array<T> {\n const before = arr.slice(0, i2);\n const after = arr.slice(i2 + 1);\n\n return [...before, ...after];\n}\n\nfunction diffLength<T>(it1: Iterable<T>, it2: Iterable<T>) {\n return getSize(it1) !== getSize(it2);\n}\n\nfunction bothZeroLength<T>(it1: Iterable<T>, it2: Iterable<T>) {\n return getSize(it1) === 0 && getSize(it2) === 0;\n}\n\nfunction getSize(it: Iterable<unknown>): number {\n return [...it].length;\n}\n","import { EqualityChecker, defaultEqualityChecker } from './have-same-items';\n\nexport function areObjectsEqual<T>(\n a: Record<string, T>,\n b: Record<string, T>,\n areEqual: EqualityChecker<T> = defaultEqualityChecker,\n): boolean {\n return (\n areObjectsEqualInOneDirection(a, b, areEqual) ||\n areObjectsEqualInOneDirection(b, a, areEqual)\n );\n}\n\nfunction areObjectsEqualInOneDirection<T>(\n a: Record<string, T>,\n b: Record<string, T>,\n areEqual: EqualityChecker<T>,\n): boolean {\n for (const key of Object.keys(a)) {\n if (!(key in b) || !areEqual(a[key], b[key])) {\n return false;\n }\n }\n\n return true;\n}\n","export function sortKeys<T extends Record<string, unknown>>(\n obj: T,\n comparer: (a: string, b: string) => number,\n): Record<string, unknown> {\n const sortedEntries = Object.entries(obj).sort(([a], [b]) => comparer(a, b));\n return Object.fromEntries(sortedEntries);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACCO,IAAM,MAAM;AAQZ,SAAS,YAAY,GAA4B;AACtD,SACE,OAAO,MAAM,YACb,MAAM,QACN,YAAY,KACZ,OAAO,EAAE,WAAW;AAExB;AAEO,IAAM,yBAAmD,CAC9D,IACA,IACA,MAAM,QACH;AACH,MAAI,YAAY,EAAE,GAAG;AACnB,WAAO,GAAG,OAAO,IAAI,GAAG;AAAA,EAC1B,OAAO;AACL,WAAO,OAAO;AAAA,EAChB;AACF;AAEO,SAAS,cACd,KACA,KACA,QAA4B,wBACnB;AACT,MAAI,eAAkB,KAAK,GAAG,GAAG;AAC/B,WAAO;AAAA,EACT;AAEA,MAAI,WAAc,KAAK,GAAG,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,SAAO,yBAAyB,KAAK,KAAK,KAAK;AACjD;AAEA,SAAS,yBACP,KACA,KACA,OACA;AACA,QAAM,OAAO,CAAC,GAAG,GAAG;AACpB,MAAI,OAAO,CAAC,GAAG,GAAG;AAElB,aAAW,SAAS,MAAM;AACxB,UAAM,KAAK,KAAK,UAAU,CAAC,UAAU,MAAM,OAAO,KAAK,CAAC;AACxD,QAAI,OAAO,IAAI;AACb,aAAO;AAAA,IACT,OAAO;AACL,aAAO,cAAc,MAAM,EAAE;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,cAAiB,KAAe,IAAsB;AAC7D,QAAM,SAAS,IAAI,MAAM,GAAG,EAAE;AAC9B,QAAM,QAAQ,IAAI,MAAM,KAAK,CAAC;AAE9B,SAAO,CAAC,GAAG,QAAQ,GAAG,KAAK;AAC7B;AAEA,SAAS,WAAc,KAAkB,KAAkB;AACzD,SAAO,QAAQ,GAAG,MAAM,QAAQ,GAAG;AACrC;AAEA,SAAS,eAAkB,KAAkB,KAAkB;AAC7D,SAAO,QAAQ,GAAG,MAAM,KAAK,QAAQ,GAAG,MAAM;AAChD;AAEA,SAAS,QAAQ,IAA+B;AAC9C,SAAO,CAAC,GAAG,EAAE,EAAE;AACjB;;;ACjFO,SAAS,gBACd,GACA,GACA,WAA+B,wBACtB;AACT,SACE,8BAA8B,GAAG,GAAG,QAAQ,KAC5C,8BAA8B,GAAG,GAAG,QAAQ;AAEhD;AAEA,SAAS,8BACP,GACA,GACA,UACS;AACT,aAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,QAAI,EAAE,OAAO,MAAM,CAAC,SAAS,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,GAAG;AAC5C,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;;;ACzBO,SAAS,SACd,KACA,UACyB;AACzB,QAAM,gBAAgB,OAAO,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3E,SAAO,OAAO,YAAY,aAAa;AACzC;","names":[]}
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
// src/have-same-items.ts
|
|
2
|
+
var TOL = 1e-8;
|
|
3
|
+
function isEqualable(t) {
|
|
4
|
+
return typeof t === "object" && t !== null && "equals" in t && typeof t.equals === "function";
|
|
5
|
+
}
|
|
6
|
+
var defaultEqualityChecker = (t1, t2, tol = TOL) => {
|
|
7
|
+
if (isEqualable(t1)) {
|
|
8
|
+
return t1.equals(t2, tol);
|
|
9
|
+
} else {
|
|
10
|
+
return t1 === t2;
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
function haveSameItems(it1, it2, check = defaultEqualityChecker) {
|
|
14
|
+
if (bothZeroLength(it1, it2)) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
if (diffLength(it1, it2)) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
return haveSameItems_sameLength(it1, it2, check);
|
|
21
|
+
}
|
|
22
|
+
function haveSameItems_sameLength(it1, it2, check) {
|
|
23
|
+
const arr1 = [...it1];
|
|
24
|
+
let arr2 = [...it2];
|
|
25
|
+
for (const item1 of arr1) {
|
|
26
|
+
const i2 = arr2.findIndex((item2) => check(item1, item2));
|
|
27
|
+
if (i2 === -1) {
|
|
28
|
+
return false;
|
|
29
|
+
} else {
|
|
30
|
+
arr2 = remoteAtIndex(arr2, i2);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
function remoteAtIndex(arr, i2) {
|
|
36
|
+
const before = arr.slice(0, i2);
|
|
37
|
+
const after = arr.slice(i2 + 1);
|
|
38
|
+
return [...before, ...after];
|
|
39
|
+
}
|
|
40
|
+
function diffLength(it1, it2) {
|
|
41
|
+
return getSize(it1) !== getSize(it2);
|
|
42
|
+
}
|
|
43
|
+
function bothZeroLength(it1, it2) {
|
|
44
|
+
return getSize(it1) === 0 && getSize(it2) === 0;
|
|
45
|
+
}
|
|
46
|
+
function getSize(it) {
|
|
47
|
+
return [...it].length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/are-objects-equal.ts
|
|
51
|
+
function areObjectsEqual(a, b, areEqual = defaultEqualityChecker) {
|
|
52
|
+
return areObjectsEqualInOneDirection(a, b, areEqual) || areObjectsEqualInOneDirection(b, a, areEqual);
|
|
53
|
+
}
|
|
54
|
+
function areObjectsEqualInOneDirection(a, b, areEqual) {
|
|
55
|
+
for (const key of Object.keys(a)) {
|
|
56
|
+
if (!(key in b) || !areEqual(a[key], b[key])) {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// src/sort-keys.ts
|
|
64
|
+
function sortKeys(obj, comparer) {
|
|
65
|
+
const sortedEntries = Object.entries(obj).sort(([a], [b]) => comparer(a, b));
|
|
66
|
+
return Object.fromEntries(sortedEntries);
|
|
67
|
+
}
|
|
68
|
+
export {
|
|
69
|
+
TOL,
|
|
70
|
+
areObjectsEqual,
|
|
71
|
+
defaultEqualityChecker,
|
|
72
|
+
haveSameItems,
|
|
73
|
+
isEqualable,
|
|
74
|
+
sortKeys
|
|
75
|
+
};
|
|
76
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/have-same-items.ts","../src/are-objects-equal.ts","../src/sort-keys.ts"],"sourcesContent":["// TODO: Break this up\nexport const TOL = 1e-8;\n\nexport type EqualityChecker<T> = (t1: T, t2: T, tol?: number) => boolean;\n\nexport interface Equalable {\n equals(other: unknown, tol?: number): boolean;\n}\n\nexport function isEqualable(t: unknown): t is Equalable {\n return (\n typeof t === 'object' &&\n t !== null &&\n 'equals' in t &&\n typeof t.equals === 'function'\n );\n}\n\nexport const defaultEqualityChecker: EqualityChecker<unknown> = (\n t1,\n t2,\n tol = TOL,\n) => {\n if (isEqualable(t1)) {\n return t1.equals(t2, tol);\n } else {\n return t1 === t2;\n }\n};\n\nexport function haveSameItems<T>(\n it1: Iterable<T>,\n it2: Iterable<T>,\n check: EqualityChecker<T> = defaultEqualityChecker,\n): boolean {\n if (bothZeroLength<T>(it1, it2)) {\n return true;\n }\n\n if (diffLength<T>(it1, it2)) {\n return false;\n }\n\n return haveSameItems_sameLength(it1, it2, check);\n}\n\nfunction haveSameItems_sameLength<T>(\n it1: Iterable<T>,\n it2: Iterable<T>,\n check: EqualityChecker<T>,\n) {\n const arr1 = [...it1];\n let arr2 = [...it2];\n\n for (const item1 of arr1) {\n const i2 = arr2.findIndex((item2) => check(item1, item2));\n if (i2 === -1) {\n return false;\n } else {\n arr2 = remoteAtIndex(arr2, i2);\n }\n }\n\n return true;\n}\n\nfunction remoteAtIndex<T>(arr: Array<T>, i2: number): Array<T> {\n const before = arr.slice(0, i2);\n const after = arr.slice(i2 + 1);\n\n return [...before, ...after];\n}\n\nfunction diffLength<T>(it1: Iterable<T>, it2: Iterable<T>) {\n return getSize(it1) !== getSize(it2);\n}\n\nfunction bothZeroLength<T>(it1: Iterable<T>, it2: Iterable<T>) {\n return getSize(it1) === 0 && getSize(it2) === 0;\n}\n\nfunction getSize(it: Iterable<unknown>): number {\n return [...it].length;\n}\n","import { EqualityChecker, defaultEqualityChecker } from './have-same-items';\n\nexport function areObjectsEqual<T>(\n a: Record<string, T>,\n b: Record<string, T>,\n areEqual: EqualityChecker<T> = defaultEqualityChecker,\n): boolean {\n return (\n areObjectsEqualInOneDirection(a, b, areEqual) ||\n areObjectsEqualInOneDirection(b, a, areEqual)\n );\n}\n\nfunction areObjectsEqualInOneDirection<T>(\n a: Record<string, T>,\n b: Record<string, T>,\n areEqual: EqualityChecker<T>,\n): boolean {\n for (const key of Object.keys(a)) {\n if (!(key in b) || !areEqual(a[key], b[key])) {\n return false;\n }\n }\n\n return true;\n}\n","export function sortKeys<T extends Record<string, unknown>>(\n obj: T,\n comparer: (a: string, b: string) => number,\n): Record<string, unknown> {\n const sortedEntries = Object.entries(obj).sort(([a], [b]) => comparer(a, b));\n return Object.fromEntries(sortedEntries);\n}\n"],"mappings":";AACO,IAAM,MAAM;AAQZ,SAAS,YAAY,GAA4B;AACtD,SACE,OAAO,MAAM,YACb,MAAM,QACN,YAAY,KACZ,OAAO,EAAE,WAAW;AAExB;AAEO,IAAM,yBAAmD,CAC9D,IACA,IACA,MAAM,QACH;AACH,MAAI,YAAY,EAAE,GAAG;AACnB,WAAO,GAAG,OAAO,IAAI,GAAG;AAAA,EAC1B,OAAO;AACL,WAAO,OAAO;AAAA,EAChB;AACF;AAEO,SAAS,cACd,KACA,KACA,QAA4B,wBACnB;AACT,MAAI,eAAkB,KAAK,GAAG,GAAG;AAC/B,WAAO;AAAA,EACT;AAEA,MAAI,WAAc,KAAK,GAAG,GAAG;AAC3B,WAAO;AAAA,EACT;AAEA,SAAO,yBAAyB,KAAK,KAAK,KAAK;AACjD;AAEA,SAAS,yBACP,KACA,KACA,OACA;AACA,QAAM,OAAO,CAAC,GAAG,GAAG;AACpB,MAAI,OAAO,CAAC,GAAG,GAAG;AAElB,aAAW,SAAS,MAAM;AACxB,UAAM,KAAK,KAAK,UAAU,CAAC,UAAU,MAAM,OAAO,KAAK,CAAC;AACxD,QAAI,OAAO,IAAI;AACb,aAAO;AAAA,IACT,OAAO;AACL,aAAO,cAAc,MAAM,EAAE;AAAA,IAC/B;AAAA,EACF;AAEA,SAAO;AACT;AAEA,SAAS,cAAiB,KAAe,IAAsB;AAC7D,QAAM,SAAS,IAAI,MAAM,GAAG,EAAE;AAC9B,QAAM,QAAQ,IAAI,MAAM,KAAK,CAAC;AAE9B,SAAO,CAAC,GAAG,QAAQ,GAAG,KAAK;AAC7B;AAEA,SAAS,WAAc,KAAkB,KAAkB;AACzD,SAAO,QAAQ,GAAG,MAAM,QAAQ,GAAG;AACrC;AAEA,SAAS,eAAkB,KAAkB,KAAkB;AAC7D,SAAO,QAAQ,GAAG,MAAM,KAAK,QAAQ,GAAG,MAAM;AAChD;AAEA,SAAS,QAAQ,IAA+B;AAC9C,SAAO,CAAC,GAAG,EAAE,EAAE;AACjB;;;ACjFO,SAAS,gBACd,GACA,GACA,WAA+B,wBACtB;AACT,SACE,8BAA8B,GAAG,GAAG,QAAQ,KAC5C,8BAA8B,GAAG,GAAG,QAAQ;AAEhD;AAEA,SAAS,8BACP,GACA,GACA,UACS;AACT,aAAW,OAAO,OAAO,KAAK,CAAC,GAAG;AAChC,QAAI,EAAE,OAAO,MAAM,CAAC,SAAS,EAAE,GAAG,GAAG,EAAE,GAAG,CAAC,GAAG;AAC5C,aAAO;AAAA,IACT;AAAA,EACF;AAEA,SAAO;AACT;;;ACzBO,SAAS,SACd,KACA,UACyB;AACzB,QAAM,gBAAgB,OAAO,QAAQ,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,SAAS,GAAG,CAAC,CAAC;AAC3E,SAAO,OAAO,YAAY,aAAa;AACzC;","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stack-dev/core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"devDependencies": {
|
|
6
|
+
"eslint": "^9.32.0",
|
|
7
|
+
"prettier": "^3.6.2",
|
|
8
|
+
"prettier-plugin-organize-imports": "^4.2.0",
|
|
9
|
+
"tsup": "^7.3.0",
|
|
10
|
+
"vitest": "^3.2.4",
|
|
11
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
12
|
+
"@stack-dev/eslint-config": "0.1.0",
|
|
13
|
+
"@stack-dev/prettier-config": "0.1.0",
|
|
14
|
+
"@stack-dev/typescript-config": "0.1.0"
|
|
15
|
+
},
|
|
16
|
+
"main": "dist/index.js",
|
|
17
|
+
"module": "dist/index.mjs",
|
|
18
|
+
"types": "dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.mjs",
|
|
23
|
+
"require": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"scripts": {
|
|
28
|
+
"check-types": "tsc --noEmit",
|
|
29
|
+
"build": "tsup",
|
|
30
|
+
"lint": "eslint",
|
|
31
|
+
"format": "prettier . --write",
|
|
32
|
+
"test": "vitest run",
|
|
33
|
+
"test:watch": "vitest"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { EqualityChecker, defaultEqualityChecker } from './have-same-items';
|
|
2
|
+
|
|
3
|
+
export function areObjectsEqual<T>(
|
|
4
|
+
a: Record<string, T>,
|
|
5
|
+
b: Record<string, T>,
|
|
6
|
+
areEqual: EqualityChecker<T> = defaultEqualityChecker,
|
|
7
|
+
): boolean {
|
|
8
|
+
return (
|
|
9
|
+
areObjectsEqualInOneDirection(a, b, areEqual) ||
|
|
10
|
+
areObjectsEqualInOneDirection(b, a, areEqual)
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function areObjectsEqualInOneDirection<T>(
|
|
15
|
+
a: Record<string, T>,
|
|
16
|
+
b: Record<string, T>,
|
|
17
|
+
areEqual: EqualityChecker<T>,
|
|
18
|
+
): boolean {
|
|
19
|
+
for (const key of Object.keys(a)) {
|
|
20
|
+
if (!(key in b) || !areEqual(a[key], b[key])) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return true;
|
|
26
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// TODO: Break this up
|
|
2
|
+
export const TOL = 1e-8;
|
|
3
|
+
|
|
4
|
+
export type EqualityChecker<T> = (t1: T, t2: T, tol?: number) => boolean;
|
|
5
|
+
|
|
6
|
+
export interface Equalable {
|
|
7
|
+
equals(other: unknown, tol?: number): boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function isEqualable(t: unknown): t is Equalable {
|
|
11
|
+
return (
|
|
12
|
+
typeof t === 'object' &&
|
|
13
|
+
t !== null &&
|
|
14
|
+
'equals' in t &&
|
|
15
|
+
typeof t.equals === 'function'
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export const defaultEqualityChecker: EqualityChecker<unknown> = (
|
|
20
|
+
t1,
|
|
21
|
+
t2,
|
|
22
|
+
tol = TOL,
|
|
23
|
+
) => {
|
|
24
|
+
if (isEqualable(t1)) {
|
|
25
|
+
return t1.equals(t2, tol);
|
|
26
|
+
} else {
|
|
27
|
+
return t1 === t2;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function haveSameItems<T>(
|
|
32
|
+
it1: Iterable<T>,
|
|
33
|
+
it2: Iterable<T>,
|
|
34
|
+
check: EqualityChecker<T> = defaultEqualityChecker,
|
|
35
|
+
): boolean {
|
|
36
|
+
if (bothZeroLength<T>(it1, it2)) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (diffLength<T>(it1, it2)) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return haveSameItems_sameLength(it1, it2, check);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function haveSameItems_sameLength<T>(
|
|
48
|
+
it1: Iterable<T>,
|
|
49
|
+
it2: Iterable<T>,
|
|
50
|
+
check: EqualityChecker<T>,
|
|
51
|
+
) {
|
|
52
|
+
const arr1 = [...it1];
|
|
53
|
+
let arr2 = [...it2];
|
|
54
|
+
|
|
55
|
+
for (const item1 of arr1) {
|
|
56
|
+
const i2 = arr2.findIndex((item2) => check(item1, item2));
|
|
57
|
+
if (i2 === -1) {
|
|
58
|
+
return false;
|
|
59
|
+
} else {
|
|
60
|
+
arr2 = remoteAtIndex(arr2, i2);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function remoteAtIndex<T>(arr: Array<T>, i2: number): Array<T> {
|
|
68
|
+
const before = arr.slice(0, i2);
|
|
69
|
+
const after = arr.slice(i2 + 1);
|
|
70
|
+
|
|
71
|
+
return [...before, ...after];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function diffLength<T>(it1: Iterable<T>, it2: Iterable<T>) {
|
|
75
|
+
return getSize(it1) !== getSize(it2);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function bothZeroLength<T>(it1: Iterable<T>, it2: Iterable<T>) {
|
|
79
|
+
return getSize(it1) === 0 && getSize(it2) === 0;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getSize(it: Iterable<unknown>): number {
|
|
83
|
+
return [...it].length;
|
|
84
|
+
}
|
package/src/index.ts
ADDED
package/src/snapshot.ts
ADDED
package/src/sort-keys.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function sortKeys<T extends Record<string, unknown>>(
|
|
2
|
+
obj: T,
|
|
3
|
+
comparer: (a: string, b: string) => number,
|
|
4
|
+
): Record<string, unknown> {
|
|
5
|
+
const sortedEntries = Object.entries(obj).sort(([a], [b]) => comparer(a, b));
|
|
6
|
+
return Object.fromEntries(sortedEntries);
|
|
7
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { areObjectsEqual } from '../are-objects-equal';
|
|
4
|
+
|
|
5
|
+
describe('areObjectsEqual', () => {
|
|
6
|
+
it('Returns true for both empty.', () => {
|
|
7
|
+
expect(areObjectsEqual({}, {})).toBe(true);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('Returns false with different keys.', () => {
|
|
11
|
+
const a = { a: 1, b: 2 };
|
|
12
|
+
const b = { a: 1, c: 3 };
|
|
13
|
+
|
|
14
|
+
expect(areObjectsEqual(a, b)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('Returns false with different values.', () => {
|
|
18
|
+
const a = { a: 1, b: 2 };
|
|
19
|
+
const b = { b: 3, a: 1 };
|
|
20
|
+
|
|
21
|
+
expect(areObjectsEqual(a, b)).toBe(false);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('Returns true with same values.', () => {
|
|
25
|
+
const a = { a: 1, b: 2 };
|
|
26
|
+
const b = { b: 2, a: 1 };
|
|
27
|
+
|
|
28
|
+
expect(areObjectsEqual(a, b)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { haveSameItems } from '../have-same-items';
|
|
4
|
+
|
|
5
|
+
const numsEqual = (n1: number, n2: number) => n1 === n2;
|
|
6
|
+
|
|
7
|
+
describe('haveSameItems', () => {
|
|
8
|
+
it('Returns true for empty.', () => {
|
|
9
|
+
expect(haveSameItems([], [], () => false)).toBe(true);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
it('Returns false for different sizes', () => {
|
|
13
|
+
expect(haveSameItems([], [1], () => true)).toBe(false);
|
|
14
|
+
expect(haveSameItems([1], [], () => true)).toBe(false);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('Returns true for equal in order.', () => {
|
|
18
|
+
expect(haveSameItems([1, 2, 3], [1, 2, 3], numsEqual)).toBe(true);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('Returns true for same items.', () => {
|
|
22
|
+
expect(haveSameItems([1, 2, 3], [2, 1, 3], numsEqual)).toBe(true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('Returns false for different items.', () => {
|
|
26
|
+
expect(haveSameItems([1, 2, 0], [2, 1, 3], numsEqual)).toBe(false);
|
|
27
|
+
expect(haveSameItems([1, 1, 1], [2, 1, 1], numsEqual)).toBe(false);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { areObjectsEqual } from '../are-objects-equal';
|
|
4
|
+
import { sortKeys } from '../sort-keys';
|
|
5
|
+
|
|
6
|
+
describe('sortKeys', () => {
|
|
7
|
+
it('sorts keys alphabetically', () => {
|
|
8
|
+
const input = {
|
|
9
|
+
z: 1,
|
|
10
|
+
a: 2,
|
|
11
|
+
m: 3,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const result = sortKeys(input, (a, b) => a.localeCompare(b));
|
|
15
|
+
|
|
16
|
+
expect(Object.keys(result)).toEqual(['a', 'm', 'z']);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('prioritizes certain keys using a custom comparer', () => {
|
|
20
|
+
const input = {
|
|
21
|
+
scripts: {},
|
|
22
|
+
version: '1.0.0',
|
|
23
|
+
name: 'my-package',
|
|
24
|
+
dependencies: {},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
function comparer(a: string, b: string) {
|
|
28
|
+
return getIndex(a) - getIndex(b);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function getIndex(s: string): number {
|
|
32
|
+
switch (s.toLowerCase()) {
|
|
33
|
+
case 'name':
|
|
34
|
+
return 1;
|
|
35
|
+
case 'version':
|
|
36
|
+
return 2;
|
|
37
|
+
case 'scripts':
|
|
38
|
+
return 3;
|
|
39
|
+
case 'dependencies':
|
|
40
|
+
return 4;
|
|
41
|
+
default:
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const result = sortKeys(input, comparer);
|
|
47
|
+
|
|
48
|
+
expect(Object.keys(result)).toEqual([
|
|
49
|
+
'name',
|
|
50
|
+
'version',
|
|
51
|
+
'scripts',
|
|
52
|
+
'dependencies',
|
|
53
|
+
]);
|
|
54
|
+
|
|
55
|
+
expect(areObjectsEqual(input, result)).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|
package/tsconfig.json
ADDED
package/tsup.config.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'tsup';
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
entry: ['src/index.ts'],
|
|
5
|
+
format: ['esm', 'cjs'], // Tree-shakable ESM + CommonJS for broader support
|
|
6
|
+
dts: true, // Emit type declarations
|
|
7
|
+
sourcemap: true,
|
|
8
|
+
clean: true,
|
|
9
|
+
target: 'esnext',
|
|
10
|
+
outExtension({ format }) {
|
|
11
|
+
return {
|
|
12
|
+
js: format === 'esm' ? '.mjs' : '.js',
|
|
13
|
+
};
|
|
14
|
+
},
|
|
15
|
+
});
|