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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/test.yml +27 -0
  3. data/Gemfile +1 -0
  4. data/Gemfile.lock +29 -31
  5. data/ar_sync.gemspec +1 -1
  6. data/core/{ActioncableAdapter.d.ts → ActionCableAdapter.d.ts} +0 -0
  7. data/core/ActionCableAdapter.js +31 -0
  8. data/core/ArSyncApi.d.ts +8 -2
  9. data/core/ArSyncApi.js +123 -49
  10. data/core/ArSyncModel.js +69 -60
  11. data/core/ArSyncStore.js +522 -381
  12. data/core/ConnectionManager.d.ts +1 -1
  13. data/core/ConnectionManager.js +45 -38
  14. data/core/DataType.d.ts +14 -9
  15. data/core/hooks.d.ts +5 -0
  16. data/core/hooks.js +64 -36
  17. data/gemfiles/Gemfile-rails-6 +9 -0
  18. data/gemfiles/Gemfile-rails-7 +9 -0
  19. data/index.js +2 -2
  20. data/lib/ar_sync/class_methods.rb +71 -36
  21. data/lib/ar_sync/collection.rb +23 -19
  22. data/lib/ar_sync/core.rb +3 -3
  23. data/lib/ar_sync/instance_methods.rb +7 -4
  24. data/lib/ar_sync/rails.rb +1 -1
  25. data/lib/ar_sync/type_script.rb +50 -14
  26. data/lib/ar_sync/version.rb +1 -1
  27. data/lib/generators/ar_sync/install/install_generator.rb +1 -1
  28. data/package-lock.json +1706 -227
  29. data/package.json +1 -1
  30. data/src/core/{ActioncableAdapter.ts → ActionCableAdapter.ts} +0 -0
  31. data/src/core/ArSyncApi.ts +20 -7
  32. data/src/core/ArSyncStore.ts +177 -125
  33. data/src/core/ConnectionManager.ts +1 -0
  34. data/src/core/DataType.ts +15 -16
  35. data/src/core/hooks.ts +31 -7
  36. data/tsconfig.json +2 -2
  37. data/vendor/assets/javascripts/{ar_sync_actioncable_adapter.js.erb → ar_sync_action_cable_adapter.js.erb} +1 -1
  38. metadata +17 -16
  39. data/core/ActioncableAdapter.js +0 -29
  40. 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 && request.params)
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 requestString = JSON.stringify(request && request.params)
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
- return [state.data, state.status, loader.update]
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": "es2017",
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/ActioncableAdapter.js' %>
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.3
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: 2019-12-16 00:00:00.000000000 Z
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: 1.0.0
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: 1.0.0
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/ActioncableAdapter.d.ts
115
- - core/ActioncableAdapter.js
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/ActioncableAdapter.ts
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/ar_sync_actioncable_adapter.js.erb
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
- rubyforge_project:
178
- rubygems_version: 2.7.3
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: []
@@ -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