@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.
@@ -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
+ CLI Building entry: src/index.ts
7
+ CLI Using tsconfig: tsconfig.json
8
+ CLI tsup v7.3.0
9
+ CLI Using tsup config: /home/benmclean/projects/stack-dev/packages/core/tsup.config.ts
10
+ CLI Target: esnext
11
+ CLI Cleaning output folder
12
+ ESM Build start
13
+ CJS Build start
14
+ ESM dist/index.mjs 1.88 KB
15
+ ESM dist/index.mjs.map 4.31 KB
16
+ ESM ⚡️ Build success in 12ms
17
+ CJS dist/index.js 3.06 KB
18
+ CJS dist/index.js.map 4.53 KB
19
+ CJS ⚡️ Build success in 12ms
20
+ DTS Build start
21
+ DTS ⚡️ Build success in 490ms
22
+ DTS dist/index.d.mts 987.00 B
23
+ DTS dist/index.d.ts 987.00 B
@@ -0,0 +1,5 @@
1
+
2
+
3
+ > @stack-dev/core@0.1.0 check-types /home/benmclean/projects/stack-dev/packages/core
4
+ > tsc --noEmit
5
+
@@ -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 };
@@ -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":[]}
@@ -0,0 +1,3 @@
1
+ import base from '@stack-dev/eslint-config/base.mjs';
2
+
3
+ export default [...base, { ignores: ['**/dist/**'] }];
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,3 @@
1
+ import base from '@stack-dev/prettier-config/base.mjs';
2
+
3
+ export default base;
@@ -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
@@ -0,0 +1,6 @@
1
+ export * from './are-objects-equal';
2
+ export * from './have-same-items';
3
+
4
+ export * from './sort-keys';
5
+
6
+ export * from './snapshot';
@@ -0,0 +1,11 @@
1
+ export type Snapshot = {
2
+ [key: string]: SnapshotValue;
3
+ };
4
+
5
+ export type SnapshotValue =
6
+ | string
7
+ | number
8
+ | boolean
9
+ | undefined
10
+ | Snapshot
11
+ | ReadonlyArray<SnapshotValue>;
@@ -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
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "@stack-dev/typescript-config/tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "outDir": "dist"
5
+ },
6
+ "include": ["src"]
7
+ }
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
+ });
@@ -0,0 +1,11 @@
1
+ import { defineConfig } from 'vitest/config';
2
+
3
+ export default defineConfig({
4
+ test: {
5
+ globals: true,
6
+ coverage: {
7
+ provider: 'v8',
8
+ },
9
+ environment: 'node',
10
+ },
11
+ });