inertia-sails 0.3.4 → 1.0.1

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,315 @@
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:
168
+
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. Works with Inertia.js v2's `<InfiniteScroll>` component:
209
+
210
+ ```js
211
+ // Controller
212
+ const page = this.req.param('page', 0)
213
+ const perPage = 20
214
+ const invoices = await Invoice.find().paginate(page, perPage)
215
+ const total = await Invoice.count()
216
+
217
+ return {
218
+ page: 'invoices/index',
219
+ props: {
220
+ invoices: sails.inertia.scroll(() => invoices, {
221
+ page,
222
+ perPage,
223
+ total,
224
+ wrapper: 'data' // Wraps in { data: [...], meta: {...} }
225
+ })
226
+ }
227
+ }
228
+ ```
229
+
230
+ ```vue
231
+ <!-- Vue component -->
232
+ <script setup>
233
+ import { InfiniteScroll } from '@inertiajs/vue3'
234
+
235
+ defineProps({ invoices: Object })
236
+ </script>
237
+
238
+ <template>
239
+ <InfiniteScroll data="invoices">
240
+ <div v-for="invoice in invoices.data" :key="invoice.id">
241
+ {{ invoice.invoiceNumber }}
242
+ </div>
243
+ </InfiniteScroll>
244
+ </template>
245
+ ```
246
+
247
+ ### History Encryption
248
+
249
+ Encrypt sensitive data in browser history:
250
+
251
+ ```js
252
+ sails.inertia.encryptHistory() // Enable for current request
253
+ sails.inertia.clearHistory() // Clear history state
254
+ ```
255
+
256
+ ### Root View
257
+
258
+ Change the root template per-request:
24
259
 
25
- The second argument is the props object where you can provide props to your components.
260
+ ```js
261
+ sails.inertia.setRootView('auth') // Use views/auth.ejs
262
+ ```
263
+
264
+ ### Back Navigation
26
265
 
27
- ## Shared Data
266
+ Get the referrer URL for redirects:
28
267
 
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.
268
+ ```js
269
+ return sails.inertia.back('/dashboard') // Fallback if no referrer
270
+ ```
30
271
 
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:
272
+ ### Optional Props
273
+
274
+ Props only included when explicitly requested via partial reload:
32
275
 
33
276
  ```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
- */
277
+ categories: sails.inertia.optional(() => Category.find())
278
+ ```
279
+
280
+ ### Always Props
281
+
282
+ Props included even in partial reloads:
40
283
 
284
+ ```js
285
+ csrf: sails.inertia.always(() => this.req.csrfToken())
286
+ ```
287
+
288
+ ## Custom Hook Example
289
+
290
+ Share user data across all authenticated pages using `once()` for caching:
291
+
292
+ ```js
293
+ // api/hooks/custom/index.js
41
294
  module.exports = function defineCustomHook(sails) {
42
295
  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
296
  routes: {
50
297
  before: {
51
- 'GET /': {
298
+ 'GET /*': {
52
299
  skipAssets: true,
53
300
  fn: async function (req, res, next) {
54
301
  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()
302
+ sails.inertia.share(
303
+ 'loggedInUser',
304
+ sails.inertia.once(async () => {
305
+ return await User.findOne({ id: req.session.userId }).select([
306
+ 'id',
307
+ 'email',
308
+ 'fullName',
309
+ 'avatarUrl'
310
+ ])
311
+ })
312
+ )
69
313
  }
70
314
  return next()
71
315
  }
@@ -76,30 +320,56 @@ module.exports = function defineCustomHook(sails) {
76
320
  }
77
321
  ```
78
322
 
79
- ## Configuration
323
+ ## Custom Responses
324
+
325
+ Copy these to `api/responses/`:
80
326
 
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:
327
+ - **inertia.js** - Handle Inertia page responses
328
+ - **inertiaRedirect.js** - Handle Inertia redirects
329
+ - **badRequest.js** - Validation errors with redirect back
330
+ - **serverError.js** - Error modal in dev, graceful redirect in prod
331
+
332
+ ## Architecture
333
+
334
+ 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.
335
+
336
+ ## Configuration
82
337
 
83
338
  ```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
- */
339
+ // config/inertia.js
340
+ module.exports.inertia = {
341
+ // Root EJS template (default: 'app')
342
+ rootView: 'app',
93
343
 
344
+ // Asset version for cache busting (optional - auto-detected by default)
345
+ // version: 'custom-version',
346
+
347
+ // History encryption settings
348
+ history: {
349
+ encrypt: false
350
+ }
351
+ }
352
+ ```
353
+
354
+ ### Automatic Asset Versioning
355
+
356
+ inertia-sails automatically handles asset versioning:
357
+
358
+ 1. **With Shipwright**: Reads `.tmp/public/manifest.json` and generates an MD5 hash. Version changes when any bundled asset changes.
359
+
360
+ 2. **Without Shipwright**: Falls back to server startup timestamp, ensuring fresh assets on each restart.
361
+
362
+ You can override this with a custom version if needed:
363
+
364
+ ```js
365
+ // config/inertia.js
94
366
  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,
367
+ version: 'v2.1.0' // Or a function: () => myCustomVersion()
102
368
  }
103
369
  ```
104
370
 
105
- Visit [inertiajs.com](https://inertiajs.com/) to learn more.
371
+ ## References
372
+
373
+ - [The Boring Stack Docs](https://docs.sailscasts.com/boring-stack)
374
+ - [Inertia.js](https://inertiajs.com)
375
+ - [Sails.js](https://sailsjs.com)