inertia_cable 0.2.2 → 0.4.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: 77598665249ec9fc7f6d8fa69ad070661e2db15513a1720c83e937e318e4b828
4
- data.tar.gz: 388cc18e32909027f18da2b65352859299120f18984e2a1e7f15fc1e3fff905a
3
+ metadata.gz: 65880c281b47d6ea0c032a6a44adb84554f9927668d817c589b8c16c53db74a5
4
+ data.tar.gz: ae6a35e548370cce9572c6e0a404dbc527c7e2ec419996defe36f72b258aac38
5
5
  SHA512:
6
- metadata.gz: 4b9ae8be00c92656c2ce2787e2ec939028a3cebc77cb7c54adbc2acf4c673cebea5e828fae83e3e8f76bef7ee7c10e266de0eea3cdcce5ba3a3180f2705586ef
7
- data.tar.gz: 16182946b7e2f836b14aed916c937762002052c8cab9c48617d47eb3599b42e4fcb35b307a1c7c93c59f1e3a08c5f8ef979b09db46a4431a365647a252d6a4b8
6
+ metadata.gz: decce67595c86d5dc150feb10a2e505612d1d64aaaeded7eb2b22ca645c30f5e84697cbd22d5bf37304dcb71897ad3d2d34e52a26758cea81f14a37444ac188c
7
+ data.tar.gz: 7150f27fc86c68df0cb96fcfd026c8122c0cadce8e355c71b6d22c80cb4a13ef45868a1d9a4e32ddd2383fe610a08a2e72f697d8a8007737215a16c5f7d35e18
data/README.md CHANGED
@@ -52,6 +52,18 @@ Optionally run the install generator:
52
52
  rails generate inertia_cable:install
53
53
  ```
54
54
 
55
+ Version 0.4 targets **Inertia.js 3.7+ and React 19**, and ships ES modules only. Applications on Inertia 1 or 2 should stay on `@inertia-cable/react@0.2` until upgrading.
56
+
57
+ ### Upgrading from 0.3
58
+
59
+ Upgrade `@inertiajs/react` to `^3.7.0`. Existing subscriptions keep working. The new coordinator uses Inertia's built-in rest-mode polling; the Ruby broadcast protocol is unchanged and works with the 0.3 gem.
60
+
61
+ ### Upgrading from 0.2
62
+
63
+ Upgrade `@inertiajs/react` to `^3.0.0`, React and React DOM to `^19.0.0`, and `inertia_rails` to `~> 3.19` or newer compatible 3.x. Follow the [Inertia Rails v3 upgrade guide](https://inertia-rails.dev/guide/upgrade-guide) for application configuration changes. Then upgrade `inertia_cable` and `@inertia-cable/react` to 0.3.
64
+
65
+ The hook and Ruby broadcast APIs are unchanged. Use ESM `import` statements; CommonJS `require()` is no longer supported. Background refreshes now preserve existing Inertia page validation errors, including reconnect catch-up reloads.
66
+
55
67
  ## Quick Start
56
68
 
57
69
  ### 1. Model — declare what broadcasts
@@ -243,7 +255,47 @@ The token is verified server-side when the client subscribes — invalid or tamp
243
255
 
244
256
  ### `useInertiaCable(signedStreamName, options?)`
245
257
 
246
- Returns `{ connected }` a boolean indicating whether the WebSocket subscription is active.
258
+ Returns `{ connected, status, refreshing, lastRefreshedAt, refreshError }`. `status` is `disabled`, `connecting`, `connected`, `reconnecting`, or `rejected`. `lastRefreshedAt` is a completion timestamp in milliseconds, initially `null`; a connected socket alone does not prove fresh page data. `onRejected` observes subscription rejection.
259
+
260
+ The optional `refresh(context)` callback replaces the default reload. Its context contains `reason` (`broadcast` or `reconnect`), `only`, and `except`. Return a promise to track completion/failure in this hook, or return void when another coordinator owns freshness. `onRefresh(payload)` remains an observer of broadcast signals and does not replace reloads.
261
+
262
+ ### Shared refresh coordination
263
+
264
+ Use one coordinator per mounted page and share it across subscriptions:
265
+
266
+ ```tsx
267
+ import { router } from '@inertiajs/react'
268
+ import { useInertiaCable, useInertiaRefresh } from '@inertia-cable/react'
269
+
270
+ const live = useInertiaRefresh({
271
+ scope: `${site.id}:${pageUrl}`,
272
+ only: ['orders', 'shipments'],
273
+ debounce: 100,
274
+ pollInterval: 60_000,
275
+ })
276
+ useInertiaCable(orderStream, {
277
+ only: ['orders'],
278
+ debounce: 0,
279
+ refresh: ({ only }) => live.refresh(only),
280
+ onConnected: () => live.refresh(),
281
+ })
282
+ useInertiaCable(shipmentStream, {
283
+ only: ['shipments'],
284
+ debounce: 0,
285
+ refresh: ({ only }) => live.refresh(only),
286
+ })
287
+
288
+ // Route scope-changing controls through the barrier.
289
+ const selectSite = (id: number) => live.visit(() => {
290
+ router.get('/operations', { site_id: id }, { preserveState: true })
291
+ })
292
+ ```
293
+
294
+ `useInertiaRefresh({ scope, only, enabled?, debounce?, pollInterval? })` returns `refresh(keys?)`, `visit(run)`, `refreshing`, `lastRefreshedAt`, and `error`. It unions invalidated prop keys, permits one owned reload at a time, and retains a trailing refresh when signals arrive during a request. The default debounce is 100ms; polling is opt-in. Hidden tabs pause polling and catch up when visible. An optional `onConnected` catch-up also covers changes between the initial HTTP response and subscription.
295
+
296
+ Use `live`'s freshness fields when delegating with a void callback. `scope` must change with the selected site/filter. `visit(run)` cancels the coordinator's request or waits for response processing to finish before running navigation, preserving the original closure and callbacks. Automatic router listeners also yield to other visits, but cannot delay a visit already started elsewhere: route same-page scope changes through `visit`. It never cancels unrelated requests. Inertia continues to own HTTP, prop merging, form state, and polling.
297
+
298
+ For non-hook integration, `createInertiaRefresh({ only, debounce?, pollInterval? })` exposes the same `refresh`/`visit` operations plus `getSnapshot()`, `subscribe(listener)` (returns unsubscribe), and `dispose()`. Create it only in a browser lifecycle and always dispose it on scope changes/unmount. The React hook handles this lifecycle, including SSR.
247
299
 
248
300
  ```tsx
