ar_sync 1.0.3 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +27 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +29 -31
- data/ar_sync.gemspec +1 -1
- data/core/{ActioncableAdapter.d.ts → ActionCableAdapter.d.ts} +0 -0
- data/core/ActionCableAdapter.js +31 -0
- data/core/ArSyncApi.d.ts +8 -2
- data/core/ArSyncApi.js +123 -49
- data/core/ArSyncModel.js +69 -60
- data/core/ArSyncStore.js +522 -381
- data/core/ConnectionManager.d.ts +1 -1
- data/core/ConnectionManager.js +45 -38
- data/core/DataType.d.ts +14 -9
- data/core/hooks.d.ts +5 -0
- data/core/hooks.js +64 -36
- data/gemfiles/Gemfile-rails-6 +9 -0
- data/gemfiles/Gemfile-rails-7 +9 -0
- data/index.js +2 -2
- data/lib/ar_sync/class_methods.rb +71 -36
- data/lib/ar_sync/collection.rb +23 -19
- data/lib/ar_sync/core.rb +3 -3
- data/lib/ar_sync/instance_methods.rb +7 -4
- data/lib/ar_sync/rails.rb +1 -1
- data/lib/ar_sync/type_script.rb +50 -14
- data/lib/ar_sync/version.rb +1 -1
- data/lib/generators/ar_sync/install/install_generator.rb +1 -1
- data/package-lock.json +1706 -227
- data/package.json +1 -1
- data/src/core/{ActioncableAdapter.ts → ActionCableAdapter.ts} +0 -0
- data/src/core/ArSyncApi.ts +20 -7
- data/src/core/ArSyncStore.ts +177 -125
- data/src/core/ConnectionManager.ts +1 -0
- data/src/core/DataType.ts +15 -16
- data/src/core/hooks.ts +31 -7
- data/tsconfig.json +2 -2
- data/vendor/assets/javascripts/{ar_sync_actioncable_adapter.js.erb → ar_sync_action_cable_adapter.js.erb} +1 -1
- metadata +17 -16
- data/core/ActioncableAdapter.js +0 -29
- data/lib/ar_sync/field.rb +0 -96
data/src/core/hooks.ts
CHANGED
@@ -4,30 +4,35 @@ import ArSyncModel from './ArSyncModel'
|
|
4
4
|
let useState: <T>(t: T | (() => T)) => [T, (t: T | ((t: T) => T)) => void]
|
5
5
|
let useEffect: (f: (() => void) | (() => (() => void)), deps: any[]) => void
|
6
6
|
let useMemo: <T>(f: () => T, deps: any[]) => T
|
7
|
+
let useRef: <T>(value: T) => { current: T }
|
7
8
|
type InitializeHooksParams = {
|
8
9
|
useState: typeof useState
|
9
10
|
useEffect: typeof useEffect
|
10
11
|
useMemo: typeof useMemo
|
12
|
+
useRef: typeof useRef
|
11
13
|
}
|
12
14
|
export function initializeHooks(hooks: InitializeHooksParams) {
|
13
15
|
useState = hooks.useState
|
14
16
|
useEffect = hooks.useEffect
|
15
17
|
useMemo = hooks.useMemo
|
18
|
+
useRef = hooks.useRef
|
16
19
|
}
|
17
20
|
function checkHooks() {
|
18
|
-
if (!useState) throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo })`'
|
21
|
+
if (!useState) throw 'uninitialized. needs `initializeHooks({ useState, useEffect, useMemo, useRef })`'
|
19
22
|
}
|
20
23
|
|
21
24
|
interface ModelStatus { complete: boolean; notfound?: boolean; connected: boolean }
|
22
25
|
export type DataAndStatus<T> = [T | null, ModelStatus]
|
23
|
-
export interface Request { api: string; params?: any; query: any }
|
26
|
+
export interface Request { api: string; params?: any; id?: number; query: any }
|
24
27
|
|
25
28
|
const initialResult: DataAndStatus<any> = [null, { complete: false, notfound: undefined, connected: true }]
|
26
29
|
export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
27
30
|
checkHooks()
|
28
31
|
const [result, setResult] = useState<DataAndStatus<T>>(initialResult)
|
29
|
-
const requestString = JSON.stringify(request
|
32
|
+
const requestString = JSON.stringify(request?.id ?? request?.params)
|
33
|
+
const prevRequestStringRef = useRef(requestString)
|
30
34
|
useEffect(() => {
|
35
|
+
prevRequestStringRef.current = requestString
|
31
36
|
if (!request) {
|
32
37
|
setResult(initialResult)
|
33
38
|
return () => {}
|
@@ -36,8 +41,9 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
36
41
|
function update() {
|
37
42
|
const { complete, notfound, connected, data } = model
|
38
43
|
setResult(resultWas => {
|
39
|
-
const [, statusWas] = resultWas
|
44
|
+
const [dataWas, statusWas] = resultWas
|
40
45
|
const statusPersisted = statusWas.complete === complete && statusWas.notfound === notfound && statusWas.connected === connected
|
46
|
+
if (dataWas === data && statusPersisted) return resultWas
|
41
47
|
const status = statusPersisted ? statusWas : { complete, notfound, connected }
|
42
48
|
return [data, status]
|
43
49
|
})
|
@@ -47,21 +53,37 @@ export function useArSyncModel<T>(request: Request | null): DataAndStatus<T> {
|
|
47
53
|
} else {
|
48
54
|
setResult(initialResult)
|
49
55
|
}
|
56
|
+
model.subscribe('load', update)
|
50
57
|
model.subscribe('change', update)
|
51
58
|
model.subscribe('connection', update)
|
52
59
|
return () => model.release()
|
53
60
|
}, [requestString])
|
54
|
-
return result
|
61
|
+
return prevRequestStringRef.current === requestString ? result : initialResult
|
55
62
|
}
|
56
63
|
|
57
64
|
interface FetchStatus { complete: boolean; notfound?: boolean }
|
58
65
|
type DataStatusUpdate<T> = [T | null, FetchStatus, () => void]
|
59
66
|
type FetchState<T> = { data: T | null; status: FetchStatus }
|
60
67
|
const initialFetchState: FetchState<any> = { data: null, status: { complete: false, notfound: undefined } }
|
68
|
+
|
69
|
+
function extractParams(query: unknown, output: any[] = []): any[] {
|
70
|
+
if (typeof(query) !== 'object' || query == null || Array.isArray(query)) return output
|
71
|
+
if ('params' in query) output.push((query as { params: any }).params)
|
72
|
+
for (const key in query) {
|
73
|
+
extractParams(query[key], output)
|
74
|
+
}
|
75
|
+
return output
|
76
|
+
}
|
77
|
+
|
61
78
|
export function useArSyncFetch<T>(request: Request | null): DataStatusUpdate<T> {
|
62
79
|
checkHooks()
|
63
80
|
const [state, setState] = useState<FetchState<T>>(initialFetchState)
|
64
|
-
const
|
81
|
+
const query = request && request.query
|
82
|
+
const resourceIdentifier = request?.id ?? request?.params
|
83
|
+
const requestString = useMemo(() => {
|
84
|
+
return JSON.stringify(extractParams(query, [resourceIdentifier]))
|
85
|
+
}, [query, resourceIdentifier])
|
86
|
+
const prevRequestStringRef = useRef(requestString)
|
65
87
|
const loader = useMemo(() => {
|
66
88
|
let lastLoadId = 0
|
67
89
|
let timer: null | number = null
|
@@ -100,9 +122,11 @@ export function useArSyncFetch<T>(request: Request | null): DataStatusUpdate<T>
|
|
100
122
|
return { update, cancel }
|
101
123
|
}, [requestString])
|
102
124
|
useEffect(() => {
|
125
|
+
prevRequestStringRef.current = requestString
|
103
126
|
setState(initialFetchState)
|
104
127
|
loader.update()
|
105
128
|
return () => loader.cancel()
|
106
129
|
}, [requestString])
|
107
|
-
|
130
|
+
const responseState = prevRequestStringRef.current === requestString ? state : initialFetchState
|
131
|
+
return [responseState.data, responseState.status, loader.update]
|
108
132
|
}
|
data/tsconfig.json
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
{
|
2
2
|
"compilerOptions": {
|
3
|
-
"incremental": true,
|
4
3
|
"outDir": ".",
|
5
4
|
"rootDir": "./src",
|
6
5
|
"module": "commonjs",
|
7
|
-
"target": "
|
6
|
+
"target": "es5",
|
7
|
+
"lib": ["es2017", "dom"],
|
8
8
|
"strictNullChecks": true,
|
9
9
|
"noUnusedLocals": true,
|
10
10
|
"noUnusedParameters": true,
|
@@ -2,6 +2,6 @@
|
|
2
2
|
var modules = { actioncable: window.ActionCable }
|
3
3
|
var exports = {}
|
4
4
|
function require(name) { return modules[name] }
|
5
|
-
<%= File.read File.dirname(__FILE__) + '/../../../core/
|
5
|
+
<%= File.read File.dirname(__FILE__) + '/../../../core/ActionCableAdapter.js' %>
|
6
6
|
window.ArSyncActionCableAdapter = ActionCableAdapter
|
7
7
|
}())
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ar_sync
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- tompng
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activerecord
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: ar_serializer
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- -
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: '0'
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- -
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -101,6 +101,7 @@ executables: []
|
|
101
101
|
extensions: []
|
102
102
|
extra_rdoc_files: []
|
103
103
|
files:
|
104
|
+
- ".github/workflows/test.yml"
|
104
105
|
- ".gitignore"
|
105
106
|
- ".travis.yml"
|
106
107
|
- Gemfile
|
@@ -111,8 +112,8 @@ files:
|
|
111
112
|
- ar_sync.gemspec
|
112
113
|
- bin/console
|
113
114
|
- bin/setup
|
114
|
-
- core/
|
115
|
-
- core/
|
115
|
+
- core/ActionCableAdapter.d.ts
|
116
|
+
- core/ActionCableAdapter.js
|
116
117
|
- core/ArSyncApi.d.ts
|
117
118
|
- core/ArSyncApi.js
|
118
119
|
- core/ArSyncModel.d.ts
|
@@ -127,6 +128,8 @@ files:
|
|
127
128
|
- core/DataType.js
|
128
129
|
- core/hooks.d.ts
|
129
130
|
- core/hooks.js
|
131
|
+
- gemfiles/Gemfile-rails-6
|
132
|
+
- gemfiles/Gemfile-rails-7
|
130
133
|
- index.d.ts
|
131
134
|
- index.js
|
132
135
|
- lib/ar_sync.rb
|
@@ -134,7 +137,6 @@ files:
|
|
134
137
|
- lib/ar_sync/collection.rb
|
135
138
|
- lib/ar_sync/config.rb
|
136
139
|
- lib/ar_sync/core.rb
|
137
|
-
- lib/ar_sync/field.rb
|
138
140
|
- lib/ar_sync/instance_methods.rb
|
139
141
|
- lib/ar_sync/rails.rb
|
140
142
|
- lib/ar_sync/type_script.rb
|
@@ -143,7 +145,7 @@ files:
|
|
143
145
|
- lib/generators/ar_sync/types/types_generator.rb
|
144
146
|
- package-lock.json
|
145
147
|
- package.json
|
146
|
-
- src/core/
|
148
|
+
- src/core/ActionCableAdapter.ts
|
147
149
|
- src/core/ArSyncApi.ts
|
148
150
|
- src/core/ArSyncModel.ts
|
149
151
|
- src/core/ArSyncStore.ts
|
@@ -154,12 +156,12 @@ files:
|
|
154
156
|
- src/index.ts
|
155
157
|
- tsconfig.json
|
156
158
|
- vendor/assets/javascripts/ar_sync.js.erb
|
157
|
-
- vendor/assets/javascripts/
|
159
|
+
- vendor/assets/javascripts/ar_sync_action_cable_adapter.js.erb
|
158
160
|
homepage: https://github.com/tompng/ar_sync
|
159
161
|
licenses:
|
160
162
|
- MIT
|
161
163
|
metadata: {}
|
162
|
-
post_install_message:
|
164
|
+
post_install_message:
|
163
165
|
rdoc_options: []
|
164
166
|
require_paths:
|
165
167
|
- lib
|
@@ -174,9 +176,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
174
176
|
- !ruby/object:Gem::Version
|
175
177
|
version: '0'
|
176
178
|
requirements: []
|
177
|
-
|
178
|
-
|
179
|
-
signing_key:
|
179
|
+
rubygems_version: 3.3.3
|
180
|
+
signing_key:
|
180
181
|
specification_version: 4
|
181
182
|
summary: ActiveRecord - JavaScript Sync
|
182
183
|
test_files: []
|
data/core/ActioncableAdapter.js
DELETED
@@ -1,29 +0,0 @@
|
|
1
|
-
"use strict";
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
-
class ActionCableAdapter {
|
4
|
-
constructor(actionCableClass) {
|
5
|
-
this.connected = true;
|
6
|
-
this.actionCableClass = actionCableClass;
|
7
|
-
this.subscribe(Math.random().toString(), () => { });
|
8
|
-
}
|
9
|
-
subscribe(key, received) {
|
10
|
-
const disconnected = () => {
|
11
|
-
if (!this.connected)
|
12
|
-
return;
|
13
|
-
this.connected = false;
|
14
|
-
this.ondisconnect();
|
15
|
-
};
|
16
|
-
const connected = () => {
|
17
|
-
if (this.connected)
|
18
|
-
return;
|
19
|
-
this.connected = true;
|
20
|
-
this.onreconnect();
|
21
|
-
};
|
22
|
-
if (!this._cable)
|
23
|
-
this._cable = this.actionCableClass.createConsumer();
|
24
|
-
return this._cable.subscriptions.create({ channel: 'SyncChannel', key }, { received, disconnected, connected });
|
25
|
-
}
|
26
|
-
ondisconnect() { }
|
27
|
-
onreconnect() { }
|
28
|
-
}
|
29
|
-
exports.default = ActionCableAdapter;
|
data/lib/ar_sync/field.rb
DELETED
@@ -1,96 +0,0 @@
|
|
1
|
-
class ArSync::Field
|
2
|
-
attr_reader :name
|
3
|
-
def initialize(name)
|
4
|
-
@name = name
|
5
|
-
end
|
6
|
-
|
7
|
-
def skip_propagation?(_parent, _child, _path)
|
8
|
-
false
|
9
|
-
end
|
10
|
-
|
11
|
-
def action_convert(action)
|
12
|
-
action
|
13
|
-
end
|
14
|
-
|
15
|
-
def order_param; end
|
16
|
-
end
|
17
|
-
|
18
|
-
class ArSync::DataField < ArSync::Field
|
19
|
-
def type
|
20
|
-
:data
|
21
|
-
end
|
22
|
-
|
23
|
-
def data(parent, _child, to_user:, **)
|
24
|
-
ArSync.serialize parent, name, user: to_user
|
25
|
-
end
|
26
|
-
|
27
|
-
def path(_child)
|
28
|
-
[]
|
29
|
-
end
|
30
|
-
|
31
|
-
def action_convert(_action)
|
32
|
-
:update
|
33
|
-
end
|
34
|
-
|
35
|
-
def skip_propagation?(_parent, _child, path)
|
36
|
-
!path.nil?
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
class ArSync::HasOneField < ArSync::Field
|
41
|
-
def type
|
42
|
-
:one
|
43
|
-
end
|
44
|
-
|
45
|
-
def data(_parent, child, action:, **)
|
46
|
-
child._sync_data new_record: action == :create
|
47
|
-
end
|
48
|
-
|
49
|
-
def path(_child)
|
50
|
-
[name]
|
51
|
-
end
|
52
|
-
end
|
53
|
-
|
54
|
-
class ArSync::HasManyField < ArSync::Field
|
55
|
-
attr_reader :limit, :order, :association, :propagate_when
|
56
|
-
def type
|
57
|
-
:many
|
58
|
-
end
|
59
|
-
|
60
|
-
def initialize(name, association: nil, limit: nil, order: nil, propagate_when: nil)
|
61
|
-
super name
|
62
|
-
@limit = limit
|
63
|
-
@order = order
|
64
|
-
@association = association || name
|
65
|
-
@propagate_when = propagate_when
|
66
|
-
end
|
67
|
-
|
68
|
-
def skip_propagation?(parent, child, _path)
|
69
|
-
return false unless limit
|
70
|
-
return !propagate_when.call(child) if propagate_when
|
71
|
-
ids = parent.send(association).order(id: order).limit(limit).ids
|
72
|
-
if child.destroyed?
|
73
|
-
ids.size == limit && (order == :asc ? ids.max < child.id : child.id < ids.min)
|
74
|
-
else
|
75
|
-
!ids.include? child.id
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def data(_parent, child, action:, **)
|
80
|
-
child._sync_data new_record: action == :create
|
81
|
-
end
|
82
|
-
|
83
|
-
def order_param
|
84
|
-
{ limit: limit, order: order } if order
|
85
|
-
end
|
86
|
-
|
87
|
-
def path(child)
|
88
|
-
[name, child.id]
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
|
-
class ArSync::CollectionField < ArSync::HasManyField
|
93
|
-
def path(child)
|
94
|
-
[child.id]
|
95
|
-
end
|
96
|
-
end
|