inertia_rails_ssr_cache 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 71e26cafbbad0fef59528cf35fa5094b095a40b1488abfc0a229415e3cc697b0
4
+ data.tar.gz: a75e0b616adfad90ac03b1161c3a3a6173b8fbbaadba928a05a6439d017bcd18
5
+ SHA512:
6
+ metadata.gz: b998c678608716c0dad23b2c52707569523b4b7c87f650ba5972c00add0d1337570c7f81a9587fda8191117d09056365bc82234d13f3013063f3fcfbef34f0a1
7
+ data.tar.gz: 72bfd354ae73778c477a42d87dae24df7c93a3b20738e383a32bef2a164be1819f6762ee24c3bfae65caa3351bf17f84d4508ef2c8de774dbbd43ebf7b31aad7
data/LICENSE.md ADDED
@@ -0,0 +1,319 @@
1
+ # The Inertia Rails SSR Cache License
2
+
3
+ The Inertia Rails SSR Cache License, version 1.0, an application of ShakaCode
4
+ Trust-Based Commercial Licensing.
5
+
6
+ Copyright (c) 2026 ShakaCode LLC, a Hawaii limited liability company
7
+ ("ShakaCode").
8
+
9
+ SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
10
+
11
+ ## Plain-language summary (informative, not part of the terms)
12
+
13
+ Inertia Rails SSR Cache is trust-based: there is no license key, nothing
14
+ phones home, and nothing in the software blocks, slows, or changes how
15
+ your app runs based on license status. Reading the source, development,
16
+ test, CI, staging, review apps, education, personal projects, and
17
+ open-source work are free for everyone, at any organization size.
18
+ Production use is also free if you can answer yes to all three
19
+ questions: *Do fewer than 10 people work for your organization (counting
20
+ all affiliates)? Was its revenue in the last twelve months under US $1M?
21
+ Has it raised under US $1M in outside capital, ever?* Charities,
22
+ educational institutions, and hospitals are free at any size. If any
23
+ answer is no and you are none of those, production use requires a
24
+ qualifying subscription: an active ShakaStack Pro subscription, which
25
+ includes React on Rails Pro, covers organizations under 250 people and
26
+ under US $10M revenue, and covers the whole organization: every
27
+ application, environment, and developer. Larger organizations need a
28
+ separate agreement. See https://pro.reactonrails.com/. Once a version
29
+ ships under these terms, those terms govern that version permanently.
30
+
31
+ ## 1. Acceptance
32
+
33
+ By using the software, you agree to these terms. If you use the software
34
+ on behalf of an organization, you represent that you have authority to
35
+ bind that organization, and "you" includes it.
36
+
37
+ ## 2. Definitions
38
+
39
+ **"Software"** means Inertia Rails SSR Cache: the
40
+ `inertia_rails_ssr_cache` Ruby gem, the `inertia-rails-ssr-cache` npm
41
+ package, and any other component ShakaCode distributes under these terms.
42
+ Inertia Rails, the Inertia.js adapters, and other dependencies that the
43
+ software works with are licensed separately by their own authors.
44
+
45
+ **"Control"** means ownership of more than 50% of the voting interests of
46
+ an entity, or the power to direct its management or policies, by contract
47
+ or otherwise.
48
+
49
+ **"Your organization"** means you together with every entity that Controls
50
+ you, that you Control, or that is under common Control with you. Teams,
51
+ departments, and other sub-units are not separate organizations.
52
+
53
+ **"Small Organization"** means an organization for which all of the
54
+ following are true, counted across the entire organization as defined
55
+ above:
56
+
57
+ a. fewer than 10 individuals perform paid work for it or on its products
58
+ and services, whether directly or through staffing, outsourcing, or
59
+ services arrangements, counted as full-time equivalents;
60
+ b. its total gross revenue, measured over the twelve months ending with
61
+ the most recently completed calendar quarter, was less than
62
+ US $1,000,000; and
63
+ c. it has received less than US $1,000,000 in aggregate outside capital
64
+ over its lifetime (equity, tokens, convertible instruments, venture or
65
+ investor debt, accelerator funding, and grants or donations, other than
66
+ academic research or charitable grants to accredited academic or
67
+ nonprofit institutions) from sources other than its own operating
68
+ revenue and its founders' personal funds. Ordinary-course bank and
69
+ trade credit do not count.
70
+
71
+ Government entities, other than public educational institutions, are not
72
+ Small Organizations.
73
+
74
+ **"Charity"** means an organization recognized as charitable or
75
+ public-benefit under the law of the jurisdiction where it is organized (for
76
+ example, a US 501(c)(3) organization or its equivalent elsewhere), no part
77
+ of whose net earnings benefits any private person.
78
+
79
+ **"Educational Institution"** means a school, college, university, or
80
+ other institution whose principal purpose is teaching or academic
81
+ research, whether public or private, including its libraries, research
82
+ institutes, and teaching hospitals.
83
+
84
+ **"Hospital"** means an institution licensed to provide inpatient
85
+ medical care to the public, whether public, non-profit, or private,
86
+ including its clinics and medical research units.
87
+
88
+ Charity, Educational Institution, and Hospital status belongs to the
89
+ qualifying entity and to affiliates that qualify on their own; it does
90
+ not extend to for-profit affiliates, spin-offs, or commercial ventures
91
+ that are not themselves one of these.
92
+
93
+ **"Production use"** means running the software to serve an application,
94
+ website, or service to its end users, whether those users are the public,
95
+ your customers, or your own staff. Development, test, continuous
96
+ integration, staging, preview, review-app, and other non-production
97
+ environments are not production use.
98
+
99
+ **"Subscription"** means an active paid subscription that ShakaCode
100
+ designates as including the software: a ShakaStack Pro subscription
101
+ (which includes React on Rails Pro) held by an organization that has
102
+ fewer than 250 individuals counted as in clause (a) of the Small
103
+ Organization definition and less than US $10,000,000 in total gross
104
+ revenue measured as in clause (b), purchased at
105
+ https://pro.reactonrails.com/ or through another order channel ShakaCode
106
+ designates; or a separate written agreement with ShakaCode that covers
107
+ the software. Any other organization needs such a separate written
108
+ agreement. Sponsorships and donations that ShakaCode has not designated
109
+ as subscriptions are appreciated but are not Subscriptions and grant no
110
+ production-use rights.
111
+
112
+ ## 3. Copyright grant
113
+
114
+ ShakaCode grants you a non-exclusive, worldwide, royalty-free license to
115
+ use, copy, modify, and create derivative works of the software, and to
116
+ include it in the builds and deployments of your organization's own
117
+ applications, in each case only as allowed by these terms. You may share
118
+ copies with contractors and service providers working on those
119
+ applications, who must observe these terms. You may not distribute the
120
+ software or your modifications as a standalone product or library, or as
121
+ part of a product whose purpose is to provide the software's
122
+ functionality to others (see Section 7).
123
+
124
+ ## 4. Free uses
125
+
126
+ The following uses are free of charge for anyone, at any organization
127
+ size:
128
+
129
+ a. reading and studying the source code;
130
+ b. development, test, continuous integration, staging, preview,
131
+ review-app, and every other non-production environment, without time
132
+ limit;
133
+ c. evaluating the software in production for up to 45 days per
134
+ organization (not per version, person, or application), solely to
135
+ decide whether to adopt it;
136
+ d. education and training of people, tutorials, demonstrations, and
137
+ academic research, in each case not directed at developing or
138
+ operating your organization's own products or services;
139
+ e. personal and hobby projects;
140
+ f. developing and operating a public open-source project distributed
141
+ under a license approved by the Open Source Initiative, where the
142
+ software runs as part of that project's own publicly available code
143
+ and primarily for that project's benefit, and not for the development
144
+ or operation of a proprietary product or service.
145
+
146
+ In addition, **all production use is free of charge for a Small
147
+ Organization, a Charity, an Educational Institution, or a Hospital, at
148
+ any size for the last three.** If your organization is one of these,
149
+ nothing in Section 5 applies to you.
150
+
151
+ ## 5. Paid uses
152
+
153
+ Production use by an organization that is not a Small Organization, a
154
+ Charity, an Educational Institution, or a Hospital requires a
155
+ Subscription. This includes, without limitation, production use beyond
156
+ the Section 4(c) evaluation window, internal applications used only by
157
+ your staff, and applications you operate for your customers.
158
+
159
+ In any conflict between Section 4 and this Section 5, use described in
160
+ this Section 5 is production use and is not a free use under Section
161
+ 4(b), (c), or (d).
162
+
163
+ **Client work.** If you use the software to build or run an application
164
+ for a client, the use is attributed to the organization whose business
165
+ the application ultimately serves, traced through any intermediaries.
166
+ That organization's status determines whether the use is free or paid,
167
+ and any required Subscription must be held by, or purchased for the
168
+ benefit of and identifying, that organization. You may rely on a client's
169
+ written self-certification of its status. A consulting firm's or agency's
170
+ own applications are attributed to the firm or agency.
171
+
172
+ ## 6. Subscriptions
173
+
174
+ One Subscription covers your whole organization: every application,
175
+ environment, server, and developer of your organization, with no per-seat
176
+ or per-application charge. A Subscription is governed by the subscription
177
+ terms, order form, or other agreement under which it is purchased; if
178
+ that agreement conflicts with these terms, that agreement controls. While
179
+ a Subscription is active, ShakaCode provides software updates and
180
+ reasonable support through its published channels; no response or
181
+ resolution times are guaranteed unless an order or separate agreement
182
+ says so. If a required Subscription expires or is terminated, your
183
+ production use remains licensed for 30 days while you renew or wind down,
184
+ and the software keeps running either way. ShakaCode and you may enter a
185
+ separate written agreement that supplements or supersedes these terms.
186
+
187
+ ## 7. No hosted or embedded offering
188
+
189
+ You may not provide the software or its cache-warming functionality to third
190
+ parties as a hosted, managed, embedded, or bundled offering, for example
191
+ a platform that builds or runs other organizations' Inertia Rails
192
+ applications, without a separate written agreement with ShakaCode. Each
193
+ organization whose applications use the software must independently
194
+ qualify for free use or hold its own Subscription. Running your own
195
+ applications for your own users, customers, or staff is not an offering
196
+ to third parties.
197
+
198
+ ## 8. Trust, self-certification, and true-up
199
+
200
+ These terms are enforced by trust, not technology: the software contains
201
+ no license key, no activation, no usage telemetry, and no runtime
202
+ lockout. You are responsible for determining in good faith whether your
203
+ use is free or paid.
204
+
205
+ If your organization stops qualifying as a Small Organization, your
206
+ production use remains licensed for 60 days after the change; after that,
207
+ continued production use requires a Subscription. No payment is owed for
208
+ use during that window, and the obligation is not avoided by
209
+ re-qualification within 12 months of the change.
210
+
211
+ If you discover in good faith that past use required a Subscription,
212
+ purchasing the applicable Subscription within 60 days of discovery is
213
+ full compliance, and no back payment is owed for the good-faith period.
214
+ If your organization made production use requiring a Subscription
215
+ without a good-faith basis for treating it as free, ShakaCode may require
216
+ payment of the applicable subscription fees for the period of that use,
217
+ up to 24 months.
218
+
219
+ You must not misrepresent your organization, its size or status, or your
220
+ license status.
221
+
222
+ ## 9. Redistribution and notices
223
+
224
+ Copies of the software and derivative works that these terms allow you to
225
+ make must remain under these same terms and must retain these terms, all
226
+ copyright and attribution notices, and the SPDX identifier.
227
+
228
+ ## 10. Patents
229
+
230
+ ShakaCode grants you a license under any patent claims it can license that
231
+ are necessarily infringed by the software, only for uses allowed by these
232
+ terms. This patent license ends immediately if you or your organization
233
+ initiate a patent claim (excluding counterclaims and cross-claims filed in
234
+ response to a patent claim first brought against you) alleging that the
235
+ software infringes a patent.
236
+
237
+ ## 11. Trademarks
238
+
239
+ These terms grant no rights in the names, logos, or trademarks of
240
+ ShakaCode or Inertia Rails SSR Cache, except for truthful factual
241
+ references.
242
+
243
+ ## 12. Per-version terms, breach, and cure
244
+
245
+ These terms apply per version: the terms delivered with a version of the
246
+ software govern that version permanently, and patch and security releases
247
+ of a version line remain under that line's terms. Later versions may be
248
+ offered under different terms.
249
+
250
+ If you breach these terms, your license ends automatically 30 days after
251
+ ShakaCode provides written notice of the breach, unless you cure it within
252
+ that period; upon cure, your license is reinstated retroactively. Curing
253
+ continued production use during a lapsed Subscription includes payment of
254
+ the applicable fees for the lapsed period.
255
+
256
+ ## 13. Feedback
257
+
258
+ If you submit feedback, suggestions, or improvements to ShakaCode, you
259
+ grant ShakaCode a worldwide, royalty-free, irrevocable license to use and
260
+ incorporate them.
261
+
262
+ ## 14. Disclaimer and limitation of liability
263
+
264
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
265
+ IMPLIED, INCLUDING WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
266
+ PURPOSE, TITLE, AND NON-INFRINGEMENT. TO THE MAXIMUM EXTENT PERMITTED BY
267
+ LAW, SHAKACODE WILL NOT BE LIABLE FOR ANY INDIRECT, SPECIAL, INCIDENTAL,
268
+ OR CONSEQUENTIAL DAMAGES ARISING FROM OR RELATED TO THE SOFTWARE OR THESE
269
+ TERMS, AND SHAKACODE'S TOTAL LIABILITY WILL NOT EXCEED THE AMOUNTS YOU
270
+ PAID SHAKACODE FOR THE SOFTWARE IN THE TWELVE MONTHS BEFORE THE CLAIM
271
+ AROSE.
272
+
273
+ ## 15. Assignment, governing law, and miscellaneous
274
+
275
+ You may assign these terms, together with any active Subscription, to a
276
+ successor in connection with a merger, acquisition, or sale of
277
+ substantially all assets, upon notice to ShakaCode; these terms are
278
+ otherwise non-transferable. These terms are governed by the laws of the
279
+ State of Hawaii, USA, excluding its conflict-of-law rules, with exclusive
280
+ jurisdiction and venue in the state and federal courts located in
281
+ Honolulu, Hawaii. If a provision of these terms is unenforceable, the
282
+ remainder stays in effect. These terms, together with any subscription
283
+ agreement or order, are the entire agreement about the software; if they
284
+ conflict, the order controls.
285
+
286
+ ## 16. Questions and commercial licensing
287
+
288
+ contact@shakacode.com · https://pro.reactonrails.com/ ·
289
+ https://github.com/shakacode/commercial-licensing
290
+
291
+ ## Machine-readable summary (informative)
292
+
293
+ ```yaml
294
+ license_name: The Inertia Rails SSR Cache License 1.0
295
+ spdx_identifier: LicenseRef-InertiaRailsSsrCache
296
+ type: trust-based commercial
297
+ free_production_use_if_all:
298
+ max_org_headcount_fte: 10 # exclusive; whole control group
299
+ max_ttm_revenue_usd: 1000000 # exclusive; trailing 12 months
300
+ max_lifetime_outside_capital_usd: 1000000 # exclusive
301
+ free_production_use_if_any:
302
+ - charity # recognized charitable / public-benefit org, any size
303
+ - educational_institution # school, college, university, any size
304
+ - hospital # licensed inpatient care provider, any size
305
+ always_free: [read, study, development, test, ci, staging, preview, review_apps, evaluate_in_production_45_days, education, personal, oss_projects]
306
+ paid: production use by any other organization; included in an active ShakaStack Pro subscription for organizations under 250 people and under USD 10M revenue; otherwise a separate written agreement
307
+ license_key: none
308
+ runtime_enforcement: none # no activation, no telemetry, no lockout
309
+ attribution_required: false
310
+ subscription_lapse_grace_days: 30
311
+ outgrow_free_line_grace_days: 60
312
+ per_version_terms: true
313
+ pricing_url: https://pro.reactonrails.com/
314
+ contact: contact@shakacode.com
315
+ ```
316
+
317
+ ## Version history (informative)
318
+
319
+ Version 1.0 is the first license under which the software is distributed.
data/README.md ADDED
@@ -0,0 +1,196 @@
1
+ # Inertia Rails SSR Cache
2
+
3
+ Warm the server-side rendering (SSR) cache of an Inertia Rails app after each
4
+ deploy, so the first visitors to your public pages get cached SSR HTML instead of
5
+ waiting for Node to render it.
6
+
7
+ You list the public paths to warm. One Active Job job requests each path the way an
8
+ anonymous visitor would and checks that a second, fresh request reuses the same
9
+ cache entry Inertia wrote. Inertia keeps owning page serialization, cache keys and
10
+ cache writes; this gem only drives and verifies them. A small npm companion,
11
+ `guardRelease`, wraps your Node SSR callback so a page from one release is never
12
+ rendered by another release's bundle.
13
+
14
+ Version 0.1.0 is an early release. Run the
15
+ [adopter pilot](docs/releasing.md#adopter-pilot-before-production-use) with your
16
+ own routes, cache topology and load before enabling warming in production.
17
+
18
+ Works with any Active Job backend; there is no Sidekiq requirement. GQL LRU warming
19
+ is out of scope.
20
+
21
+ ## What is tested
22
+
23
+ - Ruby 3.3+, Rails 7.2–8.1 and `inertia_rails` 3.22.x.
24
+ - The Rails integration suite runs against Inertia React 3.9.1, including reuse
25
+ of a FileStore cache from a separate web process.
26
+ - The `guardRelease` callback type is checked against the `createServer`
27
+ callbacks of Inertia React, Vue 3 and Svelte 3.9.1. A Node test renders a Vue 3
28
+ page through the guard. Svelte is checked for types only.
29
+ - Node 22.18+; the npm package is ESM only.
30
+
31
+ ## Installation
32
+
33
+ ```ruby
34
+ # Gemfile
35
+ gem "inertia_rails_ssr_cache", "~> 0.1.0"
36
+ ```
37
+
38
+ ```sh
39
+ bundle install
40
+ npm install inertia-rails-ssr-cache@0.1.0
41
+ ```
42
+
43
+ Install the same version of both packages.
44
+
45
+ ## Application setup
46
+
47
+ Configure Inertia normally. Use an immutable release token embedded in the SSR
48
+ bundle and a retaining cache store shared by warming workers and web processes.
49
+ Warming requires `config.enable_reloading = false` and
50
+ `config.allow_concurrency` must not be `false`. Enqueueing and execution reject
51
+ these unsupported settings: nested requests can deadlock against the surrounding
52
+ job's Rails reload or concurrency locks.
53
+
54
+ ```ruby
55
+ # config/initializers/inertia.rb
56
+ InertiaRails.configure do |config|
57
+ config.ssr_enabled = true
58
+ config.ssr_cache = { expires_in: 1.hour }
59
+ config.version = ENV.fetch("APP_RELEASE")
60
+ end
61
+
62
+ # config/initializers/inertia_rails_ssr_cache.rb
63
+ InertiaRailsSsrCache.configure do |config|
64
+ config.origin = "https://www.example.com"
65
+ config.paths = ["/", "/pricing", "/features"]
66
+ config.queue_name = :ssr_warming
67
+ end
68
+ ```
69
+
70
+ Wrap your existing Node SSR callback:
71
+
72
+ ```typescript
73
+ import { guardRelease } from 'inertia-rails-ssr-cache';
74
+ import createServer from '@inertiajs/react/server';
75
+
76
+ // Replace with a literal or bundler-defined value embedded during the build.
77
+ createServer(guardRelease('build-42', renderPage));
78
+ ```
79
+
80
+ The embedded value must equal Rails' `APP_RELEASE`. A runtime environment value
81
+ alone cannot identify which JavaScript was built. Deploy with a fresh version
82
+ when first adding the guard; cache hits do not execute the callback, so it cannot
83
+ certify old entries written by an unguarded renderer.
84
+
85
+ After the candidate Rails workers, SSR service and database are ready:
86
+
87
+ ```ruby
88
+ job_id = InertiaRailsSsrCache.warm_later
89
+ ```
90
+
91
+ Or enqueue through the Rails task:
92
+
93
+ ```sh
94
+ bin/rails inertia_rails_ssr_cache:warm
95
+ ```
96
+
97
+ The returned job ID confirms enqueueing, not completion. Monitor normal Active
98
+ Job failures and `target.inertia_rails_ssr_cache` / `warm.inertia_rails_ssr_cache`
99
+ notifications. Target results report a reason such as `reused`,
100
+ `missing_cache_evidence`, `cache_key_changed`, `cache_not_reused`, `http_failure`,
101
+ `release_mismatch` or `invalid_ssr_value`. A failed run raises
102
+ `InertiaRailsSsrCache::WarmFailed` with its report. Events contain target/result
103
+ metadata, not page props or HTML.
104
+
105
+ ## What verification means
106
+
107
+ Each job processes at most 50 distinct registered paths sequentially. Each path
108
+ gets two fresh anonymous HTML GETs through the full Rails middleware stack,
109
+ without credentials, cookie carry-over, Inertia visit headers or redirect
110
+ following. The synthetic Rack environment uses source address `192.0.2.1` and no
111
+ User-Agent header. Both responses must be HTTP 200 HTML. Each must produce exactly one
112
+ native SSR cache-fetch observation, and the second must hit the first one's key.
113
+ The retained entry must also contain a string body and an array of string head
114
+ entries; cached null/false values do not qualify.
115
+
116
+ This catches normal client-rendering fallback, disabled/nonretaining caches and
117
+ unstable page data. Success describes observed reuse at that moment; eviction,
118
+ expiry and future request variants can still cause misses. The application must
119
+ approve safe public GETs and deterministic anonymous props that do not vary by
120
+ client IP, geolocation or browser identity. Context-dependent
121
+ Inertia versions are unsupported: worker and controller versions must agree.
122
+
123
+ A native SSR hit avoids Node rendering. Controllers, queries and prop resolution
124
+ still execute. This package makes no database-cache or production latency claim.
125
+
126
+ ## Operational limits
127
+
128
+ - Concurrency is one request per job. Multiple jobs can overlap; configure a
129
+ dedicated queue's concurrency in your Active Job backend.
130
+ - Each request uses a fresh joined thread to preserve the surrounding job's
131
+ Rails executor and CurrentAttributes. Allow for an additional database
132
+ connection if job callbacks retain one while waiting for the request.
133
+ - Route count is bounded, but execution has no hard wall-clock deadline.
134
+ Inertia Rails 3.22 uses its HTTP client's timeouts. The warmer does not interrupt
135
+ Rails requests with asynchronous Ruby timeouts. If the host interrupts a job,
136
+ it waits for its active request to finish before unwinding; shutdown can be
137
+ delayed by the remaining request duration.
138
+ - Readiness, migrations, replica lag, cutover and cancellation remain deployment
139
+ responsibilities. No scheduler, distributed lock or durable run registry is
140
+ installed. Retrying repeats eligible GETs and reuses entries already present.
141
+ - Queued jobs carry their intended release and recheck it at execution. Rollbacks
142
+ use the matching guarded bundle and immutable Inertia version.
143
+ - Worker and web cache topology must match. MemoryStore only warms one process;
144
+ the integration suite proves reuse with FileStore across separate processes.
145
+ - Native cache instrumentation compatibility is intentionally limited to the
146
+ tested Inertia Rails 3.22 line. A future adapter upgrade needs contract tests.
147
+ - Cache namespaces must be absent or static strings. Callable namespaces and
148
+ custom key encodings cannot be verified. FileStore support covers the built-in
149
+ class with keys that fit in one encoded filename. Longer keys and subclasses
150
+ are unsupported and can fail verification even if native caching works.
151
+
152
+ ## Development
153
+
154
+ Ruby 3.3+, Rails 7.2–8.1, `inertia_rails` 3.22.x, Node 22.18+ and the pnpm version
155
+ pinned in `package.json`. The Rails integration fixture and demo use Inertia React
156
+ 3.9.1. The npm guard is also type-checked against Inertia Vue 3 and Svelte 3.9.1,
157
+ and renders a Vue 3 page in a Node test; Svelte has no runtime test.
158
+
159
+ ```sh
160
+ bin/setup
161
+ bin/validate
162
+ bin/audit
163
+ ```
164
+
165
+ `bin/validate` runs behavior, type, lint, workflow and package checks. `bin/audit`
166
+ checks dependency advisories and requires network access. See the manifests and
167
+ [contribution guide](CONTRIBUTING.md) for the exact tools and commands.
168
+
169
+ ## Runnable demo
170
+
171
+ Run `bin/demo` after `bin/setup` to start a small Inertia Rails app with React
172
+ SSR, a Solid Queue Active Job worker and a shared FileStore cache. Run
173
+ `bin/demo verify` for an isolated proof that warming completes and a separate
174
+ web process reuses the same native SSR entry. See the
175
+ [demo guide](examples/demo/README.md) for commands, configuration and limits.
176
+
177
+ See [architecture](docs/architecture.md), [source research](docs/grounding.md),
178
+ [design comparison](docs/design-comparison.md), the [changelog](CHANGELOG.md) and
179
+ the [release procedure](docs/releasing.md).
180
+
181
+ ## License
182
+
183
+ Inertia Rails SSR Cache uses ShakaCode Trust-Based Commercial Licensing, under
184
+ [The Inertia Rails SSR Cache License](LICENSE.md).
185
+
186
+ TL;DR: development, test, CI, staging and preview use is free for
187
+ everyone. Production use is free for organizations under 10 people, $1M
188
+ revenue and $1M raised, and for charities, schools and hospitals at any
189
+ size. Otherwise, an active ShakaStack Pro subscription (which includes
190
+ React on Rails Pro) covers it for organizations under 250 people and
191
+ $10M revenue; see [pro.reactonrails.com](https://pro.reactonrails.com/).
192
+ Larger organizations and questions:
193
+ [contact@shakacode.com](mailto:contact@shakacode.com).
194
+
195
+ There are no license keys and nothing phones home. [LICENSE.md](LICENSE.md) is the
196
+ binding text.
@@ -0,0 +1,111 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ require "uri"
11
+
12
+ module InertiaRailsSsrCache
13
+ class ConfigurationError < StandardError; end
14
+
15
+ # Boot-time registration is the only authority for eligible anonymous requests.
16
+ class Configuration
17
+ MAX_TARGETS = 50
18
+ RELEASE = /\A[a-zA-Z0-9][a-zA-Z0-9._-]{0,127}\z/
19
+ INVALID_CHARACTERS = /[\\\x00-\x20\x7f]/
20
+
21
+ attr_accessor :origin, :paths, :queue_name
22
+
23
+ def initialize
24
+ @origin = nil
25
+ @paths = []
26
+ @queue_name = :ssr_warming
27
+ end
28
+
29
+ def validate!
30
+ @origin = self.class.validate_origin(origin).dup.freeze
31
+ @paths = self.class.validate_paths(paths)
32
+ @queue_name = self.class.validate_queue_name(queue_name)
33
+ freeze
34
+ end
35
+
36
+ def verify_targets!(requested_origin, requested_paths)
37
+ checked_origin = self.class.validate_origin(requested_origin)
38
+ checked_paths = self.class.validate_paths(requested_paths)
39
+ unless checked_origin == origin && checked_paths.all? { |path| paths.include?(path) }
40
+ raise ConfigurationError, "queued targets are no longer registered"
41
+ end
42
+
43
+ checked_paths
44
+ end
45
+
46
+ def self.validate_release(value)
47
+ unless value.is_a?(String) && RELEASE.match?(value)
48
+ raise ConfigurationError,
49
+ "release must be 1-128 ASCII letters, digits, dots, underscores or hyphens, starting alphanumeric"
50
+ end
51
+
52
+ value
53
+ end
54
+
55
+ def self.validate_queue_name(value)
56
+ unless (value.is_a?(String) || value.is_a?(Symbol)) && !value.to_s.strip.empty?
57
+ raise ConfigurationError, "queue_name must be a nonempty string or symbol"
58
+ end
59
+
60
+ value.to_s.dup.freeze
61
+ end
62
+
63
+ def self.valid_origin?(parsed)
64
+ parsed.is_a?(URI::HTTP) && !parsed.host.to_s.empty? && parsed.path == "" &&
65
+ [parsed.userinfo, parsed.query, parsed.fragment].all?(&:nil?)
66
+ end
67
+
68
+ def self.validate_origin(value)
69
+ unless value.is_a?(String) && !INVALID_CHARACTERS.match?(value)
70
+ raise ConfigurationError, "origin must be an absolute HTTP(S) origin"
71
+ end
72
+
73
+ parsed = URI.parse(value)
74
+ unless valid_origin?(parsed)
75
+ raise ConfigurationError, "origin must contain only HTTP(S) scheme and host, with an optional port"
76
+ end
77
+
78
+ value
79
+ rescue URI::InvalidURIError
80
+ raise ConfigurationError, "origin is not a valid URI"
81
+ end
82
+
83
+ def self.validate_paths(value)
84
+ unless value.is_a?(Array) && !value.empty? && value.all?(String)
85
+ raise ConfigurationError, "paths must be a nonempty array of rooted paths"
86
+ end
87
+
88
+ paths = value.uniq
89
+ if paths.size > MAX_TARGETS
90
+ raise ConfigurationError,
91
+ "at most #{MAX_TARGETS} distinct paths may be warmed per job"
92
+ end
93
+
94
+ paths.each { |path| validate_path(path) }
95
+ paths.map { |path| path.dup.freeze }.freeze
96
+ end
97
+
98
+ def self.validate_path(path)
99
+ unless path.start_with?("/") && !path.start_with?("//") && !INVALID_CHARACTERS.match?(path)
100
+ raise ConfigurationError, "each path must be rooted and must not contain an authority or control characters"
101
+ end
102
+
103
+ parsed = URI.parse(path)
104
+ unless [parsed.fragment, parsed.host, parsed.scheme].all?(&:nil?)
105
+ raise ConfigurationError, "paths must not contain fragments, hosts or schemes"
106
+ end
107
+ rescue URI::InvalidURIError
108
+ raise ConfigurationError, "path is not a valid URI"
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ require "rack/mock"
11
+ require "active_support/dependencies"
12
+ require "active_support/notifications"
13
+
14
+ module InertiaRailsSsrCache
15
+ # The native-key format belongs only here; compatibility is pinned to Inertia Rails 3.22.
16
+ class Probe
17
+ Fetch = Data.define(:key, :native_key, :hit, :namespace, :version)
18
+ NATIVE_KEY = %r{\Ainertia_ssr/[0-9a-f]{32}\z}
19
+
20
+ def self.call(origin:, path:)
21
+ Thread.handle_interrupt(Object => :never) { wait_for_request(start_request(origin, path)) }
22
+ end
23
+
24
+ def self.start_request(origin, path)
25
+ Thread.new do
26
+ Thread.current.report_on_exception = false
27
+ Thread.handle_interrupt(Object => :immediate) { new(origin, path).call }
28
+ end
29
+ end
30
+
31
+ def self.wait_for_request(request_thread)
32
+ # Rails request middleware resets its executor; isolate it from the job's executor.
33
+ Thread.handle_interrupt(Object => :immediate) do
34
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads { request_thread.value }
35
+ end
36
+ ensure
37
+ drain_request(request_thread)
38
+ end
39
+
40
+ def self.drain_request(request_thread)
41
+ # Never interrupt Rails completion callbacks or replace an interrupted parent's error.
42
+ # The normal wait above already propagates request exceptions.
43
+ ActiveSupport::Dependencies.interlock.permit_concurrent_loads { request_thread.join }
44
+ rescue Exception # rubocop:disable Lint/RescueException
45
+ raise if request_thread.alive?
46
+ end
47
+ private_class_method :start_request, :wait_for_request, :drain_request
48
+
49
+ def initialize(origin, path)
50
+ @origin = origin
51
+ @path = path
52
+ @fetches = []
53
+ @owner_thread = Thread.current
54
+ @owner_fiber = Fiber.current
55
+ end
56
+
57
+ def call
58
+ response = ActiveSupport::Notifications.subscribed(method(:record), "cache_read.active_support") { request }
59
+ Observation.new(status: response.status, html: response.media_type == "text/html",
60
+ fetches: @fetches.map { |fetch| [fetch.key, fetch.hit] }, ssr: retained_ssr?)
61
+ end
62
+
63
+ private
64
+
65
+ def request
66
+ Rack::MockRequest.new(Rails.application).get(
67
+ "#{@origin}#{@path}",
68
+ "HTTP_HOST" => @origin.sub(%r{\Ahttps?://}i, ""),
69
+ "HTTP_ACCEPT" => "text/html",
70
+ "REMOTE_ADDR" => "192.0.2.1"
71
+ )
72
+ end
73
+
74
+ def record(*)
75
+ return unless Thread.current.equal?(@owner_thread) && Fiber.current.equal?(@owner_fiber)
76
+
77
+ payload = ActiveSupport::Notifications::Event.new(*).payload
78
+ return unless payload[:super_operation] == :fetch
79
+
80
+ native_key = native_key(payload)
81
+ return unless native_key
82
+
83
+ @fetches << Fetch.new(payload[:key], native_key, payload[:hit] == true, payload[:namespace], payload[:version])
84
+ end
85
+
86
+ def native_key(payload)
87
+ logical_key = logical_key(payload[:key], payload[:store])
88
+ return unless logical_key
89
+
90
+ namespace = payload[:namespace]
91
+ return unless namespace.nil? || namespace.is_a?(String)
92
+
93
+ logical_key = logical_key.delete_prefix("#{namespace}:") if namespace
94
+ logical_key if NATIVE_KEY.match?(logical_key)
95
+ rescue Encoding::CompatibilityError
96
+ nil
97
+ end
98
+
99
+ def retained_ssr?
100
+ return false unless @fetches.size == 1
101
+
102
+ fetch = @fetches.fetch(0)
103
+ value = InertiaRails.cache_store.read(fetch.native_key, namespace: fetch.namespace, version: fetch.version)
104
+ Observation.ssr_value?(value)
105
+ end
106
+
107
+ def logical_key(key, store)
108
+ return unless key.is_a?(String)
109
+
110
+ decoded = if store == "ActiveSupport::Cache::FileStore"
111
+ URI.decode_www_form_component(File.basename(key))
112
+ else
113
+ key
114
+ end
115
+ decoded if decoded.valid_encoding?
116
+ rescue ArgumentError
117
+ # Unparseable notification keys must not interrupt unrelated cache requests.
118
+ nil
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ module InertiaRailsSsrCache
11
+ class Railtie < Rails::Railtie
12
+ rake_tasks do
13
+ namespace :inertia_rails_ssr_cache do
14
+ desc "Enqueue native SSR cache warming for registered public routes"
15
+ task warm: :environment do
16
+ puts "Enqueued SSR warming job #{InertiaRailsSsrCache.warm_later}"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ module InertiaRailsSsrCache
11
+ # Evidence interpretation is pure; framework events are reduced before this boundary.
12
+ class Observation
13
+ attr_reader :status, :html, :fetches, :ssr
14
+
15
+ def initialize(status:, html:, fetches:, ssr:)
16
+ @ssr = ssr
17
+ @status = status
18
+ @html = html
19
+ @fetches = fetches.freeze
20
+ freeze
21
+ end
22
+
23
+ def self.ssr_value?(value)
24
+ return false unless value.is_a?(Hash)
25
+
26
+ head = value["head"]
27
+ value["body"].is_a?(String) && head.is_a?(Array) && head.all?(String)
28
+ end
29
+
30
+ def failure
31
+ return :http_failure unless status == 200 && html
32
+ return :missing_cache_evidence if fetches.empty?
33
+ return :ambiguous_cache_evidence unless fetches.size == 1
34
+ return :invalid_ssr_value unless ssr
35
+
36
+ nil
37
+ end
38
+ end
39
+
40
+ class TargetResult
41
+ attr_reader :path, :outcome, :initial_cache_hit
42
+
43
+ def initialize(path:, outcome:, initial_cache_hit: nil)
44
+ @path = path.dup.freeze
45
+ @outcome = outcome
46
+ @initial_cache_hit = initial_cache_hit
47
+ freeze
48
+ end
49
+
50
+ def self.compare(path, first, second)
51
+ failure = first.failure || second.failure
52
+ return new(path: path, outcome: failure) if failure
53
+
54
+ initial_key, initial_hit = first.fetches.fetch(0)
55
+ repeat_key, repeat_hit = second.fetches.fetch(0)
56
+ outcome = reuse_outcome(initial_key, repeat_key, repeat_hit)
57
+ new(path: path, outcome: outcome, initial_cache_hit: initial_hit)
58
+ end
59
+
60
+ def self.reuse_outcome(initial_key, repeat_key, repeat_hit)
61
+ return :cache_key_changed if initial_key != repeat_key
62
+ return :cache_not_reused unless repeat_hit
63
+
64
+ :reused
65
+ end
66
+ private_class_method :reuse_outcome
67
+ end
68
+
69
+ class Report
70
+ attr_reader :release, :results
71
+
72
+ def initialize(release:, results:)
73
+ @release = release.dup.freeze
74
+ @results = results.freeze
75
+ freeze
76
+ end
77
+
78
+ def success?
79
+ !results.empty? && results.all? { |result| result.outcome == :reused }
80
+ end
81
+ end
82
+
83
+ class WarmFailed < StandardError
84
+ attr_reader :report
85
+
86
+ def initialize(report)
87
+ @report = report
88
+ outcomes = report.results.reject { |result| result.outcome == :reused }.map(&:outcome).uniq
89
+ super("SSR warming could not verify cache reuse: #{outcomes.join(', ')}")
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ module InertiaRailsSsrCache
11
+ VERSION = "0.1.0"
12
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ require "active_job"
11
+
12
+ module InertiaRailsSsrCache
13
+ # Backend configuration owns fleet concurrency. This job never fans out or retries itself.
14
+ class WarmJob < ActiveJob::Base
15
+ def self.validate_environment!
16
+ config = Rails.application.config
17
+ if config.reloading_enabled?
18
+ raise ConfigurationError, "SSR cache warming requires config.enable_reloading = false in the job process"
19
+ end
20
+ return unless config.allow_concurrency == false
21
+
22
+ raise ConfigurationError, "SSR cache warming requires config.allow_concurrency to permit concurrent execution"
23
+ end
24
+
25
+ def perform(origin, paths, release)
26
+ self.class.validate_environment!
27
+ registered = InertiaRailsSsrCache.configuration
28
+ targets = registered.verify_targets!(origin, paths)
29
+ InertiaRailsSsrCache.validate_release!(release)
30
+ results = targets.map { |path| warm_target(origin, path, release) }
31
+ report = Report.new(release: release, results: results)
32
+ ActiveSupport::Notifications.instrument("warm.inertia_rails_ssr_cache", report: report)
33
+ raise WarmFailed, report unless report.success?
34
+
35
+ report
36
+ end
37
+
38
+ private
39
+
40
+ def warm_target(origin, path, release)
41
+ result = if InertiaRailsSsrCache.current_release == release
42
+ first = Probe.call(origin: origin, path: path)
43
+ second = Probe.call(origin: origin, path: path)
44
+ TargetResult.compare(path, first, second)
45
+ else
46
+ TargetResult.new(path: path, outcome: :release_mismatch)
47
+ end
48
+ ActiveSupport::Notifications.instrument("target.inertia_rails_ssr_cache", result: result, release: release)
49
+ result
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Copyright (c) 2026 ShakaCode LLC.
4
+ #
5
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
6
+ #
7
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
8
+ # Inertia Rails SSR Cache License in LICENSE.md.
9
+
10
+ require "rails"
11
+ require "inertia_rails"
12
+ require_relative "inertia_rails_ssr_cache/version"
13
+ require_relative "inertia_rails_ssr_cache/configuration"
14
+ require_relative "inertia_rails_ssr_cache/result"
15
+ require_relative "inertia_rails_ssr_cache/probe"
16
+ require_relative "inertia_rails_ssr_cache/railtie"
17
+
18
+ module InertiaRailsSsrCache
19
+ autoload :WarmJob, "#{__dir__}/inertia_rails_ssr_cache/warm_job"
20
+
21
+ class << self
22
+ def configure
23
+ configuration = Configuration.new
24
+ yield configuration
25
+ @configuration = configuration.validate!
26
+ end
27
+
28
+ def configuration
29
+ @configuration || raise(ConfigurationError, "register public warming targets with configure first")
30
+ end
31
+
32
+ def current_release
33
+ validate_release!(InertiaRails.configuration.version)
34
+ end
35
+
36
+ def validate_release!(release)
37
+ Configuration.validate_release(release)
38
+ end
39
+
40
+ def warm_later
41
+ registered = configuration
42
+ release = current_release.dup.freeze
43
+ WarmJob.validate_environment!
44
+ job = WarmJob.set(queue: registered.queue_name).perform_later(registered.origin, registered.paths, release)
45
+ raise ConfigurationError, "Active Job did not enqueue the warming job" unless job
46
+
47
+ job.job_id
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,31 @@
1
+ # Copyright (c) 2026 ShakaCode LLC.
2
+ #
3
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
4
+ #
5
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
6
+ # Inertia Rails SSR Cache License in LICENSE.md.
7
+
8
+ # These narrow interfaces describe only the framework calls used by the public facade.
9
+ # Inertia's evaluated version can be arbitrary application data; validation narrows it.
10
+ interface _InertiaVersionConfiguration
11
+ def version: () -> Object?
12
+ end
13
+
14
+ module InertiaRails
15
+ def self.configuration: () -> _InertiaVersionConfiguration
16
+ end
17
+
18
+ interface _EnqueuedWarmJob
19
+ def job_id: () -> String
20
+ end
21
+
22
+ interface _ConfiguredWarmJob
23
+ def perform_later: (String? origin, Array[String] paths, String release) -> (_EnqueuedWarmJob | false)
24
+ end
25
+
26
+ module InertiaRailsSsrCache
27
+ class WarmJob
28
+ def self.validate_environment!: () -> void
29
+ def self.set: (queue: String | Symbol) -> _ConfiguredWarmJob
30
+ end
31
+ end
@@ -0,0 +1,70 @@
1
+ # Copyright (c) 2026 ShakaCode LLC.
2
+ #
3
+ # SPDX-License-Identifier: LicenseRef-InertiaRailsSsrCache
4
+ #
5
+ # This file is part of Inertia Rails SSR Cache. Use is governed by The
6
+ # Inertia Rails SSR Cache License in LICENSE.md.
7
+
8
+ module InertiaRailsSsrCache
9
+ self.@configuration: Configuration?
10
+ def self.configure: () { (Configuration) -> void } -> Configuration
11
+ def self.configuration: () -> Configuration
12
+ def self.current_release: () -> String
13
+ def self.validate_release!: (Object? release) -> String
14
+ def self.warm_later: () -> String
15
+ class ConfigurationError < StandardError
16
+ end
17
+
18
+ class Configuration
19
+ MAX_TARGETS: Integer
20
+ RELEASE: Regexp
21
+ INVALID_CHARACTERS: Regexp
22
+ attr_accessor origin: String?
23
+ attr_accessor paths: Array[String]
24
+ attr_accessor queue_name: String | Symbol
25
+
26
+ def initialize: () -> void
27
+ def validate!: () -> self
28
+ def verify_targets!: (String requested_origin, Array[String] requested_paths) -> Array[String]
29
+ def self.validate_release: (Object? value) -> String
30
+ def self.validate_queue_name: (String | Symbol value) -> String
31
+ def self.valid_origin?: (URI::Generic parsed) -> bool
32
+ def self.validate_origin: (String? value) -> String
33
+ def self.validate_paths: (Array[String] value) -> Array[String]
34
+ def self.validate_path: (String path) -> void
35
+ end
36
+
37
+ type outcome = :reused | :http_failure | :missing_cache_evidence | :ambiguous_cache_evidence | :invalid_ssr_value | :cache_key_changed | :cache_not_reused | :release_mismatch
38
+ type cache_fetch = [String, bool]
39
+
40
+ class Observation
41
+ attr_reader ssr: bool
42
+ attr_reader status: Integer
43
+ attr_reader html: bool
44
+ attr_reader fetches: Array[cache_fetch]
45
+ def initialize: (status: Integer, html: bool, fetches: Array[cache_fetch], ssr: bool) -> void
46
+ def self.ssr_value?: (Object? value) -> bool
47
+ def failure: () -> (:http_failure | :missing_cache_evidence | :ambiguous_cache_evidence | :invalid_ssr_value)?
48
+ end
49
+
50
+ class TargetResult
51
+ attr_reader path: String
52
+ attr_reader outcome: outcome
53
+ attr_reader initial_cache_hit: bool?
54
+ def initialize: (path: String, outcome: outcome, ?initial_cache_hit: bool?) -> void
55
+ def self.reuse_outcome: (String initial_key, String repeat_key, bool repeat_hit) -> outcome
56
+ def self.compare: (String path, Observation first, Observation second) -> TargetResult
57
+ end
58
+
59
+ class Report
60
+ attr_reader release: String
61
+ attr_reader results: Array[TargetResult]
62
+ def initialize: (release: String, results: Array[TargetResult]) -> void
63
+ def success?: () -> bool
64
+ end
65
+
66
+ class WarmFailed < StandardError
67
+ attr_reader report: Report
68
+ def initialize: (Report report) -> void
69
+ end
70
+ end
metadata ADDED
@@ -0,0 +1,174 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: inertia_rails_ssr_cache
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Justin Gordon
8
+ - ShakaCode
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 1980-01-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: actionpack
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '7.2'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '8.2'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '7.2'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '8.2'
33
+ - !ruby/object:Gem::Dependency
34
+ name: activejob
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '7.2'
40
+ - - "<"
41
+ - !ruby/object:Gem::Version
42
+ version: '8.2'
43
+ type: :runtime
44
+ prerelease: false
45
+ version_requirements: !ruby/object:Gem::Requirement
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: '7.2'
50
+ - - "<"
51
+ - !ruby/object:Gem::Version
52
+ version: '8.2'
53
+ - !ruby/object:Gem::Dependency
54
+ name: activesupport
55
+ requirement: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: '7.2'
60
+ - - "<"
61
+ - !ruby/object:Gem::Version
62
+ version: '8.2'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '7.2'
70
+ - - "<"
71
+ - !ruby/object:Gem::Version
72
+ version: '8.2'
73
+ - !ruby/object:Gem::Dependency
74
+ name: inertia_rails
75
+ requirement: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - "~>"
78
+ - !ruby/object:Gem::Version
79
+ version: 3.22.0
80
+ type: :runtime
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: 3.22.0
87
+ - !ruby/object:Gem::Dependency
88
+ name: rack
89
+ requirement: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - ">="
92
+ - !ruby/object:Gem::Version
93
+ version: '3.1'
94
+ - - "<"
95
+ - !ruby/object:Gem::Version
96
+ version: '4'
97
+ type: :runtime
98
+ prerelease: false
99
+ version_requirements: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '3.1'
104
+ - - "<"
105
+ - !ruby/object:Gem::Version
106
+ version: '4'
107
+ - !ruby/object:Gem::Dependency
108
+ name: railties
109
+ requirement: !ruby/object:Gem::Requirement
110
+ requirements:
111
+ - - ">="
112
+ - !ruby/object:Gem::Version
113
+ version: '7.2'
114
+ - - "<"
115
+ - !ruby/object:Gem::Version
116
+ version: '8.2'
117
+ type: :runtime
118
+ prerelease: false
119
+ version_requirements: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '7.2'
124
+ - - "<"
125
+ - !ruby/object:Gem::Version
126
+ version: '8.2'
127
+ description: Warm explicitly registered public routes through the native Inertia SSR
128
+ cache.
129
+ email:
130
+ - justin@shakacode.com
131
+ executables: []
132
+ extensions: []
133
+ extra_rdoc_files: []
134
+ files:
135
+ - LICENSE.md
136
+ - README.md
137
+ - lib/inertia_rails_ssr_cache.rb
138
+ - lib/inertia_rails_ssr_cache/configuration.rb
139
+ - lib/inertia_rails_ssr_cache/probe.rb
140
+ - lib/inertia_rails_ssr_cache/railtie.rb
141
+ - lib/inertia_rails_ssr_cache/result.rb
142
+ - lib/inertia_rails_ssr_cache/version.rb
143
+ - lib/inertia_rails_ssr_cache/warm_job.rb
144
+ - sig/framework_boundary.rbs
145
+ - sig/inertia_rails_ssr_cache.rbs
146
+ homepage: https://github.com/shakacode/inertia_rails_ssr_cache
147
+ licenses:
148
+ - LicenseRef-InertiaRailsSsrCache
149
+ metadata:
150
+ allowed_push_host: https://rubygems.org
151
+ rubygems_mfa_required: 'true'
152
+ source_code_uri: https://github.com/shakacode/inertia_rails_ssr_cache
153
+ documentation_uri: https://github.com/shakacode/inertia_rails_ssr_cache#readme
154
+ changelog_uri: https://github.com/shakacode/inertia_rails_ssr_cache/blob/main/CHANGELOG.md
155
+ bug_tracker_uri: https://github.com/shakacode/inertia_rails_ssr_cache/issues
156
+ license_uri: https://github.com/shakacode/inertia_rails_ssr_cache/blob/main/LICENSE.md
157
+ rdoc_options: []
158
+ require_paths:
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: '3.3'
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubygems_version: 4.0.18
172
+ specification_version: 4
173
+ summary: Verified Inertia Rails SSR cache warming through Active Job.
174
+ test_files: []