wowsql-sdk 3.1.0 → 3.9.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: 6af7e027585348a21d26fd70a40e2bfa2ccc7502ae68ee311564a4c8091fbc48
4
- data.tar.gz: 3367f7b010bb6fcea604af499cf9f60b16f0725cc0060fa37a44821e3f5e2fca
3
+ metadata.gz: dda55e2b1cbe73aabfaffa2aaadb6690c8a8978e89f712284cf9507a640004cf
4
+ data.tar.gz: c88299d7c4be38e8a121865f292d0cac3d489b5de358ea002d24afc6a6715fe6
5
5
  SHA512:
6
- metadata.gz: eb28912ac3e115c0afe97492493d5832b26cee3dd4f9f7d2e2934c733847f0766d74b32bdbe776525b0192a2a4661553df7e63591c6da05f609c6e3b2972d841
7
- data.tar.gz: c351aeaeb6113c5639fb3dff407150bdece3b12b0bd13d079123bee1d30e4d58a933cc38508485bbe190c387d2f40e8916261ac6642a3c9d3cf9cee5ba51e74a
6
+ metadata.gz: f8d0524196573928f5c92b8cac0df6a0bd6789a9fa18d1cf4c16d87ffb76fb547d51365ca2dc8bca996469584497a249ae3be7fb3fdc1b06c50a2d4c083c8ee4
7
+ data.tar.gz: 1ee24eb2e9e535fdaa3ca6478ac77407e693a68914f26466b66eeb04ca92f2c561bb9a2154f9705d1eaa7ed793da1ea89e30b267a51f20a4bab91516b1e8c8a2
data/CHANGELOG.md ADDED
@@ -0,0 +1,20 @@
1
+ # Changelog
2
+
3
+ All notable changes to the WowSQL Ruby SDK will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [3.9.0] - 2026-08-20
9
+
10
+ ### Added - Realtime
11
+
12
+ - `client.realtime.subscribe` for Postgres `INSERT` / `UPDATE` / `DELETE` (and `*`)
13
+ - WebSocket auth: `wss://<project>/realtime/v1/websocket?apikey=<anon or service_role key>`
14
+ - Auto-reconnect after disconnect; unsubscribe / disconnect clean local and server state
15
+ - `client.realtime.channel(name)` — ephemeral broadcast (`send`) and presence (`track` / `presence_state`)
16
+ - `websocket-client-simple` dependency
17
+
18
+ ### Documentation
19
+
20
+ - README realtime section
data/LICENSE CHANGED
@@ -1,22 +1,86 @@
1
- MIT License
1
+ Elastic License 2.0
2
2
 
3
- Copyright (c) 2024 WowSQL
3
+ Copyright 2026 WowSQL Team
4
4
 
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
5
+ ## Acceptance
11
6
 
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
7
+ By using the software, you agree to all of the terms and conditions below.
14
8
 
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
9
+ ## Copyright License
22
10
 
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject
14
+ to the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other
27
+ notices of the licensor in the software. Any use of the licensor's trademarks
28
+ is subject to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, practice,
34
+ import, compile, run, and execute the software. If you institute, or help
35
+ institute, patent litigation against any person over claims that your use of
36
+ the software infringes or contributes to the infringement of a patent, then
37
+ your patent license for the software ends immediately.
38
+
39
+ ## Notices
40
+
41
+ You must ensure that anyone who gets a copy of any part of the software from
42
+ you also gets a copy of these terms.
43
+
44
+ If you modify the software, you must include in any modified copies of the
45
+ software prominent notices stating that you have modified the software.
46
+
47
+ ## No Other Rights
48
+
49
+ These terms do not imply any licenses other than those expressly granted in
50
+ these terms.
51
+
52
+ ## Termination
53
+
54
+ If you use the software in violation of these terms, such use is not licensed,
55
+ and your licenses will automatically terminate. If the licensor provides you
56
+ with a notice of your violation, and you cease all violation of this license no
57
+ later than 30 days after you receive that notice, your licenses will be
58
+ reinstated retroactively. However, if you violate these terms after such
59
+ reinstatement, any additional violation of these terms will cause your licenses
60
+ to terminate automatically and permanently.
61
+
62
+ ## No Liability
63
+
64
+ As far as the law allows, the software comes as is, without any warranty or
65
+ condition, and the licensor will not be liable to you for any damages arising
66
+ out of these terms or the use or nature of the software, under any kind of
67
+ legal claim.
68
+
69
+ ## Definitions
70
+
71
+ The "licensor" is the entity offering these terms, and the "software" is the
72
+ software the licensor makes available under these terms, including any portion
73
+ of it.
74
+
75
+ "you" refers to the individual or entity agreeing to these terms.
76
+
77
+ "your company" is any legal entity, sole proprietorship, or other kind of
78
+ organization that you work for, plus all organizations that have control over,
79
+ are controlled by, or are under common control with that organization.
80
+
81
+ "your licenses" are all the licenses granted to you for the software under
82
+ these terms.
83
+
84
+ "use" means anything you do with the software requiring one of your licenses.
85
+
86
+ "trademark" means trademarks, service marks, and similar rights.
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # WowSQL Ruby SDK
2
2
 
