ar_sync 1.0.1 → 1.0.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.
- checksums.yaml +4 -4
- data/Gemfile.lock +5 -2
- data/README.md +6 -6
- data/ar_sync.gemspec +2 -2
- data/bin/console +1 -2
- data/core/ActioncableAdapter.d.ts +26 -2
- data/core/ActioncableAdapter.js +3 -3
- data/core/ArSyncApi.d.ts +7 -4
- data/core/ArSyncApi.js +9 -4
- data/core/{ArSyncModelBase.d.ts → ArSyncModel.d.ts} +13 -18
- data/core/{ArSyncModelBase.js → ArSyncModel.js} +33 -10
- data/{graph → core}/ArSyncStore.d.ts +3 -3
- data/{graph → core}/ArSyncStore.js +188 -57
- data/core/DataType.d.ts +17 -13
- data/core/hooks.d.ts +28 -0
- data/core/hooks.js +105 -0
- data/index.d.ts +2 -0
- data/index.js +6 -0
- data/lib/ar_sync.rb +1 -18
- data/lib/ar_sync/class_methods.rb +31 -89
- data/lib/ar_sync/collection.rb +4 -29
- data/lib/ar_sync/core.rb +35 -67
- data/lib/ar_sync/instance_methods.rb +40 -86
- data/lib/ar_sync/rails.rb +18 -27
- data/lib/ar_sync/type_script.rb +39 -18
- data/lib/ar_sync/version.rb +1 -1
- data/lib/generators/ar_sync/install/install_generator.rb +33 -32
- data/lib/generators/ar_sync/types/types_generator.rb +6 -3
- data/package-lock.json +21 -10
- data/package.json +1 -1
- data/src/core/ActioncableAdapter.ts +28 -3
- data/src/core/ArSyncApi.ts +8 -4
- data/src/core/{ArSyncModelBase.ts → ArSyncModel.ts} +51 -20
- data/src/{graph → core}/ArSyncStore.ts +199 -84
- data/src/core/DataType.ts +33 -20
- data/src/core/hooks.ts +108 -0
- data/src/index.ts +2 -0
- data/vendor/assets/javascripts/{ar_sync_tree.js.erb → ar_sync.js.erb} +6 -7
- metadata +33 -38
- data/core/hooksBase.d.ts +0 -29
- data/core/hooksBase.js +0 -80
- data/graph/ArSyncModel.d.ts +0 -10
- data/graph/ArSyncModel.js +0 -22
- data/graph/hooks.d.ts +0 -3
- data/graph/hooks.js +0 -10
- data/graph/index.d.ts +0 -2
- data/graph/index.js +0 -4
- data/src/core/hooksBase.ts +0 -86
- data/src/graph/ArSyncModel.ts +0 -21
- data/src/graph/hooks.ts +0 -7
- data/src/graph/index.ts +0 -2
- data/src/tree/ArSyncModel.ts +0 -145
- data/src/tree/ArSyncStore.ts +0 -323
- data/src/tree/hooks.ts +0 -7
- data/src/tree/index.ts +0 -2
- data/tree/ArSyncModel.d.ts +0 -39
- data/tree/ArSyncModel.js +0 -143
- data/tree/ArSyncStore.d.ts +0 -21
- data/tree/ArSyncStore.js +0 -365
- data/tree/hooks.d.ts +0 -3
- data/tree/hooks.js +0 -10
- data/tree/index.d.ts +0 -2
- data/tree/index.js +0 -4
- data/vendor/assets/javascripts/ar_sync_graph.js.erb +0 -17
data/graph/ArSyncModel.d.ts
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
import ArSyncModelBase from '../core/ArSyncModelBase';
|
2
|
-
import ConnectionAdapter from '../core/ConnectionAdapter';
|
3
|
-
export default class ArSyncModel<T> extends ArSyncModelBase<T> {
|
4
|
-
static setConnectionAdapter(adapter: ConnectionAdapter): void;
|
5
|
-
static createRefModel(request: any, option: any): any;
|
6
|
-
refManagerClass(): typeof ArSyncModel;
|
7
|
-
connectionManager(): any;
|
8
|
-
static _cache: {};
|
9
|
-
static cacheTimeout: number;
|
10
|
-
}
|
data/graph/ArSyncModel.js
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
const ArSyncStore_1 = require("./ArSyncStore");
|
4
|
-
const ConnectionManager_1 = require("../core/ConnectionManager");
|
5
|
-
const ArSyncModelBase_1 = require("../core/ArSyncModelBase");
|
6
|
-
class ArSyncModel extends ArSyncModelBase_1.default {
|
7
|
-
static setConnectionAdapter(adapter) {
|
8
|
-
ArSyncStore_1.default.connectionManager = new ConnectionManager_1.default(adapter);
|
9
|
-
}
|
10
|
-
static createRefModel(request, option) {
|
11
|
-
return new ArSyncStore_1.default(request, option);
|
12
|
-
}
|
13
|
-
refManagerClass() {
|
14
|
-
return ArSyncModel;
|
15
|
-
}
|
16
|
-
connectionManager() {
|
17
|
-
return ArSyncStore_1.default.connectionManager;
|
18
|
-
}
|
19
|
-
}
|
20
|
-
ArSyncModel._cache = {};
|
21
|
-
ArSyncModel.cacheTimeout = 10 * 1000;
|
22
|
-
exports.default = ArSyncModel;
|
data/graph/hooks.d.ts
DELETED
data/graph/hooks.js
DELETED
@@ -1,10 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
var hooksBase_1 = require("../core/hooksBase");
|
4
|
-
exports.useArSyncFetch = hooksBase_1.useArSyncFetch;
|
5
|
-
const hooksBase_2 = require("../core/hooksBase");
|
6
|
-
const ArSyncModel_1 = require("./ArSyncModel");
|
7
|
-
function useArSyncModel(request) {
|
8
|
-
return hooksBase_2.useArSyncModelWithClass(ArSyncModel_1.default, request);
|
9
|
-
}
|
10
|
-
exports.useArSyncModel = useArSyncModel;
|
data/graph/index.d.ts
DELETED
data/graph/index.js
DELETED
data/src/core/hooksBase.ts
DELETED
@@ -1,86 +0,0 @@
|
|
1
|
-
import { useState, useEffect, useCallback } from 'react'
|
2
|
-
import ArSyncAPI from './ArSyncApi'
|
3
|
-
|
4
|
-
interface ModelStatus { complete: boolean; notfound?: boolean; connected: boolean }
|
5
|
-
export type DataAndStatus<T> = [T | null, ModelStatus]
|
6
|
-
export interface Request { api: string; params?: any; query: any }
|
7
|
-
|
8
|
-
interface ArSyncModel<T> {
|
9
|
-
data: T | null
|
10
|
-
complete: boolean
|
11
|
-
connected: boolean
|
12
|
-
notfound?: boolean
|
13
|
-
release(): void
|
14
|
-
subscribe(type: any, callback: any): any
|
15
|
-
}
|
16
|
-
export function useArSyncModelWithClass<T>(modelClass: { new<T>(req: Request, option?: any): ArSyncModel<T> }, request: Request | null): DataAndStatus<T> {
|
17
|
-
const [data, setData] = useState<T | null>(null)
|
18
|
-
const [status, setStatus] = useState<ModelStatus>({ complete: false, connected: true })
|
19
|
-
const updateStatus = (complete: boolean, notfound: boolean | undefined, connected: boolean) => {
|
20
|
-
if (complete === status.complete || notfound === status.notfound || connected === status.notfound) return
|
21
|
-
setStatus({ complete, notfound, connected })
|
22
|
-
}
|
23
|
-
useEffect(() => {
|
24
|
-
if (!request) return () => {}
|
25
|
-
const model = new modelClass<T>(request, { immutable: true })
|
26
|
-
if (model.complete) setData(model.data)
|
27
|
-
updateStatus(model.complete, model.notfound, model.connected)
|
28
|
-
model.subscribe('change', () => {
|
29
|
-
updateStatus(model.complete, model.notfound, model.connected)
|
30
|
-
setData(model.data)
|
31
|
-
})
|
32
|
-
model.subscribe('connection', () => {
|
33
|
-
updateStatus(model.complete, model.notfound, model.connected)
|
34
|
-
})
|
35
|
-
return () => model.release()
|
36
|
-
}, [JSON.stringify(request && request.params)])
|
37
|
-
return [data, status]
|
38
|
-
}
|
39
|
-
|
40
|
-
|
41
|
-
interface FetchStatus { complete: boolean; notfound?: boolean }
|
42
|
-
type DataAndStatusAndUpdater<T> = [T | null, FetchStatus, () => void]
|
43
|
-
export function useArSyncFetch<T>(request: Request | null): DataAndStatusAndUpdater<T> {
|
44
|
-
const [response, setResponse] = useState<T | null>(null)
|
45
|
-
const [status, setStatus] = useState<FetchStatus>({ complete: false })
|
46
|
-
const requestString = JSON.stringify(request && request.params)
|
47
|
-
let canceled = false
|
48
|
-
let timer: number | null = null
|
49
|
-
const update = useCallback(() => {
|
50
|
-
if (!request) {
|
51
|
-
setStatus({ complete: false, notfound: undefined })
|
52
|
-
return () => {}
|
53
|
-
}
|
54
|
-
canceled = false
|
55
|
-
timer = null
|
56
|
-
const fetch = (count: number) => {
|
57
|
-
if (timer) clearTimeout(timer)
|
58
|
-
timer = null
|
59
|
-
ArSyncAPI.fetch(request)
|
60
|
-
.then((response) => {
|
61
|
-
if (canceled) return
|
62
|
-
setResponse(response as T)
|
63
|
-
setStatus({ complete: true, notfound: false })
|
64
|
-
})
|
65
|
-
.catch(e => {
|
66
|
-
if (canceled) return
|
67
|
-
if (!e.retry) {
|
68
|
-
setResponse(null)
|
69
|
-
setStatus({ complete: true, notfound: true })
|
70
|
-
return
|
71
|
-
}
|
72
|
-
timer = setTimeout(() => fetch(count + 1), 1000 * Math.min(4 ** count, 30))
|
73
|
-
})
|
74
|
-
}
|
75
|
-
fetch(0)
|
76
|
-
}, [requestString])
|
77
|
-
useEffect(() => {
|
78
|
-
update()
|
79
|
-
return () => {
|
80
|
-
canceled = true
|
81
|
-
if (timer) clearTimeout(timer)
|
82
|
-
timer = null
|
83
|
-
}
|
84
|
-
}, [requestString])
|
85
|
-
return [response, status, update]
|
86
|
-
}
|
data/src/graph/ArSyncModel.ts
DELETED
@@ -1,21 +0,0 @@
|
|
1
|
-
import ArSyncStore from './ArSyncStore'
|
2
|
-
import ArSyncConnectionManager from '../core/ConnectionManager'
|
3
|
-
import ArSyncModelBase from '../core/ArSyncModelBase'
|
4
|
-
import ConnectionAdapter from '../core/ConnectionAdapter'
|
5
|
-
|
6
|
-
export default class ArSyncModel<T> extends ArSyncModelBase<T> {
|
7
|
-
static setConnectionAdapter(adapter: ConnectionAdapter) {
|
8
|
-
ArSyncStore.connectionManager = new ArSyncConnectionManager(adapter)
|
9
|
-
}
|
10
|
-
static createRefModel(request, option): any {
|
11
|
-
return new ArSyncStore(request, option)
|
12
|
-
}
|
13
|
-
refManagerClass() {
|
14
|
-
return ArSyncModel
|
15
|
-
}
|
16
|
-
connectionManager() {
|
17
|
-
return ArSyncStore.connectionManager
|
18
|
-
}
|
19
|
-
static _cache = {}
|
20
|
-
static cacheTimeout = 10 * 1000
|
21
|
-
}
|
data/src/graph/hooks.ts
DELETED
@@ -1,7 +0,0 @@
|
|
1
|
-
export { useArSyncFetch } from '../core/hooksBase'
|
2
|
-
import { useArSyncModelWithClass, Request, DataAndStatus } from '../core/hooksBase'
|
3
|
-
import ArSyncModel from './ArSyncModel'
|
4
|
-
|
5
|
-
export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
6
|
-
return useArSyncModelWithClass<T>(ArSyncModel, request)
|
7
|
-
}
|
data/src/graph/index.ts
DELETED
data/src/tree/ArSyncModel.ts
DELETED
@@ -1,145 +0,0 @@
|
|
1
|
-
import ArSyncStore from './ArSyncStore'
|
2
|
-
import ArSyncAPI from '../core/ArSyncApi'
|
3
|
-
import ArSyncConnectionManager from '../core/ConnectionManager'
|
4
|
-
import ArSyncModelBase from '../core/ArSyncModelBase'
|
5
|
-
import ConnectionAdapter from '../core/ConnectionAdapter'
|
6
|
-
|
7
|
-
class ArSyncRecord {
|
8
|
-
immutable
|
9
|
-
request
|
10
|
-
subscriptions
|
11
|
-
store
|
12
|
-
retryLoadTimer
|
13
|
-
data
|
14
|
-
bufferTimer
|
15
|
-
bufferedPatches
|
16
|
-
eventListeners
|
17
|
-
networkSubscription
|
18
|
-
complete: boolean
|
19
|
-
notfound?: boolean
|
20
|
-
static connectionManager
|
21
|
-
constructor(request, option = {} as { immutable?: boolean }) {
|
22
|
-
this.immutable = option.immutable ? true : false
|
23
|
-
this.request = request
|
24
|
-
this.subscriptions = []
|
25
|
-
this.store = null
|
26
|
-
this.data = null
|
27
|
-
this.complete = false
|
28
|
-
this.bufferedPatches = []
|
29
|
-
this.eventListeners = { events: {}, serial: 0 }
|
30
|
-
this.networkSubscription = ArSyncRecord.connectionManager.subscribeNetwork((status) => {
|
31
|
-
if (this.notfound) {
|
32
|
-
this.trigger('connection', status)
|
33
|
-
return
|
34
|
-
}
|
35
|
-
if (status) {
|
36
|
-
this.load(() => {
|
37
|
-
this.trigger('connection', status)
|
38
|
-
this.trigger('change', { path: [], value: this.data })
|
39
|
-
})
|
40
|
-
} else {
|
41
|
-
this.unsubscribeAll()
|
42
|
-
this.trigger('connection', false)
|
43
|
-
}
|
44
|
-
})
|
45
|
-
this.load(() => {
|
46
|
-
this.trigger('load')
|
47
|
-
this.trigger('change', { path: [], value: this.data })
|
48
|
-
})
|
49
|
-
}
|
50
|
-
release() {
|
51
|
-
this.unsubscribeAll()
|
52
|
-
this.networkSubscription.unsubscribe()
|
53
|
-
}
|
54
|
-
unsubscribeAll() {
|
55
|
-
if (this.retryLoadTimer) clearTimeout(this.retryLoadTimer)
|
56
|
-
for (const s of this.subscriptions) s.unsubscribe()
|
57
|
-
this.subscriptions = []
|
58
|
-
}
|
59
|
-
load(callback, retryCount = 0) {
|
60
|
-
ArSyncAPI.syncFetch(this.request).then(syncData => {
|
61
|
-
const { keys, data, limit, order } = syncData as any
|
62
|
-
this.initializeStore(keys, data, { limit, order, immutable: this.immutable })
|
63
|
-
if (callback) callback(true, this.data)
|
64
|
-
}).catch(e => {
|
65
|
-
console.error(e)
|
66
|
-
if (e.retry) {
|
67
|
-
this.retryLoad(callback, retryCount + 1)
|
68
|
-
} else {
|
69
|
-
this.initializeStore(null, null, null)
|
70
|
-
if (callback) callback(false, this.data)
|
71
|
-
}
|
72
|
-
})
|
73
|
-
}
|
74
|
-
retryLoad(callback, retryCount) {
|
75
|
-
const sleepSeconds = Math.min(Math.pow(2, retryCount), 30)
|
76
|
-
this.retryLoadTimer = setTimeout(() => {
|
77
|
-
this.retryLoadTimer = null
|
78
|
-
this.load(callback, retryCount)
|
79
|
-
}, sleepSeconds * 1000)
|
80
|
-
}
|
81
|
-
patchReceived(patch) {
|
82
|
-
const buffer = this.bufferedPatches
|
83
|
-
buffer.push(patch)
|
84
|
-
if (this.bufferTimer) return
|
85
|
-
this.bufferTimer = setTimeout(() => {
|
86
|
-
this.bufferTimer = null
|
87
|
-
this.bufferedPatches
|
88
|
-
const buf = this.bufferedPatches
|
89
|
-
this.bufferedPatches = []
|
90
|
-
const { changes, events } = this.store.batchUpdate(buf)
|
91
|
-
this.data = this.store.data
|
92
|
-
changes.forEach(change => this.trigger('change', change))
|
93
|
-
events.forEach(event => {
|
94
|
-
this.trigger(event.type, event.data)
|
95
|
-
})
|
96
|
-
}, 16)
|
97
|
-
}
|
98
|
-
subscribe(event, callback) {
|
99
|
-
let listeners = this.eventListeners.events[event]
|
100
|
-
if (!listeners) this.eventListeners.events[event] = listeners = {}
|
101
|
-
const id = this.eventListeners.serial++
|
102
|
-
listeners[id] = callback
|
103
|
-
return { unsubscribe: () => { delete listeners[id] } }
|
104
|
-
}
|
105
|
-
trigger(event, arg?) {
|
106
|
-
const listeners = this.eventListeners.events[event]
|
107
|
-
if (!listeners) return
|
108
|
-
for (const id in listeners) listeners[id](arg)
|
109
|
-
}
|
110
|
-
initializeStore(keys, data, option) {
|
111
|
-
this.complete = true
|
112
|
-
if (!keys) {
|
113
|
-
this.notfound = true
|
114
|
-
return
|
115
|
-
}
|
116
|
-
this.notfound = false
|
117
|
-
const query = this.request.query
|
118
|
-
if (this.store) {
|
119
|
-
this.store.replaceData(data)
|
120
|
-
} else {
|
121
|
-
this.store = new ArSyncStore(query, data, option)
|
122
|
-
this.data = this.store.data
|
123
|
-
}
|
124
|
-
this.subscriptions = keys.map(key => {
|
125
|
-
return ArSyncRecord.connectionManager.subscribe(key, patch => this.patchReceived(patch))
|
126
|
-
})
|
127
|
-
}
|
128
|
-
}
|
129
|
-
|
130
|
-
export default class ArSyncModel<T> extends ArSyncModelBase<T> {
|
131
|
-
static setConnectionAdapter(adapter: ConnectionAdapter) {
|
132
|
-
ArSyncRecord.connectionManager = new ArSyncConnectionManager(adapter)
|
133
|
-
}
|
134
|
-
static createRefModel(request, option) {
|
135
|
-
return new ArSyncRecord(request, option)
|
136
|
-
}
|
137
|
-
refManagerClass() {
|
138
|
-
return ArSyncModel
|
139
|
-
}
|
140
|
-
connectionManager() {
|
141
|
-
return ArSyncRecord.connectionManager
|
142
|
-
}
|
143
|
-
static _cache = {}
|
144
|
-
static cacheTimeout = 10 * 1000
|
145
|
-
}
|
data/src/tree/ArSyncStore.ts
DELETED
@@ -1,323 +0,0 @@
|
|
1
|
-
class Updator {
|
2
|
-
changes
|
3
|
-
markedForFreezeObjects
|
4
|
-
immutable
|
5
|
-
data
|
6
|
-
constructor(immutable) {
|
7
|
-
this.changes = []
|
8
|
-
this.markedForFreezeObjects = []
|
9
|
-
this.immutable = immutable
|
10
|
-
}
|
11
|
-
static createFrozenObject(obj) {
|
12
|
-
if (!obj) return obj
|
13
|
-
if (obj.constructor === Array) {
|
14
|
-
obj = obj.map(el => Updator.createFrozenObject(el))
|
15
|
-
} else if (typeof obj === 'object') {
|
16
|
-
obj = Object.assign({}, obj)
|
17
|
-
for (const key in obj) {
|
18
|
-
obj[key] = Updator.createFrozenObject(obj[key])
|
19
|
-
}
|
20
|
-
}
|
21
|
-
Object.freeze(obj)
|
22
|
-
return obj
|
23
|
-
}
|
24
|
-
replaceData(data, newData) {
|
25
|
-
if (this.immutable) return Updator.createFrozenObject(newData)
|
26
|
-
return this.recursivelyReplaceData(data, newData)
|
27
|
-
}
|
28
|
-
recursivelyReplaceData(data, newData) {
|
29
|
-
const replaceArray = (as, bs) => {
|
30
|
-
const aids = {}
|
31
|
-
for (const a of as) {
|
32
|
-
if (!a.id) return false
|
33
|
-
aids[a.id] = a
|
34
|
-
}
|
35
|
-
const order = {}
|
36
|
-
bs.forEach((b, i) => {
|
37
|
-
if (!b.id) return false
|
38
|
-
if (aids[b.id]) {
|
39
|
-
replaceObject(aids[b.id], b)
|
40
|
-
} else {
|
41
|
-
as.push(b)
|
42
|
-
}
|
43
|
-
order[b.id] = i + 1
|
44
|
-
})
|
45
|
-
as.sort((a, b) => {
|
46
|
-
const oa = order[a.id] || Infinity
|
47
|
-
const ob = order[b.id] || Infinity
|
48
|
-
return oa > ob ? +1 : oa < ob ? -1 : 0
|
49
|
-
})
|
50
|
-
while (as.length && !order[as[as.length - 1].id]) as.pop()
|
51
|
-
return true
|
52
|
-
}
|
53
|
-
const replaceObject = (aobj, bobj) => {
|
54
|
-
const keys = {}
|
55
|
-
for (const key in aobj) keys[key] = true
|
56
|
-
for (const key in bobj) keys[key] = true
|
57
|
-
for (const key in keys) {
|
58
|
-
const a = aobj[key]
|
59
|
-
const b = bobj[key]
|
60
|
-
if ((a instanceof Array) && (b instanceof Array)) {
|
61
|
-
if (!replaceArray(a, b)) aobj[key] = b
|
62
|
-
} else if(a && b && (typeof a === 'object') && (typeof b === 'object') && !(a instanceof Array) && !(b instanceof Array)) {
|
63
|
-
replaceObject(a, b)
|
64
|
-
} else if (a !== b) {
|
65
|
-
aobj[key] = b
|
66
|
-
}
|
67
|
-
}
|
68
|
-
}
|
69
|
-
replaceObject(data, newData)
|
70
|
-
return data
|
71
|
-
}
|
72
|
-
mark(obj) {
|
73
|
-
if (!this.immutable) return obj
|
74
|
-
if (!Object.isFrozen(this.data)) return obj
|
75
|
-
const mobj = (obj.constructor === Array) ? [...obj] : { ...obj }
|
76
|
-
this.markedForFreezeObjects.push(mobj)
|
77
|
-
return mobj
|
78
|
-
}
|
79
|
-
trace(data, path) {
|
80
|
-
path.forEach(key => {
|
81
|
-
if (this.immutable) data[key] = this.mark(data[key])
|
82
|
-
data = data[key]
|
83
|
-
})
|
84
|
-
return data
|
85
|
-
}
|
86
|
-
assign(el, path, column, value, orderParam) {
|
87
|
-
if (this.immutable) value = Updator.createFrozenObject(value)
|
88
|
-
if (el.constructor === Array && !el[column]) {
|
89
|
-
this.changes.push({
|
90
|
-
path: path.concat([value.id]),
|
91
|
-
target: el,
|
92
|
-
id: value.id,
|
93
|
-
valueWas: null,
|
94
|
-
value
|
95
|
-
})
|
96
|
-
const limitReached = orderParam && orderParam.limit != null && el.length === orderParam.limit
|
97
|
-
let removed
|
98
|
-
if (orderParam && orderParam.order == 'desc') {
|
99
|
-
el.unshift(value)
|
100
|
-
if (limitReached) removed = el.pop()
|
101
|
-
} else {
|
102
|
-
el.push(value)
|
103
|
-
if (limitReached) removed = el.pop()
|
104
|
-
}
|
105
|
-
if (removed) this.changes.push({
|
106
|
-
path: path.concat([removed.id]),
|
107
|
-
target: el,
|
108
|
-
id: removed.id,
|
109
|
-
valueWas: removed,
|
110
|
-
value: null
|
111
|
-
})
|
112
|
-
} else if (!this.valueEquals(el[column], value)) {
|
113
|
-
this.changes.push({
|
114
|
-
path: path.concat([column]),
|
115
|
-
target: el,
|
116
|
-
column: column,
|
117
|
-
valueWas: el[column],
|
118
|
-
value
|
119
|
-
})
|
120
|
-
el[column] = value
|
121
|
-
}
|
122
|
-
}
|
123
|
-
valueEquals(a, b) {
|
124
|
-
if (a === b) return true
|
125
|
-
if (!a || !b) return a == b
|
126
|
-
if (typeof a !== 'object') return false
|
127
|
-
if (typeof b !== 'object') return false
|
128
|
-
const ja = JSON.stringify(a)
|
129
|
-
const jb = JSON.stringify(b)
|
130
|
-
return ja === jb
|
131
|
-
}
|
132
|
-
add(tree, accessKeys, path, column, value, orderParam) {
|
133
|
-
const root = this.mark(tree)
|
134
|
-
const data = this.trace(root, accessKeys)
|
135
|
-
if (data) this.assign(data, path, column, value, orderParam)
|
136
|
-
return root
|
137
|
-
}
|
138
|
-
remove(tree, accessKeys, path, column) {
|
139
|
-
const root = this.mark(tree)
|
140
|
-
let data = this.trace(root, accessKeys)
|
141
|
-
if (!data) return root
|
142
|
-
if (data.constructor === Array) {
|
143
|
-
this.changes.push({
|
144
|
-
path: path.concat([data[column].id]),
|
145
|
-
target: data,
|
146
|
-
id: data[column].id,
|
147
|
-
valueWas: data[column],
|
148
|
-
value: null
|
149
|
-
})
|
150
|
-
data.splice(column, 1)
|
151
|
-
} else if (data[column] !== null) {
|
152
|
-
this.changes.push({
|
153
|
-
path: path.concat([column]),
|
154
|
-
target: data,
|
155
|
-
column: column,
|
156
|
-
valueWas: data[column],
|
157
|
-
value: null
|
158
|
-
})
|
159
|
-
data[column] = null
|
160
|
-
}
|
161
|
-
return root
|
162
|
-
}
|
163
|
-
cleanup() {
|
164
|
-
this.markedForFreezeObjects.forEach(mobj => Object.freeze(mobj))
|
165
|
-
}
|
166
|
-
}
|
167
|
-
|
168
|
-
export default class ArSyncStore {
|
169
|
-
data
|
170
|
-
query
|
171
|
-
immutable
|
172
|
-
constructor(query, data, option = {} as { immutable?: boolean }) {
|
173
|
-
this.data = option.immutable ? Updator.createFrozenObject(data) : data
|
174
|
-
this.query = ArSyncStore.parseQuery(query)
|
175
|
-
this.immutable = option.immutable
|
176
|
-
}
|
177
|
-
replaceData(data) {
|
178
|
-
this.data = new Updator(this.immutable).replaceData(this.data, data)
|
179
|
-
}
|
180
|
-
batchUpdate(patches) {
|
181
|
-
const events = []
|
182
|
-
const updator = new Updator(this.immutable)
|
183
|
-
patches.forEach(patch => this._update(patch, updator, events))
|
184
|
-
updator.cleanup()
|
185
|
-
return { changes: updator.changes, events }
|
186
|
-
}
|
187
|
-
update(patch) {
|
188
|
-
return this.batchUpdate([patch])
|
189
|
-
}
|
190
|
-
_slicePatch(patchData, query) {
|
191
|
-
const obj = {}
|
192
|
-
for (const key in patchData) {
|
193
|
-
if (key === 'id' || query.attributes['*']) {
|
194
|
-
obj[key] = patchData[key]
|
195
|
-
} else {
|
196
|
-
const subq = query.attributes[key]
|
197
|
-
if (subq) {
|
198
|
-
obj[subq.column || key] = patchData[key]
|
199
|
-
}
|
200
|
-
}
|
201
|
-
}
|
202
|
-
return obj
|
203
|
-
}
|
204
|
-
_applyPatch(data, accessKeys, actualPath, updator, query, patchData) {
|
205
|
-
for (const key in patchData) {
|
206
|
-
const subq = query.attributes[key]
|
207
|
-
const value = patchData[key]
|
208
|
-
if (subq || query.attributes['*']) {
|
209
|
-
const subcol = (subq && subq.column) || key
|
210
|
-
if (data[subcol] !== value) {
|
211
|
-
this.data = updator.add(this.data, accessKeys, actualPath, subcol, value)
|
212
|
-
}
|
213
|
-
}
|
214
|
-
}
|
215
|
-
}
|
216
|
-
_update(patch, updator, events) {
|
217
|
-
const { action, path } = patch
|
218
|
-
const patchData = patch.data
|
219
|
-
let query = this.query
|
220
|
-
let data = this.data
|
221
|
-
const actualPath: (string | number)[] = []
|
222
|
-
const accessKeys: (string | number)[] = []
|
223
|
-
for (let i = 0; i < path.length - 1; i++) {
|
224
|
-
const nameOrId = path[i]
|
225
|
-
if (typeof(nameOrId) === 'number') {
|
226
|
-
const idx = data.findIndex(o => o.id === nameOrId)
|
227
|
-
if (idx < 0) return
|
228
|
-
actualPath.push(nameOrId)
|
229
|
-
accessKeys.push(idx)
|
230
|
-
data = data[idx]
|
231
|
-
} else {
|
232
|
-
const { attributes } = query
|
233
|
-
if (!attributes[nameOrId]) return
|
234
|
-
const column = attributes[nameOrId].column || nameOrId
|
235
|
-
query = attributes[nameOrId]
|
236
|
-
actualPath.push(column)
|
237
|
-
accessKeys.push(column)
|
238
|
-
data = data[column]
|
239
|
-
}
|
240
|
-
if (!data) return
|
241
|
-
}
|
242
|
-
const nameOrId = path[path.length - 1]
|
243
|
-
let id, idx, column, target = data
|
244
|
-
if (typeof(nameOrId) === 'number') {
|
245
|
-
id = nameOrId
|
246
|
-
idx = data.findIndex(o => o.id === id)
|
247
|
-
target = data[idx]
|
248
|
-
} else if (nameOrId) {
|
249
|
-
const { attributes } = query
|
250
|
-
if (!attributes[nameOrId]) return
|
251
|
-
column = attributes[nameOrId].column || nameOrId
|
252
|
-
query = attributes[nameOrId]
|
253
|
-
target = data[column]
|
254
|
-
}
|
255
|
-
if (action === 'create') {
|
256
|
-
const obj = this._slicePatch(patchData, query)
|
257
|
-
if (column) {
|
258
|
-
this.data = updator.add(this.data, accessKeys, actualPath, column, obj)
|
259
|
-
} else if (!target) {
|
260
|
-
const ordering = Object.assign({}, patch.ordering)
|
261
|
-
const limitOverride = query.params && query.params.limit
|
262
|
-
ordering.order = query.params && query.params.order || ordering.order
|
263
|
-
if (ordering.limit == null || limitOverride != null && limitOverride < ordering.limit) ordering.limit = limitOverride
|
264
|
-
this.data = updator.add(this.data, accessKeys, actualPath, data.length, obj, ordering)
|
265
|
-
}
|
266
|
-
return
|
267
|
-
}
|
268
|
-
if (action === 'destroy') {
|
269
|
-
if (column) {
|
270
|
-
this.data = updator.remove(this.data, accessKeys, actualPath, column)
|
271
|
-
} else if (idx >= 0) {
|
272
|
-
this.data = updator.remove(this.data, accessKeys, actualPath, idx)
|
273
|
-
}
|
274
|
-
return
|
275
|
-
}
|
276
|
-
if (!target) return
|
277
|
-
if (column) {
|
278
|
-
actualPath.push(column)
|
279
|
-
accessKeys.push(column)
|
280
|
-
} else if (id) {
|
281
|
-
actualPath.push(id)
|
282
|
-
accessKeys.push(idx)
|
283
|
-
}
|
284
|
-
if (action === 'update') {
|
285
|
-
this._applyPatch(target, accessKeys, actualPath, updator, query, patchData)
|
286
|
-
} else {
|
287
|
-
const eventData = { target, path: actualPath, data: patchData.data }
|
288
|
-
events.push({ type: patchData.type, data: eventData })
|
289
|
-
}
|
290
|
-
}
|
291
|
-
|
292
|
-
static parseQuery(query, attrsonly?){
|
293
|
-
const attributes = {}
|
294
|
-
let column = null
|
295
|
-
let params = null
|
296
|
-
if (query.constructor !== Array) query = [query]
|
297
|
-
for (const arg of query) {
|
298
|
-
if (typeof(arg) === 'string') {
|
299
|
-
attributes[arg] = {}
|
300
|
-
} else if (typeof(arg) === 'object') {
|
301
|
-
for (const key in arg){
|
302
|
-
const value = arg[key]
|
303
|
-
if (attrsonly) {
|
304
|
-
attributes[key] = this.parseQuery(value)
|
305
|
-
continue
|
306
|
-
}
|
307
|
-
if (key === 'attributes') {
|
308
|
-
const child = this.parseQuery(value, true)
|
309
|
-
for (const k in child) attributes[k] = child[k]
|
310
|
-
} else if (key === 'as') {
|
311
|
-
column = value
|
312
|
-
} else if (key === 'params') {
|
313
|
-
params = value
|
314
|
-
} else {
|
315
|
-
attributes[key] = this.parseQuery(value)
|
316
|
-
}
|
317
|
-
}
|
318
|
-
}
|
319
|
-
}
|
320
|
-
if (attrsonly) return attributes
|
321
|
-
return { attributes, column, params }
|
322
|
-
}
|
323
|
-
}
|