goldlapel 0.0.1.pre.rc1-aarch64-linux

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 8a2ede97de5a466d34bef7406ef0d4891eb085b2c826a9b471287323ee07e44b
4
+ data.tar.gz: c6e806544b943d0addb3bd921a31f4deea2c0af36f3e8d0693d66bea0510af22
5
+ SHA512:
6
+ metadata.gz: '0368b6e9ca5dc6685d34df7d4f28d51df78cf85f85b58f964541bc70baa07b107de4bca9d71ee4141ad5702e434c50d3fc6addc6165d2b0efb8d423049f67b73'
7
+ data.tar.gz: d844fe2fb94bc4071cef2764be5f50c936cbc31a35d47d634ec32a1cca593de09d86f1255d2ec9dd8f788d19c06fbc51673e36d69a2bf2e36578be9da9c55439
data/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Gold Lapel
2
+
3
+ Self-optimizing Postgres proxy — automatic materialized views and indexes. Zero code changes required.
4
+
5
+ Gold Lapel sits between your app and Postgres, watches query patterns, and automatically creates materialized views and indexes to make your database faster. Port 7932 (79 = atomic number for gold, 32 from Postgres).
6
+
7
+ ## Install
8
+
9
+ ```bash
10
+ gem install goldlapel
11
+ ```
12
+
13
+ Or add to your Gemfile:
14
+
15
+ ```ruby
16
+ gem "goldlapel"
17
+ ```
18
+
19
+ ## Quick Start
20
+
21
+ ```ruby
22
+ require "goldlapel"
23
+
24
+ # Start the proxy — returns a connection string pointing at Gold Lapel
25
+ url = GoldLapel.start("postgresql://user:pass@localhost:5432/mydb")
26
+
27
+ # Use the URL with any Postgres driver
28
+ require "pg"
29
+ conn = PG.connect(url)
30
+
31
+ # Or Sequel, ActiveRecord, ROM — anything that speaks Postgres
32
+ ```
33
+
34
+ Gold Lapel is driver-agnostic. `start` returns a connection string (`postgresql://...@localhost:7932/...`) that works with any Postgres driver or ORM.
35
+
36
+ ## API
37
+
38
+ ### `GoldLapel.start(upstream, port: nil, config: {}, extra_args: [])`
39
+
40
+ Starts the Gold Lapel proxy and returns the proxy connection string.
41
+
42
+ - `upstream` — your Postgres connection string (e.g. `postgresql://user:pass@localhost:5432/mydb`)
43
+ - `port` — proxy port (default: 7932)
44
+ - `config` — configuration hash (see [Configuration](#configuration) below)
45
+ - `extra_args` — additional CLI flags passed to the binary (e.g. `["--threshold-impact", "5000"]`)
46
+
47
+ ### `GoldLapel.stop`
48
+
49
+ Stops the proxy. Also called automatically on process exit.
50
+
51
+ ### `GoldLapel.proxy_url`
52
+
53
+ Returns the current proxy URL, or `nil` if not running.
54
+
55
+ ### `GoldLapel.dashboard_url`
56
+
57
+ Returns the dashboard URL (e.g. `http://127.0.0.1:7933`), or `nil` if not running. The dashboard port defaults to 7933 and can be configured via `config: { dashboard_port: 9090 }` or disabled with `dashboard_port: 0`.
58
+
59
+ ### `GoldLapel.config_keys`
60
+
61
+ Returns an array of all valid configuration key names (as strings).
62
+
63
+ ### `GoldLapel::Proxy.new(upstream, port: nil, config: {}, extra_args: [])`
64
+
65
+ Class interface for managing multiple instances:
66
+
67
+ ```ruby
68
+ proxy = GoldLapel::Proxy.new("postgresql://user:pass@localhost:5432/mydb", port: 7932)
69
+ url = proxy.start
70
+ # ...
71
+ proxy.stop
72
+ ```
73
+
74
+ ## Configuration
75
+
76
+ Pass a config hash to configure the proxy:
77
+
78
+ ```ruby
79
+ require "goldlapel"
80
+
81
+ url = GoldLapel.start("postgresql://user:pass@localhost/mydb", config: {
82
+ mode: "butler",
83
+ pool_size: 50,
84
+ disable_matviews: true,
85
+ replica: ["postgresql://user:pass@replica1/mydb"],
86
+ })
87
+ ```
88
+
89
+ Keys use `snake_case` (symbols or strings) and map to CLI flags (`pool_size` -> `--pool-size`). Boolean keys are flags -- `true` enables them. Array keys produce repeated flags.
90
+
91
+ Unknown keys raise `ArgumentError`. To see all valid keys:
92
+
93
+ ```ruby
94
+ GoldLapel.config_keys
95
+ ```
96
+
97
+ For the full configuration reference, see the [main documentation](https://github.com/goldlapel/goldlapel#setting-reference).
98
+
99
+ You can also pass raw CLI flags via `extra_args`, or set environment variables (`GOLDLAPEL_PORT`, `GOLDLAPEL_UPSTREAM`, etc.) -- the binary reads them automatically.
100
+
101
+ ## How It Works
102
+
103
+ This gem bundles the Gold Lapel Rust binary for your platform. When you call `start`, it:
104
+
105
+ 1. Locates the binary (bundled in gem, on PATH, or via `GOLDLAPEL_BINARY` env var)
106
+ 2. Spawns it as a subprocess listening on localhost
107
+ 3. Waits for the port to be ready
108
+ 4. Returns a connection string pointing at the proxy
109
+ 5. Cleans up automatically on process exit
110
+
111
+ The binary does all the work — this wrapper just manages its lifecycle.
112
+
113
+ ## Links
114
+
115
+ - [Website](https://goldlapel.com)
116
+ - [Documentation](https://github.com/goldlapel/goldlapel)
Binary file
@@ -0,0 +1,293 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require "socket"
5
+ require "rbconfig"
6
+
7
+ module GoldLapel
8
+ DEFAULT_PORT = 7932
9
+ DEFAULT_DASHBOARD_PORT = 7933
10
+ STARTUP_TIMEOUT = 10.0
11
+ STARTUP_POLL_INTERVAL = 0.05
12
+
13
+ class Proxy
14
+ attr_reader :url, :upstream, :dashboard_url
15
+
16
+ VALID_CONFIG_KEYS = %w[
17
+ mode min_pattern_count refresh_interval_secs pattern_ttl_secs
18
+ max_tables_per_view max_columns_per_view deep_pagination_threshold
19
+ report_interval_secs result_cache_size batch_cache_size
20
+ batch_cache_ttl_secs redis_url pool_size pool_timeout_secs
21
+ pool_mode mgmt_idle_timeout fallback read_after_write_secs
22
+ n1_threshold n1_window_ms n1_cross_threshold
23
+ tls_cert tls_key tls_client_ca config dashboard_port
24
+ disable_matviews disable_consolidation disable_btree_indexes
25
+ disable_trigram_indexes disable_expression_indexes
26
+ disable_partial_indexes disable_rewrite disable_prepared_cache
27
+ disable_result_cache disable_redis_cache disable_pool
28
+ disable_n1 disable_n1_cross_connection disable_shadow_mode
29
+ enable_coalescing replica exclude_tables
30
+ ].freeze
31
+
32
+ BOOLEAN_KEYS = %w[
33
+ disable_matviews disable_consolidation disable_btree_indexes
34
+ disable_trigram_indexes disable_expression_indexes
35
+ disable_partial_indexes disable_rewrite disable_prepared_cache
36
+ disable_result_cache disable_redis_cache disable_pool
37
+ disable_n1 disable_n1_cross_connection disable_shadow_mode
38
+ enable_coalescing
39
+ ].freeze
40
+
41
+ LIST_KEYS = %w[
42
+ replica exclude_tables
43
+ ].freeze
44
+
45
+ def self.config_keys
46
+ VALID_CONFIG_KEYS.dup
47
+ end
48
+
49
+ def self.config_to_args(config)
50
+ return [] if config.nil? || config.empty?
51
+
52
+ args = []
53
+ config.each do |key, value|
54
+ key = key.to_s
55
+ unless VALID_CONFIG_KEYS.include?(key)
56
+ raise ArgumentError, "Unknown config key: #{key}"
57
+ end
58
+
59
+ flag = "--#{key.tr('_', '-')}"
60
+
61
+ if BOOLEAN_KEYS.include?(key)
62
+ unless value == true || value == false
63
+ raise TypeError, "Config key '#{key}' expects a boolean, got #{value.class}"
64
+ end
65
+ args << flag if value
66
+ elsif LIST_KEYS.include?(key)
67
+ Array(value).each do |item|
68
+ args.push(flag, item.to_s)
69
+ end
70
+ else
71
+ args.push(flag, value.to_s)
72
+ end
73
+ end
74
+ args
75
+ end
76
+
77
+ def initialize(upstream, port: nil, config: {}, extra_args: [])
78
+ @upstream = upstream
79
+ @port = port || DEFAULT_PORT
80
+ @config = config
81
+ @extra_args = extra_args
82
+ @pid = nil
83
+ @url = nil
84
+ @dashboard_url = nil
85
+ @stderr_reader = nil
86
+
87
+ @dashboard_port = if config.key?(:dashboard_port) || config.key?("dashboard_port")
88
+ config.fetch(:dashboard_port, config.fetch("dashboard_port", DEFAULT_DASHBOARD_PORT)).to_i
89
+ else
90
+ DEFAULT_DASHBOARD_PORT
91
+ end
92
+ end
93
+
94
+ def start
95
+ return @url if running?
96
+
97
+ binary = self.class.find_binary
98
+ cmd = [
99
+ binary,
100
+ "--upstream", @upstream,
101
+ "--port", @port.to_s,
102
+ *self.class.config_to_args(@config),
103
+ *@extra_args,
104
+ ]
105
+
106
+ stdin, stdout, stderr, wait_thr = Open3.popen3(*cmd)
107
+ stdin.close
108
+ stdout.close
109
+ @pid = wait_thr.pid
110
+ @stderr_reader = stderr
111
+ @wait_thr = wait_thr
112
+
113
+ unless self.class.wait_for_port("127.0.0.1", @port, STARTUP_TIMEOUT)
114
+ Process.kill("KILL", @pid) rescue Errno::ESRCH
115
+ @wait_thr.join rescue nil
116
+ stderr_output = stderr.read
117
+ stderr.close
118
+ @pid = nil
119
+ @wait_thr = nil
120
+ @stderr_reader = nil
121
+ raise "Gold Lapel failed to start on port #{@port} " \
122
+ "within #{STARTUP_TIMEOUT}s.\nstderr: #{stderr_output}"
123
+ end
124
+
125
+ @stderr_reader.close
126
+ @stderr_reader = nil
127
+ @url = self.class.make_proxy_url(@upstream, @port)
128
+ @dashboard_url = @dashboard_port > 0 ? "http://127.0.0.1:#{@dashboard_port}" : nil
129
+
130
+ if @dashboard_port > 0
131
+ puts "goldlapel → :#{@port} (proxy) | http://127.0.0.1:#{@dashboard_port} (dashboard)"
132
+ else
133
+ puts "goldlapel → :#{@port} (proxy)"
134
+ end
135
+
136
+ @url
137
+ end
138
+
139
+ def stop
140
+ if @pid
141
+ begin
142
+ Process.kill("TERM", @pid)
143
+ unless @wait_thr.join(5)
144
+ Process.kill("KILL", @pid) rescue Errno::ESRCH
145
+ @wait_thr.join(5) rescue nil
146
+ end
147
+ rescue Errno::ESRCH
148
+ # Process already exited
149
+ end
150
+ @stderr_reader&.close rescue IOError
151
+ @pid = nil
152
+ @url = nil
153
+ @dashboard_url = nil
154
+ @wait_thr = nil
155
+ @stderr_reader = nil
156
+ end
157
+ end
158
+
159
+ def running?
160
+ return false unless @pid
161
+ Process.kill(0, @pid)
162
+ true
163
+ rescue Errno::ESRCH, Errno::EPERM
164
+ false
165
+ end
166
+
167
+ # --- Class-level helpers ---
168
+
169
+ def self.find_binary
170
+ # 1. Explicit override via env var
171
+ env_path = ENV["GOLDLAPEL_BINARY"]
172
+ if env_path
173
+ return env_path if File.file?(env_path)
174
+ raise "GOLDLAPEL_BINARY points to #{env_path} but file not found"
175
+ end
176
+
177
+ # 2. Bundled binary (inside the installed gem)
178
+ system_name = case RbConfig::CONFIG["host_os"]
179
+ when /linux/i then "linux"
180
+ when /darwin/i then "darwin"
181
+ when /mswin|mingw|cygwin/i then "windows"
182
+ else RbConfig::CONFIG["host_os"]
183
+ end
184
+ machine = RbConfig::CONFIG["host_cpu"]
185
+ arch = case machine
186
+ when /x86_64|amd64/i then "x86_64"
187
+ when /arm64|aarch64/i then "aarch64"
188
+ else machine
189
+ end
190
+
191
+ binary_name = "goldlapel-#{system_name}-#{arch}"
192
+ binary_name += ".exe" if system_name == "windows"
193
+ bundled = File.join(__dir__, "..", "..", "bin", binary_name)
194
+ return bundled if File.file?(bundled)
195
+
196
+ # 3. On PATH
197
+ on_path = which("goldlapel")
198
+ return on_path if on_path
199
+
200
+ raise "Gold Lapel binary not found. Set GOLDLAPEL_BINARY env var, " \
201
+ "install the platform-specific package, or ensure 'goldlapel' is on PATH."
202
+ end
203
+
204
+ def self.make_proxy_url(upstream, port)
205
+ # pg URL with explicit port
206
+ if upstream =~ /\A(postgres(?:ql)?:\/\/(?:.*@)?)([^:\/?#]+):(\d+)(.*)\z/
207
+ return "#{$1}localhost:#{port}#{$4}"
208
+ end
209
+ # pg URL without port
210
+ if upstream =~ /\A(postgres(?:ql)?:\/\/(?:.*@)?)([^:\/?#]+)(.*)\z/
211
+ return "#{$1}localhost:#{port}#{$3}"
212
+ end
213
+ # bare host:port (guard against scheme colons)
214
+ if !upstream.include?("://") && upstream.include?(":")
215
+ return "localhost:#{port}"
216
+ end
217
+ # bare host
218
+ "localhost:#{port}"
219
+ end
220
+
221
+ def self.wait_for_port(host, port, timeout)
222
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout
223
+ while Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline
224
+ begin
225
+ sock = TCPSocket.new(host, port)
226
+ sock.close
227
+ return true
228
+ rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, Errno::EHOSTUNREACH
229
+ sleep STARTUP_POLL_INTERVAL
230
+ end
231
+ end
232
+ false
233
+ end
234
+
235
+ # Module-level singleton
236
+ @instance = nil
237
+ @cleanup_registered = false
238
+
239
+ class << self
240
+ def start(upstream, port: nil, config: {}, extra_args: [])
241
+ if @instance&.running?
242
+ if @instance.upstream != upstream
243
+ raise "Gold Lapel is already running for a different upstream. " \
244
+ "Call GoldLapel.stop before starting with a new upstream."
245
+ end
246
+ return @instance.url
247
+ end
248
+
249
+ @instance = Proxy.new(upstream, port: port, config: config, extra_args: extra_args)
250
+ unless @cleanup_registered
251
+ at_exit { cleanup }
252
+ @cleanup_registered = true
253
+ end
254
+ @instance.start
255
+ end
256
+
257
+ def stop
258
+ if @instance
259
+ @instance.stop
260
+ @instance = nil
261
+ end
262
+ end
263
+
264
+ def proxy_url
265
+ @instance&.url
266
+ end
267
+
268
+ def dashboard_url
269
+ @instance&.dashboard_url
270
+ end
271
+
272
+ private
273
+
274
+ def cleanup
275
+ if @instance
276
+ @instance.stop
277
+ @instance = nil
278
+ end
279
+ end
280
+
281
+ def which(cmd)
282
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
283
+ (ENV["PATH"] || "").split(File::PATH_SEPARATOR).each do |path|
284
+ exts.each do |ext|
285
+ full = File.join(path, "#{cmd}#{ext}")
286
+ return full if File.executable?(full) && File.file?(full)
287
+ end
288
+ end
289
+ nil
290
+ end
291
+ end
292
+ end
293
+ end
data/lib/goldlapel.rb ADDED
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "goldlapel/proxy"
4
+
5
+ module GoldLapel
6
+ # Module-level convenience methods (singleton pattern)
7
+ def self.start(upstream, port: nil, config: {}, extra_args: [])
8
+ Proxy.start(upstream, port: port, config: config, extra_args: extra_args)
9
+ end
10
+
11
+ def self.stop
12
+ Proxy.stop
13
+ end
14
+
15
+ def self.proxy_url
16
+ Proxy.proxy_url
17
+ end
18
+
19
+ def self.dashboard_url
20
+ Proxy.dashboard_url
21
+ end
22
+
23
+ def self.config_keys
24
+ Proxy.config_keys
25
+ end
26
+ end
metadata ADDED
@@ -0,0 +1,50 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: goldlapel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1.pre.rc1
5
+ platform: aarch64-linux
6
+ authors:
7
+ - Stephen Gibson
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2026-03-15 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: Gold Lapel sits between your app and Postgres, watches query patterns,
14
+ and automatically creates materialized views and indexes to make your database faster.
15
+ Zero code changes required.
16
+ email:
17
+ executables: []
18
+ extensions: []
19
+ extra_rdoc_files: []
20
+ files:
21
+ - README.md
22
+ - bin/goldlapel-linux-aarch64
23
+ - lib/goldlapel.rb
24
+ - lib/goldlapel/proxy.rb
25
+ homepage: https://goldlapel.com
26
+ licenses:
27
+ - Proprietary
28
+ metadata:
29
+ homepage_uri: https://goldlapel.com
30
+ source_code_uri: https://github.com/goldlapel/goldlapel-ruby
31
+ post_install_message:
32
+ rdoc_options: []
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: 3.2.0
40
+ required_rubygems_version: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - ">"
43
+ - !ruby/object:Gem::Version
44
+ version: 1.3.1
45
+ requirements: []
46
+ rubygems_version: 3.4.20
47
+ signing_key:
48
+ specification_version: 4
49
+ summary: Self-optimizing Postgres proxy — automatic materialized views and indexes
50
+ test_files: []