inertia-sails 0.3.3 → 1.0.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.
package/README.md CHANGED
@@ -1,71 +1,297 @@
1
- # Inertia.js Sails Adapter
1
+ # inertia-sails
2
+
3
+ The official Inertia.js adapter for Sails.js, powering [The Boring JavaScript Stack](https://sailscasts.com/boring).
2
4
 
3
5
  ## Installation
4
6
 
5
- ## Backend
7
+ ```bash
8
+ npm install inertia-sails
9
+ ```
6
10
 
7
- The quickest way to get setup an Inertia powered Sails app is to use the [create-sails](https://github.com/sailscastshq/create-sails) scaffolding tool. Just run
11
+ Or use [create-sails](https://github.com/sailscastshq/create-sails) to scaffold a complete app:
8
12
 
9
- ```sh
10
- npx create-sails <project-name>
13
+ ```bash
14
+ npx create-sails my-app
11
15
  ```
12
16
 
13
- > Do replace `<project-name>` with the name you want your project to be.
17
+ ## Quick Start
14
18
 
15
- ## Frontend
19
+ ### 1. Configure Inertia
16
20
 
17
- If you are using the [create-sails](https://github.com/sailscastshq/create-sails) scaffolding tool then the Frontend framework you choose from the CLI prompt should already be setup for you.
21
+ ```js
22
+ // config/inertia.js
23
+ module.exports.inertia = {
24
+ rootView: 'app', // views/app.ejs
25
+ version: 1 // Asset version for cache busting
26
+ }
27
+ ```
28
+
29
+ ### 2. Create a root view
30
+
31
+ ```ejs
32
+ <!-- views/app.ejs -->
33
+ <!DOCTYPE html>
34
+ <html>
35
+ <head>
36
+ <meta charset="utf-8">
37
+ <meta name="viewport" content="width=device-width, initial-scale=1">
38
+ <%- shipwright.styles() %>
39
+ </head>
40
+ <body>
41
+ <div id="app" data-page="<%- JSON.stringify(page) %>"></div>
42
+ <%- shipwright.scripts() %>
43
+ </body>
44
+ </html>
45
+ ```
46
+
47
+ ### 3. Create an action
48
+
49
+ ```js
50
+ // api/controllers/dashboard/view-dashboard.js
51
+ module.exports = {
52
+ exits: {
53
+ success: { responseType: 'inertia' }
54
+ },
55
+ fn: async function () {
56
+ return {
57
+ page: 'dashboard/index',
58
+ props: {
59
+ stats: await Stats.find()
60
+ }
61
+ }
62
+ }
63
+ }
64
+ ```
18
65
 
19
- ## Usage
66
+ ## API Reference
20
67
 
21
68
  ### Responses
22
69
 
23
- Sending back an Inertia responses is pretty simple, just use the `sails.inertia.render` method in your Sails actions(You can look at the example actions if you used create-sails). The `render` method accepts two arguments, the first is the name of the component you want to render from within your `pages` directory in `assets/js` (without the file extension).
70
+ #### `responseType: 'inertia'`
71
+
72
+ Return an Inertia page response:
73
+
74
+ ```js
75
+ return {
76
+ page: 'users/index', // Component name
77
+ props: { users: [...] }, // Props passed to component
78
+ viewData: { title: '...' } // Data for root EJS template
79
+ }
80
+ ```
81
+
82
+ #### `responseType: 'inertiaRedirect'`
83
+
84
+ Return a URL string to redirect:
85
+
86
+ ```js
87
+ return '/dashboard'
88
+ ```
89
+
90
+ ### Sharing Data
91
+
92
+ #### `share(key, value)`
93
+
94
+ Share data with the current request (request-scoped):
95
+
96
+ ```js
97
+ sails.inertia.share('flash', { success: 'Saved!' })
98
+ ```
99
+
100
+ #### `shareGlobally(key, value)`
101
+
102
+ Share data across all requests (app-wide):
103
+
104
+ ```js
105
+ // In hook initialization
106
+ sails.inertia.shareGlobally('appName', 'My App')
107
+ ```
108
+
109
+ #### `viewData(key, value)`
110
+
111
+ Share data with the root EJS template:
112
+
113
+ ```js
114
+ sails.inertia.viewData('title', 'Dashboard')
115
+ ```
116
+
117
+ ### Once Props (Cached)
118
+
119
+ Cache expensive props across navigations. The client tracks cached props and skips re-fetching.
120
+
121
+ #### `once(callback)`
122
+
123
+ ```js
124
+ // In custom hook
125
+ sails.inertia.share(
126
+ 'loggedInUser',
127
+ sails.inertia.once(async () => {
128
+ return await User.findOne({ id: req.session.userId })
129
+ })
130
+ )
131
+ ```
132
+
133
+ **Chainable methods:**
134
+
135
+ - `.as(key)` - Custom cache key
136
+ - `.until(seconds)` - TTL expiration
137
+ - `.fresh(condition)` - Force refresh
138
+
139
+ ```js
140
+ sails.inertia
141
+ .once(() => fetchPermissions())
142
+ .as('user-permissions')
143
+ .until(3600) // Cache for 1 hour
144
+ .fresh(req.query.refresh === 'true')
145
+ ```
146
+
147
+ #### `shareOnce(key, callback)`
148
+
149
+ Shorthand for `share()` + `once()`:
150
+
151
+ ```js
152
+ sails.inertia.shareOnce('countries', () => Country.find())
153
+ ```
154
+
155
+ #### `refreshOnce(keys)`
156
+
157
+ Force refresh cached props from an action:
158
+
159
+ ```js
160
+ // After updating user profile
161
+ await User.updateOne({ id: userId }).set({ fullName })
162
+ sails.inertia.refreshOnce('loggedInUser')
163
+ ```
164
+
165
+ ### Flash Messages
166
+
167
+ One-time messages that don't persist in browser history:
24
168
 
25
- The second argument is the props object where you can provide props to your components.
169
+ ```js
170
+ sails.inertia.flash('success', 'Profile updated!')
171
+ sails.inertia.flash({ error: 'Failed', field: 'email' })
172
+ ```
173
+
174
+ Access in your frontend via `page.props.flash`.
175
+
176
+ ### Deferred Props
177
+
178
+ Load props after initial page render:
179
+
180
+ ```js
181
+ return {
182
+ page: 'dashboard',
183
+ props: {
184
+ // Loads immediately
185
+ user: currentUser,
186
+ // Loads after render
187
+ analytics: sails.inertia.defer(async () => {
188
+ return await Analytics.getExpensiveReport()
189
+ })
190
+ }
191
+ }
192
+ ```
193
+
194
+ ### Merge Props
195
+
196
+ Merge with existing client-side data (useful for infinite scroll):
197
+
198
+ ```js
199
+ // Shallow merge
200
+ messages: sails.inertia.merge(() => newMessages)
201
+
202
+ // Deep merge (nested objects)
203
+ settings: sails.inertia.deepMerge(() => updatedSettings)
204
+ ```
205
+
206
+ ### Infinite Scroll
207
+
208
+ Paginate data with automatic merge behavior:
209
+
210
+ ```js
211
+ const page = this.req.param('page', 0)
212
+ const perPage = 20
213
+ const invoices = await Invoice.find().paginate(page, perPage)
214
+ const total = await Invoice.count()
215
+
216
+ return {
217
+ page: 'invoices/index',
218
+ props: {
219
+ invoices: sails.inertia.scroll(() => invoices, {
220
+ page,
221
+ perPage,
222
+ total,
223
+ wrapper: 'data' // Wraps in { data: [...], meta: {...} }
224
+ })
225
+ }
226
+ }
227
+ ```
228
+
229
+ ### History Encryption
230
+
231
+ Encrypt sensitive data in browser history:
232
+
233
+ ```js
234
+ sails.inertia.encryptHistory() // Enable for current request
235
+ sails.inertia.clearHistory() // Clear history state
236
+ ```
237
+
238
+ ### Root View
239
+
240
+ Change the root template per-request:
241
+
242
+ ```js
243
+ sails.inertia.setRootView('auth') // Use views/auth.ejs
244
+ ```
245
+
246
+ ### Back Navigation
247
+
248
+ Get the referrer URL for redirects:
26
249
 
27
- ## Shared Data
250
+ ```js
251
+ return sails.inertia.back('/dashboard') // Fallback if no referrer
252
+ ```
28
253
 
29
- If you have data that you want to be provided as a prop to every component (a common use-case is informationa about the authenticated user) you can use the `sails.inertia.share` method.
254
+ ### Optional Props
30
255
 
31
- In Sails having a custom hook by running `sails generate hook custom` will scaffolding a project level hook in which you can share the loggedIn user information for example. Below is a real world use case:
256
+ Props only included when explicitly requested via partial reload:
32
257
 
33
258
  ```js
34
- /**
35
- * custom hook
36
- *
37
- * @description :: A hook definition. Extends Sails by adding shadow routes, implicit actions, and/or initialization logic.
38
- * @docs :: https://sailsjs.com/docs/concepts/extending-sails/hooks
39
- */
259
+ categories: sails.inertia.optional(() => Category.find())
260
+ ```
261
+
262
+ ### Always Props
40
263
 
264
+ Props included even in partial reloads:
265
+
266
+ ```js
267
+ csrf: sails.inertia.always(() => this.req.csrfToken())
268
+ ```
269
+
270
+ ## Custom Hook Example
271
+
272
+ Share user data across all authenticated pages using `once()` for caching:
273
+
274
+ ```js
275
+ // api/hooks/custom/index.js
41
276
  module.exports = function defineCustomHook(sails) {
42
277
  return {
43
- /**
44
- * Runs when this Sails app loads/lifts.
45
- */
46
- initialize: async function () {
47
- sails.log.info('Initializing custom hook (`custom`)')
48
- },
49
278
  routes: {
50
279
  before: {
51
- 'GET /': {
280
+ 'GET /*': {
52
281
  skipAssets: true,
53
282
  fn: async function (req, res, next) {
54
283
  if (req.session.userId) {
55
- const loggedInUser = await User.findOne({
56
- id: req.session.userId
57
- })
58
- if (!loggedInUser) {
59
- sails.log.warn(
60
- 'Somehow, the user record for the logged-in user (`' +
61
- req.session.userId +
62
- '`) has gone missing....'
63
- )
64
- delete req.session.userId
65
- return res.redirect('/signin')
66
- }
67
- sails.inertia.share('loggedInUser', loggedInUser)
68
- return next()
284
+ sails.inertia.share(
285
+ 'loggedInUser',
286
+ sails.inertia.once(async () => {
287
+ return await User.findOne({ id: req.session.userId }).select([
288
+ 'id',
289
+ 'email',
290
+ 'fullName',
291
+ 'avatarUrl'
292
+ ])
293
+ })
294
+ )
69
295
  }
70
296
  return next()
71
297
  }
@@ -76,30 +302,56 @@ module.exports = function defineCustomHook(sails) {
76
302
  }
77
303
  ```
78
304
 
79
- ## Configuration
305
+ ## Custom Responses
306
+
307
+ Copy these to `api/responses/`:
308
+
309
+ - **inertia.js** - Handle Inertia page responses
310
+ - **inertiaRedirect.js** - Handle Inertia redirects
311
+ - **badRequest.js** - Validation errors with redirect back
312
+ - **serverError.js** - Error modal in dev, graceful redirect in prod
80
313
 
81
- If you used the `create-sails` scaffolding tool, you will find the configuration file for Inertia.js in `config/inertia.js`. You will mostly use this file for asset-versioning in Inertia by setting either a number or string that you can update when your assets changes. Below is an example of how this file looks like:
314
+ ## Architecture
315
+
316
+ inertia-sails uses **AsyncLocalStorage** for request-scoped state, preventing data leaks between concurrent requests. This is critical for `share()`, `flash()`, `setRootView()`, and other per-request APIs.
317
+
318
+ ## Configuration
82
319
 
83
320
  ```js
84
- /**
85
- * Inertia configuration
86
- * (sails.config.inertia)
87
- *
88
- * Use the settings below to configure session integration in your app.
89
- *
90
- * For more information on Inertia configuration, visit:
91
- * https://inertia-sails.sailscasts.com
92
- */
321
+ // config/inertia.js
322
+ module.exports.inertia = {
323
+ // Root EJS template (default: 'app')
324
+ rootView: 'app',
325
+
326
+ // Asset version for cache busting (optional - auto-detected by default)
327
+ // version: 'custom-version',
328
+
329
+ // History encryption settings
330
+ history: {
331
+ encrypt: false
332
+ }
333
+ }
334
+ ```
335
+
336
+ ### Automatic Asset Versioning
337
+
338
+ inertia-sails automatically handles asset versioning:
339
+
340
+ 1. **With Shipwright**: Reads `.tmp/public/manifest.json` and generates an MD5 hash. Version changes when any bundled asset changes.
93
341
 
342
+ 2. **Without Shipwright**: Falls back to server startup timestamp, ensuring fresh assets on each restart.
343
+
344
+ You can override this with a custom version if needed:
345
+
346
+ ```js
347
+ // config/inertia.js
94
348
  module.exports.inertia = {
95
- /**
96
- * https://inertiajs.com/asset-versioning
97
- * You can pass a string, number that changes when your assets change
98
- * or a function that returns the said string, number.
99
- * e.g 4 or () => 4
100
- */
101
- // version: 1,
349
+ version: 'v2.1.0' // Or a function: () => myCustomVersion()
102
350
  }
103
351
  ```
104
352
 
105
- Visit [inertiajs.com](https://inertiajs.com/) to learn more.
353
+ ## References
354
+
355
+ - [The Boring Stack Docs](https://docs.sailscasts.com/boring-stack)
356
+ - [Inertia.js](https://inertiajs.com)
357
+ - [Sails.js](https://sailsjs.com)