upkeep-rails 0.2.5-aarch64-linux-gnu

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 (81) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +21 -0
  3. data/README.md +244 -0
  4. data/docs/drafts/turbo-streams-is-cache-invalidation-you-write-by-hand.md +240 -0
  5. data/docs/how-it-works.md +329 -0
  6. data/docs/plans/sqlglot-active-record-migration.md +211 -0
  7. data/docs/plans/turbo-frame-subscription-composition.md +90 -0
  8. data/docs/spikes/SQLGLOT_VS_AREL_FINDINGS.md +136 -0
  9. data/docs/spikes/query-source-analysis-comparison/FINDINGS.md +169 -0
  10. data/docs/spikes/sqlglot-first-active-record/README.md +26 -0
  11. data/docs/spikes/sqlglot-query-analysis/FINDINGS.md +110 -0
  12. data/docs/spikes/sqlglot-query-analysis/PULSE_1802_FINDINGS.md +95 -0
  13. data/docs/spikes/sqlglot-semantic-bindings/README.md +29 -0
  14. data/lib/generators/upkeep/install/install_generator.rb +192 -0
  15. data/lib/generators/upkeep/install/templates/create_upkeep_subscriptions.rb.erb +49 -0
  16. data/lib/generators/upkeep/install/templates/subscription.js +288 -0
  17. data/lib/generators/upkeep/install/templates/upkeep.rb +65 -0
  18. data/lib/upkeep/active_record_query.rb +248 -0
  19. data/lib/upkeep/capture/request.rb +150 -0
  20. data/lib/upkeep/dag/subscription_shape.rb +244 -0
  21. data/lib/upkeep/dag.rb +454 -0
  22. data/lib/upkeep/delivery/action_cable_adapter.rb +48 -0
  23. data/lib/upkeep/delivery/async_dispatcher.rb +102 -0
  24. data/lib/upkeep/delivery/broadcast_transport.rb +89 -0
  25. data/lib/upkeep/delivery/transport.rb +194 -0
  26. data/lib/upkeep/delivery/turbo_streams.rb +339 -0
  27. data/lib/upkeep/delivery.rb +7 -0
  28. data/lib/upkeep/dependencies.rb +600 -0
  29. data/lib/upkeep/herb/developer_report.rb +135 -0
  30. data/lib/upkeep/herb/manifest_cache.rb +83 -0
  31. data/lib/upkeep/herb/manifest_diff.rb +183 -0
  32. data/lib/upkeep/herb/source_instrumenter.rb +149 -0
  33. data/lib/upkeep/herb/template_manifest.rb +548 -0
  34. data/lib/upkeep/invalidation/collection_append.rb +84 -0
  35. data/lib/upkeep/invalidation/collection_member_replace.rb +78 -0
  36. data/lib/upkeep/invalidation/collection_prepend.rb +84 -0
  37. data/lib/upkeep/invalidation/collection_remove.rb +57 -0
  38. data/lib/upkeep/invalidation/planner.rb +411 -0
  39. data/lib/upkeep/invalidation.rb +7 -0
  40. data/lib/upkeep/rails/action_view_capture.rb +1007 -0
  41. data/lib/upkeep/rails/activation_token.rb +55 -0
  42. data/lib/upkeep/rails/cable/channel.rb +165 -0
  43. data/lib/upkeep/rails/cable/subscriber_identity.rb +361 -0
  44. data/lib/upkeep/rails/cable.rb +4 -0
  45. data/lib/upkeep/rails/client_subscription.rb +65 -0
  46. data/lib/upkeep/rails/cluster_guard.rb +57 -0
  47. data/lib/upkeep/rails/configuration.rb +252 -0
  48. data/lib/upkeep/rails/controller_runtime.rb +187 -0
  49. data/lib/upkeep/rails/install.rb +28 -0
  50. data/lib/upkeep/rails/job_runtime.rb +43 -0
  51. data/lib/upkeep/rails/railtie.rb +44 -0
  52. data/lib/upkeep/rails/replay.rb +244 -0
  53. data/lib/upkeep/rails/testing.rb +259 -0
  54. data/lib/upkeep/rails.rb +466 -0
  55. data/lib/upkeep/replay.rb +462 -0
  56. data/lib/upkeep/runtime.rb +1276 -0
  57. data/lib/upkeep/shared_streams.rb +86 -0
  58. data/lib/upkeep/sql_dependency_analysis.rb +553 -0
  59. data/lib/upkeep/sqlglot/libsqlglot_rust.so +0 -0
  60. data/lib/upkeep/sqlglot/native.rb +121 -0
  61. data/lib/upkeep/sqlglot/native_library.rb +23 -0
  62. data/lib/upkeep/sqlglot.rb +367 -0
  63. data/lib/upkeep/subscriptions/active_record_store.rb +398 -0
  64. data/lib/upkeep/subscriptions/active_record_subscription_persistence.rb +411 -0
  65. data/lib/upkeep/subscriptions/active_registry.rb +80 -0
  66. data/lib/upkeep/subscriptions/base_store.rb +110 -0
  67. data/lib/upkeep/subscriptions/json_snapshot.rb +98 -0
  68. data/lib/upkeep/subscriptions/layered_reverse_index.rb +125 -0
  69. data/lib/upkeep/subscriptions/lookup_instrumentation.rb +32 -0
  70. data/lib/upkeep/subscriptions/persistent_reverse_index.rb +228 -0
  71. data/lib/upkeep/subscriptions/registrar.rb +36 -0
  72. data/lib/upkeep/subscriptions/reverse_index.rb +313 -0
  73. data/lib/upkeep/subscriptions/shape.rb +117 -0
  74. data/lib/upkeep/subscriptions/store.rb +349 -0
  75. data/lib/upkeep/subscriptions.rb +7 -0
  76. data/lib/upkeep/targeting.rb +146 -0
  77. data/lib/upkeep/version.rb +5 -0
  78. data/lib/upkeep-rails.rb +3 -0
  79. data/lib/upkeep.rb +15 -0
  80. data/upkeep-rails.gemspec +66 -0
  81. metadata +327 -0
