inertia_cable 0.1.1 → 0.2.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: 0e8633ccfd90aba7b7ae8a00203730d29755a65c1fc2f825ad9bde1225445387
4
- data.tar.gz: 43f041337383fd933d2e7a6b2b9bafa17e8876f22b0bccc9a0b48ffd3b8bd007
3
+ metadata.gz: 25d0d8cfcadeb38db163745b520998196f61fa48865253227abe0e3ee22d5686
4
+ data.tar.gz: 7f7f8affa983fa0c0a1543d1118cf67eb3abd609606b1c16fac076b8e5767ad4
5
5
  SHA512:
6
- metadata.gz: ae7f293c9a1988f85bb2589119dd9cca2752ad4a81ebabc4c3bc2e656e6ee74d6808cc1068c7c026d8a9fa3aa47b95f55cf49e31681a4612b485d2d6633988f4
7
- data.tar.gz: 239981663473b24453c8029787f2f869e9f39da6af222943b36adc1d6d58084d8c7f17fa5e5ca153e2635efe496d1c7415e2b8bdc25e59db07b7475d857d35be
6
+ metadata.gz: 504d3453823e7cf2887da87a0aa9d56839292e2d8fbe2504e7ca76648d0addafef076e2c655eb3d4a1a88d6f688c7709b60237c3c9ed320676dd9fc7a8544a9f
7
+ data.tar.gz: aba9941405aea7ca05100162c5f7555c0257a9aee672662614167b30d4d75b3e32dd418f8e24d1ce8d014e2e81c4638d3e7d2ab172714d14cc677b51f3bac3a3
data/README.md CHANGED
@@ -19,6 +19,7 @@ Inertia HTTP request → controller re-evaluates props → React re-renders
19
19
  - [Model DSL](#model-dsl)
20
20
  - [Controller Helper](#controller-helper)
21
21
  - [React Hook](#react-hook)
22
+ - [Direct Messages](#direct-messages)
22
23
  - [Suppressing Broadcasts](#suppressing-broadcasts)
23
24
  - [Server-Side Debounce](#server-side-debounce)
24
25
  - [Testing](#testing)
@@ -181,11 +182,16 @@ post.broadcast_refresh_later_to(board, debounce: 2.0)
181
182
 
182
183
  # With inline condition (block — skips broadcast if falsy)
183
184
  post.broadcast_refresh_to(board) { published? }
185
+
186
+ # Direct messages (ephemeral data, no prop reload)
187
+ post.broadcast_message_to(board, data: { progress: 50 })
188
+ post.broadcast_message_later_to(board, data: { progress: 50 })
189
+ post.broadcast_message_to(board, data: { progress: 50 }) { running? }
184
190
  ```
185
191
 
186
192
  ### Broadcast payload
187
193
 
188
- Every broadcast sends this JSON:
194
+ Refresh broadcasts send this JSON:
189
195
 
190
196
  ```json
191
197
  {
@@ -200,6 +206,17 @@ Every broadcast sends this JSON:
200
206
 
201
207
  The `action` field is `"create"`, `"update"`, or `"destroy"`. The `extra` field contains data from the `extra:` option (empty object if not set).
202
208
 
209
+ Message broadcasts send a minimal payload:
210
+
211
+ ```json
212
+ {
213
+ "type": "message",
214
+ "data": { "progress": 50, "total": 200 }
215
+ }
216
+ ```
217
+
218
+ Messages are ephemeral — no `model`, `id`, `action`, or `timestamp` fields.
219
+
203
220
  ---
204
221
 
205
222
  ## Controller Helper
@@ -234,6 +251,9 @@ const { connected } = useInertiaCable(cable_stream, {
234
251
  console.log(`${data.model} #${data.id} was ${data.action}`)
235
252
  if (data.extra?.priority === 'high') toast.warn('Priority update!')
236
253
  },
254
+ onMessage: (data) => { // receive direct messages (no reload)
255
+ setProgress(data.progress)
256
+ },
237
257
  onConnected: () => {}, // subscription connected
238
258
  onDisconnected: () => {}, // connection dropped
239
259
  debounce: 200, // client-side debounce in ms (default: 100)
@@ -246,6 +266,7 @@ const { connected } = useInertiaCable(cable_stream, {
246
266
  | `only` | `string[]` | — | Only reload these props |
247
267
  | `except` | `string[]` | — | Reload all props except these |
248
268
  | `onRefresh` | `(data) => void` | — | Callback before each reload |
269
+ | `onMessage` | `(data) => void` | — | Receive direct message data (no reload) |
249
270
  | `onConnected` | `() => void` | — | Called when subscription connects |
250
271
  | `onDisconnected` | `() => void` | — | Called when connection drops |
251
272
  | `debounce` | `number` | `100` | Debounce delay in ms |
@@ -297,7 +318,70 @@ createInertiaApp({
297
318
 
298
319
  `getConsumer()` and `setConsumer()` are also exported for low-level access to the ActionCable consumer singleton.
299
320
 
300
- TypeScript types (`RefreshPayload`, `UseInertiaCableOptions`, `UseInertiaCableReturn`, `InertiaCableProviderProps`) are exported from `@inertia-cable/react`.
321
+ TypeScript types (`RefreshPayload`, `MessagePayload`, `CablePayload`, `UseInertiaCableOptions`, `UseInertiaCableReturn`, `InertiaCableProviderProps`) are exported from `@inertia-cable/react`. `CablePayload` is a discriminated union of `RefreshPayload | MessagePayload` for type-safe handling of raw payloads.
322
+
323
+ ---
324
+
325
+ ## Direct Messages
326
+
327
+ Push ephemeral data directly into React state over the same signed stream — no prop reload, no extra hook.
328
+
329
+ ### Job progress example
330
+
331
+ ```ruby
332
+ # app/jobs/csv_import_job.rb
333
+ class CsvImportJob < ApplicationJob
334
+ def perform(import)
335
+ rows = CSV.read(import.file.path)
336
+ rows.each_with_index do |row, i|
337
+ process_row(row)
338
+ import.broadcast_message_to(import.user, data: { progress: i + 1, total: rows.size })
339
+ end
340
+ import.broadcast_refresh_to(import.user) # final reload with completed data
341
+ end
342
+ end
343
+ ```
344
+
345
+ ```tsx
346
+ import { useState } from 'react'
347
+ import { useInertiaCable } from '@inertia-cable/react'
348
+
349
+ export default function ImportShow({ import_record, cable_stream }) {
350
+ const [progress, setProgress] = useState<{ progress: number; total: number } | null>(null)
351
+
352
+ useInertiaCable(cable_stream, {
353
+ only: ['import_record'],
354
+ onMessage: (data) => setProgress({ progress: data.progress as number, total: data.total as number }),
355
+ })
356
+
357
+ return (
358
+ <div>
359
+ <h1>Import #{import_record.id}</h1>
360
+ {progress && <p>Processing {progress.progress} / {progress.total}</p>}
361
+ </div>
362
+ )
363
+ }
364
+ ```
365
+
366
+ ### Usage patterns
367
+
368
+ ```tsx
369
+ // Prop reload only (unchanged)
370
+ useInertiaCable(stream, { only: ['messages'] })
371
+
372
+ // Direct data only (no reload)
373
+ useInertiaCable(stream, {
374
+ onMessage: (data) => setProgress(data.progress)
375
+ })
376
+
377
+ // Both — progress during job, final reload on completion
378
+ useInertiaCable(stream, {
379
+ only: ['imports'],
380
+ onMessage: (data) => setProgress(data.progress)
381
+ })
382
+ ```
383
+
384
+ Messages are delivered immediately with no debouncing. Each `broadcast_message_to` call triggers exactly one `onMessage` callback.
301
385
 
302
386
  ---
303
387
 
@@ -128,6 +128,33 @@ module InertiaCable
128
128
  broadcast_refresh_later_to(self.class.model_name.plural, &block)
129
129
  end
130
130
 
131
+ # Broadcast a direct message synchronously to explicit stream(s).
132
+ #
133
+ # post.broadcast_message_to(board, data: { progress: 50 })
134
+ # post.broadcast_message_to(board, :posts, data: { progress: 50 })
135
+ # post.broadcast_message_to(board, data: { progress: 50 }) { running? }
136
+ #
137
+ def broadcast_message_to(*streamables, data:, &block)
138
+ return if self.class.suppressed_inertia_cable_broadcasts?
139
+ return if block && !instance_exec(&block)
140
+
141
+ InertiaCable.broadcast(streamables, message_payload(data: data))
142
+ end
143
+
144
+ # Broadcast a direct message asynchronously to explicit stream(s).
145
+ #
146
+ # post.broadcast_message_later_to(board, data: { progress: 50 })
147
+ # post.broadcast_message_later_to(board, :posts, data: { progress: 50 })
148
+ # post.broadcast_message_later_to(board, data: { progress: 50 }) { running? }
149
+ #
150
+ def broadcast_message_later_to(*streamables, data:, &block)
151
+ return if self.class.suppressed_inertia_cable_broadcasts?
152
+ return if block && !instance_exec(&block)
153
+
154
+ resolved = InertiaCable::Streams::StreamName.stream_name_from(streamables)
155
+ InertiaCable::BroadcastJob.perform_later(resolved, message_payload(data: data))
156
+ end
157
+
131
158
  private
132
159
 
133
160
  def resolve_stream(stream)
@@ -168,5 +195,9 @@ module InertiaCable
168
195
  payload[:extra] = extra if extra.present?
169
196
  payload
170
197
  end
198
+
199
+ def message_payload(data:)
200
+ { type: "message", data: data }
201
+ end
171
202
  end
172
203
  end
@@ -1,3 +1,3 @@
1
1
  module InertiaCable
2
- VERSION = "0.1.1"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inertia_cable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cole Robertson