ar_sync 1.0.0
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 +7 -0
- data/.gitignore +13 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +128 -0
- data/Rakefile +10 -0
- data/ar_sync.gemspec +28 -0
- data/bin/console +12 -0
- data/bin/setup +8 -0
- data/core/ActioncableAdapter.d.ts +10 -0
- data/core/ActioncableAdapter.js +29 -0
- data/core/ArSyncApi.d.ts +5 -0
- data/core/ArSyncApi.js +74 -0
- data/core/ArSyncModelBase.d.ts +71 -0
- data/core/ArSyncModelBase.js +110 -0
- data/core/ConnectionAdapter.d.ts +7 -0
- data/core/ConnectionAdapter.js +2 -0
- data/core/ConnectionManager.d.ts +19 -0
- data/core/ConnectionManager.js +75 -0
- data/core/DataType.d.ts +60 -0
- data/core/DataType.js +2 -0
- data/core/hooksBase.d.ts +29 -0
- data/core/hooksBase.js +80 -0
- data/graph/ArSyncModel.d.ts +10 -0
- data/graph/ArSyncModel.js +22 -0
- data/graph/ArSyncStore.d.ts +28 -0
- data/graph/ArSyncStore.js +593 -0
- data/graph/hooks.d.ts +3 -0
- data/graph/hooks.js +10 -0
- data/graph/index.d.ts +2 -0
- data/graph/index.js +4 -0
- data/lib/ar_sync.rb +25 -0
- data/lib/ar_sync/class_methods.rb +215 -0
- data/lib/ar_sync/collection.rb +83 -0
- data/lib/ar_sync/config.rb +18 -0
- data/lib/ar_sync/core.rb +138 -0
- data/lib/ar_sync/field.rb +96 -0
- data/lib/ar_sync/instance_methods.rb +130 -0
- data/lib/ar_sync/rails.rb +155 -0
- data/lib/ar_sync/type_script.rb +80 -0
- data/lib/ar_sync/version.rb +3 -0
- data/lib/generators/ar_sync/install/install_generator.rb +87 -0
- data/lib/generators/ar_sync/types/types_generator.rb +11 -0
- data/package-lock.json +1115 -0
- data/package.json +19 -0
- data/src/core/ActioncableAdapter.ts +30 -0
- data/src/core/ArSyncApi.ts +75 -0
- data/src/core/ArSyncModelBase.ts +126 -0
- data/src/core/ConnectionAdapter.ts +5 -0
- data/src/core/ConnectionManager.ts +69 -0
- data/src/core/DataType.ts +73 -0
- data/src/core/hooksBase.ts +86 -0
- data/src/graph/ArSyncModel.ts +21 -0
- data/src/graph/ArSyncStore.ts +567 -0
- data/src/graph/hooks.ts +7 -0
- data/src/graph/index.ts +2 -0
- data/src/tree/ArSyncModel.ts +145 -0
- data/src/tree/ArSyncStore.ts +323 -0
- data/src/tree/hooks.ts +7 -0
- data/src/tree/index.ts +2 -0
- data/tree/ArSyncModel.d.ts +39 -0
- data/tree/ArSyncModel.js +143 -0
- data/tree/ArSyncStore.d.ts +21 -0
- data/tree/ArSyncStore.js +365 -0
- data/tree/hooks.d.ts +3 -0
- data/tree/hooks.js +10 -0
- data/tree/index.d.ts +2 -0
- data/tree/index.js +4 -0
- data/tsconfig.json +15 -0
- data/vendor/assets/javascripts/ar_sync_actioncable_adapter.js.erb +7 -0
- data/vendor/assets/javascripts/ar_sync_graph.js.erb +17 -0
- data/vendor/assets/javascripts/ar_sync_tree.js.erb +17 -0
- metadata +187 -0
data/src/graph/hooks.ts
ADDED
@@ -0,0 +1,7 @@
|
|
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
ADDED
@@ -0,0 +1,145 @@
|
|
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
|
+
}
|
@@ -0,0 +1,323 @@
|
|
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
|
+
}
|