@live-change/pattern-db 0.2.2 → 0.8.3
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/LICENSE.md +11 -0
- package/lib/relations-store.js +38 -25
- package/package.json +5 -4
- package/tests/chain-with-expire.test.js +143 -0
- package/tests/linked-chain-with-expire.test.js +145 -0
- package/tests/linked-simple-chain.test.js +124 -0
- package/tests/simple-chain.test.js +306 -0
- package/tests/svg.js +115 -0
- package/tests/testDb.js +60 -0
- package/tests/viral-loop.test.js +413 -0
- package/.github/workflows/npmpublish.yml +0 -31
|
@@ -0,0 +1,306 @@
|
|
|
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("simple chain", 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(1)
|
|
18
|
+
|
|
19
|
+
let { model: compiled } = lcp.chain([
|
|
20
|
+
"enter-website",
|
|
21
|
+
"sessionId",
|
|
22
|
+
"start-register",
|
|
23
|
+
"userId",
|
|
24
|
+
"finish-register"
|
|
25
|
+
])
|
|
26
|
+
|
|
27
|
+
console.log(JSON.stringify(compiled, null, ' '))
|
|
28
|
+
|
|
29
|
+
model = compiled
|
|
30
|
+
|
|
31
|
+
t.deepEqual(model,{
|
|
32
|
+
"elements": {
|
|
33
|
+
"enter-website": {
|
|
34
|
+
"type": "enter-website",
|
|
35
|
+
"id": "enter-website",
|
|
36
|
+
"prev": [],
|
|
37
|
+
"next": [
|
|
38
|
+
"enter-website/sessionId/start-register"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"enter-website/sessionId/start-register": {
|
|
42
|
+
"type": "start-register",
|
|
43
|
+
"id": "enter-website/sessionId/start-register",
|
|
44
|
+
"prev": [
|
|
45
|
+
"enter-website/sessionId/start-register"
|
|
46
|
+
],
|
|
47
|
+
"next": [
|
|
48
|
+
"enter-website/sessionId/start-register/userId/finish-register"
|
|
49
|
+
]
|
|
50
|
+
},
|
|
51
|
+
"enter-website/sessionId/start-register/userId/finish-register": {
|
|
52
|
+
"type": "finish-register",
|
|
53
|
+
"id": "enter-website/sessionId/start-register/userId/finish-register",
|
|
54
|
+
"prev": [
|
|
55
|
+
"enter-website/sessionId/start-register/userId/finish-register"
|
|
56
|
+
],
|
|
57
|
+
"next": []
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
"relations": {
|
|
61
|
+
"enter-website/sessionId/start-register": {
|
|
62
|
+
"eq": [
|
|
63
|
+
{
|
|
64
|
+
"prev": "sessionId",
|
|
65
|
+
"next": "sessionId"
|
|
66
|
+
}
|
|
67
|
+
],
|
|
68
|
+
"id": "enter-website/sessionId/start-register",
|
|
69
|
+
"prev": [
|
|
70
|
+
"enter-website"
|
|
71
|
+
],
|
|
72
|
+
"next": [
|
|
73
|
+
"enter-website/sessionId/start-register"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"enter-website/sessionId/start-register/userId/finish-register": {
|
|
77
|
+
"eq": [
|
|
78
|
+
{
|
|
79
|
+
"prev": "userId",
|
|
80
|
+
"next": "userId"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
83
|
+
"id": "enter-website/sessionId/start-register/userId/finish-register",
|
|
84
|
+
"prev": [
|
|
85
|
+
"enter-website/sessionId/start-register"
|
|
86
|
+
],
|
|
87
|
+
"next": [
|
|
88
|
+
"enter-website/sessionId/start-register/userId/finish-register"
|
|
89
|
+
]
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}, 'model compiled')
|
|
93
|
+
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
t.test('live processor', async (t) => {
|
|
97
|
+
t.plan(2)
|
|
98
|
+
|
|
99
|
+
lcp.prepareModelForLive(model)
|
|
100
|
+
const processor = new lcp.LiveProcessor(model, store)
|
|
101
|
+
const sessionId = (Math.random()*1000).toFixed()
|
|
102
|
+
const userId = (Math.random()*1000).toFixed()
|
|
103
|
+
|
|
104
|
+
t.test('push first event', async (t) => {
|
|
105
|
+
t.plan(1)
|
|
106
|
+
await processor.processEvent({ type: 'enter-website', keys: { sessionId }, time: 0 })
|
|
107
|
+
if((await store.getRelations('start-register', { sessionId })).length > 0) {
|
|
108
|
+
t.pass('processed')
|
|
109
|
+
} else {
|
|
110
|
+
t.fail('no reaction')
|
|
111
|
+
}
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
t.test('push second event', async (t) => {
|
|
115
|
+
t.plan(1)
|
|
116
|
+
await processor.processEvent({ type: 'start-register', keys: { sessionId, userId }, time: 100 })
|
|
117
|
+
if((await store.getRelations('finish-register', { userId })).length > 0) {
|
|
118
|
+
t.pass('processed')
|
|
119
|
+
} else {
|
|
120
|
+
t.fail('no reaction')
|
|
121
|
+
}
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
t.test("test relations search", async (t) => {
|
|
127
|
+
t.plan(4)
|
|
128
|
+
|
|
129
|
+
const sessionId = (Math.random()*1000).toFixed()
|
|
130
|
+
const userId = (Math.random()*1000).toFixed()
|
|
131
|
+
|
|
132
|
+
const events = [
|
|
133
|
+
{ id: 0, type: 'enter-website', keys: { sessionId } },
|
|
134
|
+
{ id: 1, type: 'start-register', keys: { sessionId, userId } },
|
|
135
|
+
{ id: 2, type: 'finish-register', keys: { userId } }
|
|
136
|
+
]
|
|
137
|
+
|
|
138
|
+
async function getEventsByRelation( types, keys, from, to ) { /// TODO: use events store
|
|
139
|
+
console.log("GETEVENTS", types, keys, from, to)
|
|
140
|
+
return events.filter(ev => {
|
|
141
|
+
if(types.indexOf(ev.type) == -1) return
|
|
142
|
+
for(let key in keys) if(ev.keys[key] != keys[key]) return
|
|
143
|
+
return true
|
|
144
|
+
})
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
t.test("related previous events", async (t) => {
|
|
148
|
+
t.plan(1)
|
|
149
|
+
const related = await lcp.findRelatedEvents([events[2]], true, model,
|
|
150
|
+
-Infinity, Infinity, getEventsByRelation)
|
|
151
|
+
console.log("RELATED", Array.from(related.values()))
|
|
152
|
+
t.deepEqual(Array.from(related.values()), [{
|
|
153
|
+
...events[1], elements: ['enter-website/sessionId/start-register']
|
|
154
|
+
}], "found related events")
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
t.test("all related previous events", async (t) => {
|
|
158
|
+
t.plan(1)
|
|
159
|
+
const related = await lcp.findAllRelatedEvents([events[2]], true, model,
|
|
160
|
+
-Infinity, Infinity, getEventsByRelation)
|
|
161
|
+
console.log("RELATED", Array.from(related.values()))
|
|
162
|
+
t.deepEqual(Array.from(related.values()), [
|
|
163
|
+
{ ...events[1], elements: [ 'enter-website/sessionId/start-register' ] },
|
|
164
|
+
{ ...events[0], elements: [ 'enter-website' ] }
|
|
165
|
+
], "found related events")
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
t.test("related next events", async (t) => {
|
|
169
|
+
t.plan(1)
|
|
170
|
+
const related = await lcp.findRelatedEvents([events[0]], false, model,
|
|
171
|
+
-Infinity, Infinity, getEventsByRelation)
|
|
172
|
+
console.log("RELATED", Array.from(related.values()))
|
|
173
|
+
t.deepEqual(Array.from(related.values()), [{
|
|
174
|
+
...events[1], elements: ['enter-website/sessionId/start-register']
|
|
175
|
+
}], "found related events")
|
|
176
|
+
})
|
|
177
|
+
|
|
178
|
+
t.test("all related previous events", async (t) => {
|
|
179
|
+
t.plan(1)
|
|
180
|
+
const related = await lcp.findAllRelatedEvents([events[0]], false, model,
|
|
181
|
+
-Infinity, Infinity, getEventsByRelation)
|
|
182
|
+
console.log("RELATED", Array.from(related.values()))
|
|
183
|
+
t.deepEqual(Array.from(related.values()), [
|
|
184
|
+
{ ...events[1], elements: [ 'enter-website/sessionId/start-register' ] },
|
|
185
|
+
{ ...events[2], elements: [ 'enter-website/sessionId/start-register/userId/finish-register' ] }
|
|
186
|
+
], "found related events")
|
|
187
|
+
})
|
|
188
|
+
})
|
|
189
|
+
|
|
190
|
+
t.test("test graphs", async (t) => {
|
|
191
|
+
t.plan(3)
|
|
192
|
+
|
|
193
|
+
const sessionId = (Math.random() * 1000).toFixed()
|
|
194
|
+
const userId = (Math.random() * 1000).toFixed()
|
|
195
|
+
const userId2 = userId + 1
|
|
196
|
+
|
|
197
|
+
const events = [
|
|
198
|
+
{id: 0, type: 'enter-website', keys: {sessionId}, time: 0},
|
|
199
|
+
{id: 1, type: 'enter-website', keys: {sessionId}, time: 1000},
|
|
200
|
+
{id: 2, type: 'start-register', keys: {sessionId, userId}, time: 2000},
|
|
201
|
+
{id: 3, type: 'start-register', keys: {sessionId, userId: userId2}, time: 3000},
|
|
202
|
+
{id: 4, type: 'finish-register', keys: {userId}, time: 4000}
|
|
203
|
+
]
|
|
204
|
+
|
|
205
|
+
t.test("build full graph", async (t) => {
|
|
206
|
+
t.plan(1)
|
|
207
|
+
const processor = new lcp.FullGraphProcessor(model, lcp.relationsStore())
|
|
208
|
+
for(const ev of events) await processor.processEvent(ev)
|
|
209
|
+
const graph = processor.graph
|
|
210
|
+
console.log("GRAPH\n "+Array.from(graph.values()).map(n => JSON.stringify(n)).join('\n '))
|
|
211
|
+
t.deepEqual(Array.from(graph.values()), [
|
|
212
|
+
{"id":0,"type":"enter-website","keys":{"sessionId":""+sessionId},"time":0,"prev":[],"next":[
|
|
213
|
+
{"relation":"enter-website/sessionId/start-register","to":2},
|
|
214
|
+
{"relation":"enter-website/sessionId/start-register","to":3}],
|
|
215
|
+
"start":true},
|
|
216
|
+
{"id":1,"type":"enter-website","keys":{"sessionId":""+sessionId},"time":1000,"prev":[],"next":[
|
|
217
|
+
{"relation":"enter-website/sessionId/start-register","to":2},
|
|
218
|
+
{"relation":"enter-website/sessionId/start-register","to":3}],
|
|
219
|
+
"start":true},
|
|
220
|
+
{"id":2,"type":"start-register","keys":{"sessionId":""+sessionId,"userId":""+userId},"time":2000,
|
|
221
|
+
"prev":[
|
|
222
|
+
{"relation":"enter-website/sessionId/start-register","to":0},
|
|
223
|
+
{"relation":"enter-website/sessionId/start-register","to":1}],
|
|
224
|
+
"next":[
|
|
225
|
+
{"relation":"enter-website/sessionId/start-register/userId/finish-register","to":4}],
|
|
226
|
+
"start":false},
|
|
227
|
+
{"id":3,"type":"start-register","keys":{"sessionId":""+sessionId,"userId":""+userId2},"time":3000,"prev":[
|
|
228
|
+
{"relation":"enter-website/sessionId/start-register","to":0},
|
|
229
|
+
{"relation":"enter-website/sessionId/start-register","to":1}],
|
|
230
|
+
"next":[],"start":false},
|
|
231
|
+
{"id":4,"type":"finish-register","keys":{"userId":""+userId},"time":4000,"prev":[
|
|
232
|
+
{"relation":"enter-website/sessionId/start-register/userId/finish-register","to":2}],
|
|
233
|
+
"next":[],"start":false}
|
|
234
|
+
], 'proper graph generated')
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
await svg.generateGraphSvg("simple-chain-full-graph.svg", graph,
|
|
238
|
+
n => ({ ...n, label: n.type, title: `${n.id} at ${n.time}`, sort: n.time }),
|
|
239
|
+
(rel, source, target) => ({ ...rel, value: 1, label: rel.relation, title: rel.relation })
|
|
240
|
+
)
|
|
241
|
+
})
|
|
242
|
+
|
|
243
|
+
t.test("build summary graph with count", async (t) => {
|
|
244
|
+
t.plan(1)
|
|
245
|
+
const processor = new lcp.SummaryGraphProcessor(model, lcp.relationsStore())
|
|
246
|
+
for(const ev of events) await processor.processEvent(ev)
|
|
247
|
+
const graph = processor.graph
|
|
248
|
+
console.log("GRAPH\n "+Array.from(graph.values()).map(n => JSON.stringify(n)).join('\n '))
|
|
249
|
+
t.deepEqual(Array.from(graph.values()), [
|
|
250
|
+
{"id":"enter-website:0","prev":[],
|
|
251
|
+
"next":[{"to":"enter-website/sessionId/start-register:1","counter":4}],
|
|
252
|
+
"start":true,"counter":2},
|
|
253
|
+
{"id":"enter-website/sessionId/start-register:1",
|
|
254
|
+
"prev":[{"to":"enter-website:0","counter":4}],
|
|
255
|
+
"next":[{"to":"enter-website/sessionId/start-register/userId/finish-register:2","counter":1}],
|
|
256
|
+
"start":false,"counter":2},
|
|
257
|
+
{"id":"enter-website/sessionId/start-register/userId/finish-register:2",
|
|
258
|
+
"prev":[{"to":"enter-website/sessionId/start-register:1","counter":1}],
|
|
259
|
+
"next":[],"start":false,"counter":1}
|
|
260
|
+
], 'proper graph generated')
|
|
261
|
+
|
|
262
|
+
lcp.computeGraphDepth(graph,['enter-website:0'])
|
|
263
|
+
|
|
264
|
+
await svg.generateGraphSvg("simple-chain-summary-count.svg", graph,
|
|
265
|
+
n => ({ ...n, label: n.id.split(':')[0], title: `${n.id} at ${n.time}`, sort: n.depth }),
|
|
266
|
+
(rel, source, target) => ({ ...rel, value: rel.counter, label: rel.relation, title: rel.relation })
|
|
267
|
+
)
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
t.test("build summary graph with events", async (t) => {
|
|
271
|
+
t.plan(1)
|
|
272
|
+
const processor = new lcp.SummaryGraphProcessor(model, lcp.relationsStore(), {
|
|
273
|
+
...lcp.graphAggregation.nodeElementDepth,
|
|
274
|
+
...lcp.graphAggregation.relationSimple,
|
|
275
|
+
...lcp.graphAggregation.summaryEvents
|
|
276
|
+
})
|
|
277
|
+
for (const ev of events) await processor.processEvent(ev)
|
|
278
|
+
const graph = processor.graph
|
|
279
|
+
console.log("GRAPH\n " + Array.from(graph.values()).map(n => JSON.stringify(n)).join('\n '))
|
|
280
|
+
t.deepEqual(Array.from(graph.values()), [
|
|
281
|
+
{"id":"enter-website:0","prev":[],
|
|
282
|
+
"next":[{"to":"enter-website/sessionId/start-register:1","events":[2,3]}],
|
|
283
|
+
"start":true,"events":[0,1]},
|
|
284
|
+
{"id":"enter-website/sessionId/start-register:1",
|
|
285
|
+
"prev":[{"to":"enter-website:0","events":[2,3]}],
|
|
286
|
+
"next":[{"to":"enter-website/sessionId/start-register/userId/finish-register:2","events":[4]}],
|
|
287
|
+
"start":false,"events":[2,3]},
|
|
288
|
+
{"id":"enter-website/sessionId/start-register/userId/finish-register:2",
|
|
289
|
+
"prev":[{"to":"enter-website/sessionId/start-register:1","events":[4]}],
|
|
290
|
+
"next":[],
|
|
291
|
+
"start":false,"events":[4]}
|
|
292
|
+
|
|
293
|
+
], 'proper graph generated')
|
|
294
|
+
|
|
295
|
+
lcp.computeGraphDepth(graph,['enter-website:0'])
|
|
296
|
+
|
|
297
|
+
await svg.generateGraphSvg("simple-chain-summary-events-count.svg", graph,
|
|
298
|
+
n => ({ ...n, label: n.id.split(':')[0], title: `${n.id}`, sort: n.depth }),
|
|
299
|
+
(rel, source, target) => ({ ...rel, value: rel.events.length, label: rel.relation, title: rel.relation })
|
|
300
|
+
)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
})
|
|
305
|
+
|
|
306
|
+
|
package/tests/svg.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
const rp = require('../index.js')
|
|
2
|
+
const d3 = Object.assign({}, require('d3'), require('d3-sankey-circular'), require('d3-path-arrows'))
|
|
3
|
+
const D3Node = require('d3-node')
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
|
|
6
|
+
function generateGraphSvg(filePath, graph,
|
|
7
|
+
nodeFunc = n => ({ ...n, label: n.type, title: `${n.id} at ${n.time}`, sort: n.time }),
|
|
8
|
+
linkFunc = (rel, source, target) => ({ ...rel, value: 1, label: rel.relation, title: rel.relation })
|
|
9
|
+
) {
|
|
10
|
+
|
|
11
|
+
const width = 1280, height = 800, margin = { top: 30, right: 50, bottom: 30, left: 50}
|
|
12
|
+
const sankey = d3
|
|
13
|
+
.sankeyCircular()
|
|
14
|
+
.nodeWidth(10)
|
|
15
|
+
.nodePadding(20)
|
|
16
|
+
//.nodePaddingRatio(0.5)
|
|
17
|
+
.size([width - margin.left - margin.right, height - margin.top - margin.bottom])
|
|
18
|
+
.nodeId(d => d.id)
|
|
19
|
+
.nodeAlign(d3.sankeyLeft)
|
|
20
|
+
.iterations(5)
|
|
21
|
+
.circularLinkGap(1)
|
|
22
|
+
.sortNodes("sort")
|
|
23
|
+
|
|
24
|
+
const data = rp.graphToD3Sankey(graph, nodeFunc, linkFunc)
|
|
25
|
+
//console.log("SDATA", data)
|
|
26
|
+
const sankeyData = sankey(data)
|
|
27
|
+
/* const sankeyData = sankey(rp.graphToD3Sankey(
|
|
28
|
+
graph,
|
|
29
|
+
nodeFunc = n => ({ ...n, col:depth, name: n.id, label: n.type, }),
|
|
30
|
+
|
|
31
|
+
linkFunc = (rel, source, target) => ({ ...rel, value: 1, label: rel.relation })
|
|
32
|
+
))*/
|
|
33
|
+
const sankeyNodes = sankeyData.nodes
|
|
34
|
+
const sankeyLinks = sankeyData.links
|
|
35
|
+
const depthExtent = d3.extent(sankeyNodes, function (d) { return d.depth })
|
|
36
|
+
const nodeColour = d3.scaleSequential(d3.interpolateCool)
|
|
37
|
+
.domain([0, width])
|
|
38
|
+
|
|
39
|
+
const d3n = new D3Node({ d3Module: d3 })
|
|
40
|
+
const svg = d3n.createSVG(width, height)
|
|
41
|
+
const g = svg.append("g")
|
|
42
|
+
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
|
|
43
|
+
const linkG = g.append("g")
|
|
44
|
+
.attr("class", "links")
|
|
45
|
+
.attr("stroke-opacity", 0.2)
|
|
46
|
+
.selectAll("path")
|
|
47
|
+
const nodeG = g.append("g")
|
|
48
|
+
.attr("class", "nodes")
|
|
49
|
+
.attr("font-family", "sans-serif")
|
|
50
|
+
.attr("font-size", 10)
|
|
51
|
+
.selectAll("g")
|
|
52
|
+
const linkLabels = g.append("g")
|
|
53
|
+
|
|
54
|
+
const node = nodeG.data(sankeyNodes)
|
|
55
|
+
.enter()
|
|
56
|
+
.append("g")
|
|
57
|
+
|
|
58
|
+
node.append("rect")
|
|
59
|
+
.attr("x", d => d.x0 )
|
|
60
|
+
.attr("y", d => d.y0 )
|
|
61
|
+
.attr("height", d => d.y1 - d.y0 )
|
|
62
|
+
.attr("width", d => d.x1 - d.x0 )
|
|
63
|
+
.style("fill", d => nodeColour(d.x0) )
|
|
64
|
+
.style("opacity", 0.5)
|
|
65
|
+
node.append("text")
|
|
66
|
+
.attr("x", d => (d.x0 + d.x1) / 2 )
|
|
67
|
+
.attr("y", d => d.y0 - 12 )
|
|
68
|
+
.attr("dy", "0.35em")
|
|
69
|
+
.attr("text-anchor", "middle")
|
|
70
|
+
.text( d => d.label )
|
|
71
|
+
node.append("title")
|
|
72
|
+
.text( d => d.title)
|
|
73
|
+
|
|
74
|
+
const link = linkG.data(sankeyLinks)
|
|
75
|
+
.enter()
|
|
76
|
+
.append("g")
|
|
77
|
+
link.append("path")
|
|
78
|
+
.attr("fill", "none")
|
|
79
|
+
.attr("class", "sankey-link")
|
|
80
|
+
.attr("d", link => link.path)
|
|
81
|
+
.style("stroke-width", d => Math.max(1, d.width))
|
|
82
|
+
.style("opacity", 0.7)
|
|
83
|
+
.style("stroke", (link, i) => link.circular ? "red" : "black")
|
|
84
|
+
link.append("title")
|
|
85
|
+
.text(d => d.title )
|
|
86
|
+
link.append("text")
|
|
87
|
+
.attr("x", d => d.source.x1 + 5)
|
|
88
|
+
.attr("y", d => d.y0)
|
|
89
|
+
.attr("text-anchor", "start")
|
|
90
|
+
.attr("font-family", "sans-serif")
|
|
91
|
+
.attr("font-size", 10)
|
|
92
|
+
.attr("dy", "0.35em")
|
|
93
|
+
.text(d => d.sourceLabel || d.label)
|
|
94
|
+
link.append("text")
|
|
95
|
+
.attr("x", d => d.target.x0 - 5)
|
|
96
|
+
.attr("y", d => d.y1)
|
|
97
|
+
.attr("text-anchor", "end")
|
|
98
|
+
.attr("font-family", "sans-serif")
|
|
99
|
+
.attr("font-size", 10)
|
|
100
|
+
.attr("dy", "0.35em")
|
|
101
|
+
.text(d => d.targetLabel || d.label)
|
|
102
|
+
|
|
103
|
+
// link.each(l => console.log("L",l))
|
|
104
|
+
|
|
105
|
+
return new Promise((resolve, reject) => {
|
|
106
|
+
fs.writeFile(filePath, d3n.svgString(), (err) => {
|
|
107
|
+
if(err) return reject(err)
|
|
108
|
+
resolve('ok')
|
|
109
|
+
})
|
|
110
|
+
})
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = {
|
|
114
|
+
generateGraphSvg
|
|
115
|
+
}
|
package/tests/testDb.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
const DbServer = require('@live-change/db-server')
|
|
2
|
+
const Dao = require("@live-change/dao")
|
|
3
|
+
|
|
4
|
+
async function createLoopbackDao(credentials, daoFactory) {
|
|
5
|
+
const server = new Dao.ReactiveServer(daoFactory)
|
|
6
|
+
const loopback = new Dao.LoopbackConnection(credentials, server, {})
|
|
7
|
+
const dao = new Dao(credentials, {
|
|
8
|
+
remoteUrl: 'dao',
|
|
9
|
+
protocols: { local: null },
|
|
10
|
+
defaultRoute: {
|
|
11
|
+
type: "remote",
|
|
12
|
+
generator: Dao.ObservableList
|
|
13
|
+
},
|
|
14
|
+
connectionSettings: {
|
|
15
|
+
disconnectDebug: true,
|
|
16
|
+
logLevel: 10,
|
|
17
|
+
},
|
|
18
|
+
})
|
|
19
|
+
dao.connections.set('local:dao', loopback)
|
|
20
|
+
await loopback.initialize()
|
|
21
|
+
if(!loopback.connected) {
|
|
22
|
+
console.error("LOOPBACK NOT CONNECTED?!")
|
|
23
|
+
process.exit(1)
|
|
24
|
+
}
|
|
25
|
+
return dao
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function testDb() {
|
|
29
|
+
const dbServer = new DbServer({
|
|
30
|
+
dbRoot: 'mem',
|
|
31
|
+
backend: 'mem',
|
|
32
|
+
slowStart: true,
|
|
33
|
+
temporary: true
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
37
|
+
if(reason.stack && reason.stack.match(/\s(userCode:([a-z0-9_.\/-]+):([0-9]+):([0-9]+))\n/i)) {
|
|
38
|
+
dbServer.handleUnhandledRejectionInQuery(reason, promise)
|
|
39
|
+
}
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
await dbServer.initialize()
|
|
43
|
+
console.info(`database initialized!`)
|
|
44
|
+
|
|
45
|
+
const loopbackDao = await createLoopbackDao('local', () => dbServer.createDao('local'))
|
|
46
|
+
|
|
47
|
+
const oldDispose = loopbackDao.dispose
|
|
48
|
+
loopbackDao.dbServer = dbServer
|
|
49
|
+
loopbackDao.dispose = () => {
|
|
50
|
+
dbServer.close()
|
|
51
|
+
oldDispose.apply(loopbackDao)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
loopbackDao.databaseName = 'test'
|
|
55
|
+
await loopbackDao.request(['database', 'createDatabase'], loopbackDao.databaseName, { }).catch(err => 'exists')
|
|
56
|
+
|
|
57
|
+
return loopbackDao
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
module.exports = testDb
|