@live-change/dao-vue3 0.1.16 → 0.1.20
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/index.js +6 -371
- package/lib/RangeBuckets.js +233 -0
- package/lib/ReactiveObservableList.js +16 -0
- package/lib/live.js +193 -0
- package/lib/reactiveMixin.js +83 -0
- package/lib/reactivePrefetchMixin.js +57 -0
- package/package.json +2 -2
package/index.js
CHANGED
|
@@ -1,384 +1,19 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const reactiveMixin = dao => ({
|
|
7
|
-
data() {
|
|
8
|
-
if(!this.$options.reactive) return {} // Avoid distributed fat
|
|
9
|
-
let data = {}
|
|
10
|
-
for (let key in this.$options.reactive) {
|
|
11
|
-
data[key] = undefined
|
|
12
|
-
data[key+"Error"] = undefined
|
|
13
|
-
}
|
|
14
|
-
return data
|
|
15
|
-
},
|
|
16
|
-
beforeCreate() {
|
|
17
|
-
if(!this.$options.reactive) return; // Avoid distributed fat
|
|
18
|
-
if (!this.$options.computed) this.$options.computed = {}
|
|
19
|
-
for(let key in this.$options.reactive) {
|
|
20
|
-
let path = this.$options.reactive[key]
|
|
21
|
-
if(typeof path == 'function'){
|
|
22
|
-
this.$options.computed[prefix + key] = path
|
|
23
|
-
} else if(typeof path == 'string') {
|
|
24
|
-
} else if(path.length !== undefined) {
|
|
25
|
-
} else throw new Error("unknown reactive path "+path)
|
|
26
|
-
}
|
|
27
|
-
},
|
|
28
|
-
created() {
|
|
29
|
-
if(!this.$options.reactive) return; // Avoid distributed fat
|
|
30
|
-
this.reactiveObservables = {}
|
|
31
|
-
let reactiveObservables = this.reactiveObservables
|
|
32
|
-
for(let key in this.$options.reactive) {
|
|
33
|
-
let path = this.$options.reactive[key]
|
|
34
|
-
if(typeof path == 'function'){
|
|
35
|
-
let p = this[prefix + key]
|
|
36
|
-
if(p) {
|
|
37
|
-
reactiveObservables[key] = dao.observable(p)
|
|
38
|
-
reactiveObservables[key].bindProperty(this, key)
|
|
39
|
-
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
40
|
-
}
|
|
41
|
-
let oldPathJson
|
|
42
|
-
watch(() => this[prefix + key], newPath => {
|
|
43
|
-
const json = JSON.stringify(newPath)
|
|
44
|
-
const match = JSON.stringify(newPath) == oldPathJson
|
|
45
|
-
oldPathJson = json
|
|
46
|
-
if(match) return
|
|
47
|
-
if(reactiveObservables[key]) {
|
|
48
|
-
this[key] = undefined
|
|
49
|
-
this[key+"Error"] = undefined
|
|
50
|
-
reactiveObservables[key].unbindProperty(this, key)
|
|
51
|
-
reactiveObservables[key].unbindErrorProperty(this, key+"Error")
|
|
52
|
-
}
|
|
53
|
-
delete reactiveObservables[key]
|
|
54
|
-
if(newPath) {
|
|
55
|
-
reactiveObservables[key] = dao.observable(newPath)
|
|
56
|
-
reactiveObservables[key].bindProperty(this, key)
|
|
57
|
-
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
58
|
-
} else {
|
|
59
|
-
this[key] = undefined
|
|
60
|
-
}
|
|
61
|
-
})
|
|
62
|
-
} else if(typeof path == 'string') {
|
|
63
|
-
reactiveObservables[key] = dao.observable(path)
|
|
64
|
-
reactiveObservables[key].bindProperty(this, key)
|
|
65
|
-
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
66
|
-
} else if(path.length !== undefined) {
|
|
67
|
-
//console.log("DAO", dao)
|
|
68
|
-
reactiveObservables[key] = dao.observable(path)
|
|
69
|
-
reactiveObservables[key].bindProperty(this, key)
|
|
70
|
-
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
71
|
-
} else throw new Error("unknown reactive path "+path)
|
|
72
|
-
}
|
|
73
|
-
},
|
|
74
|
-
beforeUnmount() {
|
|
75
|
-
if(!this.$options.reactive) return; // Avoid distributed fat
|
|
76
|
-
let reactiveObservables = this.reactiveObservables
|
|
77
|
-
for(let key in reactiveObservables) {
|
|
78
|
-
reactiveObservables[key].unbindProperty(this, key)
|
|
79
|
-
reactiveObservables[key].unbindErrorProperty(this, key+"Error")
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
const reactivePrefetchMixin = dao => ({
|
|
85
|
-
beforeCreate() {
|
|
86
|
-
if(typeof window == 'undefined') return // NO REACTIVE PREFETCH ON SERVER
|
|
87
|
-
if(!this.$options.reactivePreFetch) return
|
|
88
|
-
if (!this.$options.computed) this.$options.computed = {}
|
|
89
|
-
this.$options.computed[prefix+"_reactivePreFetch"] = function() {
|
|
90
|
-
return this.$options.reactivePreFetch.call(this, this.$route, this.$router)
|
|
91
|
-
}
|
|
92
|
-
const optionData = this.$options.data
|
|
93
|
-
this.$options.data = function vueReactiveDaoInjectedDataFn () {
|
|
94
|
-
const data = (
|
|
95
|
-
(typeof optionData === 'function')
|
|
96
|
-
? optionData.call(this)
|
|
97
|
-
: optionData
|
|
98
|
-
) || {}
|
|
99
|
-
data.reactivePreFetchedPaths = []
|
|
100
|
-
data.reactivePreFetchError = null
|
|
101
|
-
return data
|
|
102
|
-
}
|
|
103
|
-
},
|
|
104
|
-
created() {
|
|
105
|
-
if(typeof window == 'undefined') return // NO REACTIVE PREFETCH ON SERVER
|
|
106
|
-
if(!this.$options.reactivePreFetch) return
|
|
107
|
-
let paths = this[prefix+"_reactivePreFetch"]
|
|
108
|
-
if(paths) {
|
|
109
|
-
this.reactivePreFetchObservable = dao.observable({ paths })
|
|
110
|
-
this.reactivePreFetchObservable.bindProperty(this, "reactivePreFetchedPaths")
|
|
111
|
-
this.reactivePreFetchObservable.bindErrorProperty(this, "reactivePreFetchError")
|
|
112
|
-
}
|
|
113
|
-
watch(() => this[prefix + "_reactivePreFetch"], paths => {
|
|
114
|
-
if(this.reactivePreFetchObservable) {
|
|
115
|
-
this.reactivePreFetchObservable.unbindProperty(this, "reactivePreFetchedPaths")
|
|
116
|
-
this.reactivePreFetchObservable.unbindErrorProperty(this, "reactivePreFetchError")
|
|
117
|
-
}
|
|
118
|
-
delete this.reactivePreFetchObservable
|
|
119
|
-
if(paths) {
|
|
120
|
-
this.reactivePreFetchObservable = dao.observable({ paths })
|
|
121
|
-
this.reactivePreFetchObservable.bindProperty(this, "reactivePreFetchedPaths")
|
|
122
|
-
this.reactivePreFetchObservable.bindErrorProperty(this, "reactivePreFetchError")
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
},
|
|
126
|
-
beforeUnmount() {
|
|
127
|
-
if(typeof window == 'undefined') return; // NO REACTIVE PREFETCH ON SERVER
|
|
128
|
-
if(!this.$options.reactivePreFetch) return; // Avoid distributed fat
|
|
129
|
-
if(this.reactivePreFetchObservable) {
|
|
130
|
-
this.reactivePreFetchObservable.unbindProperty(this, "reactivePreFetchedPaths")
|
|
131
|
-
this.reactivePreFetchObservable.unbindErrorProperty(this, "reactivePreFetchError")
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
const reactiveComponent = dao => ({
|
|
137
|
-
name: "Reactive",
|
|
138
|
-
props: {
|
|
139
|
-
what: {
|
|
140
|
-
type: Object
|
|
141
|
-
}
|
|
142
|
-
},
|
|
143
|
-
data() {
|
|
144
|
-
let values = {}, errors = {}
|
|
145
|
-
for(const key in this.what) {
|
|
146
|
-
values[key] = undefined
|
|
147
|
-
values[key + 'Error'] = undefined
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
values
|
|
151
|
-
}
|
|
152
|
-
},
|
|
153
|
-
created() {
|
|
154
|
-
this.observables = {}
|
|
155
|
-
for(const name in this.what) {
|
|
156
|
-
const what = this.what[key]
|
|
157
|
-
const observable = dao.observable(what)
|
|
158
|
-
this.observables[name] = observable
|
|
159
|
-
observable.bindProperty(this.values[name])
|
|
160
|
-
observable.bindErrorProperty(this.values[name+'Error'])
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
beforeDestroy() {
|
|
164
|
-
for(const name in this.observables) {
|
|
165
|
-
const observable = this.observables[name]
|
|
166
|
-
observable.unbindProperty(this, "reactivePreFetchedPaths")
|
|
167
|
-
observable.unbindErrorProperty(this, "reactivePreFetchError")
|
|
168
|
-
}
|
|
169
|
-
},
|
|
170
|
-
render(createElement) {
|
|
171
|
-
return this.$scopedSlots.default(this.values)[0]
|
|
172
|
-
}
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
class ReactiveObservableList extends lcdao.ObservableList {
|
|
176
|
-
constructor(value, what, dispose) {
|
|
177
|
-
super(value, what, dispose, (data) => {
|
|
178
|
-
if(data && typeof data == 'object') {
|
|
179
|
-
const activated = reactive(data)
|
|
180
|
-
return activated
|
|
181
|
-
}
|
|
182
|
-
return data
|
|
183
|
-
})
|
|
184
|
-
}
|
|
185
|
-
}
|
|
1
|
+
import reactiveMixin from './lib/reactiveMixin.js'
|
|
2
|
+
import reactivePrefetchMixin from './lib/reactivePrefetchMixin.js'
|
|
3
|
+
import live from './lib/live.js'
|
|
4
|
+
import ReactiveObservableList from './lib/ReactiveObservableList.js'
|
|
5
|
+
import RangeBuckets from './lib/RangeBuckets.js'
|
|
186
6
|
|
|
187
7
|
const ReactiveDaoVue = {
|
|
188
8
|
install(Vue, options) {
|
|
189
9
|
if(!options || !options.dao) throw new Error("dao option required")
|
|
190
10
|
const dao = options.dao
|
|
191
|
-
|
|
192
11
|
Vue.mixin(reactiveMixin(dao))
|
|
193
|
-
|
|
194
12
|
Vue.mixin(reactivePrefetchMixin(dao))
|
|
195
|
-
|
|
196
|
-
Vue.component('reactive', reactiveComponent(dao))
|
|
197
13
|
}
|
|
198
14
|
}
|
|
199
15
|
|
|
200
16
|
//// TODO: rename reactive to live
|
|
201
|
-
export { ReactiveDaoVue, reactiveMixin, reactivePrefetchMixin,
|
|
202
|
-
|
|
203
|
-
const liveSymbol = Symbol('live')
|
|
204
|
-
|
|
205
|
-
async function live(api, path, onUnmountedCb) {
|
|
206
|
-
if(!onUnmounted && typeof window != 'undefined') {
|
|
207
|
-
if(getCurrentInstance()) {
|
|
208
|
-
onUnmountedCb = onUnmounted
|
|
209
|
-
} else {
|
|
210
|
-
onUnmountedCb = () => {
|
|
211
|
-
console.error("live fetch outside component instance - possible memory leak")
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const paths = [ path ]
|
|
217
|
-
if(typeof window == 'undefined') {
|
|
218
|
-
const preFetchPaths = await api.get({ paths })
|
|
219
|
-
console.log("PRE FETCH DATA", preFetchPaths)
|
|
220
|
-
const preFetchMap = new Map(preFetchPaths.map((res) => [JSON.stringify(res.what), res] ))
|
|
221
|
-
function createObject(what, more) {
|
|
222
|
-
const res = preFetchMap.get(JSON.stringify(what))
|
|
223
|
-
if(res.error) throw new Error(res.error)
|
|
224
|
-
const data = res.data
|
|
225
|
-
if(more) {
|
|
226
|
-
if(Array.isArray(data)) {
|
|
227
|
-
for(let i = 0; i < data.length; i ++) {
|
|
228
|
-
for(const moreElement of more) {
|
|
229
|
-
if(moreElement.to) {
|
|
230
|
-
console.log("COLLECT POINTERS FROM", data[i], "SC", moreElement.schema)
|
|
231
|
-
const pointers = lcdao.collectPointers(data[i], moreElement.schema ,
|
|
232
|
-
(what) => preFetchMap.get(JSON.stringify(what)))
|
|
233
|
-
console.log("POINTERS COLLECTED", pointers)
|
|
234
|
-
const values = pointers.map(pointer => createObject(pointer, moreElement.more))
|
|
235
|
-
console.log("VALUES", values)
|
|
236
|
-
console.log("MANY", pointers.many)
|
|
237
|
-
if(pointers.many) {
|
|
238
|
-
data[i][moreElement.to] = values
|
|
239
|
-
} else {
|
|
240
|
-
data[i][moreElement.to] = values[0] || null
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
} else {
|
|
246
|
-
for(const moreElement of more) {
|
|
247
|
-
if(moreElement.to) {
|
|
248
|
-
const pointers = lcdao.collectPointers(data, moreElement.schema,
|
|
249
|
-
(what) => preFetchMap.get(JSON.stringify(what)))
|
|
250
|
-
const values = pointers.map(pointer => createObject(pointer, moreElement.more))
|
|
251
|
-
if(pointers.many) {
|
|
252
|
-
data[moreElement.to] = values
|
|
253
|
-
} else {
|
|
254
|
-
data[moreElement.to] = values[0] || null
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
return data
|
|
261
|
-
}
|
|
262
|
-
return createObject(path.what, path.more)
|
|
263
|
-
} else {
|
|
264
|
-
const preFetchPaths = api.observable({ paths })
|
|
265
|
-
const observables = []
|
|
266
|
-
function bindResult(what, more, object, property) {
|
|
267
|
-
if(!what) throw new Error("what parameter required!")
|
|
268
|
-
const observable = api.observable(what)
|
|
269
|
-
if(more && more.some(m => m.to)) {
|
|
270
|
-
const extendedObservable = new lcdao.ExtendedObservableList(observable,
|
|
271
|
-
newElement => {
|
|
272
|
-
if(!newElement) return newElement
|
|
273
|
-
const extendedElement = { ...newElement }
|
|
274
|
-
const props = {}
|
|
275
|
-
for(const moreElement of more) {
|
|
276
|
-
if(moreElement.to) {
|
|
277
|
-
const prop = {
|
|
278
|
-
bounds: [],
|
|
279
|
-
sources: []
|
|
280
|
-
}
|
|
281
|
-
props[moreElement.to] = prop
|
|
282
|
-
let requiredSrcs = []
|
|
283
|
-
const srcs = new Map()
|
|
284
|
-
function getSource(ptr) {
|
|
285
|
-
const exists = srcs.get(ptr)
|
|
286
|
-
if(exists !== undefined) return exists.list
|
|
287
|
-
requiredSrcs.push(exists)
|
|
288
|
-
return undefined
|
|
289
|
-
}
|
|
290
|
-
function computePointers() {
|
|
291
|
-
while(true) {
|
|
292
|
-
const pointers = lcdao.collectPointers(newElement, moreElement.schema, getSource)
|
|
293
|
-
if(requiredSrcs.length == 0) return pointers
|
|
294
|
-
for(const requiredSrc of requiredSrcs) {
|
|
295
|
-
const observable = api.observable(requiredSrc)
|
|
296
|
-
const observer = () => {
|
|
297
|
-
bindPointers(computePointers())
|
|
298
|
-
}
|
|
299
|
-
srcs.set(JSON.stringify(requiredSrc), observable)
|
|
300
|
-
prop.sources.push({ observable, observer })
|
|
301
|
-
observable.observe(observer)
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
function bindPointers(pointers) {
|
|
306
|
-
if(pointers.many) {
|
|
307
|
-
const oldBound = prop.bounds.slice()
|
|
308
|
-
const newArray = new Array(pointers.length)
|
|
309
|
-
const newBounds = new Array(pointers.length)
|
|
310
|
-
for(let i = 0; i < pointers.length; i++) {
|
|
311
|
-
newBounds[i] = bindResult(pointers[i], moreElements.more, newArray, i)
|
|
312
|
-
}
|
|
313
|
-
prop.bounds = newBounds
|
|
314
|
-
oldBound.forEach(b => b.dispose())
|
|
315
|
-
extendedElement[moreElement.to] = newArray
|
|
316
|
-
} else if(pointers.length > 0) {
|
|
317
|
-
const oldBound = prop.bounds
|
|
318
|
-
if(!oldBound || oldBound.length == 0 ||
|
|
319
|
-
JSON.stringify(oldBound[0].what) != JSON.stringify(pointers[0])) {
|
|
320
|
-
if(oldBound) {
|
|
321
|
-
prop.bounds.forEach(b => b.dispose())
|
|
322
|
-
}
|
|
323
|
-
if(pointers[0]) {
|
|
324
|
-
prop.bounds = [
|
|
325
|
-
bindResult(pointers[0], moreElement.more, extendedElement, moreElement.to)
|
|
326
|
-
]
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
bindPointers(computePointers())
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
extendedElement[liveSymbol] = props
|
|
335
|
-
return extendedElement
|
|
336
|
-
},
|
|
337
|
-
disposedElement => {
|
|
338
|
-
if(!disposedElement) return
|
|
339
|
-
const boundProps = disposedElement[liveSymbol]
|
|
340
|
-
for(const propName in boundProps) {
|
|
341
|
-
const prop = boundProps[propName]
|
|
342
|
-
const propBounds = prop.bounds
|
|
343
|
-
for(const propBound of propBounds) {
|
|
344
|
-
console.log("PROP BOUND DISPOSE", propBound)
|
|
345
|
-
propBound.dispose()
|
|
346
|
-
}
|
|
347
|
-
const propSources = prop.sources
|
|
348
|
-
for(const propSource of propSources) {
|
|
349
|
-
console.log("PROP SOURCE DISPOSE", propSource)
|
|
350
|
-
propSource.observable.unobserve(propSource.observer)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
)
|
|
355
|
-
extendedObservable.bindProperty(object, property)
|
|
356
|
-
return {
|
|
357
|
-
what,
|
|
358
|
-
property,
|
|
359
|
-
dispose() {
|
|
360
|
-
extendedObservable.unbindProperty(object, property)
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
} else {
|
|
364
|
-
observable.bindProperty(object, property)
|
|
365
|
-
return {
|
|
366
|
-
what, property,
|
|
367
|
-
dispose() {
|
|
368
|
-
observable.unbindProperty(object, property)
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
const resultRef = ref()
|
|
374
|
-
bindResult(path.what, path.more, resultRef, 'value')
|
|
375
|
-
/// TODO: unobserve on unmounted
|
|
376
|
-
await preFetchPaths.wait()
|
|
377
|
-
return resultRef
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
export { live }
|
|
382
|
-
|
|
17
|
+
export { ReactiveDaoVue, reactiveMixin, reactivePrefetchMixin, ReactiveObservableList, RangeBuckets, live }
|
|
383
18
|
|
|
384
19
|
export default ReactiveDaoVue
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import live from "./live.js"
|
|
2
|
+
import { computed, reactive, ref, unref, watch } from "vue"
|
|
3
|
+
|
|
4
|
+
class Bucket {
|
|
5
|
+
|
|
6
|
+
constructor(id, api, range, pathFunction, bucketSize, waitForComponents, hardClose) {
|
|
7
|
+
this.id = id
|
|
8
|
+
this.api = () => api
|
|
9
|
+
this.range = reactive(range)
|
|
10
|
+
this.pathFunction = pathFunction
|
|
11
|
+
this.bucketSize = bucketSize
|
|
12
|
+
this.waitForComponents = waitForComponents
|
|
13
|
+
this.hardClose = hardClose
|
|
14
|
+
|
|
15
|
+
this.disposed = false
|
|
16
|
+
this.onDispose = []
|
|
17
|
+
this.domElements = []
|
|
18
|
+
|
|
19
|
+
this.data = computed(() => {
|
|
20
|
+
const ldv = this.liveData.value
|
|
21
|
+
if(!ldv) return []
|
|
22
|
+
const source = unref(ldv)
|
|
23
|
+
if(this.hardClose) {
|
|
24
|
+
return source
|
|
25
|
+
}
|
|
26
|
+
return source.filter(element => (
|
|
27
|
+
(!this.range.gt || element.id > this.range.gt ) &&
|
|
28
|
+
(!this.range.gte || element.id >= this.range.gte) &&
|
|
29
|
+
(!this.range.lt || element.id < this.range.lt ) &&
|
|
30
|
+
(!this.range.lte || element.id <= this.range.lte)
|
|
31
|
+
))
|
|
32
|
+
})
|
|
33
|
+
this.liveData = ref(null)
|
|
34
|
+
|
|
35
|
+
this.load()
|
|
36
|
+
|
|
37
|
+
this.promise = waitForComponents ? this.createPromise() : this.dataPromise
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
createPromise() {
|
|
41
|
+
const promise = new Promise((r1, r2) => { this.resolve = r1; this.reject = r2 })
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async load() {
|
|
45
|
+
this.path = this.pathFunction(this.range)
|
|
46
|
+
this.dataPromise = live(this.api(), this.path, fun => onDispose.push(fun))
|
|
47
|
+
this.dataPromise.then(data => {
|
|
48
|
+
this.liveData.value = data
|
|
49
|
+
// if(this.hardClose) {
|
|
50
|
+
// this.data.value = this.liveData.value
|
|
51
|
+
// return
|
|
52
|
+
// }
|
|
53
|
+
// return this.data.value = computed(() => source.filter(element => (
|
|
54
|
+
// (!this.range.gt || element.id > this.range.gt ) &&
|
|
55
|
+
// (!this.range.gte || element.id >= this.range.gte) &&
|
|
56
|
+
// (!this.range.lt || element.id < this.range.lt ) &&
|
|
57
|
+
// (!this.range.lte || element.id <= this.range.lte)
|
|
58
|
+
// )))
|
|
59
|
+
})
|
|
60
|
+
return this.dataPromise
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
isTopClosed() {
|
|
64
|
+
return !!(this.range && (this.range.gt || this.range.gte))
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
isBottomClosed() {
|
|
68
|
+
return !!(this.range && (this.range.lt || this.range.lte))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
canClose() {
|
|
72
|
+
const data = unref(this.data)
|
|
73
|
+
return data && data.length == this.bucketSize
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
async closeTop() {
|
|
77
|
+
const data = unref(await this.dataPromise)
|
|
78
|
+
if(!this.data) throw new Error("can't close - bucket not loaded!")
|
|
79
|
+
if(this.data.length < this.bucketSize) throw new Error("can't close - bucket not full")
|
|
80
|
+
this.range.gte = data[0].id
|
|
81
|
+
if(this.hardClose) await this.load()
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async closeBottom() {
|
|
85
|
+
const data = unref(await this.dataPromise)
|
|
86
|
+
if(data.length < this.bucketSize) throw new Error("can't close - bucket not full")
|
|
87
|
+
this.range.lte = data[data.length - 1].id
|
|
88
|
+
if(this.hardClose) await this.load()
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
dispose() {
|
|
92
|
+
this.disposed = true
|
|
93
|
+
for(const disposeFunction of this.onDispose) {
|
|
94
|
+
disposeFunction()
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class RangeBuckets {
|
|
100
|
+
constructor(api, pathFunction, options) {
|
|
101
|
+
this.api = api
|
|
102
|
+
this.pathFunction = pathFunction
|
|
103
|
+
this.bucketSize = options?.bucketSize || 3
|
|
104
|
+
this.position = options?.position
|
|
105
|
+
this.softClose = options?.softClose
|
|
106
|
+
this.waitForComponents = options?.waitForComponents
|
|
107
|
+
|
|
108
|
+
this.lastBucketId = 0
|
|
109
|
+
this.buckets = reactive([])
|
|
110
|
+
|
|
111
|
+
this.canLoadTop = computed(() => this.isTopLoadPossible())
|
|
112
|
+
this.canLoadBottom = computed(() => this.isBottomLoadPossible())
|
|
113
|
+
|
|
114
|
+
this.loadFirstBucket()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
isTopLoadPossible() {
|
|
118
|
+
if(this.buckets.length == 0) return false
|
|
119
|
+
const firstBucket = this.buckets[0]
|
|
120
|
+
return firstBucket.isTopClosed() || firstBucket.canClose()
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
isBottomLoadPossible() {
|
|
124
|
+
if(this.buckets.length == 0) return false
|
|
125
|
+
const lastBucket = this.buckets[this.buckets.length - 1]
|
|
126
|
+
return lastBucket.isBottomClosed() || lastBucket.canClose()
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
loadFirstBucket() {
|
|
130
|
+
const firstBucket = this.createBucket({
|
|
131
|
+
gte: this.position,
|
|
132
|
+
limit: this.bucketSize
|
|
133
|
+
})
|
|
134
|
+
this.buckets.push(firstBucket)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
createBucket(range) {
|
|
138
|
+
return new Bucket(++this.lastBucketId, this.api, range, this.pathFunction,
|
|
139
|
+
this.bucketSize, this.waitForComponents, !this.softClose)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
async wait() {
|
|
143
|
+
console.log("WAIT FOR BUCKETS", this.buckets.length)
|
|
144
|
+
await Promise.all(this.buckets.map(bucket => bucket.promise)).then(loaded => this)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async loadTop() {
|
|
148
|
+
if(this.buckets.length == 0) return this.loadFirstBucket()
|
|
149
|
+
const firstBucket = this.buckets[0]
|
|
150
|
+
await firstBucket.promise
|
|
151
|
+
if(!this.isTopLoadPossible()) return
|
|
152
|
+
if(firstBucket != this.buckets[0]) return this.buckets[0].promise
|
|
153
|
+
let range = { limit: this.bucketSize }
|
|
154
|
+
if(!firstBucket.isTopClosed()) {
|
|
155
|
+
if(firstBucket.canClose()) {
|
|
156
|
+
await firstBucket.closeTop()
|
|
157
|
+
if(!firstBucket.isTopClosed()) throw new Error('top not closed!!!')
|
|
158
|
+
if(!this.isTopLoadPossible()) return
|
|
159
|
+
return this.loadTop()
|
|
160
|
+
} else {
|
|
161
|
+
console.log("FBD", unref(firstBucket.data))
|
|
162
|
+
throw new Error('impossible to read before bucket that is not closeable')
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if(firstBucket.range.gte) {
|
|
166
|
+
range.lt = firstBucket.range.gte
|
|
167
|
+
} else if(firstBucket.range.gt) {
|
|
168
|
+
range.lte = firstBucket.range.gt
|
|
169
|
+
} else {
|
|
170
|
+
throw new Error('imposible to read before bucket '+ JSON.stringify(firstBucket.range))
|
|
171
|
+
}
|
|
172
|
+
const bucket = this.createBucket(range)
|
|
173
|
+
this.buckets.unshift(bucket)
|
|
174
|
+
return bucket.promise
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async loadBottom() {
|
|
178
|
+
if(this.buckets.length == 0) return this.loadFirstBucket()
|
|
179
|
+
const lastBucket = this.buckets[this.buckets.length - 1]
|
|
180
|
+
await lastBucket.promise
|
|
181
|
+
if(!this.isBottomLoadPossible()) return
|
|
182
|
+
if(lastBucket != this.buckets[this.buckets.length - 1]) return this.buckets[this.buckets.length - 1].promise
|
|
183
|
+
let range = { limit: this.bucketSize }
|
|
184
|
+
if(!lastBucket.isBottomClosed()) {
|
|
185
|
+
if(lastBucket.canClose()) {
|
|
186
|
+
await lastBucket.closeBottom()
|
|
187
|
+
if(!lastBucket.isBottomClosed()) throw new Error('bottom not closed!!!')
|
|
188
|
+
if(!this.isBottomLoadPossible()) return
|
|
189
|
+
return this.loadBottom()
|
|
190
|
+
} else {
|
|
191
|
+
throw new Error('impossible to read after bucket that is not closeable')
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if(lastBucket.range.lte) {
|
|
195
|
+
range.gt = lastBucket.range.lte
|
|
196
|
+
} else if(lastBucket.range.lt) {
|
|
197
|
+
range.lte = lastBucket.range.lt
|
|
198
|
+
} else {
|
|
199
|
+
throw new Error('imposible to read after bucket '+ JSON.stringify(lastBucket.range))
|
|
200
|
+
}
|
|
201
|
+
const bucket = this.createBucket(range)
|
|
202
|
+
this.buckets.push(bucket)
|
|
203
|
+
return bucket.promise
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
dropTop() {
|
|
207
|
+
if(this.buckets.length == 0) throw new Error('impossible to drop from empty')
|
|
208
|
+
const droppedBucket = this.buckets[0]
|
|
209
|
+
const height = droppedBucket.domElements.reduce((acc, el) => acc + el?.offsetHeight, 0)
|
|
210
|
+
this.buckets.shift()
|
|
211
|
+
droppedBucket.dispose()
|
|
212
|
+
return height
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
dropBottom() {
|
|
216
|
+
if(this.buckets.length == 0) throw new Error('impossible to drop from empty')
|
|
217
|
+
const droppedBucket = this.buckets[this.buckets.length - 1]
|
|
218
|
+
const height = droppedBucket.domElements.reduce((acc, el) => acc + el?.offsetHeight, 0)
|
|
219
|
+
this.buckets.pop()
|
|
220
|
+
droppedBucket.dispose()
|
|
221
|
+
return height
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
dispose() {
|
|
225
|
+
for(const bucket of this.buckets) {
|
|
226
|
+
bucket.dispose()
|
|
227
|
+
}
|
|
228
|
+
this.buckets = []
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export default RangeBuckets
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { reactive } from 'vue'
|
|
2
|
+
import { ObservableList } from '@live-change/dao'
|
|
3
|
+
|
|
4
|
+
class ReactiveObservableList extends ObservableList {
|
|
5
|
+
constructor(value, what, dispose) {
|
|
6
|
+
super(value, what, dispose, (data) => {
|
|
7
|
+
if(data && typeof data == 'object') {
|
|
8
|
+
const activated = reactive(data)
|
|
9
|
+
return activated
|
|
10
|
+
}
|
|
11
|
+
return data
|
|
12
|
+
})
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export default ReactiveObservableList
|
package/lib/live.js
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { ref, onUnmounted, getCurrentInstance, unref, reactive } from 'vue'
|
|
2
|
+
import { collectPointers, ExtendedObservableList } from '@live-change/dao'
|
|
3
|
+
|
|
4
|
+
const liveSymbol = Symbol('live')
|
|
5
|
+
|
|
6
|
+
async function live(api, path, onUnmountedCb) {
|
|
7
|
+
if(!onUnmountedCb && typeof window != 'undefined') {
|
|
8
|
+
if(getCurrentInstance()) {
|
|
9
|
+
onUnmountedCb = onUnmounted
|
|
10
|
+
} else {
|
|
11
|
+
onUnmountedCb = () => {
|
|
12
|
+
console.error("live fetch outside component instance - possible memory leak")
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
if(Array.isArray(path)) path = { what: path }
|
|
18
|
+
const paths = [ path ]
|
|
19
|
+
if(typeof window == 'undefined') {
|
|
20
|
+
const preFetchPaths = await api.get({ paths })
|
|
21
|
+
console.log("PRE FETCH DATA", preFetchPaths)
|
|
22
|
+
const preFetchMap = new Map(preFetchPaths.map((res) => [JSON.stringify(res.what), res] ))
|
|
23
|
+
function createObject(what, more) {
|
|
24
|
+
const res = preFetchMap.get(JSON.stringify(what))
|
|
25
|
+
if(res.error) throw new Error(res.error)
|
|
26
|
+
const data = res.data
|
|
27
|
+
if(more) {
|
|
28
|
+
if(Array.isArray(data)) {
|
|
29
|
+
for(let i = 0; i < data.length; i ++) {
|
|
30
|
+
for(const moreElement of more) {
|
|
31
|
+
if(moreElement.to) {
|
|
32
|
+
console.log("COLLECT POINTERS FROM", data[i], "SC", moreElement.schema)
|
|
33
|
+
const pointers = collectPointers(data[i], moreElement.schema ,
|
|
34
|
+
(what) => preFetchMap.get(JSON.stringify(what)))
|
|
35
|
+
console.log("POINTERS COLLECTED", pointers)
|
|
36
|
+
const values = pointers.map(pointer => createObject(pointer, moreElement.more))
|
|
37
|
+
console.log("VALUES", values)
|
|
38
|
+
console.log("MANY", pointers.many)
|
|
39
|
+
if(pointers.many) {
|
|
40
|
+
data[i][moreElement.to] = values
|
|
41
|
+
} else {
|
|
42
|
+
data[i][moreElement.to] = values[0] || null
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
for(const moreElement of more) {
|
|
49
|
+
if(moreElement.to) {
|
|
50
|
+
const pointers = collectPointers(data, moreElement.schema,
|
|
51
|
+
(what) => preFetchMap.get(JSON.stringify(what)))
|
|
52
|
+
const values = pointers.map(pointer => createObject(pointer, moreElement.more))
|
|
53
|
+
if(pointers.many) {
|
|
54
|
+
data[moreElement.to] = values
|
|
55
|
+
} else {
|
|
56
|
+
data[moreElement.to] = values[0] || null
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return data
|
|
63
|
+
}
|
|
64
|
+
return createObject(path.what, path.more)
|
|
65
|
+
} else {
|
|
66
|
+
const preFetchPaths = api.observable({ paths })
|
|
67
|
+
const observables = []
|
|
68
|
+
function bindResult(what, more, object, property) {
|
|
69
|
+
if(!what) throw new Error("what parameter required!")
|
|
70
|
+
const observable = api.observable(what)
|
|
71
|
+
if(more && more.some(m => m.to)) {
|
|
72
|
+
const extendedObservable = new ExtendedObservableList(observable,
|
|
73
|
+
newElement => {
|
|
74
|
+
if(!newElement) return newElement
|
|
75
|
+
const extendedElement = { ...newElement }
|
|
76
|
+
const props = {}
|
|
77
|
+
for(const moreElement of more) {
|
|
78
|
+
if(moreElement.to) {
|
|
79
|
+
const prop = {
|
|
80
|
+
bounds: [],
|
|
81
|
+
sources: []
|
|
82
|
+
}
|
|
83
|
+
props[moreElement.to] = prop
|
|
84
|
+
let requiredSrcs = []
|
|
85
|
+
const srcs = new Map()
|
|
86
|
+
function getSource(ptr) {
|
|
87
|
+
const exists = srcs.get(ptr)
|
|
88
|
+
if(exists !== undefined) return exists.list
|
|
89
|
+
requiredSrcs.push(exists)
|
|
90
|
+
return undefined
|
|
91
|
+
}
|
|
92
|
+
function computePointers() {
|
|
93
|
+
while(true) {
|
|
94
|
+
const pointers = collectPointers(newElement, moreElement.schema, getSource)
|
|
95
|
+
if(requiredSrcs.length == 0) return pointers
|
|
96
|
+
for(const requiredSrc of requiredSrcs) {
|
|
97
|
+
const observable = api.observable(requiredSrc)
|
|
98
|
+
const observer = () => {
|
|
99
|
+
bindPointers(computePointers())
|
|
100
|
+
}
|
|
101
|
+
srcs.set(JSON.stringify(requiredSrc), observable)
|
|
102
|
+
prop.sources.push({ observable, observer })
|
|
103
|
+
observable.observe(observer)
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function bindPointers(pointers) {
|
|
108
|
+
if(pointers.many) {
|
|
109
|
+
const oldBound = prop.bounds.slice()
|
|
110
|
+
const newArray = new Array(pointers.length)
|
|
111
|
+
const newBounds = new Array(pointers.length)
|
|
112
|
+
for(let i = 0; i < pointers.length; i++) {
|
|
113
|
+
newBounds[i] = bindResult(pointers[i], moreElements.more, newArray, i)
|
|
114
|
+
}
|
|
115
|
+
prop.bounds = newBounds
|
|
116
|
+
oldBound.forEach(b => b.dispose())
|
|
117
|
+
extendedElement[moreElement.to] = newArray
|
|
118
|
+
} else if(pointers.length > 0) {
|
|
119
|
+
const oldBound = prop.bounds
|
|
120
|
+
if(!oldBound || oldBound.length == 0 ||
|
|
121
|
+
JSON.stringify(oldBound[0].what) != JSON.stringify(pointers[0])) {
|
|
122
|
+
if(oldBound) {
|
|
123
|
+
prop.bounds.forEach(b => b.dispose())
|
|
124
|
+
}
|
|
125
|
+
if(pointers[0]) {
|
|
126
|
+
prop.bounds = [
|
|
127
|
+
bindResult(pointers[0], moreElement.more, extendedElement, moreElement.to)
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
bindPointers(computePointers())
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
extendedElement[liveSymbol] = props
|
|
137
|
+
return extendedElement
|
|
138
|
+
},
|
|
139
|
+
disposedElement => {
|
|
140
|
+
if(!disposedElement) return
|
|
141
|
+
const boundProps = disposedElement[liveSymbol]
|
|
142
|
+
for(const propName in boundProps) {
|
|
143
|
+
const prop = boundProps[propName]
|
|
144
|
+
const propBounds = prop.bounds
|
|
145
|
+
for(const propBound of propBounds) {
|
|
146
|
+
//console.log("PROP BOUND DISPOSE", propBound)
|
|
147
|
+
propBound.dispose()
|
|
148
|
+
}
|
|
149
|
+
const propSources = prop.sources
|
|
150
|
+
for(const propSource of propSources) {
|
|
151
|
+
//console.log("PROP SOURCE DISPOSE", propSource)
|
|
152
|
+
propSource.observable.unobserve(propSource.observer)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
(data) => {
|
|
157
|
+
if(data && typeof data == 'object') {
|
|
158
|
+
const activated = reactive(data)
|
|
159
|
+
return activated
|
|
160
|
+
}
|
|
161
|
+
return data
|
|
162
|
+
}
|
|
163
|
+
)
|
|
164
|
+
extendedObservable.bindProperty(object, property)
|
|
165
|
+
return {
|
|
166
|
+
what,
|
|
167
|
+
property,
|
|
168
|
+
dispose() {
|
|
169
|
+
extendedObservable.unbindProperty(object, property)
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
} else {
|
|
173
|
+
observable.bindProperty(object, property)
|
|
174
|
+
return {
|
|
175
|
+
what, property,
|
|
176
|
+
dispose() {
|
|
177
|
+
observable.unbindProperty(object, property)
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const resultRef = ref()
|
|
183
|
+
bindResult(path.what, path.more, resultRef, 'value')
|
|
184
|
+
/// TODO: unobserve on unmounted
|
|
185
|
+
await preFetchPaths.wait()
|
|
186
|
+
while(unref(resultRef) === undefined) { // wait for next tick
|
|
187
|
+
await new Promise((resolve) => setTimeout(resolve, 0))
|
|
188
|
+
}
|
|
189
|
+
return resultRef
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export default live
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
const prefix = "$lcDaoPath_"
|
|
4
|
+
|
|
5
|
+
const reactiveMixin = dao => ({
|
|
6
|
+
data() {
|
|
7
|
+
if(!this.$options.reactive) return {} // Avoid distributed fat
|
|
8
|
+
let data = {}
|
|
9
|
+
for (let key in this.$options.reactive) {
|
|
10
|
+
data[key] = undefined
|
|
11
|
+
data[key+"Error"] = undefined
|
|
12
|
+
}
|
|
13
|
+
return data
|
|
14
|
+
},
|
|
15
|
+
beforeCreate() {
|
|
16
|
+
if(!this.$options.reactive) return; // Avoid distributed fat
|
|
17
|
+
if (!this.$options.computed) this.$options.computed = {}
|
|
18
|
+
for(let key in this.$options.reactive) {
|
|
19
|
+
let path = this.$options.reactive[key]
|
|
20
|
+
if(typeof path == 'function'){
|
|
21
|
+
this.$options.computed[prefix + key] = path
|
|
22
|
+
} else if(typeof path == 'string') {
|
|
23
|
+
} else if(path.length !== undefined) {
|
|
24
|
+
} else throw new Error("unknown reactive path "+path)
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
created() {
|
|
28
|
+
if(!this.$options.reactive) return; // Avoid distributed fat
|
|
29
|
+
this.reactiveObservables = {}
|
|
30
|
+
let reactiveObservables = this.reactiveObservables
|
|
31
|
+
for(let key in this.$options.reactive) {
|
|
32
|
+
let path = this.$options.reactive[key]
|
|
33
|
+
if(typeof path == 'function'){
|
|
34
|
+
let p = this[prefix + key]
|
|
35
|
+
if(p) {
|
|
36
|
+
reactiveObservables[key] = dao.observable(p)
|
|
37
|
+
reactiveObservables[key].bindProperty(this, key)
|
|
38
|
+
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
39
|
+
}
|
|
40
|
+
let oldPathJson
|
|
41
|
+
watch(() => this[prefix + key], newPath => {
|
|
42
|
+
const json = JSON.stringify(newPath)
|
|
43
|
+
const match = JSON.stringify(newPath) == oldPathJson
|
|
44
|
+
oldPathJson = json
|
|
45
|
+
if(match) return
|
|
46
|
+
if(reactiveObservables[key]) {
|
|
47
|
+
this[key] = undefined
|
|
48
|
+
this[key+"Error"] = undefined
|
|
49
|
+
reactiveObservables[key].unbindProperty(this, key)
|
|
50
|
+
reactiveObservables[key].unbindErrorProperty(this, key+"Error")
|
|
51
|
+
}
|
|
52
|
+
delete reactiveObservables[key]
|
|
53
|
+
if(newPath) {
|
|
54
|
+
reactiveObservables[key] = dao.observable(newPath)
|
|
55
|
+
reactiveObservables[key].bindProperty(this, key)
|
|
56
|
+
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
57
|
+
} else {
|
|
58
|
+
this[key] = undefined
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
} else if(typeof path == 'string') {
|
|
62
|
+
reactiveObservables[key] = dao.observable(path)
|
|
63
|
+
reactiveObservables[key].bindProperty(this, key)
|
|
64
|
+
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
65
|
+
} else if(path.length !== undefined) {
|
|
66
|
+
//console.log("DAO", dao)
|
|
67
|
+
reactiveObservables[key] = dao.observable(path)
|
|
68
|
+
reactiveObservables[key].bindProperty(this, key)
|
|
69
|
+
reactiveObservables[key].bindErrorProperty(this, key+"Error")
|
|
70
|
+
} else throw new Error("unknown reactive path "+path)
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
beforeUnmount() {
|
|
74
|
+
if(!this.$options.reactive) return; // Avoid distributed fat
|
|
75
|
+
let reactiveObservables = this.reactiveObservables
|
|
76
|
+
for(let key in reactiveObservables) {
|
|
77
|
+
reactiveObservables[key].unbindProperty(this, key)
|
|
78
|
+
reactiveObservables[key].unbindErrorProperty(this, key+"Error")
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
export default reactiveMixin
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { watch } from 'vue'
|
|
2
|
+
|
|
3
|
+
let prefix = "$lcDaoPath_"
|
|
4
|
+
|
|
5
|
+
const reactivePrefetchMixin = dao => ({
|
|
6
|
+
beforeCreate() {
|
|
7
|
+
if(typeof window == 'undefined') return // NO REACTIVE PREFETCH ON SERVER
|
|
8
|
+
if(!this.$options.reactivePreFetch) return
|
|
9
|
+
if (!this.$options.computed) this.$options.computed = {}
|
|
10
|
+
this.$options.computed[prefix + "_reactivePreFetch"] = function() {
|
|
11
|
+
return this.$options.reactivePreFetch.call(this, this.$route, this.$router)
|
|
12
|
+
}
|
|
13
|
+
const optionData = this.$options.data
|
|
14
|
+
this.$options.data = function vueReactiveDaoInjectedDataFn () {
|
|
15
|
+
const data = (
|
|
16
|
+
(typeof optionData === 'function')
|
|
17
|
+
? optionData.call(this)
|
|
18
|
+
: optionData
|
|
19
|
+
) || {}
|
|
20
|
+
data.reactivePreFetchedPaths = []
|
|
21
|
+
data.reactivePreFetchError = null
|
|
22
|
+
return data
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
created() {
|
|
26
|
+
if(typeof window == 'undefined') return // NO REACTIVE PREFETCH ON SERVER
|
|
27
|
+
if(!this.$options.reactivePreFetch) return
|
|
28
|
+
let paths = this[prefix + "_reactivePreFetch"]
|
|
29
|
+
if(paths) {
|
|
30
|
+
this.reactivePreFetchObservable = dao.observable({ paths })
|
|
31
|
+
this.reactivePreFetchObservable.bindProperty(this, "reactivePreFetchedPaths")
|
|
32
|
+
this.reactivePreFetchObservable.bindErrorProperty(this, "reactivePreFetchError")
|
|
33
|
+
}
|
|
34
|
+
watch(() => this[prefix + "_reactivePreFetch"], paths => {
|
|
35
|
+
if(this.reactivePreFetchObservable) {
|
|
36
|
+
this.reactivePreFetchObservable.unbindProperty(this, "reactivePreFetchedPaths")
|
|
37
|
+
this.reactivePreFetchObservable.unbindErrorProperty(this, "reactivePreFetchError")
|
|
38
|
+
}
|
|
39
|
+
delete this.reactivePreFetchObservable
|
|
40
|
+
if(paths) {
|
|
41
|
+
this.reactivePreFetchObservable = dao.observable({ paths })
|
|
42
|
+
this.reactivePreFetchObservable.bindProperty(this, "reactivePreFetchedPaths")
|
|
43
|
+
this.reactivePreFetchObservable.bindErrorProperty(this, "reactivePreFetchError")
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
},
|
|
47
|
+
beforeUnmount() {
|
|
48
|
+
if(typeof window == 'undefined') return; // NO REACTIVE PREFETCH ON SERVER
|
|
49
|
+
if(!this.$options.reactivePreFetch) return; // Avoid distributed fat
|
|
50
|
+
if(this.reactivePreFetchObservable) {
|
|
51
|
+
this.reactivePreFetchObservable.unbindProperty(this, "reactivePreFetchedPaths")
|
|
52
|
+
this.reactivePreFetchObservable.unbindErrorProperty(this, "reactivePreFetchError")
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export default reactivePrefetchMixin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@live-change/dao-vue3",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
4
4
|
"author": {
|
|
5
5
|
"email": "m8@em8.pl",
|
|
6
6
|
"name": "Michał Łaszczewski",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"url": "https://github.com/live-change/dao-vue3/issues"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@live-change/dao": "^0.
|
|
13
|
+
"@live-change/dao": "^0.3.9"
|
|
14
14
|
},
|
|
15
15
|
"devDependencies": {},
|
|
16
16
|
"description": "Vue.js integration for live-change dao",
|