ar_sync 1.0.3 → 1.1.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 +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
|