forest_admin_rpc_agent 1.22.1 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0c5038efc82d0b8c302a85a51749a386fdae5aa4575e115af8b761127eb4a6d
4
- data.tar.gz: 3df8ee383615e9e79e25bf06bca0365a30959ecf2b3d8a25b15df6930985e6e7
3
+ metadata.gz: '06883f603b147464ddf1ad642f47af2c3230b41cd043cf663ad9214ad22f98ec'
4
+ data.tar.gz: 13cdf53f4abc7147fd38d73efa4ee74f7829abbf320ce363ac315f42e8f1e322
5
5
  SHA512:
6
- metadata.gz: d192a048eb065db1dd74fc6b30780846f71409ea8a14743ad447ec00e291c9c7fac45e950167883a490d2819e53051d7c3d2c580934321eaf097aa73b84ac2a8
7
- data.tar.gz: cd9d2e52d408948837ec1ab594049160887991b8c3f7302f0ed22c6c6b8e8c3a6c77bb6c2675205bcedb3c5c40702997b37f7653b8573209c1343f31708f779d
6
+ metadata.gz: 2e680dc41c11c375aafe885afee8870b78ee67c81b9853f33c8eb214f1ddd4a60e82c4fe11bbd78f30c894c7288ad66f211c1d354cc7c780fb4c22e5abe4cb48
7
+ data.tar.gz: 02e09ab9ba06a77619dcd42785c3965f578e799014517a85535234c6079e673d4d31b0491fced672764b82fa7186022394b8570415d63f7797a4cc7ee2d9e760
@@ -31,12 +31,9 @@ admin work on any Ruby application."
31
31
  spec.require_paths = ["lib"]
32
32
 
33
33
  spec.add_dependency "base64"
34
- spec.add_dependency "benchmark"
35
34
  spec.add_dependency "bigdecimal"
36
- spec.add_dependency "cgi"
37
35
  spec.add_dependency "csv"
38
36
  spec.add_dependency "dry-configurable", "~> 1.1"
39
- spec.add_dependency "logger"
40
37
  spec.add_dependency "mutex_m"
41
38
  spec.add_dependency "ostruct"
42
39
  spec.add_dependency "thor", "~> 1.3"
@@ -2,7 +2,7 @@ require 'digest'
2
2
  require 'fileutils'
3
3
 
4
4
  module ForestAdminRpcAgent
5
- class Agent < ForestAdminAgent::Builder::AgentFactory
5
+ class Agent < ForestAdminAgent::Builder::AgentFactory # rubocop:disable Metrics/ClassLength
6
6
  include ForestAdminAgent::Http::Exceptions
7
7
 
8
8
  attr_reader :rpc_collections, :cached_schema, :cached_schema_hash
@@ -17,23 +17,35 @@ module ForestAdminRpcAgent
17
17
  def send_schema(force: false)
18
18
  if should_skip_schema_update? && !force
19
19
  log_schema_skip
20
+ load_and_cache_schema
20
21
  return
21
22
  end
22
23
 
23
- datasource = @container.resolve(:datasource)
24
+ schema_path = ForestAdminRpcAgent::Facades::Container.cache(:schema_path)
24
25
 
25
- # Build and cache RPC schema from live datasource
26
- @cached_schema = build_rpc_schema_from_datasource(datasource)
27
- compute_and_cache_hash
26
+ if ForestAdminRpcAgent::Facades::Container.cache(:is_production)
27
+ unless schema_path && File.exist?(schema_path)
28
+ raise InternalServerError.new(
29
+ 'Schema file not found in production',
30
+ details: { schema_path: schema_path }
31
+ )
32
+ end
28
33
 
29
- # Write schema file for reference (only in development mode)
30
- # Uses the same serialization as the /rpc-schema route
31
- write_schema_file_for_reference unless ForestAdminRpcAgent::Facades::Container.cache(:is_production)
34
+ load_and_cache_schema_from_file(schema_path)
35
+
36
+ ForestAdminRpcAgent::Facades::Container.logger.log(
37
+ 'Info',
38
+ 'RPC agent running in production mode, using existing schema file.'
39
+ )
40
+ else
41
+ generate_and_cache_schema(schema_path)
42
+
43
+ ForestAdminRpcAgent::Facades::Container.logger.log(
44
+ 'Info',
45
+ "RPC agent schema generated and saved to #{schema_path}"
46
+ )
47
+ end
32
48
 