249
301
  const { connected } = useInertiaCable(cable_stream, {
@@ -515,7 +567,7 @@ InertiaCable.debounce_delay = 0.5 # server-side debounce
515
567
 
516
568
  ## Security
517
569
 
518
- Stream tokens are HMAC-SHA256 signed using `secret_key_base` and verified server-side on subscription. Invalid tokens are rejected. No data travels over the WebSocket actual data is fetched via Inertia's normal HTTP cycle, which runs through your controller and its authorization logic on every reload. Token rotation follows `secret_key_base` rotation.
570
+ Stream tokens are HMAC-SHA256 signed using `secret_key_base` and verified server-side on subscription. Invalid tokens are rejected. Refresh signals carry model metadata and any `extra` fields over the WebSocket; page props are fetched through your controller and its authorization logic on every reload. Direct messages send their `data` payload over the WebSocket without running controller authorization again. Only issue stream tokens to authorized users, and ensure every subscriber is allowed to receive the stream's metadata and direct messages. Token rotation follows `secret_key_base` rotation.
519
571
 
520
572
  ---
521
573
 
@@ -534,17 +586,9 @@ inertia_cable_stream(@post.board) # ✓ signs gid://app/Board/1
534
586
  inertia_cable_stream(@post) # ✗ signs gid://app/Post/1
535
587
  ```
536
588
 
537
- ### `only`/`except` crashes
538
-
539
- Always pass arrays, never `undefined`:
540
-
541
- ```tsx
542
- // Bad
543
- useInertiaCable(stream, { only: someCondition ? ['messages'] : undefined })
589
+ ### Reloading selected props
544
590
 
545
- // Good
546
- useInertiaCable(stream, { ...(someCondition ? { only: ['messages'] } : {}) })
547
- ```
591
+ Pass arrays for `only` and `except`. Omitted or `undefined` filters are safely ignored. Without either filter, refresh signals reload all normally included props.
548
592
 
549
593
  ### Server-side debounce not working across processes
550
594
 
@@ -560,16 +604,24 @@ config.cache_store = :redis_cache_store, { url: ENV["REDIS_URL"] }
560
604
 
561
605
  - Ruby >= 3.1
562
606
  - Rails >= 7.0 (ActionCable, ActiveJob, ActiveSupport)
563
- - Inertia.js >= 1.0 with React (`@inertiajs/react`)
607
+ - Inertia.js >= 3.7, < 4 (`@inertiajs/react`) with React and React DOM 19.x
608
+ - `inertia_rails` >= 3.19, < 4 for Inertia.js 3 applications
609
+ - An ESM-capable frontend build setup (such as Vite)
564
610
  - ActionCable configured with Redis or SolidCable (production) or async (development)
565
611
 
566
612
  ## Development
567
613
 
568
614
  ```bash
569
615
  bundle install && bundle exec rspec # Ruby specs
570
- cd frontend && npm install && npm test # Frontend
616
+ cd frontend
617
+ npm ci
618
+ npm run typecheck
619
+ npm test
620
+ npm run build
571
621
  ```
572
622
 
623
+ The integration app imports the built package through a local file dependency. After building `frontend`, run `npm ci`, `npm run check`, and `npx vite build` in `integration_test`. Rebuild the package after changing its source; use `npm run dev` in `frontend` while developing the example app.
624
+
573
625
  ## License
574
626
 
575
627
  MIT
@@ -1,3 +1,3 @@
1
1
  module InertiaCable
2
- VERSION = "0.2.2"
2
+ VERSION = "0.4.0"
3
3
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: inertia_cable
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cole Robertson
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2026-02-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: actioncable
@@ -104,7 +104,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
104
104
  - !ruby/object:Gem::Version
105
105
  version: '0'
106
106
  requirements: []
107
- rubygems_version: 3.6.2
107
+ rubygems_version: 3.6.9
108
108
  specification_version: 4
109
109
  summary: ActionCable broadcast DSL for Inertia Rails
110
110
  test_files: []