Gathering detailed insights and metrics for snapman
Gathering detailed insights and metrics for snapman
Snapshots manager for taking snapshots where the values are automatically deep cloned when taken. Track timeline. And can be used as events source. Navigate snapshots and there timeline. Can be used for testing and any other application.
npm install snapman
Typescript
Module System
Node Version
NPM Version
73.4
Supply Chain
99
Quality
75.5
Maintenance
100
Vulnerability
99.6
License
HTML (65.68%)
TypeScript (16.44%)
JavaScript (12.46%)
CSS (5.42%)
Love this project? Help keep it running — sponsor us today! 🚀
Total Downloads
1,117
Last Day
1
Last Week
19
Last Month
42
Last Year
344
2 Stars
28 Commits
1 Watching
1 Branches
1 Contributors
Minified
Minified + Gzipped
Latest Version
1.0.10
Package Id
snapman@1.0.10
Unpacked Size
127.88 kB
Size
23.90 kB
File Count
34
NPM Version
9.2.0
Node Version
19.3.0
Cumulative downloads
Total Downloads
Last day
-50%
1
Compared to previous day
Last week
35.7%
19
Compared to previous week
Last month
366.7%
42
Compared to previous month
Last year
34.9%
344
Compared to previous year
2
32
Snapshots manager for taking snapshots where the values are automatically deep cloned when taken. Track timeline. And can be used as events source. Navigate snapshots and there timeline. Can be used for testing and any other application.
See examples at the end for usage with jest. Here some articles and content showing that.
1npm install snapman 2pnpm add snapman 3pnpm add snapman
For usage with browser you can use bundlers like webpack or vite or rollup.
Otherwise you can use the CDN link:
1<!-- Latest version --> 2<script src="https://unpkg.com/snapman"></script> 3 4<!-- Specific: --> 5<script src="https://unpkg.com/snapman@1.0.6"></script>
And also you can use the already bundled umd index.umd.js
file in releases.
The library is exposed as SnapmanJs
1const sm = new SnapmanJs.Snapman();
1interface ITypeDef { 2 [id: string]: { 3 id: string; 4 }; 5} 6 7const s = new Snapman<ITypeDef>();
1public snap<TId extends string>(id: string, val?: TMapDef[TId]): this
1for (let i = 0; i < 5; i++) { 2 s.snap(`category1:snap-${i}`, { 3 id: `snap-${i}`, 4 }); 5} 6 7for (let i = 0; i < 3; i++) { 8 s.snap(`category2:snap-${i}`, { 9 id: `snap-${i}`, 10 }); 11}
That is 8 snapshots taken
For the example above, the timeline is
1// type: Snap[] 2[Snap-category1:snap-1, ...., Snap-category1:snap-4, Snap-category2:snap-0, ..., Snap-category2:snap-3]
to access a timeline
1sm.getSnapTimeLine()
1// by id 2sm.getSnap('category1:snap-4') 3 4// by index in the timeline, start from zero 5sm.getSnapAtIndex(index) 6 7// Search and get the snapshots from the timeline that start with the part of the id. (start) 8const snaps = sm.getSnaps('category1'); 9// ---> Will get all the snapshots of an id equal to `category1` or starting by `category1:` (the `:` delimiter is used) 10const snaps = sm.getSnaps('tegory1'); 11// ---> will return an empty array. getSnaps() doesn't match substring but only t he one that start from the start. 12// You can use searchSnaps() instead of you want to match against just any substring 13const snaps = sm.getSnaps('category'); 14// ---> Will return an empty array as well. because no `category` id or an id that start with `category:` exist. 15 16 17// Searching for snapshots matching against id 18// ---- substring 19sm.searchSnaps('category1') 20// return all snapshots that category1 is a substring of there id 21// ---- regex 22sm.searchSnaps(/category1/) 23// return all snapshots that the regex /category1/ match there id 24 25// Snap.next() Snap.previous() and navigating timeline 26const snap = sm.getSnapAtIndex(index) 27const nextSnap = snap.next() // access the next snap in the timeline 28const prevSnap = snap.previous() // access the previous snap in the timeline
For convenience and ease of use. If you are in a case where you need to take snapshots of the same event (id). Snapman help with that in the following way:
If we take multiple snaps with same id like in:
1for (let i = 1; i < 5; i++) { 2 sm2.snap('sameId', { 3 id: `snap-${i}`, 4 }); 5}
That would do the following:
sameId
sameId:{index}
while index gonna start with 2
.Meaning sameId, sameId:2, sameId:3 ...
you got it.
sameId
. Is added to the timeline and added to the map.sameId:2, sameId:3 ...
will be added to the timeline in the order they were taken. So if we do:1sm.snap('some', {}) 2sm.snap('sameId', {}) 3sm.snap('someOther', {}) 4sm.snap('sameId', {}) 5sm.snap('sameId', {}) 6sm.snap('someOther', {})
timeline ==>
some, someId, someOther, sameId:2, sameId:3, someOther:2
sm.getSnapsOfId()
1for (let i = 1; i < 5; i++) { 2 sm.snap('sameId', { 3 id: `snap-${i}`, 4 }); 5} 6 7const snaps = sm.getSnapsOfId('sameId') 8// Snap[] -> [sameId, sameId:2, sameId:3, sameId:4]
sm.getSnap(id)
1const snap3 = sm.getSnap('sameId:3')
1// sameId:3 already exists 2sm.snap('sameId:3', {}) 3sm.snap('sameId:3', {})
=> This will create sameId:3:2, sameId:3:3
. Making the totality of sameId:3, sameId:3:2, sameId:3:3
.
1const snaps = sm.getSnapsOfId('sameId:3') 2// Snap[] -> [sameId:3, sameId:3:2, sameId:3:3]
Do that only when you need it.
Also given that the above is done.
1const snaps = sm.getSnapsOfId('sameId') 2// Snap[] -> [sameId, sameId:2, sameId:3, sameId:4]
Will still return the same as before. As getSnapsOfId()
will return the snapshots that were taken by the same id when using snap()
.
And surely to access just all in case you ever need. use:
1const snaps = sm.getSnaps('sameId'); 2// Snap[] -> [sameId, sameId:2, sameId:3, sameId:4, sameId:3:2, sameId:3:3]
And it follows the timeline, in matter of order.
Snap
object and accessing values1 const snap = s.getSnapAtIndex(3); 2 3 // accessing id of the snap 4 snap.getId() 5 snap.id() // alias 6 7 // accessing the value of the snap, (safely cloned at the time the snapshot was created) 8 snap.getVal() 9 snap.val() // alias 10 11 // accessing the index of the snapshot in the timeline 12 snap.getTimelineIndex() 13 snap.tIndex() // alias
Funny enough one of the main usage intended for snapman is testing.
First get to know the api through the test file of snapman
itself.
1import { Snapman } from './index.js'; 2import { Snap } from '/Snap/index.js'; 3import { last } from '/Utils/helpers.js'; 4 5interface ITypeDef { 6 [id: string]: { 7 id: string; 8 }; 9} 10 11const sm = new Snapman<ITypeDef>(); 12 13for (let i = 0; i < 5; i++) { 14 sm.snap(`category1:snap-${i}`, { 15 id: `snap-${i}`, 16 }); 17} 18 19for (let i = 0; i < 3; i++) { 20 sm.snap(`category2:snap-${i}`, { 21 id: `snap-${i}`, 22 }); 23} 24 25const sm2 = new Snapman<ITypeDef>(); 26 27for (let i = 0; i < 3; i++) { 28 sm2.snap(`before-sameId:${i}`, { 29 id: `snap-${i}`, 30 }); 31} 32 33for (let i = 1; i < 5; i++) { 34 sm2.snap('sameId', { 35 id: `snap-${i}`, 36 }); 37} 38 39sm2.snap('after-sameId', { id: 'afterSameId' }); 40 41test('snap() and Timeline is working well', () => { 42 expect(sm.getSnapsCount()).toBe(8); 43 expect(sm.getSnapTimeLine().map((snap) => snap.id())).toEqual( 44 Array(8) 45 .fill(0) 46 .map((_, i) => { 47 if (i < 5) { 48 return `category1:snap-${i}`; 49 } 50 return `category2:snap-${i - 5}`; 51 }), 52 ); 53 expect(sm.getSnapAtIndex(4).id()).toBe('category1:snap-4'); 54 expect(sm.getSnapAtIndex(2).next(2)?.id()).toBe('category1:snap-4'); 55 56 let snap = sm.getSnapAtIndex(4); 57 for (let i = 5; i < 8; i++) { 58 snap = snap.next() as Snap; 59 expect(snap.id()).toBe(`category2:snap-${i - 5}`); 60 } 61 expect(snap.next()).toBe(undefined); 62}); 63 64test('getSnap(), getVal()', () => { 65 expect(sm.getSnap('category1:snap-4').val().id).toBe('snap-4'); 66 expect(sm.getSnap('category1:snap-3').getVal().id).toBe('snap-3'); 67}); 68 69test('getSnapAtIndex()', () => { 70 for (let i = 0; i < 5; i++) { 71 expect(sm.getSnapAtIndex(i).id()).toBe(`category1:snap-${i}`); 72 } 73}); 74 75test('searchSnaps() substring', () => { 76 expect(sm.searchSnaps('category1').map((snap) => snap.id())).toEqual( 77 Array(5) 78 .fill(0) 79 .map((_, i) => `category1:snap-${i}`), 80 ); 81}); 82 83test('searchSnaps() regex', () => { 84 expect(sm.searchSnaps(/category1/).map((snap) => snap.id())).toEqual( 85 Array(5) 86 .fill(0) 87 .map((_, i) => `category1:snap-${i}`), 88 ); 89}); 90 91test('Snap api works well for accessor', () => { 92 const snap = sm.getSnapAtIndex(3); 93 expect(snap.id()).toBe('category1:snap-3'); 94 expect(snap.getId()).toBe('category1:snap-3'); 95 expect(snap.getVal().id).toBe('snap-3'); 96 expect(snap.val().id).toBe('snap-3'); 97 expect(snap.getTimelineIndex()).toBe(3); 98 expect(snap.tIndex()).toBe(3); 99}); 100 101test('previous(), next() navigation', () => { 102 /** 103 * next() 104 */ 105 let snap = sm.getSnapAtIndex(4); 106 for (let i = 5; i < 8; i++) { 107 snap = snap.next() as Snap; 108 expect(snap.id()).toBe(`category2:snap-${i - 5}`); 109 } 110 expect(snap.next()).toBe(undefined); 111 112 snap = sm.getSnapAtIndex(5); 113 for (let i = 4; i >= 0; i--) { 114 snap = snap.previous() as Snap; 115 expect(snap.id()).toBe(`category1:snap-${i}`); 116 } 117 expect(snap.previous()).toBe(undefined); 118}); 119 120test('Testing same id snap taking and getter (getSnapsOfId())', () => { 121 const snaps = sm2.getSnapsOfId('sameId'); 122 snaps.forEach((snap, index) => { 123 let id = 'sameId'; 124 if (index > 0) { 125 id += `:${index + 1}`; 126 } 127 expect(snap.id()).toBe(id); 128 expect(snap.val().id).toBe(`snap-${index + 1}`); 129 }); 130 const lastSnap = last(snaps); 131 const nextSnapInTimeLine = lastSnap.next(); 132 expect(nextSnapInTimeLine?.id()).toBe('after-sameId'); 133 expect(nextSnapInTimeLine?.val().id).toBe('afterSameId'); 134 135 let backSnap: Snap = snaps[0]; 136 for (let i = 2; i >= 0; i--) { 137 backSnap = backSnap.previous()!; 138 expect(backSnap.id()).toBe(`before-sameId:${i}`); 139 } 140 expect(backSnap.previous()).toBe(undefined); 141}); 142 143test('getting snaps using getSnaps() and from start matching', () => { 144 { 145 const snaps = sm.getSnaps('category1'); 146 for (let i = 0; i < 5; i++) { 147 expect(snaps[i].getId()).toBe(`category1:snap-${i}`); 148 } 149 } 150 151 { 152 // testing that it still work if : was included 153 const snaps = sm.getSnaps('category1:'); 154 for (let i = 0; i < 5; i++) { 155 expect(snaps[i].getId()).toBe(`category1:snap-${i}`); 156 } 157 } 158 159 { 160 const snaps = sm.getSnaps('category'); 161 expect(snaps.length).toBe(0); 162 } 163 164 { 165 const snaps = sm.getSnaps('ategory'); 166 expect(snaps.length).toBe(0); 167 } 168 169 { 170 const snaps = sm2.getSnaps('sameId'); 171 expect(snaps[0].getId()).toBe('sameId'); 172 173 for (let i = 1; i < 4; i++) { 174 expect(snaps[i].getId()).toBe(`sameId:${i + 1}`); 175 } 176 } 177 178 // testing sub category 179 { 180 const _sm = new Snapman<ITypeDef>(); 181 _sm.snap('experience1:target1', { id: 'target1:1' }); 182 _sm.snap('experience1:target1', { id: 'target1:2' }); 183 _sm.snap('experience1:target1', { id: 'target1:3' }); 184 _sm.snap('experience1:target2', { id: 'target2:1' }); 185 _sm.snap('experience1:target2', { id: 'target2:2' }); 186 _sm.snap('experience1:target2', { id: 'target2:3' }); 187 188 const experienceSnaps = _sm.getSnaps('experience1'); 189 for (let i = 0; i < 6; i++) { 190 expect(experienceSnaps[i].getId()).toBe( 191 `experience1:target${i < 3 ? 1 : 2}${ 192 i === 0 || i === 3 ? '' : `:${(i % 3) + 1}` 193 }`, 194 ); 195 } 196 197 const target1Snaps = _sm.getSnaps('experience1:target1'); 198 for (let i = 0; i < 3; i++) { 199 expect(target1Snaps[i].getId()).toBe( 200 `experience1:target1${i === 0 ? '' : `:${i + 1}`}`, 201 ); 202 } 203 204 const target2Snaps = _sm.getSnaps('experience1:target2'); 205 for (let i = 0; i < 3; i++) { 206 expect(target2Snaps[i].getId()).toBe( 207 `experience1:target2${i === 0 ? '' : `:${i + 1}`}`, 208 ); 209 } 210 211 // testing when there is no such sub category and it's just a substring 212 const noMatchSnaps = _sm.getSnaps('experience1:target'); 213 expect(noMatchSnaps.length).toBe(0); 214 } 215}); 216test('getting sameId snapshots using getSnap()', () => { 217 for (let i = 2; i < 5; i++) { 218 const snap = sm2.getSnap(`sameId:${i}`); 219 expect(snap).toBeTruthy(); 220 expect(snap.id()).toBe(`sameId:${i}`); 221 } 222}); 223 224test('Taking extra snaps on the sameId auto incremented snaps', () => { 225 const _sm = new Snapman<ITypeDef>(); 226 _sm.snap('sameId', { id: 'sameId-1' }); 227 _sm.snap('sameId', { id: 'sameId-2' }); 228 _sm.snap('sameId', { id: 'sameId-3' }); 229 230 _sm.snap('sameId:2', { id: 'sameId-1-2' }); 231 _sm.snap('sameId:2', { id: 'sameId-1-3' }); 232 _sm.snap('sameId:2', { id: 'sameId-1-4' }); 233 234 const sameIdSnaps = _sm.getSnapsOfId('sameId'); 235 expect(sameIdSnaps.length).toBe(3); 236 for (let i = 0; i < 3; i++) { 237 const secondPart = i === 0 ? '' : `:${i + 1}`; 238 expect(sameIdSnaps[i].id()).toBe(`sameId${secondPart}`); 239 } 240 241 const sameId2Snaps = _sm.getSnapsOfId('sameId:2'); 242 expect(sameId2Snaps.length).toBe(4); 243 for (let i = 0; i < 4; i++) { 244 const secondPart = i === 0 ? '' : `:${i + 1}`; 245 expect(sameId2Snaps[i].id()).toBe(`sameId:2${secondPart}`); 246 } 247 248 expect(_sm.getSnaps('sameId').length).toBe(6); 249 expect(_sm.getSnaps('sameId:2').length).toBe(4); 250});
If you have something that works through time. Like for instance a client. ...
If you do e2e
testing like testing something like laravel-mix
or laravel-mix-glob
or webpack
... Something cli
based. You account for output ...
A great pattern is to create experiments and run them all at first. While at it, you collect all sort of relevant events and there data. And then we write the test by consuming and testing against the experiments collected data. Kind like with event sourcing.
Snapman
was created to help with that process. Taking snapshot. Automatically the values are deeply cloned. And a timeline is created and managed. You can access any snapshot. And you can navigate the timeline and in different ways. And you can too search as well.
And by using the right ids structure. You can also categorize events that are alike. And group them.
1id = `category1:sub2:someEvent1` 2 3s.searchSnaps(/^category1/) // would give all the events of category1 4s.searchSnaps(/^category1\:sub2/) // would give all the events of category1:sub2
Example of a real experiment based testing:
[to be added]
No vulnerabilities found.
No security vulnerabilities found.