import ArSyncStore from './ArSyncStore' import ArSyncConnectionManager from './ConnectionManager' import ConnectionAdapter from './ConnectionAdapter' interface Request { api: string; query: any; params?: any } type Path = Readonly<(string | number)[]> interface Change { path: Path; value: any } type ChangeCallback = (change: Change) => void type LoadCallback = () => void type ConnectionCallback = (status: boolean) => void type SubscriptionType = 'load' | 'change' | 'connection' type SubscriptionCallback = ChangeCallback | LoadCallback | ConnectionCallback type PathFirst
> = ((...args: P) => void) extends (first: infer First, ...other: any) => void ? First : never
type PathRest = U extends Readonly extends never ? Data :
PathFirst extends keyof Data ?
(Data extends Readonly ];
1: DigResult], PathRest >
}[PathRest extends never ? 0 : 1]
: undefined
export default class ArSyncModel (path: P) {
return ArSyncModel.digData(this.data, path)
}
static digData(data: Data, path: P): DigResult {
function dig(data: Data, path: Path) {
if (path.length === 0) return data as any
if (data == null) return data
const key = path[0]
const other = path.slice(1)
if (Array.isArray(data)) {
return this.digData(data.find(el => el.id === key), other)
} else {
return this.digData(data[key], other)
}
}
return dig(data, path)
}
subscribe(event: SubscriptionType, callback: SubscriptionCallback): { unsubscribe: () => void } {
const id = this._listenerSerial++
const subscription = this._ref.model.subscribe(event, callback)
let unsubscribed = false
const unsubscribe = () => {
unsubscribed = true
subscription.unsubscribe()
delete this._listeners[id]
}
if (this.complete) {
if (event === 'load') setTimeout(() => {
if (!unsubscribed) (callback as LoadCallback)()
}, 0)
if (event === 'change') setTimeout(() => {
if (!unsubscribed) (callback as ChangeCallback)({ path: [], value: this.data })
}, 0)
}
return this._listeners[id] = { unsubscribe }
}
release() {
for (const id in this._listeners) this._listeners[id].unsubscribe()
this._listeners = {}
ArSyncModel._detach(this._ref)
this._ref = null
}
static retrieveRef(
request: Request,
option?: { immutable: boolean }
): { key: string; count: number; timer: number | null; model } {
const key = JSON.stringify([request, option])
let ref = this._cache[key]
if (!ref) {
const model = new ArSyncStore(request, option)
ref = this._cache[key] = { key, count: 0, timer: null, model }
}
this._attach(ref)
return ref
}
static _detach(ref) {
ref.count--
const timeout = this.cacheTimeout
if (ref.count !== 0) return
const timedout = () => {
ref.model.release()
delete this._cache[ref.key]
}
if (timeout) {
ref.timer = setTimeout(timedout, timeout)
} else {
timedout()
}
}
private static _attach(ref) {
ref.count++
if (ref.timer) clearTimeout(ref.timer)
}
static setConnectionAdapter(adapter: ConnectionAdapter) {
ArSyncStore.connectionManager = new ArSyncConnectionManager(adapter)
}
static waitForLoad(...models: ArSyncModel<{}>[]) {
return new Promise((resolve) => {
let count = 0
for (const model of models) {
model.onload(() => {
count++
if (models.length == count) resolve(models)
})
}
})
}
}