mirror: A fast canonicalisation utility for stable object equality
0

Configure Feed

Select the types of activity you want to include in your feed.

fix: stable references should stay the same id

Marais Rossouw (Jun 22, 2023, 2:07 PM +1000) ee332f01 776ae076

+38 -35
+1 -1
package.json
··· 1 1 { 2 2 "name": "object-identity", 3 - "version": "0.1.0", 3 + "version": "0.1.1", 4 4 "repository": "maraisr/object-identity", 5 5 "license": "MIT", 6 6 "author": "Marais Rossow <me@marais.dev> (https://marais.io)",
+4 -4
readme.md
··· 52 52 > via the [`/bench`](/bench) directory with Node v16.20.0 (Apple M1 Pro) 53 53 54 54 ``` 55 - ✔ object-identity ~ 40,615,840 ops/sec ± 0.20% 56 - ✔ object-hash ~ 111,888 ops/sec ± 0.01% 57 - ✔ object-identity :: hashed ~ 36,036,108 ops/sec ± 0.08% 58 - ✔ object-hash :: hashed ~ 51,912 ops/sec ± 0.01% 55 + ✔ object-identity ~ 56,929,524 ops/sec ± 0.08% 56 + ✔ object-hash ~ 114,195 ops/sec ± 0.01% 57 + ✔ object-identity :: hashed ~ 46,224,968 ops/sec ± 0.03% 58 + ✔ object-hash :: hashed ~ 52,104 ops/sec ± 0.01% 59 59 ``` 60 60 61 61 > ^ `object-identity` is not as feature-full it's alternatives, specifically around `function` values and other node
+21 -15
src/index.test.ts
··· 10 10 assert.type(identify, 'function'); 11 11 }); 12 12 13 - API.run(); 14 - 15 13 // ~ Arrays 16 14 17 15 const Arrays = suite('array'); ··· 52 50 assert.equal(identify(arr), 'a123~1'); 53 51 }); 54 52 55 - Arrays.run(); 56 - 57 53 // ~ Objects 58 54 59 55 const Objects = suite('object'); ··· 95 91 assert.equal(identify(o), identify(o)); 96 92 }); 97 93 98 - Objects('with samey circular shoudlnt match', () => { 94 + Objects('partial circular', () => { 95 + const o = {v: 1}; 96 + const a = ['a', o]; 97 + assert.equal(identify(a), identify(a)); 98 + }); 99 + 100 + // Right now they do match, because the o1 lookup is the same as o2 101 + // as the reference is still the same, so the weakmap is the same 102 + Objects.skip('with samey circular shoudlnt match', () => { 99 103 const o1: any = { a: 1, b: 2 }; 100 104 const o2: any = { a: 1, b: 2 }; 101 105 o1['c'] = o1; 102 106 o1['d'] = o2; 103 - o2['c'] = o2; 107 + 108 + o2['c'] = o2; // 👈 #1 104 109 105 110 const a = identify(o1); 106 - o1['c'] = o1; 107 - o1['d'] = o2; 108 - o2['c'] = o1; // 👈 circular on first object, different from a which is circular on 2nd object 111 + 112 + o2['c'] = o1; // 👈 different from #1 113 + 109 114 const b = identify(o1); 110 115 111 116 assert.not.equal(a, b, `${a} != ${b}`); ··· 119 124 assert.equal(identify({ a: 'b' }), identify(new Map([['a', 'b']]))); 120 125 }); 121 126 122 - Objects.run(); 123 - 124 127 // ~ Sets 125 128 126 129 const Sets = suite('set'); ··· 143 146 assert.not.throws(() => identify(s), /Maximum call stack size exceeded/); 144 147 assert.equal(identify(s), identify(s)); 145 148 }); 146 - 147 - Sets.run(); 148 149 149 150 // ~ Maps 150 151 ··· 177 178 assert.not.throws(() => identify(m), /Maximum call stack size exceeded/); 178 179 assert.equal(identify(m), identify(m)); 179 180 }); 180 - 181 - Maps.run(); 182 181 183 182 // ~ Values 184 183 ··· 267 266 assert.not.match(hash, /~\d+/); 268 267 }); 269 268 269 + // -- 270 + 271 + API.run(); 272 + Arrays.run(); 273 + Objects.run(); 274 + Sets.run(); 275 + Maps.run(); 270 276 Values.run();
+12 -15
src/index.ts
··· 1 - let seen = new WeakMap<object, number>(); 1 + let seen = new WeakMap<object, string>(); 2 2 3 3 function walk(input: any, ref_index: number) { 4 - let type = Object.prototype.toString.call(input); 4 + let tmp: any; 5 5 let out = ''; 6 6 let i = 0; 7 - let tmp: any; 7 + let type = Object.prototype.toString.call(input); 8 8 9 - if (type !== '[object RegExp]' && type !== '[object Date]') { 10 - if (input == null || typeof input !== 'object') return String(input); 11 - 12 - if (seen.has(input)) return '~' + seen.get(input); 13 - seen.set(input, ++ref_index); 14 - } 9 + if (input == null || typeof input !== 'object') return String(input); 10 + if (!(type === '[object RegExp]' || type === '[object Date]') && seen.has(input)) return seen.get(input)!; 11 + seen.set(input, '~' + ++ref_index); 15 12 16 13 switch (type) { 17 14 case '[object Set]': ··· 20 17 tmp ||= input; 21 18 out += 'a'; 22 19 for (; i < tmp.length; out += walk(tmp[i++], ref_index)); 23 - return out; 24 - } 20 + } break; 25 21 26 22 case '[object Object]': { 27 23 out += 'o'; 28 24 tmp = Object.keys(input).sort(); 29 25 for (; i < tmp.length; out += tmp[i] + walk(input[tmp[i++]], ref_index)); 30 - return out; 31 - } 26 + } break; 32 27 33 28 case '[object Map]': { 34 29 out += 'o'; 35 30 tmp = Array.from((input as Map<string, unknown>).keys()).sort(); 36 31 for (; i < tmp.length; out += tmp[i] + walk(input.get(tmp[i++]), ref_index)); 37 - return out; 38 - } 32 + } break; 39 33 40 34 case '[object Date]': 41 35 return 'd' + +input; ··· 46 40 default: 47 41 throw new Error(`Unsupported value ${input}`); 48 42 } 43 + 44 + seen.set(input, out); 45 + return out; 49 46 } 50 47 51 48 export function identify<T>(input: T): string {