turbine_rb 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rubygems"
4
+ require "bundler/setup"
5
+ require "turbine_rb"
6
+
7
+ class MyApp
8
+ def call(app)
9
+ database = app.resource(name: "demopg")
10
+
11
+ # ELT pipeline example
12
+ # records = database.records(collection: 'events')
13
+ # database.write(records: records, collection: 'events_copy')
14
+
15
+ # procedural API
16
+ records = database.records(collection: "events")
17
+
18
+ # This register the secret to be available in the turbine application
19
+ app.register_secrets("MY_ENV_TEST")
20
+
21
+ # you can also register several secrets at once
22
+ # app.register_secrets(["MY_ENV_TEST", "MY_OTHER_ENV_TEST"])
23
+
24
+ # Passthrough just has to match the signature
25
+ processed_records = app.process(records: records, process: Passthrough.new)
26
+ database.write(records: processed_records, collection: "events_copy")
27
+
28
+ # out_records = processed_records.join(records, key: "user_id", window: 1.day) # stream joins
29
+
30
+ # chaining API
31
+ # database.records(collection: "events").
32
+ # process_with(process: Passthrough.new).
33
+ # write_to(resource: database, collection: "events_copy")
34
+ end
35
+ end
36
+
37
+ # might be useful to signal that this is a special Turbine call
38
+ class Passthrough < TurbineRb::Process
39
+ def call(records:)
40
+ puts "got records: #{records}"
41
+ # to get the value of unformatted records, use record .value getter method
42
+ # records.map { |r| puts r.value }
43
+ #
44
+ # to transform unformatted records, use record .value setter method
45
+ # records.map { |r| r.value = "newdata" }
46
+ #
47
+ # to get the value of json formatted records, use record .get method
48
+ # records.map { |r| puts r.get("message") }
49
+ #
50
+ # to transform json formatted records, use record .set methods
51
+ # records.map { |r| r.set('message', 'goodbye') }
52
+ records
53
+ end
54
+ end
55
+
56
+ TurbineRb.register(MyApp.new)
@@ -0,0 +1,9 @@
1
+ {
2
+ "events": [
3
+ {
4
+ "key": "1",
5
+ "value": {"message":"hello"},
6
+ "timestamp": "1662758822"
7
+ }
8
+ ]
9
+ }
File without changes
data/lib/turbine_pb.rb ADDED
@@ -0,0 +1,100 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # source: turbine.proto
3
+
4
+ require 'google/protobuf'
5
+
6
+ require 'google/protobuf/empty_pb'
7
+ require 'google/protobuf/timestamp_pb'
8
+ require 'google/protobuf/wrappers_pb'
9
+ require 'validate/validate_pb'
10
+
11
+ Google::Protobuf::DescriptorPool.generated_pool.build do
12
+ add_file("turbine.proto", :syntax => :proto3) do
13
+ add_message "turbine_core.InitRequest" do
14
+ optional :appName, :string, 1
15
+ optional :configFilePath, :string, 2
16
+ optional :language, :enum, 3, "turbine_core.Language"
17
+ optional :gitSHA, :string, 4
18
+ optional :turbineVersion, :string, 5
19
+ end
20
+ add_message "turbine_core.GetResourceRequest" do
21
+ optional :name, :string, 1
22
+ end
23
+ add_message "turbine_core.Resource" do
24
+ optional :name, :string, 1
25
+ end
26
+ add_message "turbine_core.Collection" do
27
+ optional :name, :string, 1
28
+ optional :stream, :string, 2
29
+ repeated :records, :message, 3, "turbine_core.Record"
30
+ end
31
+ add_message "turbine_core.Record" do
32
+ optional :key, :string, 1
33
+ optional :value, :bytes, 2
34
+ optional :timestamp, :message, 3, "google.protobuf.Timestamp"
35
+ end
36
+ add_message "turbine_core.ReadCollectionRequest" do
37
+ optional :resource, :message, 1, "turbine_core.Resource"
38
+ optional :collection, :string, 2
39
+ optional :configs, :message, 3, "turbine_core.Configs"
40
+ end
41
+ add_message "turbine_core.WriteCollectionRequest" do
42
+ optional :resource, :message, 1, "turbine_core.Resource"
43
+ optional :sourceCollection, :message, 2, "turbine_core.Collection"
44
+ optional :targetCollection, :string, 3
45
+ optional :configs, :message, 4, "turbine_core.Configs"
46
+ end
47
+ add_message "turbine_core.Configs" do
48
+ repeated :config, :message, 1, "turbine_core.Config"
49
+ end
50
+ add_message "turbine_core.Config" do
51
+ optional :field, :string, 1
52
+ optional :value, :string, 2
53
+ end
54
+ add_message "turbine_core.ProcessCollectionRequest" do
55
+ optional :process, :message, 1, "turbine_core.ProcessCollectionRequest.Process"
56
+ optional :collection, :message, 2, "turbine_core.Collection"
57
+ end
58
+ add_message "turbine_core.ProcessCollectionRequest.Process" do
59
+ optional :name, :string, 1
60
+ end
61
+ add_message "turbine_core.Secret" do
62
+ optional :name, :string, 1
63
+ optional :value, :string, 2
64
+ end
65
+ add_message "turbine_core.ListResourcesResponse" do
66
+ repeated :resources, :message, 1, "turbine_core.Resource"
67
+ end
68
+ add_message "turbine_core.GetSpecRequest" do
69
+ optional :image, :string, 1
70
+ end
71
+ add_message "turbine_core.GetSpecResponse" do
72
+ optional :spec, :bytes, 1
73
+ end
74
+ add_enum "turbine_core.Language" do
75
+ value :GOLANG, 0
76
+ value :PYTHON, 1
77
+ value :JAVASCRIPT, 2
78
+ value :RUBY, 3
79
+ end
80
+ end
81
+ end
82
+
83
+ module TurbineCore
84
+ InitRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.InitRequest").msgclass
85
+ GetResourceRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.GetResourceRequest").msgclass
86
+ Resource = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Resource").msgclass
87
+ Collection = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Collection").msgclass
88
+ Record = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Record").msgclass
89
+ ReadCollectionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.ReadCollectionRequest").msgclass
90
+ WriteCollectionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.WriteCollectionRequest").msgclass
91
+ Configs = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Configs").msgclass
92
+ Config = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Config").msgclass
93
+ ProcessCollectionRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.ProcessCollectionRequest").msgclass
94
+ ProcessCollectionRequest::Process = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.ProcessCollectionRequest.Process").msgclass
95
+ Secret = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Secret").msgclass
96
+ ListResourcesResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.ListResourcesResponse").msgclass
97
+ GetSpecRequest = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.GetSpecRequest").msgclass
98
+ GetSpecResponse = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.GetSpecResponse").msgclass
99
+ Language = ::Google::Protobuf::DescriptorPool.generated_pool.lookup("turbine_core.Language").enummodule
100
+ end
@@ -0,0 +1,108 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurbineRb
4
+ module Client
5
+ class MissingSecretError < StandardError; end
6
+
7
+ class App
8
+ attr_reader :core_server
9
+
10
+ def initialize(grpc_server, is_recording: false)
11
+ @core_server = grpc_server
12
+ @is_recording = is_recording
13
+ end
14
+
15
+ def resource(name:)
16
+ req = TurbineCore::GetResourceRequest.new(name: name)
17
+ res = @core_server.get_resource(req)
18
+ Resource.new(res, self)
19
+ end
20
+
21
+ def process(records:, process:)
22
+ unwrapped_records = records.unwrap if records.instance_of?(Collection)
23
+
24
+ pr = TurbineCore::ProcessCollectionRequest::Process.new(
25
+ name: process.class.name
26
+ )
27
+
28
+ req = TurbineCore::ProcessCollectionRequest.new(collection: unwrapped_records, process: pr)
29
+ @core_server.add_process_to_collection(req)
30
+ records.pb_collection = process.call(records: records.pb_collection) unless @is_recording
31
+
32
+ records
33
+ end
34
+
35
+ # register_secrets accepts either a single string or an array of strings
36
+ def register_secrets(secrets)
37
+ [*secrets].map do |secret|
38
+ raise MissingSecretError, "secret #{secret} is not an environment variable" unless ENV.key?(secret)
39
+
40
+ req = TurbineCore::Secret.new(name: secret, value: ENV[secret])
41
+ @core_server.register_secret(req)
42
+ end
43
+ end
44
+
45
+ class Resource
46
+ attr_reader :pb_resource
47
+
48
+ def initialize(res, app)
49
+ @pb_resource = res
50
+ @app = app
51
+ end
52
+
53
+ def records(collection:, configs: nil)
54
+ req = TurbineCore::ReadCollectionRequest.new(resource: @pb_resource, collection: collection)
55
+ if configs
56
+ pb_configs = configs.keys.map { |key| TurbineCore::Config.new(field: key, value: configs[key]) }
57
+ req.configs = TurbineCore::Configs.new(config: pb_configs)
58
+ end
59
+
60
+ @app.core_server.read_collection(req).wrap(@app) # wrap in Collection to enable chaining
61
+ end
62
+
63
+ def write(records:, collection:, configs: nil)
64
+ if records.instance_of?(Collection) # it has been processed by a function, so unwrap back to gRPC collection
65
+ records = records.unwrap
66
+ end
67
+
68
+ req = TurbineCore::WriteCollectionRequest.new(resource: @pb_resource, sourceCollection: records,
69
+ targetCollection: collection)
70
+
71
+ if configs
72
+ pb_configs = configs.keys.map { |key| TurbineCore::Config.new(field: key, value: configs[key]) }
73
+ req.configs = TurbineCore::Configs.new(config: pb_configs)
74
+ end
75
+
76
+ @app.core_server.write_collection_to_resource(req)
77
+ end
78
+ end
79
+
80
+ class Collection
81
+ attr_accessor :pb_collection, :pb_stream, :name
82
+
83
+ def initialize(name, collection, stream, app)
84
+ @name = name
85
+ @pb_collection = collection
86
+ @pb_stream = stream
87
+ @app = app
88
+ end
89
+
90
+ def write_to(resource:, collection:, configs: nil)
91
+ resource.write(records: self, collection: collection, configs: configs)
92
+ end
93
+
94
+ def process_with(process:)
95
+ @app.process(records: self, process: process)
96
+ end
97
+
98
+ def unwrap
99
+ TurbineCore::Collection.new( # convert back to TurbineCore::Collection
100
+ name: name,
101
+ records: pb_collection.to_a,
102
+ stream: pb_stream
103
+ )
104
+ end
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ TurbineCore::Collection.class_eval do
4
+ def wrap(app)
5
+ TurbineRb::Client::App::Collection.new(
6
+ name,
7
+ records,
8
+ stream,
9
+ app
10
+ )
11
+ end
12
+ end
@@ -0,0 +1,129 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "hash_dot"
5
+
6
+ module TurbineRb
7
+ class Record
8
+ attr_accessor :key, :value, :timestamp
9
+
10
+ def initialize(pb_record)
11
+ @key = pb_record.key
12
+ @timestamp = pb_record.timestamp
13
+
14
+ begin
15
+ @value = JSON.parse(pb_record.value)
16
+ rescue JSON::ParserError
17
+ @value = pb_record.value
18
+ end
19
+
20
+ @value = @value.to_dot if value_hash?
21
+ end
22
+
23
+ def serialize
24
+ Io::Meroxa::Funtime::Record.new(key: @key, value: @value.to_json, timestamp: @timestamp)
25
+ end
26
+
27
+ def get(key)
28
+ if value_string? || value_array?
29
+ @value
30
+ elsif cdc_format?
31
+ @value.send("payload.after.#{key}")
32
+ else
33
+ @value.send("payload.#{key}")
34
+ end
35
+ end
36
+
37
+ def set(key, value)
38
+ if !value_hash?
39
+ @value = value
40
+ else
41
+ payload_key = cdc_format? ? "payload.after" : "payload"
42
+
43
+ begin
44
+ @value.send("#{payload_key}.#{key}")
45
+ rescue NoMethodError
46
+ schema = @value.send("schema.fields")
47
+ new_schema_field = { field: key, optional: true, type: "string" }.to_dot
48
+
49
+ if cdc_format?
50
+ schema_fields = schema.find { |f| f.field == "after" }
51
+ schema_fields.fields.unshift(new_schema_field)
52
+ else
53
+ schema.unshift(new_schema_field)
54
+ end
55
+ end
56
+
57
+ @value.send("#{payload_key}.#{key}=", value)
58
+ end
59
+ end
60
+
61
+ def unwrap!
62
+ return unless cdc_format?
63
+
64
+ payload = @value.send("payload")
65
+ schema = @value.send("schema.fields")
66
+ schema_fields = schema.find { |f| f.field == "after" }
67
+ unless schema_fields.nil?
68
+ schema_fields.delete("field")
69
+ schema_fields.name = @value.send("schema.name")
70
+ @value.send("schema=", schema_fields)
71
+ end
72
+
73
+ @value.send("payload=", payload.after)
74
+ end
75
+
76
+ private
77
+
78
+ def value_string?
79
+ @value.is_a?(String)
80
+ end
81
+
82
+ def value_array?
83
+ @value.is_a?(Array)
84
+ end
85
+
86
+ def value_hash?
87
+ @value.is_a?(Hash)
88
+ end
89
+
90
+ def json_schema?
91
+ value_hash? &&
92
+ @value.key?("payload") &&
93
+ @value.key?("schema")
94
+ end
95
+
96
+ def cdc_format?
97
+ json_schema? &&
98
+ @value.payload.key?("source")
99
+ end
100
+
101
+ def type_of_value(value)
102
+ case value
103
+ when String
104
+ "string"
105
+ when Integer
106
+ "int32"
107
+ when Float
108
+ "float32"
109
+ when true, false
110
+ "boolean"
111
+ else
112
+ "unsupported"
113
+ end
114
+ end
115
+ end
116
+
117
+ class Records < SimpleDelegator
118
+ def initialize(pb_records)
119
+ super
120
+ records = pb_records.map { |r| Record.new(r) }
121
+ __setobj__(records)
122
+ end
123
+
124
+ def unwrap!
125
+ records = __getobj__
126
+ records.each(&:unwrap!)
127
+ end
128
+ end
129
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module TurbineRb
4
+ VERSION = "0.1.0"
5
+ end
data/lib/turbine_rb.rb ADDED
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "service_services_pb"
4
+ require "turbine_services_pb"
5
+
6
+ require "turbine_rb/collection_patch"
7
+ require "turbine_rb/version"
8
+ require "turbine_rb/client"
9
+ require "turbine_rb/records"
10
+
11
+ require "optparse"
12
+ require "fileutils"
13
+
14
+ require "grpc"
15
+ require "grpc/health/v1/health_pb"
16
+ require "grpc/health/checker"
17
+
18
+ module TurbineRb
19
+ class Error < StandardError; end
20
+
21
+ class << self
22
+ attr_reader :app, :process_klass
23
+
24
+ def register(app)
25
+ @app = app
26
+ end
27
+
28
+ def register_fn(fn_klass)
29
+ @process_klass = fn_klass
30
+ end
31
+
32
+ def serve
33
+ process_function = @process_klass.new
34
+ process_function_impl = ProcessImpl.new(process_function)
35
+ function_addr = ENV["MEROXA_FUNCTION_ADDR"] ||= "0.0.0.0:50500"
36
+
37
+ @grpc_server = GRPC::RpcServer.new
38
+ @grpc_server.add_http2_port(function_addr, :this_port_is_insecure)
39
+ @grpc_server.handle(process_function_impl)
40
+ @grpc_server.handle(HealthCheck)
41
+ puts "serving function #{process_function.class.name} on #{function_addr}"
42
+ @grpc_server.run_till_terminated_or_interrupted([1, "int", "SIGQUIT"])
43
+ end
44
+
45
+ def run
46
+ app = TurbineRb::Client::App.new(init_core_server)
47
+ TurbineRb.app.call(app)
48
+ end
49
+
50
+ def record
51
+ app = TurbineRb::Client::App.new(init_core_server, is_recording: true)
52
+ TurbineRb.app.call(app)
53
+ end
54
+
55
+ def build
56
+ docker_file = File.join(__dir__, "templates", "Dockerfile")
57
+ dest_app = Dir.getwd
58
+ FileUtils.cp(docker_file, dest_app)
59
+ end
60
+
61
+ private
62
+
63
+ def init_core_server
64
+ # TODO: figure out what the deal is with :this_channel_is_insecure
65
+ core_server = TurbineCore::TurbineService::Stub.new(ENV["TURBINE_CORE_SERVER"], :this_channel_is_insecure)
66
+ git_sha = ARGV[0]
67
+
68
+ req = TurbineCore::InitRequest.new(
69
+ appName: app.class.name,
70
+ configFilePath: Dir.getwd,
71
+ language: :RUBY,
72
+ gitSHA: git_sha,
73
+ turbineVersion: Gem.loaded_specs["turbine_rb"].version.version
74
+ )
75
+
76
+ core_server.init(req)
77
+ core_server
78
+ end
79
+ end
80
+
81
+ class ProcessImpl < Io::Meroxa::Funtime::Function::Service
82
+ def initialize(process)
83
+ @process = process
84
+ super()
85
+ end
86
+
87
+ def process(request, _call)
88
+ records = TurbineRb::Records.new(request.records)
89
+
90
+ # records are processed but not in proto format
91
+ processed_records = @process.call(records: records)
92
+
93
+ # to proto
94
+ serialized_records = processed_records.map(&:serialize)
95
+
96
+ Io::Meroxa::Funtime::ProcessRecordResponse.new(records: serialized_records)
97
+ end
98
+ end
99
+
100
+ class Process
101
+ def self.inherited(subclass)
102
+ TurbineRb.register_fn(subclass)
103
+ super
104
+ end
105
+ end
106
+
107
+ class HealthCheck < Grpc::Health::V1::Health::Service
108
+ def check(req, req_view)
109
+ checker = Grpc::Health::Checker.new
110
+ checker.set_status_for_services(
111
+ Grpc::Health::V1::HealthCheckResponse::ServingStatus::SERVING,
112
+ "function"
113
+ )
114
+ checker.check(req, req_view)
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,30 @@
1
+ # Generated by the protocol buffer compiler. DO NOT EDIT!
2
+ # Source: turbine.proto for package 'turbine_core'
3
+
4
+ require 'grpc'
5
+ require 'turbine_pb'
6
+
7
+ module TurbineCore
8
+ module TurbineService
9
+ class Service
10
+
11
+ include ::GRPC::GenericService
12
+
13
+ self.marshal_class_method = :encode
14
+ self.unmarshal_class_method = :decode
15
+ self.service_name = 'turbine_core.TurbineService'
16
+
17
+ rpc :Init, ::TurbineCore::InitRequest, ::Google::Protobuf::Empty
18
+ rpc :GetResource, ::TurbineCore::GetResourceRequest, ::TurbineCore::Resource
19
+ rpc :ReadCollection, ::TurbineCore::ReadCollectionRequest, ::TurbineCore::Collection
20
+ rpc :WriteCollectionToResource, ::TurbineCore::WriteCollectionRequest, ::Google::Protobuf::Empty
21
+ rpc :AddProcessToCollection, ::TurbineCore::ProcessCollectionRequest, ::TurbineCore::Collection
22
+ rpc :RegisterSecret, ::TurbineCore::Secret, ::Google::Protobuf::Empty
23
+ rpc :HasFunctions, ::Google::Protobuf::Empty, ::Google::Protobuf::BoolValue
24
+ rpc :ListResources, ::Google::Protobuf::Empty, ::TurbineCore::ListResourcesResponse
25
+ rpc :GetSpec, ::TurbineCore::GetSpecRequest, ::TurbineCore::GetSpecResponse
26
+ end
27
+
28
+ Stub = Service.rpc_stub_class
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ # This file is a place holder, do not remove.
2
+ # The plugin used for `validate/validate.proto` does not support ruby, however
3
+ # the `require` file in the emitted code references it even though it doesn't exist.
@@ -0,0 +1,4 @@
1
+ module TurbineRb
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,112 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: turbine_rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Meroxa
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-12-01 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: grpc
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.48'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.48'
27
+ - !ruby/object:Gem::Dependency
28
+ name: hash_dot
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: 2.5.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: 2.5.0
41
+ description: |
42
+ Turbine is a data application framework for building
43
+ server-side applications that are event-driven,
44
+ respond to data in real-time, and scale using cloud-native best practices
45
+ email:
46
+ - production@meroxa.io
47
+ executables:
48
+ - turbine-function
49
+ - turbine-build
50
+ - turbine-record
51
+ - turbine-run
52
+ extensions: []
53
+ extra_rdoc_files: []
54
+ files:
55
+ - ".rspec"
56
+ - ".rubocop.yml"
57
+ - CHANGELOG.md
58
+ - CODE_OF_CONDUCT.md
59
+ - Gemfile
60
+ - Gemfile.lock
61
+ - Guardfile
62
+ - LICENSE.txt
63
+ - README.md
64
+ - Rakefile
65
+ - bin/turbine-build
66
+ - bin/turbine-function
67
+ - bin/turbine-record
68
+ - bin/turbine-run
69
+ - lib/service_pb.rb
70
+ - lib/service_services_pb.rb
71
+ - lib/templates/Dockerfile
72
+ - lib/templates/app/Gemfile
73
+ - lib/templates/app/app.json
74
+ - lib/templates/app/app.rb
75
+ - lib/templates/app/fixtures/demo.json
76
+ - lib/templates/app/ignoregit
77
+ - lib/turbine_pb.rb
78
+ - lib/turbine_rb.rb
79
+ - lib/turbine_rb/client.rb
80
+ - lib/turbine_rb/collection_patch.rb
81
+ - lib/turbine_rb/records.rb
82
+ - lib/turbine_rb/version.rb
83
+ - lib/turbine_services_pb.rb
84
+ - lib/validate/validate_pb.rb
85
+ - sig/turbine_framework.rbs
86
+ homepage: https://github.com/meroxa/turbine-core/tree/main/lib/ruby/turbine_rb
87
+ licenses:
88
+ - LicenseRef-LICENSE.txt
89
+ metadata:
90
+ homepage_uri: https://github.com/meroxa/turbine-core/tree/main/lib/ruby/turbine_rb
91
+ source_code_uri: https://github.com/meroxa/turbine-core
92
+ changelog_uri: https://github.com/meroxa/turbine-core
93
+ post_install_message:
94
+ rdoc_options: []
95
+ require_paths:
96
+ - lib
97
+ required_ruby_version: !ruby/object:Gem::Requirement
98
+ requirements:
99
+ - - ">="
100
+ - !ruby/object:Gem::Version
101
+ version: 2.6.0
102
+ required_rubygems_version: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - ">="
105
+ - !ruby/object:Gem::Version
106
+ version: '0'
107
+ requirements: []
108
+ rubygems_version: 3.3.7
109
+ signing_key:
110
+ specification_version: 4
111
+ summary: Meroxa data application framework for Ruby
112
+ test_files: []