33
- ForestAdminRpcAgent::Facades::Container.logger.log(
34
- 'Info',
35
- 'RPC agent schema computed from datasource and cached.'
36
- )
37
49
  ForestAdminRpcAgent::Facades::Container.logger.log(
38
50
  'Info',
39
51
  'RPC agent does not send schema to Forest Admin servers.'
@@ -45,6 +57,15 @@ module ForestAdminRpcAgent
45
57
  self
46
58
  end
47
59
 
60
+ # Returns the cached schema for the /rpc-schema route
61
+ # Falls back to building schema from datasource if not cached
62
+ def rpc_schema
63
+ return @cached_schema if @cached_schema
64
+
65
+ build_and_cache_schema_from_datasource
66
+ @cached_schema
67
+ end
68
+
48
69
  # Check if provided hash matches the cached schema hash
49
70
  def schema_hash_matches?(provided_hash)
50
71
  return false unless @cached_schema_hash && provided_hash
@@ -61,70 +82,170 @@ module ForestAdminRpcAgent
61
82
  def log_schema_skip
62
83
  logger = ForestAdminRpcAgent::Facades::Container.logger
63
84
  logger.log('Warn', '[ForestAdmin] Schema update skipped (skip_schema_update flag is true)')
85
+ environment = ForestAdminRpcAgent::Facades::Container.cache(:is_production) ? 'production' : 'development'
86
+ logger.log('Info', "[ForestAdmin] RPC agent running in #{environment} mode")
64
87
  end
65
88
 
66
- def write_schema_file_for_reference
89
+ def load_and_cache_schema
67
90
  schema_path = ForestAdminRpcAgent::Facades::Container.cache(:schema_path)
91
+
92
+ if ForestAdminRpcAgent::Facades::Container.cache(:is_production) && schema_path && File.exist?(schema_path)
93
+ load_and_cache_schema_from_file(schema_path)
94
+ else
95
+ # In development with skip_schema_update, still build from datasource
96
+ build_and_cache_schema_from_datasource
97
+ end
98
+ end
99
+
100
+ def load_and_cache_schema_from_file(_schema_path)
101
+ # File exists but RPC schema needs internal format - build from datasource
102
+ # The file is kept for reference/frontend but RPC always uses internal format
103
+ datasource = @container.resolve(:datasource)
104
+ @cached_schema = build_rpc_schema_from_datasource(datasource)
105
+ compute_and_cache_hash
106
+ end
107
+
108
+ def generate_and_cache_schema(schema_path)
109
+ datasource = @container.resolve(:datasource)
110
+
111
+ # Generate frontend schema for file (used by Forest Admin)
112
+ generated = ForestAdminAgent::Utils::Schema::SchemaEmitter.generate(datasource)
113
+ meta = ForestAdminAgent::Utils::Schema::SchemaEmitter.meta
114
+
115
+ schema = {
116
+ meta: meta,
117
+ collections: generated
118
+ }
119
+
68
120
  FileUtils.mkdir_p(File.dirname(schema_path))
69
- # Use the same serialization as the /rpc-schema route (.to_json)
70
- File.write(schema_path, JSON.pretty_generate(JSON.parse(@cached_schema.to_json)))
121
+ File.write(schema_path, format_schema_json(schema))
71
122
 
72
- ForestAdminRpcAgent::Facades::Container.logger.log(
73
- 'Info',
74
- "RPC agent schema file saved to #{schema_path}"
75
- )
123
+ # Build RPC schema in internal format (used by master agent)
124
+ @cached_schema = build_rpc_schema_from_datasource(datasource)
125
+ compute_and_cache_hash
76
126
  end
77
127
 
78
- def build_rpc_schema_from_datasource(datasource)
79
- schema = customizer.schema
128
+ def build_and_cache_schema_from_datasource
129
+ datasource = @container.resolve(:datasource)
80
130
 
81
- rpc_relations = {}
82
- collections = []
83
-
84
- datasource.collections.each_value do |collection|
85
- relations = {}
86
-
87
- if @rpc_collections.include?(collection.name)
88
- # RPC collection → extract relations to non-RPC collections
89
- collection.schema[:fields].each do |field_name, field|
90
- next if field.type == 'Column'
91
- next if @rpc_collections.include?(field.foreign_collection)
92
-
93
- relations[field_name] = field
94
- end
95
- else
96
- fields = {}
97
-
98
- collection.schema[:fields].each do |field_name, field|
99
- if field.type != 'Column' && @rpc_collections.include?(field.foreign_collection)
100
- relations[field_name] = field
101
- else
102
- if field.type == 'Column'
103
- field.filter_operators = ForestAdminAgent::Utils::Schema::FrontendFilterable.sort_operators(
104
- field.filter_operators
105
- )
106
- end
107
-
108
- fields[field_name] = field
109
- end
110
- end
111
-
112
- # Normal collection → include in schema
113
- collections << collection.schema.merge({ name: collection.name, fields: fields })
114
- end
131
+ @cached_schema = build_rpc_schema_from_datasource(datasource)
132
+ compute_and_cache_hash
133
+ end
115
134
 
116
- rpc_relations[collection.name] = relations unless relations.empty?
117
- end
135
+ def build_rpc_schema_from_datasource(datasource)
136
+ schema = customizer.schema
118
137
 
138
+ # Serialize collections with internal schema format (fields as hash with :type keys)
139
+ collections = datasource.collections.map { |_name, collection| serialize_collection_for_rpc(collection) }
119
140
  schema[:collections] = collections.sort_by { |c| c[:name] }
120
- schema[:rpc_relations] = rpc_relations
121
141
 
122
- schema[:native_query_connections] = datasource.live_query_connections.keys
123
- .map { |connection_name| { name: connection_name } }
142
+ connections = datasource.live_query_connections.keys.map { |connection_name| { name: connection_name } }
143
+ schema[:native_query_connections] = connections
124
144
 
125
145
  schema
126
146
  end
127
147
 
148
+ def serialize_collection_for_rpc(collection)
149
+ {
150
+ name: collection.name,
151
+ countable: collection.schema[:countable],
152
+ searchable: collection.schema[:searchable],
153
+ segments: collection.schema[:segments] || [],
154
+ charts: collection.schema[:charts] || [],
155
+ actions: serialize_actions_for_rpc(collection.schema[:actions] || {}),
156
+ fields: serialize_fields_for_rpc(collection.schema[:fields] || {})
157
+ }
158
+ end
159
+
160
+ def serialize_fields_for_rpc(fields)
161
+ fields.transform_values do |field_schema|
162
+ serialize_field_schema(field_schema)
163
+ end
164
+ end
165
+
166
+ def serialize_field_schema(field_schema)
167
+ case field_schema
168
+ when ForestAdminDatasourceToolkit::Schema::ColumnSchema
169
+ {
170
+ type: 'Column',
171
+ column_type: field_schema.column_type,
172
+ filter_operators: field_schema.filter_operators,
173
+ is_primary_key: field_schema.is_primary_key,
174
+ is_read_only: field_schema.is_read_only,
175
+ is_sortable: field_schema.is_sortable,
176
+ default_value: field_schema.default_value,
177
+ enum_values: field_schema.enum_values,
178
+ validation: field_schema.validation
179
+ }
180
+ when ForestAdminDatasourceToolkit::Schema::Relations::ManyToOneSchema
181
+ {
182
+ type: 'ManyToOne',
183
+ foreign_collection: field_schema.foreign_collection,
184
+ foreign_key: field_schema.foreign_key,
185
+ foreign_key_target: field_schema.foreign_key_target
186
+ }
187
+ when ForestAdminDatasourceToolkit::Schema::Relations::OneToOneSchema
188
+ {
189
+ type: 'OneToOne',
190
+ foreign_collection: field_schema.foreign_collection,
191
+ origin_key: field_schema.origin_key,
192
+ origin_key_target: field_schema.origin_key_target
193
+ }
194
+ when ForestAdminDatasourceToolkit::Schema::Relations::OneToManySchema
195
+ {
196
+ type: 'OneToMany',
197
+ foreign_collection: field_schema.foreign_collection,
198
+ origin_key: field_schema.origin_key,
199
+ origin_key_target: field_schema.origin_key_target
200
+ }
201
+ when ForestAdminDatasourceToolkit::Schema::Relations::ManyToManySchema
202
+ {
203
+ type: 'ManyToMany',
204
+ foreign_collection: field_schema.foreign_collection,
205
+ foreign_key: field_schema.foreign_key,
206
+ foreign_key_target: field_schema.foreign_key_target,
207
+ origin_key: field_schema.origin_key,
208
+ origin_key_target: field_schema.origin_key_target,
209
+ through_collection: field_schema.through_collection
210
+ }
211
+ when ForestAdminDatasourceToolkit::Schema::Relations::PolymorphicManyToOneSchema
212
+ {
213
+ type: 'PolymorphicManyToOne',
214
+ foreign_collections: field_schema.foreign_collections,
215
+ foreign_key: field_schema.foreign_key,
216
+ foreign_key_type_field: field_schema.foreign_key_type_field,
217
+ foreign_key_targets: field_schema.foreign_key_targets
218
+ }
219
+ when ForestAdminDatasourceToolkit::Schema::Relations::PolymorphicOneToOneSchema
220
+ {
221
+ type: 'PolymorphicOneToOne',
222
+ foreign_collection: field_schema.foreign_collection,
223
+ origin_key: field_schema.origin_key,
224
+ origin_key_target: field_schema.origin_key_target,
225
+ origin_type_field: field_schema.origin_type_field,
226
+ origin_type_value: field_schema.origin_type_value
227
+ }
228
+ when ForestAdminDatasourceToolkit::Schema::Relations::PolymorphicOneToManySchema
229
+ {
230
+ type: 'PolymorphicOneToMany',
231
+ foreign_collection: field_schema.foreign_collection,
232
+ origin_key: field_schema.origin_key,
233
+ origin_key_target: field_schema.origin_key_target,
234
+ origin_type_field: field_schema.origin_type_field,
235
+ origin_type_value: field_schema.origin_type_value
236
+ }
237
+ else
238
+ # Fallback: try to convert to hash if possible
239
+ field_schema.respond_to?(:to_h) ? field_schema.to_h : field_schema
240
+ end
241
+ end
242
+
243
+ def serialize_actions_for_rpc(actions)
244
+ actions.transform_values do |action|
245
+ action.respond_to?(:to_h) ? action.to_h : action
246
+ end
247
+ end
248
+
128
249
  def compute_and_cache_hash
129
250
  return unless @cached_schema
130
251
 
@@ -27,7 +27,7 @@ module ForestAdminRpcAgent
27
27
  end
28
28
 
29
29
  # Get schema from cache (or build from datasource if not cached)
30
- schema = agent.cached_schema
30
+ schema = agent.rpc_schema
31
31
  etag = agent.cached_schema_hash
32
32
 
33
33
  # Return schema with ETag header
@@ -1,3 +1,3 @@
1
1
  module ForestAdminRpcAgent
2
- VERSION = "1.22.1"
2
+ VERSION = "2.0.0"
3
3
  end
@@ -20,13 +20,12 @@ module ForestAdminRpcAgent
20
20
  setting :prefix, default: nil
21
21
  setting :cache_dir, default: :'tmp/cache/forest_admin'
22
22
  setting :project_dir, default: Dir.pwd
23
- setting :schema_path, default: File.join(Dir.pwd, '.forestadmin-rpc-schema.json')
23
+ setting :schema_path, default: File.join(Dir.pwd, '.forestadmin-schema.json')
24
24
  setting :skip_schema_update, default: false
25
25
  setting :logger_level, default: 'info'
26
26
  setting :logger, default: nil
27
27
  setting :customize_error_message, default: nil
28
28
  setting :disable_route_cache, default: false
29
- setting :rpc_max_polling_threads, default: nil
30
29
 
31
30
  begin
32
31
  require 'thor'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: forest_admin_rpc_agent
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.22.1
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matthieu
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2026-01-13 00:00:00.000000000 Z
12
+ date: 2025-12-11 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: base64
@@ -25,20 +25,6 @@ dependencies:
25
25
  - - ">="
26
26
  - !ruby/object:Gem::Version
27
27
  version: '0'
28
- - !ruby/object:Gem::Dependency
29
- name: benchmark
30
- requirement: !ruby/object:Gem::Requirement
31
- requirements:
32
- - - ">="
33
- - !ruby/object:Gem::Version
34
- version: '0'
35
- type: :runtime
36
- prerelease: false
37
- version_requirements: !ruby/object:Gem::Requirement
38
- requirements:
39
- - - ">="
40
- - !ruby/object:Gem::Version
41
- version: '0'
42
28
  - !ruby/object:Gem::Dependency
43
29
  name: bigdecimal
44
30
  requirement: !ruby/object:Gem::Requirement
@@ -53,20 +39,6 @@ dependencies:
53
39
  - - ">="
54
40
  - !ruby/object:Gem::Version
55
41
  version: '0'
56
- - !ruby/object:Gem::Dependency
57
- name: cgi
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - ">="
61
- - !ruby/object:Gem::Version
62
- version: '0'
63
- type: :runtime
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - ">="
68
- - !ruby/object:Gem::Version
69
- version: '0'
70
42
  - !ruby/object:Gem::Dependency
71
43
  name: csv
72
44
  requirement: !ruby/object:Gem::Requirement
@@ -95,20 +67,6 @@ dependencies:
95
67
  - - "~>"
96
68
  - !ruby/object:Gem::Version
97
69
  version: '1.1'
98
- - !ruby/object:Gem::Dependency
99
- name: logger
100
- requirement: !ruby/object:Gem::Requirement
101
- requirements:
102
- - - ">="
103
- - !ruby/object:Gem::Version
104
- version: '0'
105
- type: :runtime
106
- prerelease: false
107
- version_requirements: !ruby/object:Gem::Requirement
108
- requirements:
109
- - - ">="
110
- - !ruby/object:Gem::Version
111
- version: '0'
112
70
  - !ruby/object:Gem::Dependency
113
71
  name: mutex_m
114
72
  requirement: !ruby/object:Gem::Requirement