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.
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