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.
Files changed (75) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +13 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +53 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +128 -0
  8. data/Rakefile +10 -0
  9. data/ar_sync.gemspec +28 -0
  10. data/bin/console +12 -0
  11. data/bin/setup +8 -0
  12. data/core/ActioncableAdapter.d.ts +10 -0
  13. data/core/ActioncableAdapter.js +29 -0
  14. data/core/ArSyncApi.d.ts +5 -0
  15. data/core/ArSyncApi.js +74 -0
  16. data/core/ArSyncModelBase.d.ts +71 -0
  17. data/core/ArSyncModelBase.js +110 -0
  18. data/core/ConnectionAdapter.d.ts +7 -0
  19. data/core/ConnectionAdapter.js +2 -0
  20. data/core/ConnectionManager.d.ts +19 -0
  21. data/core/ConnectionManager.js +75 -0
  22. data/core/DataType.d.ts +60 -0
  23. data/core/DataType.js +2 -0
  24. data/core/hooksBase.d.ts +29 -0
  25. data/core/hooksBase.js +80 -0
  26. data/graph/ArSyncModel.d.ts +10 -0
  27. data/graph/ArSyncModel.js +22 -0
  28. data/graph/ArSyncStore.d.ts +28 -0
  29. data/graph/ArSyncStore.js +593 -0
  30. data/graph/hooks.d.ts +3 -0
  31. data/graph/hooks.js +10 -0
  32. data/graph/index.d.ts +2 -0
  33. data/graph/index.js +4 -0
  34. data/lib/ar_sync.rb +25 -0
  35. data/lib/ar_sync/class_methods.rb +215 -0
  36. data/lib/ar_sync/collection.rb +83 -0
  37. data/lib/ar_sync/config.rb +18 -0
  38. data/lib/ar_sync/core.rb +138 -0
  39. data/lib/ar_sync/field.rb +96 -0
  40. data/lib/ar_sync/instance_methods.rb +130 -0
  41. data/lib/ar_sync/rails.rb +155 -0
  42. data/lib/ar_sync/type_script.rb +80 -0
  43. data/lib/ar_sync/version.rb +3 -0
  44. data/lib/generators/ar_sync/install/install_generator.rb +87 -0
  45. data/lib/generators/ar_sync/types/types_generator.rb +11 -0
  46. data/package-lock.json +1115 -0
  47. data/package.json +19 -0
  48. data/src/core/ActioncableAdapter.ts +30 -0
  49. data/src/core/ArSyncApi.ts +75 -0
  50. data/src/core/ArSyncModelBase.ts +126 -0
  51. data/src/core/ConnectionAdapter.ts +5 -0
  52. data/src/core/ConnectionManager.ts +69 -0
  53. data/src/core/DataType.ts +73 -0
  54. data/src/core/hooksBase.ts +86 -0
  55. data/src/graph/ArSyncModel.ts +21 -0
  56. data/src/graph/ArSyncStore.ts +567 -0
  57. data/src/graph/hooks.ts +7 -0
  58. data/src/graph/index.ts +2 -0
  59. data/src/tree/ArSyncModel.ts +145 -0
  60. data/src/tree/ArSyncStore.ts +323 -0
  61. data/src/tree/hooks.ts +7 -0
  62. data/src/tree/index.ts +2 -0
  63. data/tree/ArSyncModel.d.ts +39 -0
  64. data/tree/ArSyncModel.js +143 -0
  65. data/tree/ArSyncStore.d.ts +21 -0
  66. data/tree/ArSyncStore.js +365 -0
  67. data/tree/hooks.d.ts +3 -0
  68. data/tree/hooks.js +10 -0
  69. data/tree/index.d.ts +2 -0
  70. data/tree/index.js +4 -0
  71. data/tsconfig.json +15 -0
  72. data/vendor/assets/javascripts/ar_sync_actioncable_adapter.js.erb +7 -0
  73. data/vendor/assets/javascripts/ar_sync_graph.js.erb +17 -0
  74. data/vendor/assets/javascripts/ar_sync_tree.js.erb +17 -0
  75. metadata +187 -0
