-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathintrospection.rb
More file actions
132 lines (109 loc) · 4.23 KB
/
Copy pathintrospection.rb
File metadata and controls
132 lines (109 loc) · 4.23 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
# frozen_string_literal: true
#
# Example: Restate Ruby SDK — Introspection with Arel
#
# Query Restate's built-in SQL introspection API using Arel, the same
# relational algebra library that powers ActiveRecord. This gives you
# composable, type-safe queries against system tables like sys_invocation,
# sys_journal, and state — without string interpolation or injection risk.
#
# Prerequisites:
# - A running Restate server (admin API on localhost:9070)
# - At least one registered service with some invocations
#
# Usage (standalone, without Rails):
# ruby examples/introspection.rb
#
# In a Rails app, `Restate::Sys` is loaded automatically via the Railtie.
# Outside Rails, require the introspection module explicitly (requires
# activerecord in your Gemfile).
#
# If you have the full SDK loaded (native extension compiled), you can
# simply `require "restate"`. Otherwise, load just the client layer:
require 'restate/config'
require 'restate/client'
require 'restate/introspection'
module Restate
class << self
def config
@config ||= Config.new
end
def client
cfg = config
Client.new(ingress_url: cfg.ingress_url, admin_url: cfg.admin_url,
ingress_headers: cfg.ingress_headers, admin_headers: cfg.admin_headers)
end
end
end
Restate.config.admin_url = ENV.fetch('RESTATE_ADMIN', 'http://localhost:9070')
# Helper to print SQL without requiring an AR connection
def show_sql(query)
puts "SQL: #{Restate.arel_to_sql(query)}\n\n"
end
# ── Table aliases ──
i = Restate::Sys::Invocation
j = Restate::Sys::Journal
s = Restate::Sys::State
# ── Example 1: List recent invocations ──
puts '=== Recent invocations ==='
query = i.project(i[:id], i[:target_service_name], i[:target_handler_name], i[:status], i[:created_at])
.order(i[:created_at].desc)
.take(10)
show_sql(query)
rows = Restate.query(query)
rows.each do |row|
puts " #{row['id']} | #{row['target_service_name']}.#{row['target_handler_name']} | #{row['status']}"
end
# ── Example 2: Running invocations for a specific service ──
puts "\n=== Running invocations for 'Greeter' ==="
query = i.project(i[:id], i[:target_handler_name], i[:retry_count], i[:created_at])
.where(i[:target_service_name].eq('Greeter'))
.where(i[:status].eq('running'))
.order(i[:created_at].desc)
.take(20)
show_sql(query)
rows = Restate.query(query)
rows.each do |row|
puts " #{row['id']} | #{row['target_handler_name']} | retries=#{row['retry_count']}"
end
# ── Example 3: Join with journal to get input payloads ──
puts "\n=== Invocations with their input journal entry ==="
query = i.project(i[:id], i[:target], i[:status], j[:entry_json])
.join(j, Arel::Nodes::OuterJoin)
.on(j[:id].eq(i[:id]).and(j[:index].eq(0)))
.where(i[:target_service_name].eq('Greeter'))
.order(i[:created_at].desc)
.take(5)
show_sql(query)
rows = Restate.query(query)
rows.each do |row|
puts " #{row['id']} | #{row['target']} | #{row['status']}"
puts " input: #{row['entry_json']&.slice(0, 80)}..."
end
# ── Example 4: Virtual object state ──
puts "\n=== Virtual object state ==="
query = s.project(s[:service_name], s[:service_key], s[:key], s[:value_utf8])
.where(s[:service_name].eq('Counter'))
.take(20)
show_sql(query)
rows = Restate.query(query)
rows.each do |row|
puts " #{row['service_name']}/#{row['service_key']} => #{row['key']}=#{row['value_utf8']}"
end
# ── Example 5: Composable query building ──
puts "\n=== Composable queries ==="
# Build a base query and refine it conditionally
service_name = ENV.fetch('SERVICE', nil)
handler_name = ENV.fetch('HANDLER', nil)
status_filter = ENV.fetch('STATUS', nil)
query = i.project(i[:id], i[:target], i[:status], i[:created_at])
query = query.where(i[:target_service_name].eq(service_name)) if service_name
query = query.where(i[:target_handler_name].eq(handler_name)) if handler_name
query = query.where(i[:status].eq(status_filter)) if status_filter
query = query.order(i[:created_at].desc).take(10)
show_sql(query)
rows = Restate.query(query)
puts " Found #{rows.size} invocations"
rows.each do |row|
puts " #{row['id']} | #{row['target']} | #{row['status']} | #{row['created_at']}"
end