3
- The official Ruby SDK for [WowSQL](https://wowsqlconnect.com). Provides a clean, chainable interface for all PostgREST database operations, authentication, file storage, and schema management.
3
+ The official Ruby SDK for [WowSQL](https://wowsqlconnect.com). Provides a clean, chainable interface for all PostgREST database operations, authentication, file storage, realtime, and schema management.
4
4
 
5
5
  ---
6
6
 
@@ -46,6 +46,7 @@ The official Ruby SDK for [WowSQL](https://wowsqlconnect.com). Provides a clean,
46
46
  - [upload / upload_from_path](#upload--upload_from_path)
47
47
  - [list_files / download / delete_file](#list_files--download--delete_file)
48
48
  - [get_public_url](#get_public_url)
49
+ - [Realtime](#realtime)
49
50
  - [Schema Management](#schema-management)
50
51
  - [create_table](#create_table)
51
52
  - [add_column / drop_column / rename_column](#add_column--drop_column--rename_column)
@@ -60,6 +61,7 @@ The official Ruby SDK for [WowSQL](https://wowsqlconnect.com). Provides a clean,
60
61
  - Ruby 2.7 or higher
61
62
  - `faraday` >= 1.0
62
63
  - `faraday-multipart` >= 1.0
64
+ - `websocket-client-simple` (realtime; declared in the gemspec)
63
65
 
64
66
  ---
65
67
 
@@ -593,6 +595,37 @@ puts url # https://myproject.wowsqlconnect.com/api/v1/storage/...
593
595
 
594
596
  ---
595
597
 
598
+ ## Realtime
599
+
600
+ Subscribe to INSERT / UPDATE / DELETE, broadcast ephemeral events, and track presence. Uses the **same anon or service_role key** as REST.
601
+
602
+ The SDK connects to:
603
+
604
+ ```
605
+ wss://<project>.wowsqlconnect.com/realtime/v1/websocket?apikey=<wowsql_anon_... or wowsql_service_...>
606
+ ```
607
+
608
+ Enable a table first (`POST /realtime/v1/enable` with `schema_name` and `table_name`) before postgres changes. Channel `send` / presence do **not** need a Postgres trigger. Payloads are capped at 64 KiB. Missing key closes **4001**; invalid key closes **4003** (do not reconnect). Multi-replica deployments need `REDIS_URL`.
609
+
610
+ ```ruby
611
+ unsub = client.realtime.subscribe("messages") do |change|
612
+ puts "#{change['event']} #{change['new'] || change['old']}"
613
+ end
614
+
615
+ channel = client.realtime.channel("chat")
616
+ channel.on("broadcast", event: "typing") { |msg| puts msg["payload"] }
617
+ channel.on("presence") { |msg| puts channel.presence_state }
618
+ channel.subscribe
619
+ channel.track("user" => "alice")
620
+ channel.send(event: "typing", payload: { "user" => "alice" })
621
+
622
+ unsub.call
623
+ channel.unsubscribe
624
+ client.realtime.disconnect
625
+ ```
626
+
627
+ ---
628
+
596
629
  ## Schema Management
597
630
 
598
631
  Schema operations require a **service role key** (`wowsql_service_...`).
data/lib/wowsql/client.rb CHANGED
@@ -66,6 +66,10 @@ module WOWSQL
66
66
  Table.new(self, table_name)
67
67
  end
68
68
 
69
+ def realtime
70
+ @realtime ||= WowSQLRealtime.new(@base_url, @api_key)
71
+ end
72
+
69
73
  # Close the HTTP connection (no-op, provided for API parity).
70
74
  def close
71
75
  @conn.close if @conn.respond_to?(:close)
@@ -0,0 +1,201 @@
1
+ require 'json'
2
+ require 'thread'
3
+ require 'uri'
4
+
5
+ module WOWSQL
6
+ def self.build_realtime_websocket_url(project_url, api_key)
7
+ origin = project_url.to_s.sub(%r{/$}, '')
8
+ ws = origin.sub(/\Ahttp/, 'ws')
9
+ "#{ws}/realtime/v1/websocket?apikey=#{URI.encode_www_form_component(api_key)}"
10
+ end
11
+
12
+ class RealtimeChannel
13
+ attr_reader :name
14
+
15
+ def initialize(rt, name)
16
+ @rt = rt
17
+ @name = name
18
+ @joined = false
19
+ @state = {}
20
+ @broadcast = []
21
+ @presence = []
22
+ @tracked = nil
23
+ end
24
+
25
+ def on(kind, filter = {}, &cb)
26
+ if kind.to_s == 'broadcast'
27
+ ev = (filter[:event] || filter['event'] || '*').to_s
28
+ @broadcast << ->(msg) { cb.call(msg) if ev == '*' || ev == msg['event'].to_s }
29
+ else
30
+ @presence << cb
31
+ end
32
+ self
33
+ end
34
+
35
+ def subscribe(&on_status)
36
+ @rt.ensure_connected
37
+ @rt.send_json(type: 'join', channel: @name)
38
+ @joined = true
39
+ on_status&.call('SUBSCRIBED')
40
+ self
41
+ end
42
+
43
+ def send(event:, payload: {})
44
+ @rt.ensure_connected
45
+ unless @joined
46
+ @rt.send_json(type: 'join', channel: @name)
47
+ @joined = true
48
+ end
49
+ @rt.send_json(type: 'broadcast', channel: @name, event: event, payload: payload || {})
50
+ end
51
+
52
+ def track(payload)
53
+ @tracked = payload
54
+ @rt.ensure_connected
55
+ unless @joined
56
+ @rt.send_json(type: 'join', channel: @name)
57
+ @joined = true
58
+ end
59
+ @rt.send_json(type: 'presence', event: 'track', channel: @name, payload: payload)
60
+ end
61
+
62
+ def presence_state
63
+ @state.dup
64
+ end
65
+
66
+ def unsubscribe
67
+ @rt.send_json(type: 'leave', channel: @name)
68
+ @joined = false
69
+ @rt.drop_channel(@name)
70
+ end
71
+
72
+ def rejoin
73
+ @rt.send_json(type: 'join', channel: @name)
74
+ @rt.send_json(type: 'presence', event: 'track', channel: @name, payload: @tracked) if @tracked
75
+ end
76
+
77
+ def handle_server(message)
78
+ case message['type']
79
+ when 'joined' then @joined = true
80
+ when 'presence'
81
+ ev = message['event']
82
+ if ev == 'sync' && message['state'].is_a?(Hash)
83
+ @state = message['state']
84
+ elsif ev == 'join' && message['key']
85
+ @state[message['key']] = message['payload']
86
+ elsif ev == 'leave' && message['key']
87
+ @state.delete(message['key'])
88
+ end
89
+ @presence.each { |cb| cb.call(message) }
90
+ when 'broadcast'
91
+ wrapped = { 'event' => message['event'], 'payload' => message['payload'] || {}, 'channel' => @name }
92
+ @broadcast.each { |cb| cb.call(wrapped) }
93
+ end
94
+ end
95
+ end
96
+
97
+ class WowSQLRealtime
98
+ def initialize(project_url, api_key)
99
+ @project_url = project_url.to_s.sub(%r{/$}, '')
100
+ @api_key = api_key
101
+ @ws = nil
102
+ @manual = false
103
+ @subs = []
104
+ @channels = {}
105
+ @mutex = Mutex.new
106
+ end
107
+
108
+ def url
109
+ WOWSQL.build_realtime_websocket_url(@project_url, @api_key)
110
+ end
111
+
112
+ def channel(name)
113
+ @channels[name] ||= RealtimeChannel.new(self, name)
114
+ end
115
+
116
+ def subscribe(table, schema: 'public', event: '*', &callback)
117
+ sub = { schema: schema, table: table, event: event, callback: callback }
118
+ @subs << sub
119
+ Thread.new do
120
+ ensure_connected
121
+ send_json(type: 'subscribe', schema: schema, table: table, event: event)
122
+ end
123
+ -> { unsubscribe(sub) }
124
+ end
125
+
126
+ def disconnect
127
+ @manual = true
128
+ @ws&.close
129
+ @ws = nil
130
+ end
131
+
132
+ def drop_channel(name)
133
+ @channels.delete(name)
134
+ end
135
+
136
+ def send_json(obj)
137
+ @ws&.send(JSON.dump(obj))
138
+ end
139
+
140
+ def ensure_connected
141
+ return if @ws
142
+ begin
143
+ require 'websocket-client-simple'
144
+ rescue LoadError
145
+ raise LoadError, 'Realtime requires the websocket-client-simple gem'
146
+ end
147
+ @manual = false
148
+ rt = self
149
+ @ws = WebSocket::Client::Simple.connect(url) do |ws|
150
+ ws.on :message do |msg|
151
+ rt.send(:handle, msg.data)
152
+ end
153
+ ws.on :open do
154
+ rt.send(:on_open)
155
+ end
156
+ ws.on :close do |_e|
157
+ rt.send(:on_close)
158
+ end
159
+ end
160
+ sleep 0.2
161
+ end
162
+
163
+ private
164
+
165
+ def on_open
166
+ @subs.each { |s| send_json(type: 'subscribe', schema: s[:schema], table: s[:table], event: s[:event]) }
167
+ @channels.each_value(&:rejoin)
168
+ end
169
+
170
+ def on_close
171
+ @ws = nil
172
+ ensure_connected if !@manual && (!@subs.empty? || !@channels.empty?)
173
+ end
174
+
175
+ def unsubscribe(sub)
176
+ @subs.delete(sub)
177
+ send_json(type: 'unsubscribe', schema: sub[:schema], table: sub[:table])
178
+ end
179
+
180
+ def handle(raw)
181
+ message = JSON.parse(raw)
182
+ name = message['channel']
183
+ @channels[name]&.handle_server(message) if name
184
+ return unless message['type'] == 'broadcast'
185
+ return if message['channel'] && !message['table']
186
+ nested = message['payload'].is_a?(Hash) ? message['payload'] : {}
187
+ event = (message['event'] || nested['type'] || '').to_s.upcase
188
+ schema = (message['schema'] || nested['schema'] || 'public').to_s
189
+ table = (message['table'] || nested['table'] || '').to_s
190
+ return if table.empty? || !%w[INSERT UPDATE DELETE].include?(event)
191
+ change = { 'event' => event, 'schema' => schema, 'table' => table, 'payload' => nested }
192
+ change['new'] = nested['new'] if nested.key?('new')
193
+ change['old'] = nested['old'] if nested.key?('old')
194
+ @subs.each do |s|
195
+ next unless s[:schema] == schema && s[:table] == table
196
+ next unless s[:event] == '*' || s[:event].to_s.upcase == event
197
+ s[:callback].call(change)
198
+ end
199
+ end
200
+ end
201
+ end
@@ -1,4 +1,4 @@
1
1
  module WOWSQL
2
- VERSION = "3.0.1"
2
+ VERSION = "3.9.0"
3
3
  end
4
4
 
data/lib/wowsql.rb CHANGED
@@ -6,6 +6,7 @@ require_relative 'wowsql/query_builder'
6
6
  require_relative 'wowsql/auth'
7
7
  require_relative 'wowsql/storage'
8
8
  require_relative 'wowsql/schema'
9
+ require_relative 'wowsql/realtime'
9
10
 
10
11
  module WOWSQL
11
12
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: wowsql-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.1.0
4
+ version: 3.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - WOWSQL Team
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: websocket-client-simple
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '0.3'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '0.3'
54
68
  - !ruby/object:Gem::Dependency
55
69
  name: rspec
56
70
  requirement: !ruby/object:Gem::Requirement
@@ -87,6 +101,7 @@ executables: []
87
101
  extensions: []
88
102
  extra_rdoc_files: []
89
103
  files:
104
+ - CHANGELOG.md
90
105
  - LICENSE
91
106
  - README.md
92
107
  - lib/wowmysql.rb
@@ -95,6 +110,7 @@ files:
95
110
  - lib/wowsql/client.rb
96
111
  - lib/wowsql/exceptions.rb
97
112
  - lib/wowsql/query_builder.rb
113
+ - lib/wowsql/realtime.rb
98
114
  - lib/wowsql/schema.rb
99
115
  - lib/wowsql/storage.rb
100
116
  - lib/wowsql/table.rb