@live-change/pattern-db 0.2.1 → 0.8.2

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,413 @@
1
+ const test = require('tape')
2
+ const testDb = require('./testDb.js')
3
+ const lcp = require('@live-change/pattern')
4
+ const { relationsStore } = require('../lib/relations-store.js')
5
+ const svg = require('./svg.js')
6
+
7
+ let model
8
+
9
+ test("viral loop", async (t) => {
10
+ t.plan(4)
11
+
12
+ const db = await testDb()
13
+ const store = relationsStore(db, 'test', 'relations')
14
+ await store.createTable()
15
+
16
+ t.test('compile', (t) => {
17
+ t.plan(2)
18
+
19
+ const generatedLink = lcp.first({ id: 'generated-link', type: 'generated-link' })
20
+
21
+ const registelcpart = generatedLink
22
+ .link("refId", lcp.first({ id: 'visitor', type: 'enter-website' }))
23
+ .link("sessionId", lcp.first({ id: 'started-registration', type: 'start-register' }))
24
+ .link("userId", lcp.first({ id: 'registered', type: 'finish-register' }))
25
+ .link("userId", lcp.first({ id: 'shared', type: 'share' }))
26
+
27
+ const { model: compiled } = registelcpart
28
+ .link({ eq: [{ prev: "userId", next: "refId" }] },
29
+ { model: registelcpart, first: 'visitor' } )
30
+
31
+ console.log(JSON.stringify(compiled, null, ' '))
32
+
33
+ model = compiled
34
+
35
+ t.deepEqual(model, {
36
+ "elements": {
37
+ "generated-link": {
38
+ "id": "generated-link",
39
+ "type": "generated-link",
40
+ "prev": [],
41
+ "next": [
42
+ "generated-link/refId/visitor"
43
+ ]
44
+ },
45
+ "visitor": {
46
+ "id": "visitor",
47
+ "type": "enter-website",
48
+ "prev": [
49
+ "generated-link/refId/visitor",
50
+ "shared/userId=refId/visitor"
51
+ ],
52
+ "next": [
53
+ "visitor/sessionId/started-registration"
54
+ ]
55
+ },
56
+ "started-registration": {
57
+ "id": "started-registration",
58
+ "type": "start-register",
59
+ "prev": [
60
+ "visitor/sessionId/started-registration"
61
+ ],
62
+ "next": [
63
+ "started-registration/userId/registered"
64
+ ]
65
+ },
66
+ "registered": {
67
+ "id": "registered",
68
+ "type": "finish-register",
69
+ "prev": [
70
+ "started-registration/userId/registered"
71
+ ],
72
+ "next": [
73
+ "registered/userId/shared"
74
+ ]
75
+ },
76
+ "shared": {
77
+ "id": "shared",
78
+ "type": "share",
79
+ "prev": [
80
+ "registered/userId/shared"
81
+ ],
82
+ "next": [
83
+ "shared/userId=refId/visitor"
84
+ ]
85
+ }
86
+ },
87
+ "relations": {
88
+ "generated-link/refId/visitor": {
89
+ "eq": [
90
+ {
91
+ "prev": "refId",
92
+ "next": "refId"
93
+ }
94
+ ],
95
+ "id": "generated-link/refId/visitor",
96
+ "prev": [
97
+ "generated-link"
98
+ ],
99
+ "next": [
100
+ "visitor"
101
+ ]
102
+ },
103
+ "visitor/sessionId/started-registration": {
104
+ "eq": [
105
+ {
106
+ "prev": "sessionId",
107
+ "next": "sessionId"
108
+ }
109
+ ],
110
+ "id": "visitor/sessionId/started-registration",
111
+ "prev": [
112
+ "visitor"
113
+ ],
114
+ "next": [
115
+ "started-registration"
116
+ ]
117
+ },
118
+ "started-registration/userId/registered": {
119
+ "eq": [
120
+ {
121
+ "prev": "userId",
122
+ "next": "userId"
123
+ }
124
+ ],
125
+ "id": "started-registration/userId/registered",
126
+ "prev": [
127
+ "started-registration"
128
+ ],
129
+ "next": [
130
+ "registered"
131
+ ]
132
+ },
133
+ "registered/userId/shared": {
134
+ "eq": [
135
+ {
136
+ "prev": "userId",
137
+ "next": "userId"
138
+ }
139
+ ],
140
+ "id": "registered/userId/shared",
141
+ "prev": [
142
+ "registered"
143
+ ],
144
+ "next": [
145
+ "shared"
146
+ ]
147
+ },
148
+ "shared/userId=refId/visitor": {
149
+ "eq": [
150
+ {
151
+ "prev": "userId",
152
+ "next": "refId"
153
+ }
154
+ ],
155
+ "id": "shared/userId=refId/visitor",
156
+ "prev": [
157
+ "shared"
158
+ ],
159
+ "next": [
160
+ "visitor"
161
+ ]
162
+ }
163
+ }
164
+ }, 'model is ok')
165
+
166
+ lcp.prepareModelForLive(model)
167
+
168
+ t.pass('prepared model')
169
+ })
170
+
171
+ const events = []
172
+ async function getEventsByRelation( types, keys, from, to ) {
173
+ //console.log("GETEVENTS", types, keys, from, to)
174
+ return events.filter(ev => {
175
+ if(types.indexOf(ev.type) == -1) return
176
+ for(let key in keys) if(ev.keys[key] != keys[key]) return
177
+ return true
178
+ })
179
+ }
180
+
181
+ let stats = {
182
+ "link-1": {
183
+ visitor: 0,
184
+ "started-registration": 0,
185
+ "registered": 0,
186
+ "shared": 0,
187
+ visitorFromShared: 0
188
+ },
189
+ "link-2": {
190
+ visitor: 0,
191
+ "started-registration": 0,
192
+ "registered": 0,
193
+ "shared": 0,
194
+ visitorFromShared: 0
195
+ },
196
+ "link-3": {
197
+ visitor: 0,
198
+ "started-registration": 0,
199
+ "registered": 0,
200
+ "shared": 0,
201
+ visitorFromShared: 0
202
+ }
203
+ }
204
+
205
+ t.test('generate random events', async t => {
206
+ t.plan(1)
207
+
208
+ let uid = 0
209
+ let id = 1
210
+ let time = 0
211
+
212
+ events.push({ id: id++, type: 'generated-link', keys: { refId: 'link-1' }, time })
213
+ time += 1
214
+ events.push({ id: id++, type: 'generated-link', keys: { refId: 'link-2' }, time })
215
+ time += 1
216
+ events.push({ id: id++, type: 'generated-link', keys: { refId: 'link-3' }, time })
217
+
218
+ time += 1000
219
+
220
+ function random() {
221
+ return Math.random()
222
+ }
223
+
224
+ function randTime(f = 1) {
225
+ return Math.floor(random()*10000*f)
226
+ }
227
+
228
+ let queued = []
229
+
230
+ function simulateEntry(keys, time, prob) {
231
+ events.push({ id: id++, type: 'enter-website', keys, time })
232
+ if(Number.isInteger(keys.refId)) prob.stats.visitorFromShared++
233
+ else prob.stats.visitor++
234
+ if(random() < prob.startRegister) {
235
+ queued.push(() =>
236
+ simulateStartRegister({ sessionId: keys.sessionId, userId: ++uid }, time + randTime(), prob)
237
+ )
238
+ } /*else if(random() < prob.reentry) {
239
+ queued.push(() =>
240
+ simulateEntry({sessionId: keys.sessionId}, time + randTime(), prob)
241
+ )
242
+ }*/
243
+ }
244
+
245
+ function simulateStartRegister(keys, time, prob) {
246
+ events.push({ id: id++, type: 'start-register', keys, time })
247
+ prob.stats['started-registration']++
248
+ if(random() < prob.finishRegister) {
249
+ queued.push(() =>
250
+ simulateFinishRegister({ userId: keys.userId }, time + randTime(), prob)
251
+ )
252
+ } /*else if(random() < prob.reentry) {
253
+ queued.push(() =>
254
+ simulateEntry({sessionId: keys.sessionId}, time + randTime(), prob)
255
+ )
256
+ }*/
257
+ }
258
+
259
+ function simulateFinishRegister(keys, time, prob) {
260
+ events.push({ id: id++, type: 'finish-register', keys, time })
261
+ prob.stats['registered']++
262
+ if(random() < prob.share) {
263
+ queued.push(() =>
264
+ simulateShare(keys, time + randTime(), prob)
265
+ )
266
+ }
267
+ }
268
+
269
+ function simulateShare(keys, time, prob) {
270
+ events.push({ id: id++, type: 'share', keys, time })
271
+ prob.stats['shared']++
272
+ for(let i = 0; i < prob.shareViews; i++) {
273
+ if (random() < prob.shareEntry) {
274
+ queued.push(() =>
275
+ simulateEntry({ refId: keys.userId, sessionId: ++uid }, time + randTime(), prob)
276
+ )
277
+ }
278
+ }
279
+ }
280
+
281
+ const link1P = {
282
+ entry: 0.1,
283
+ reentry: 0.2,
284
+ startRegister: 0.3,
285
+ finishRegister: 0.9,
286
+ share: 0.4,
287
+ shareViews: 50,
288
+ shareEntry: 0.3,
289
+ stats: stats["link-1"]
290
+ }
291
+
292
+ const link2P = {
293
+ entry: 0.5,
294
+ reentry: 0.2,
295
+ startRegister: 0.3,
296
+ finishRegister: 0.9,
297
+ share: 0.2,
298
+ shareViews: 30,
299
+ shareEntry: 0.3,
300
+ stats: stats["link-2"]
301
+ }
302
+
303
+ const link3P = {
304
+ entry: 0.4,
305
+ reentry: 0.2,
306
+ startRegister: 0.5,
307
+ finishRegister: 0.9,
308
+ share: 0.5,
309
+ shareViews: 20,
310
+ shareEntry: 0.3,
311
+ stats: stats["link-3"]
312
+ }
313
+
314
+ for(let i = 0; i < 15; i++) {
315
+ time += randTime(3);
316
+ let r = random()
317
+ if(r < link1P.entry)
318
+ simulateEntry({ refId: 'link-1', sessionId: ++uid }, time, link1P)
319
+ else if(r - link1P.entry < link2P.entry)
320
+ simulateEntry({ refId: 'link-2', sessionId: ++uid }, time, link2P)
321
+ else if(r - link1P.entry - link2P.entry < link3P.entry)
322
+ simulateEntry({ refId: 'link-3', sessionId: ++uid }, time, link3P)
323
+ }
324
+
325
+ let next, steps = 1
326
+ do {
327
+ next = queued
328
+ queued = []
329
+ for(const fun of next) fun()
330
+ if(events.length > 200) break; //throw new Error("Too much!")
331
+ steps += next.length && 1
332
+ } while(next.length > 0)
333
+
334
+ events.sort((a,b) => a.time == b.time ? 0 : (a.time > b.time ? 1 : -1))
335
+
336
+ console.log("GENERATED EVENTS COUNT", events.length, "IN", steps, "STEPS")
337
+ t.pass("events generated")
338
+
339
+ })
340
+
341
+ const nodeViz = n =>
342
+ ({ ...n, label: `${n.id.split(':')[0]} ${n.events.length}`, title: `${n.id}`, sort: n.depth })
343
+ const linkViz = (rel, source, target) =>
344
+ ({ ...rel, value: rel.events.length, sourceLabel: rel.events.length, targetLabel: rel.relation , title: rel.relation })
345
+
346
+ t.test("test graph of all events", async (t) => {
347
+ t.plan(1)
348
+ t.test("summary graph with events", async (t) => {
349
+ t.plan(1)
350
+
351
+ const processor = new lcp.SummaryGraphProcessor(model, store, {
352
+ ...lcp.graphAggregation.nodeElement,
353
+ ...lcp.graphAggregation.relationSimple,
354
+ ...lcp.graphAggregation.summaryEvents,
355
+ nodes: ({ element, relation }, keys) => element == 'generated-link' ? [ keys.refId ] : [ element ],
356
+ })
357
+ for (const ev of events) {
358
+ await processor.processEvent(ev)
359
+ }
360
+ const graph = processor.graph
361
+ console.log("GRAPH\n " + Array.from(graph.values()).map(n => JSON.stringify(n)).join('\n '))
362
+
363
+ console.log("ALL VISITORS FROM SHARE", stats['link-1'].visitorFromShared
364
+ + stats['link-2'].visitorFromShared + stats['link-3'].visitorFromShared)
365
+ console.log("ALL VISITORS FROM LINK", stats['link-1'].visitor + stats['link-2'].visitor + stats['link-3'].visitor)
366
+ console.log("ALL STARTED REGISTRATION", stats['link-1']['started-registration']
367
+ + stats['link-2']['started-registration'] + stats['link-3']['started-registration'])
368
+ console.log("ALL REGISTERED", stats['link-1']['registered']
369
+ + stats['link-2']['registered'] + stats['link-3']['registered'])
370
+ console.log("ALL SHARED", stats['link-1']['shared']
371
+ + stats['link-2']['shared'] + stats['link-3']['shared'])
372
+
373
+ t.pass('ok')
374
+
375
+ lcp.computeGraphDepth(graph,['visitor'])
376
+
377
+ await svg.generateGraphSvg("viral-loop-summary-events-count.svg", graph, nodeViz, linkViz)
378
+ })
379
+ })
380
+
381
+ t.test("test graph of link events", async (t) => {
382
+ t.plan(3)
383
+
384
+ for(let link of ['link-1', 'link-2', 'link-3']) {
385
+
386
+ const start = [events.find(ev => ev.type == 'generated-link' && ev.keys.refId == link)]
387
+ const related = await lcp.findAllRelatedEvents(start, false, model,
388
+ -Infinity, Infinity, getEventsByRelation)
389
+ const filtered = start.concat(Array.from(related.values()))
390
+ //console.log("FILTERED", filtered)
391
+ filtered.sort((a, b) => a.time == b.time ? 0 : (a.time > b.time ? 1 : -1))
392
+
393
+ t.test("summary graph of "+link+" with events", async (t) => {
394
+ t.plan(1)
395
+ const processor = new lcp.SummaryGraphProcessor(model, store, {
396
+ ...lcp.graphAggregation.nodeElement,
397
+ ...lcp.graphAggregation.relationSimple,
398
+ ...lcp.graphAggregation.summaryEvents,
399
+ nodes: ({element, relation}, keys) => element == 'generated-link' ? [keys.refId] : [element],
400
+ })
401
+ for (const ev of filtered) await processor.processEvent(ev)
402
+ const graph = processor.graph
403
+ console.log("GRAPH\n " + Array.from(graph.values()).map(n => JSON.stringify(n)).join('\n '))
404
+
405
+ t.pass('ok')
406
+
407
+ lcp.computeGraphDepth(graph, [link])
408
+
409
+ await svg.generateGraphSvg("viral-loop-"+link+"-summary-events-count.svg", graph, nodeViz, linkViz)
410
+ })
411
+ }
412
+ })
413
+ })
@@ -1,31 +0,0 @@
1
- name: Node.js Package
2
-
3
- on:
4
- release:
5
- types: [created]
6
-
7
- jobs:
8
- build:
9
- runs-on: ubuntu-latest
10
- steps:
11
- - uses: actions/checkout@v1
12
- - uses: actions/setup-node@v1
13
- with:
14
- node-version: 12
15
- - run: npm ci
16
- - run: npm test
17
-
18
- publish-npm:
19
- needs: build
20
- runs-on: ubuntu-latest
21
- steps:
22
- - uses: actions/checkout@v1
23
- - uses: actions/setup-node@v1
24
- with:
25
- node-version: 12
26
- registry-url: https://registry.npmjs.org/
27
- - run: npm ci
28
- - run: npm publish
29
- env:
30
- NODE_AUTH_TOKEN: ${{secrets.npm_token}}
31
-