data/package.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "ar_sync",
3
+ "version": "0.0.1",
4
+ "scripts": {
5
+ "prepare": "tsc",
6
+ "build": "tsc"
7
+ },
8
+ "dependencies": {},
9
+ "devDependencies": {
10
+ "react": "^16.8.6",
11
+ "@types/react": "^16.8.6",
12
+ "actioncable": "^5.2.0",
13
+ "@types/actioncable": "^5.2.0",
14
+ "eslint": "^5.16.0",
15
+ "typescript": "^3.4.5",
16
+ "@typescript-eslint/eslint-plugin": "^1.6.0",
17
+ "@typescript-eslint/parser": "^1.6.0"
18
+ }
19
+ }
@@ -0,0 +1,30 @@
1
+ import * as ActionCable from 'actioncable'
2
+ import ConnectionAdapter from './ConnectionAdapter'
3
+
4
+ export default class ActionCableAdapter implements ConnectionAdapter {
5
+ connected: boolean
6
+ _cable: ActionCable.Cable
7
+ constructor() {
8
+ this.connected = true
9
+ this.subscribe(Math.random().toString(), () => {})
10
+ }
11
+ subscribe(key: string, received: (data: any) => void) {
12
+ const disconnected = () => {
13
+ if (!this.connected) return
14
+ this.connected = false
15
+ this.ondisconnect()
16
+ }
17
+ const connected = () => {
18
+ if (this.connected) return
19
+ this.connected = true
20
+ this.onreconnect()
21
+ }
22
+ if (!this._cable) this._cable = ActionCable.createConsumer()
23
+ return this._cable.subscriptions.create(
24
+ { channel: 'SyncChannel', key },
25
+ { received, disconnected, connected }
26
+ )
27
+ }
28
+ ondisconnect() {}
29
+ onreconnect() {}
30
+ }
@@ -0,0 +1,75 @@
1
+ async function apiBatchFetch(endpoint: string, requests: object[]) {
2
+ const headers = {
3
+ 'Accept': 'application/json',
4
+ 'Content-Type': 'application/json'
5
+ }
6
+ const body = JSON.stringify({ requests })
7
+ const option = { credentials: 'include', method: 'POST', headers, body } as const
8
+ const res = await fetch(endpoint, option)
9
+ if (res.status === 200) return res.json()
10
+ throw new Error(res.statusText)
11
+ }
12
+
13
+ interface PromiseCallback {
14
+ resolve: (data: object) => void
15
+ reject: (error: object) => void
16
+ }
17
+
18
+ class ApiFetcher {
19
+ endpoint: string
20
+ batches: [object, PromiseCallback][] = []
21
+ batchFetchTimer: number | null = null
22
+ constructor(endpoint: string) {
23
+ this.endpoint = endpoint
24
+ }
25
+ fetch(request: object) {
26
+ return new Promise((resolve, reject) => {
27
+ this.batches.push([request, { resolve, reject }])
28
+ if (this.batchFetchTimer) return
29
+ this.batchFetchTimer = setTimeout(() => {
30
+ this.batchFetchTimer = null
31
+ const compacts: { [key: string]: PromiseCallback[] } = {}
32
+ const requests: object[] = []
33
+ const callbacksList: PromiseCallback[][] = []
34
+ for (const batch of this.batches) {
35
+ const request = batch[0]
36
+ const callback = batch[1]
37
+ const key = JSON.stringify(request)
38
+ if (compacts[key]) {
39
+ compacts[key].push(callback)
40
+ } else {
41
+ requests.push(request)
42
+ callbacksList.push(compacts[key] = [callback])
43
+ }
44
+ }
45
+ this.batches = []
46
+ apiBatchFetch(this.endpoint, requests).then((results) => {
47
+ for (const i in callbacksList) {
48
+ const result = results[i]
49
+ const callbacks = callbacksList[i]
50
+ for (const callback of callbacks) {
51
+ if (result.data) {
52
+ callback.resolve(result.data)
53
+ } else {
54
+ const error = result.error || { type: 'Unknown Error' }
55
+ callback.reject(error)
56
+ }
57
+ }
58
+ }
59
+ }).catch((e) => {
60
+ const error = { type: e.name, message: e.message, retry: true }
61
+ for (const callbacks of callbacksList) {
62
+ for (const callback of callbacks) callback.reject(error)
63
+ }
64
+ })
65
+ }, 16)
66
+ })
67
+ }
68
+ }
69
+
70
+ const staticFetcher = new ApiFetcher('/static_api')
71
+ const syncFetcher = new ApiFetcher('/sync_api')
72
+ export default {
73
+ fetch: (request: object) => staticFetcher.fetch(request),
74
+ syncFetch: (request: object) => syncFetcher.fetch(request),
75
+ }
@@ -0,0 +1,126 @@
1
+ interface Request { api: string; query: any; params?: any }
2
+ type Path = (string | number)[]
3
+ interface Change { path: Path; value: any }
4
+ type ChangeCallback = (change: Change) => void
5
+ type LoadCallback = () => void
6
+ type ConnectionCallback = (status: boolean) => void
7
+ type SubscriptionType = 'load' | 'change' | 'connection'
8
+ type SubscriptionCallback = ChangeCallback | LoadCallback | ConnectionCallback
9
+ interface Adapter {
10
+ subscribe: (key: string, received: (data: any) => void) => { unsubscribe: () => void }
11
+ ondisconnect: () => void
12
+ onreconnect: () => void
13
+ }
14
+
15
+ export default abstract class ArSyncModelBase<T> {
16
+ private _ref
17
+ private _listenerSerial: number
18
+ private _listeners
19
+ complete: boolean
20
+ notfound?: boolean
21
+ connected: boolean
22
+ data: T | null
23
+ static _cache: { [key: string]: { key: string; count: number; timer: number | null; model } }
24
+ static cacheTimeout: number
25
+ abstract refManagerClass(): any
26
+ abstract connectionManager(): { networkStatus: boolean }
27
+ constructor(request: Request, option?: { immutable: boolean }) {
28
+ this._ref = this.refManagerClass().retrieveRef(request, option)
29
+ this._listenerSerial = 0
30
+ this._listeners = {}
31
+ this.complete = false
32
+ this.connected = this.connectionManager().networkStatus
33
+ const setData = () => {
34
+ this.data = this._ref.model.data
35
+ this.complete = this._ref.model.complete
36
+ this.notfound = this._ref.model.notfound
37
+ }
38
+ setData()
39
+ this.subscribe('load', setData)
40
+ this.subscribe('change', setData)
41
+ this.subscribe('connection', (status: boolean) => {
42
+ this.connected = status
43
+ })
44
+ }
45
+ onload(callback: LoadCallback) {
46
+ this.subscribeOnce('load', callback)
47
+ }
48
+ subscribeOnce(event: SubscriptionType, callback: SubscriptionCallback) {
49
+ const subscription = this.subscribe(event, (arg) => {
50
+ (callback as (arg: any) => void)(arg)
51
+ subscription.unsubscribe()
52
+ })
53
+ return subscription
54
+ }
55
+ subscribe(event: SubscriptionType, callback: SubscriptionCallback): { unsubscribe: () => void } {
56
+ const id = this._listenerSerial++
57
+ const subscription = this._ref.model.subscribe(event, callback)
58
+ let unsubscribed = false
59
+ const unsubscribe = () => {
60
+ unsubscribed = true
61
+ subscription.unsubscribe()
62
+ delete this._listeners[id]
63
+ }
64
+ if (this.complete) {
65
+ if (event === 'load') setTimeout(() => {
66
+ if (!unsubscribed) (callback as LoadCallback)()
67
+ }, 0)
68
+ if (event === 'change') setTimeout(() => {
69
+ if (!unsubscribed) (callback as ChangeCallback)({ path: [], value: this.data })
70
+ }, 0)
71
+ }
72
+ return this._listeners[id] = { unsubscribe }
73
+ }
74
+ release() {
75
+ for (const id in this._listeners) this._listeners[id].unsubscribe()
76
+ this._listeners = {}
77
+ this.refManagerClass()._detach(this._ref)
78
+ this._ref = null
79
+ }
80
+ static retrieveRef(
81
+ request: Request,
82
+ option?: { immutable: boolean }
83
+ ): { key: string; count: number; timer: number | null; model } {
84
+ const key = JSON.stringify([request, option])
85
+ let ref = this._cache[key]
86
+ if (!ref) {
87
+ const model = this.createRefModel(request, option)
88
+ ref = this._cache[key] = { key, count: 0, timer: null, model }
89
+ }
90
+ this._attach(ref)
91
+ return ref
92
+ }
93
+ static createRefModel(_request: Request, _option?: { immutable: boolean }) {
94
+ throw 'abstract method'
95
+ }
96
+ static _detach(ref) {
97
+ ref.count--
98
+ const timeout = this.cacheTimeout
99
+ if (ref.count !== 0) return
100
+ const timedout = () => {
101
+ ref.model.release()
102
+ delete this._cache[ref.key]
103
+ }
104
+ if (timeout) {
105
+ ref.timer = setTimeout(timedout, timeout)
106
+ } else {
107
+ timedout()
108
+ }
109
+ }
110
+ private static _attach(ref) {
111
+ ref.count++
112
+ if (ref.timer) clearTimeout(ref.timer)
113
+ }
114
+ static setConnectionAdapter(_adapter: Adapter) {}
115
+ static waitForLoad(...models: ArSyncModelBase<{}>[]) {
116
+ return new Promise((resolve) => {
117
+ let count = 0
118
+ for (const model of models) {
119
+ model.onload(() => {
120
+ count++
121
+ if (models.length == count) resolve(models)
122
+ })
123
+ }
124
+ })
125
+ }
126
+ }
@@ -0,0 +1,5 @@
1
+ export default interface ConnectionAdapter {
2
+ ondisconnect: (() => void) | null
3
+ onreconnect: (() => void) | null
4
+ subscribe(key: string, callback: (data: any) => void): { unsubscribe: () => void }
5
+ }
@@ -0,0 +1,69 @@
1
+ export default class ConnectionManager {
2
+ subscriptions
3
+ adapter
4
+ networkListeners
5
+ networkListenerSerial
6
+ networkStatus
7
+ constructor(adapter) {
8
+ this.subscriptions = {}
9
+ this.adapter = adapter
10
+ this.networkListeners = {}
11
+ this.networkListenerSerial = 0
12
+ this.networkStatus = true
13
+ adapter.ondisconnect = () => {
14
+ this.unsubscribeAll()
15
+ this.triggerNetworkChange(false)
16
+ }
17
+ adapter.onreconnect = () => this.triggerNetworkChange(true)
18
+ }
19
+ triggerNetworkChange(status) {
20
+ if (this.networkStatus == status) return
21
+ this.networkStatus = status
22
+ for (const id in this.networkListeners) this.networkListeners[id](status)
23
+ }
24
+ unsubscribeAll() {
25
+ for (const id in this.subscriptions) {
26
+ const subscription = this.subscriptions[id]
27
+ subscription.listeners = {}
28
+ subscription.connection.unsubscribe()
29
+ }
30
+ this.subscriptions = {}
31
+ }
32
+ subscribeNetwork(func) {
33
+ const id = this.networkListenerSerial++
34
+ this.networkListeners[id] = func
35
+ const unsubscribe = () => {
36
+ delete this.networkListeners[id]
37
+ }
38
+ return { unsubscribe }
39
+ }
40
+ subscribe(key, func) {
41
+ const subscription = this.connect(key)
42
+ const id = subscription.serial++
43
+ subscription.ref++
44
+ subscription.listeners[id] = func
45
+ const unsubscribe = () => {
46
+ if (!subscription.listeners[id]) return
47
+ delete subscription.listeners[id]
48
+ subscription.ref--
49
+ if (subscription.ref === 0) this.disconnect(key)
50
+ }
51
+ return { unsubscribe }
52
+ }
53
+ connect(key) {
54
+ if (this.subscriptions[key]) return this.subscriptions[key]
55
+ const connection = this.adapter.subscribe(key, data => this.received(key, data))
56
+ return this.subscriptions[key] = { connection, listeners: {}, ref: 0, serial: 0 }
57
+ }
58
+ disconnect(key) {
59
+ const subscription = this.subscriptions[key]
60
+ if (!subscription || subscription.ref !== 0) return
61
+ delete this.subscriptions[key]
62
+ subscription.connection.unsubscribe()
63
+ }
64
+ received(key, data) {
65
+ const subscription = this.subscriptions[key]
66
+ if (!subscription) return
67
+ for (const id in subscription.listeners) subscription.listeners[id](data)
68
+ }
69
+ }
@@ -0,0 +1,73 @@
1
+ type RecordType = { _meta?: { query: any } }
2
+ type Values<T> = T extends { [K in keyof T]: infer U } ? U : never
3
+ type DataTypeExtractField<BaseType, Key extends keyof BaseType> = BaseType[Key] extends RecordType
4
+ ? (null extends BaseType[Key] ? {} | null : {})
5
+ : BaseType[Key] extends RecordType[]
6
+ ? {}[]
7
+ : BaseType[Key]
8
+
9
+ type DataTypeExtractFieldsFromQuery<BaseType, Fields> = '*' extends Fields
10
+ ? { [key in Exclude<keyof BaseType, '_meta'>]: DataTypeExtractField<BaseType, key> }
11
+ : { [key in Fields & keyof (BaseType)]: DataTypeExtractField<BaseType, key> }
12
+
13
+ interface ExtraFieldErrorType {
14
+ extraFieldError: any
15
+ }
16
+
17
+ type DataTypeExtractFromQueryHash<BaseType, QueryType> = '*' extends keyof QueryType
18
+ ? {
19
+ [key in Exclude<(keyof BaseType) | (keyof QueryType), '_meta' | '_params' | '*'>]: (key extends keyof BaseType
20
+ ? (key extends keyof QueryType
21
+ ? (QueryType[key] extends true
22
+ ? DataTypeExtractField<BaseType, key>
23
+ : DataTypeFromQuery<BaseType[key] & {}, QueryType[key]>)
24
+ : DataTypeExtractField<BaseType, key>)
25
+ : ExtraFieldErrorType)
26
+ }
27
+ : {
28
+ [key in keyof QueryType]: (key extends keyof BaseType
29
+ ? (QueryType[key] extends true
30
+ ? DataTypeExtractField<BaseType, key>
31
+ : DataTypeFromQuery<BaseType[key] & {}, QueryType[key]>)
32
+ : ExtraFieldErrorType)
33
+ }
34
+
35
+ type _DataTypeFromQuery<BaseType, QueryType> = QueryType extends keyof BaseType | '*'
36
+ ? DataTypeExtractFieldsFromQuery<BaseType, QueryType>
37
+ : QueryType extends Readonly<(keyof BaseType | '*')[]>
38
+ ? DataTypeExtractFieldsFromQuery<BaseType, Values<QueryType>>
39
+ : QueryType extends { as: string }
40
+ ? { error: 'type for alias field is not supported' } | undefined
41
+ : DataTypeExtractFromQueryHash<BaseType, QueryType>
42
+
43
+ export type DataTypeFromQuery<BaseType, QueryType> = BaseType extends any[]
44
+ ? CheckAttributesField<BaseType[0], QueryType>[]
45
+ : null extends BaseType
46
+ ? CheckAttributesField<BaseType & {}, QueryType> | null
47
+ : CheckAttributesField<BaseType & {}, QueryType>
48
+
49
+ type CheckAttributesField<P, Q> = Q extends { attributes: infer R }
50
+ ? _DataTypeFromQuery<P, R>
51
+ : _DataTypeFromQuery<P, Q>
52
+
53
+ type IsAnyCompareLeftType = { __any: never }
54
+
55
+ type CollectExtraFields<Type, Path> = ExtraFieldErrorType extends Type
56
+ ? (IsAnyCompareLeftType extends Type ? null : Path)
57
+ : _CollectExtraFields<Type extends (infer R)[] ? R : (Type extends object ? Type : null)>
58
+
59
+ type _CollectExtraFields<Type> = keyof (Type) extends never
60
+ ? null
61
+ : Values<{ [key in keyof Type]: CollectExtraFields<Type[key], [key]> }>
62
+
63
+ type SelectString<T> = T extends string ? T : never
64
+ type _ValidateDataTypeExtraFileds<Extra, Type> = SelectString<Values<Extra>> extends never
65
+ ? Type
66
+ : { error: { extraFields: SelectString<Values<Extra>> } }
67
+ type ValidateDataTypeExtraFileds<Type> = _ValidateDataTypeExtraFileds<CollectExtraFields<Type, []>, Type>
68
+
69
+ type RequestBase = { api: string; query: any; params?: any; _meta?: { data: any } }
70
+ type DataTypeBaseFromRequestType<R> = R extends { _meta?: { data: infer DataType } } ? DataType : never
71
+ export type DataTypeFromRequest<Req extends RequestBase, R extends RequestBase> = ValidateDataTypeExtraFileds<
72
+ DataTypeFromQuery<DataTypeBaseFromRequestType<Req>, R['query']>
73
+ >
@@ -0,0 +1,86 @@
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
+ }