@@ -0,0 +1,462 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Upkeep
4
+ module Replay
5
+ module Payload
6
+ module_function
7
+
8
+ def from_h(snapshot)
9
+ return snapshot if snapshot.is_a?(Payload)
10
+
11
+ snapshot = Replay.symbolize_keys(snapshot || {})
12
+ return Empty.new if snapshot.empty?
13
+
14
+ case snapshot.fetch(:type).to_s
15
+ when "controller_page"
16
+ ControllerPage.new(
17
+ controller_class: snapshot[:controller_class],
18
+ action: snapshot.fetch(:action),
19
+ env: snapshot.fetch(:env)
20
+ )
21
+ when "controller_turbo_frame"
22
+ ControllerTurboFrame.new(
23
+ controller_class: snapshot[:controller_class],
24
+ action: snapshot.fetch(:action),
25
+ env: snapshot.fetch(:env),
26
+ target_id: snapshot.fetch(:target_id)
27
+ )
28
+ when "template"
29
+ Template.new(
30
+ controller_class: snapshot[:controller_class],
31
+ template: snapshot.fetch(:template),
32
+ locals: Replay.value_hash_from_h(snapshot.fetch(:locals)),
33
+ assigns: Replay.value_hash_from_h(snapshot.fetch(:assigns, {}))
34
+ )
35
+ when "fragment"
36
+ Fragment.new(
37
+ controller_class: snapshot[:controller_class],
38
+ template: snapshot.fetch(:template),
39
+ locals: Replay.value_hash_from_h(snapshot.fetch(:locals)),
40
+ assigns: Replay.value_hash_from_h(snapshot.fetch(:assigns, {}))
41
+ )
42
+ when "collection"
43
+ Collection.new(
44
+ controller_class: snapshot[:controller_class],
45
+ partial: snapshot.fetch(:partial),
46
+ collection: Value.from_h(snapshot.fetch(:collection)),
47
+ options: Replay.value_hash_from_h(snapshot.fetch(:options))
48
+ )
49
+ when "collection_member"
50
+ CollectionMember.new(
51
+ controller_class: snapshot[:controller_class],
52
+ partial: snapshot.fetch(:partial),
53
+ record: Value.from_h(snapshot.fetch(:record)),
54
+ options: Replay.value_hash_from_h(snapshot.fetch(:options))
55
+ )
56
+ else
57
+ raise ArgumentError, "unknown replay payload type: #{snapshot.fetch(:type).inspect}"
58
+ end
59
+ end
60
+
61
+ def empty?
62
+ false
63
+ end
64
+ end
65
+
66
+ module Value
67
+ module_function
68
+
69
+ def from_h(snapshot)
70
+ return snapshot if snapshot.is_a?(Value)
71
+ return LiteralValue.new(value: snapshot) unless snapshot.is_a?(Hash)
72
+
73
+ snapshot = Replay.symbolize_keys(snapshot)
74
+
75
+ case snapshot.fetch(:type).to_s
76
+ when "active_record"
77
+ ActiveRecordValue.new(
78
+ model: snapshot.fetch(:model),
79
+ id: snapshot.fetch(:id)
80
+ )
81
+ when "active_record_relation"
82
+ ActiveRecordRelationValue.new(
83
+ model: snapshot.fetch(:model),
84
+ sql: snapshot.fetch(:sql),
85
+ primary_key: snapshot[:primary_key],
86
+ appendable: snapshot.fetch(:appendable),
87
+ limit_value: snapshot.fetch(:limit_value),
88
+ predicates: snapshot.fetch(:predicates),
89
+ member_ids: snapshot.fetch(:member_ids)
90
+ )
91
+ when "array"
92
+ ArrayValue.new(items: snapshot.fetch(:items).map { |item| from_h(item) })
93
+ when "hash"
94
+ HashValue.new(entries: Replay.value_hash_from_h(snapshot.fetch(:entries)))
95
+ when "literal"
96
+ LiteralValue.new(value: snapshot[:value])
97
+ when "rails_form_builder"
98
+ RailsFormBuilderValue.new(
99
+ builder_class: snapshot.fetch(:builder_class),
100
+ object_name: snapshot.fetch(:object_name),
101
+ object: Value.from_h(snapshot.fetch(:object)),
102
+ options: Replay.value_hash_from_h(snapshot.fetch(:options, {}))
103
+ )
104
+ when "unsupported"
105
+ UnsupportedValue.new(class_name: snapshot.fetch(:class))
106
+ when "refused_active_record_relation"
107
+ RefusedActiveRecordRelationValue.new(
108
+ model: snapshot.fetch(:model),
109
+ sql_digest: snapshot.fetch(:sql_digest),
110
+ reason: snapshot.fetch(:reason)
111
+ )
112
+ else
113
+ raise ArgumentError, "unknown replay value type: #{snapshot.fetch(:type).inspect}"
114
+ end
115
+ end
116
+ end
117
+
118
+ class Empty
119
+ include Payload
120
+
121
+ def empty?
122
+ true
123
+ end
124
+
125
+ def to_h
126
+ {}
127
+ end
128
+ end
129
+
130
+ class ControllerPage < Data.define(:controller_class, :action, :env)
131
+ include Payload
132
+
133
+ def type = "controller_page"
134
+
135
+ def to_h
136
+ {
137
+ type: type,
138
+ controller_class: controller_class,
139
+ action: action,
140
+ env: env
141
+ }.compact
142
+ end
143
+ end
144
+
145
+ class ControllerTurboFrame < Data.define(:controller_class, :action, :env, :target_id)
146
+ include Payload
147
+
148
+ def type = "controller_turbo_frame"
149
+
150
+ def to_h
151
+ {
152
+ type: type,
153
+ controller_class: controller_class,
154
+ action: action,
155
+ env: env,
156
+ target_id: target_id
157
+ }.compact
158
+ end
159
+ end
160
+
161
+ class Template < Data.define(:controller_class, :template, :locals, :assigns)
162
+ include Payload
163
+
164
+ def type = "template"
165
+
166
+ def to_h
167
+ payload = {
168
+ type: type,
169
+ controller_class: controller_class,
170
+ template: template,
171
+ locals: Replay.value_hash_to_h(locals)
172
+ }.compact
173
+ replay_assigns = Replay.value_hash_to_h(assigns)
174
+ payload[:assigns] = replay_assigns if replay_assigns.any?
175
+ payload
176
+ end
177
+ end
178
+
179
+ class Fragment < Data.define(:controller_class, :template, :locals, :assigns)
180
+ include Payload
181
+
182
+ def type = "fragment"
183
+
184
+ def to_h
185
+ payload = {
186
+ type: type,
187
+ controller_class: controller_class,
188
+ template: template,
189
+ locals: Replay.value_hash_to_h(locals)
190
+ }.compact
191
+ replay_assigns = Replay.value_hash_to_h(assigns)
192
+ payload[:assigns] = replay_assigns if replay_assigns.any?
193
+ payload
194
+ end
195
+ end
196
+
197
+ class Collection < Data.define(:controller_class, :partial, :collection, :options)
198
+ include Payload
199
+
200
+ def type = "collection"
201
+
202
+ def derived_partial?
203
+ partial == "derived"
204
+ end
205
+
206
+ def to_h
207
+ {
208
+ type: type,
209
+ controller_class: controller_class,
210
+ partial: partial,
211
+ collection: collection.to_h,
212
+ options: Replay.value_hash_to_h(options)
213
+ }.compact
214
+ end
215
+ end
216
+
217
+ class CollectionMember < Data.define(:controller_class, :partial, :record, :options)
218
+ include Payload
219
+
220
+ def type = "collection_member"
221
+
222
+ def to_h
223
+ {
224
+ type: type,
225
+ controller_class: controller_class,
226
+ partial: partial,
227
+ record: record.to_h,
228
+ options: Replay.value_hash_to_h(options)
229
+ }.compact
230
+ end
231
+ end
232
+
233
+ class ActiveRecordValue < Data.define(:model, :id)
234
+ include Value
235
+
236
+ def type = "active_record"
237
+
238
+ def to_h
239
+ { type: type, model: model, id: id }
240
+ end
241
+ end
242
+
243
+ class ActiveRecordRelationValue < Data.define(:model, :sql, :primary_key, :appendable, :limit_value, :predicates, :member_ids)
244
+ include Value
245
+
246
+ def type = "active_record_relation"
247
+
248
+ def appendable?
249
+ !!appendable
250
+ end
251
+
252
+ def to_h
253
+ {
254
+ type: type,
255
+ model: model,
256
+ sql: sql,
257
+ primary_key: primary_key,
258
+ appendable: appendable,
259
+ limit_value: limit_value,
260
+ predicates: predicates,
261
+ member_ids: member_ids
262
+ }
263
+ end
264
+ end
265
+
266
+ class ArrayValue < Data.define(:items)
267
+ include Value
268
+
269
+ def type = "array"
270
+
271
+ def to_h
272
+ { type: type, items: items.map(&:to_h) }
273
+ end
274
+ end
275
+
276
+ class HashValue < Data.define(:entries)
277
+ include Value
278
+
279
+ def type = "hash"
280
+
281
+ def to_h
282
+ { type: type, entries: Replay.value_hash_to_h(entries) }
283
+ end
284
+ end
285
+
286
+ class LiteralValue < Data.define(:value)
287
+ include Value
288
+
289
+ def type = "literal"
290
+
291
+ def to_h
292
+ { type: type, value: value }
293
+ end
294
+ end
295
+
296
+ class RailsFormBuilderValue < Data.define(:builder_class, :object_name, :object, :options)
297
+ include Value
298
+
299
+ def type = "rails_form_builder"
300
+
301
+ def to_h
302
+ {
303
+ type: type,
304
+ builder_class: builder_class,
305
+ object_name: object_name,
306
+ object: Value.from_h(object).to_h,
307
+ options: Replay.value_hash_to_h(options)
308
+ }
309
+ end
310
+ end
311
+
312
+ class UnsupportedValue < Data.define(:class_name)
313
+ include Value
314
+
315
+ def type = "unsupported"
316
+
317
+ def to_h
318
+ { type: type, class: class_name }
319
+ end
320
+ end
321
+
322
+ class RefusedActiveRecordRelationValue < Data.define(:model, :sql_digest, :reason)
323
+ include Value
324
+
325
+ def type = "refused_active_record_relation"
326
+
327
+ def to_h
328
+ {
329
+ type: type,
330
+ model: model,
331
+ sql_digest: sql_digest,
332
+ reason: reason
333
+ }
334
+ end
335
+ end
336
+
337
+ module_function
338
+
339
+ def payload(value)
340
+ Payload.from_h(value)
341
+ end
342
+
343
+ def value(value)
344
+ Value.from_h(value)
345
+ end
346
+
347
+ def value_hash_from_h(values)
348
+ values.to_h.each_with_object({}) do |(key, nested_value), snapshot|
349
+ snapshot[key.to_s] = Value.from_h(nested_value)
350
+ end
351
+ end
352
+
353
+ def value_hash_to_h(values)
354
+ values.to_h.each_with_object({}) do |(key, nested_value), snapshot|
355
+ snapshot[key] = Value.from_h(nested_value).to_h
356
+ end
357
+ end
358
+
359
+ def active_record_value(record)
360
+ ActiveRecordValue.new(model: record.class.name, id: record.id)
361
+ end
362
+
363
+ class Recipe
364
+ attr_reader :kind, :frame_id, :target_kind, :target_id, :template, :metadata, :runtime, :replay
365
+
366
+ def initialize(kind:, frame_id:, target_kind:, target_id:, template: nil, metadata: {}, runtime: nil, replay: nil, &renderer)
367
+ @kind = kind
368
+ @frame_id = frame_id
369
+ @target_kind = target_kind
370
+ @target_id = target_id
371
+ @template = template
372
+ @metadata = metadata
373
+ @runtime = runtime
374
+ @replay = Replay.payload(replay)
375
+ @renderer = renderer
376
+ end
377
+
378
+ def render
379
+ return @renderer.call if @renderer
380
+
381
+ runtime_renderer.render(self)
382
+ end
383
+
384
+ def render_target(target)
385
+ html = render
386
+ return html if target_match?(target)
387
+
388
+ require_relative "targeting"
389
+ Targeting::Extraction.extract_target_html(html, target)
390
+ end
391
+
392
+ def target_match?(target)
393
+ target && target.kind != "page" && target.kind == target_kind && target.id == target_id
394
+ end
395
+
396
+ def manifest_target_render?(target)
397
+ !!manifest_reference && target_match?(target)
398
+ end
399
+
400
+ def manifest_reference
401
+ metadata[:manifest] || metadata["manifest"]
402
+ end
403
+
404
+ def to_h
405
+ snapshot = {
406
+ kind: kind,
407
+ frame_id: frame_id,
408
+ target_kind: target_kind,
409
+ target_id: target_id,
410
+ template: template,
411
+ metadata: metadata
412
+ }.compact
413
+
414
+ snapshot[:runtime] = runtime if runtime
415
+ replay_snapshot = replay&.to_h
416
+ snapshot[:replay] = replay_snapshot if replay_snapshot && !replay_snapshot.empty?
417
+ snapshot
418
+ end
419
+
420
+ def self.from_h(snapshot)
421
+ snapshot = Replay.symbolize_keys(snapshot)
422
+
423
+ new(
424
+ kind: snapshot.fetch(:kind),
425
+ frame_id: snapshot.fetch(:frame_id),
426
+ target_kind: snapshot.fetch(:target_kind),
427
+ target_id: snapshot.fetch(:target_id),
428
+ template: snapshot[:template],
429
+ metadata: snapshot.fetch(:metadata),
430
+ runtime: snapshot[:runtime],
431
+ replay: snapshot.fetch(:replay, {})
432
+ )
433
+ end
434
+
435
+ private
436
+
437
+ def runtime_renderer
438
+ case runtime
439
+ when "rails"
440
+ require_relative "rails/replay"
441
+ Upkeep::Rails::Replay
442
+ else
443
+ raise "replay recipe has no renderer"
444
+ end
445
+ end
446
+ end
447
+
448
+ def symbolize_keys(value)
449
+ case value
450
+ when Hash
451
+ value.each_with_object({}) do |(key, nested_value), result|
452
+ normalized_key = key.respond_to?(:to_sym) ? key.to_sym : key
453
+ result[normalized_key] = symbolize_keys(nested_value)
454
+ end
455
+ when Array
456
+ value.map { |nested_value| symbolize_keys(nested_value) }
457
+ else
458
+ value
459
+ end
460
+ end
461
+ end
462
+ end