inertia-sails 1.2.0 → 1.3.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
@@ -75,7 +75,7 @@ Return an Inertia page response:
75
75
  return {
76
76
  page: 'users/index', // Component name
77
77
  props: { users: [...] }, // Props passed to component
78
- viewData: { title: '...' } // Data for root EJS template
78
+ locals: { title: '...' } // Locals for root EJS template
79
79
  }
80
80
  ```
81
81
 
@@ -106,12 +106,12 @@ Share data across all requests (app-wide):
106
106
  sails.inertia.shareGlobally('appName', 'My App')
107
107
  ```
108
108
 
109
- #### `viewData(key, value)`
109
+ #### `local(key, value)`
110
110
 
111
- Share data with the root EJS template:
111
+ Set a local variable for the root EJS template:
112
112
 
113
113
  ```js
114
- sails.inertia.viewData('title', 'Dashboard')
114
+ sails.inertia.local('title', 'Dashboard')
115
115
  ```
116
116
 
117
117
  ### Once Props (Cached)
package/index.js CHANGED
@@ -30,7 +30,7 @@
30
30
  * @typedef {Object} InertiaRenderData
31
31
  * @property {string} page - The component name to render
32
32
  * @property {Object.<string, *>} [props] - Props to pass to the component
33
- * @property {Object.<string, *>} [viewData] - Additional view data for the root template
33
+ * @property {Object.<string, *>} [locals] - Additional locals for the root EJS template
34
34
  */
35
35
 
36
36
  /**
@@ -110,13 +110,45 @@ module.exports = function defineInertiaHook(sails) {
110
110
  }
111
111
  }
112
112
  },
113
+
114
+ /**
115
+ * Configure phase — runs after all hooks' defaults are merged but BEFORE
116
+ * any hook's initialize(). This is the right place to inject HTTP middleware
117
+ * because the HTTP hook hasn't read the middleware order yet.
118
+ */
119
+ configure: function () {
120
+ // Inject AsyncLocalStorage context middleware into the HTTP stack
121
+ // BEFORE the router. This guarantees context is available in ALL
122
+ // routes.before handlers from all hooks, regardless of hook load order.
123
+ //
124
+ // Lifecycle:
125
+ // cookieParser → session → bodyParser → ... → inertiaContext → router
126
+ //
127
+ // By the time any routes.before handler calls sails.inertia.share(),
128
+ // the request is already wrapped in AsyncLocalStorage context.
129
+ if (sails.config.http && sails.config.http.middleware) {
130
+ const mw = sails.config.http.middleware
131
+ if (mw.order && mw.order.indexOf('inertiaContext') === -1) {
132
+ const routerIdx = mw.order.indexOf('router')
133
+ if (routerIdx !== -1) {
134
+ mw.order.splice(routerIdx, 0, 'inertiaContext')
135
+ }
136
+ }
137
+ if (!mw.inertiaContext) {
138
+ mw.inertiaContext = function inertiaContext(req, res, next) {
139
+ requestContext.run(req, res, next)
140
+ }
141
+ }
142
+ }
143
+ },
144
+
113
145
  initialize: async function () {
114
146
  hook = this
115
147
  sails.inertia = hook
116
148
  // Global shared props (for app-wide data like app name, version)
117
149
  // These are merged with request-scoped shares
118
150
  sails.inertia.globalSharedProps = {}
119
- sails.inertia.globalSharedViewData = {}
151
+ sails.inertia.globalSharedLocals = {}
120
152
  // Default history encryption from config
121
153
  sails.inertia.defaultEncryptHistory = sails.config.inertia.history.encrypt
122
154
  sails.on('router:before', function () {
@@ -127,19 +159,36 @@ module.exports = function defineInertiaHook(sails) {
127
159
  },
128
160
 
129
161
  /**
130
- * Hook routes - sets up AsyncLocalStorage context early so other hooks
131
- * can use sails.inertia.share() with proper request-scoped context.
162
+ * Hook routes fallback context setup for socket requests.
163
+ * HTTP requests already have context from the inertiaContext middleware.
164
+ * Socket requests bypass Express middleware, so they need this.
132
165
  */
133
166
  routes: {
134
167
  before: {
135
168
  'GET /*': {
136
169
  skipAssets: true,
137
- fn: (req, res, next) => requestContext.run(req, res, next)
170
+ fn: (req, res, next) => {
171
+ // Skip if context already set up by HTTP middleware
172
+ if (requestContext.getContext()) return next()
173
+ requestContext.run(req, res, next)
174
+ }
138
175
  },
139
- 'POST /*': (req, res, next) => requestContext.run(req, res, next),
140
- 'PUT /*': (req, res, next) => requestContext.run(req, res, next),
141
- 'PATCH /*': (req, res, next) => requestContext.run(req, res, next),
142
- 'DELETE /*': (req, res, next) => requestContext.run(req, res, next)
176
+ 'POST /*': (req, res, next) => {
177
+ if (requestContext.getContext()) return next()
178
+ requestContext.run(req, res, next)
179
+ },
180
+ 'PUT /*': (req, res, next) => {
181
+ if (requestContext.getContext()) return next()
182
+ requestContext.run(req, res, next)
183
+ },
184
+ 'PATCH /*': (req, res, next) => {
185
+ if (requestContext.getContext()) return next()
186
+ requestContext.run(req, res, next)
187
+ },
188
+ 'DELETE /*': (req, res, next) => {
189
+ if (requestContext.getContext()) return next()
190
+ requestContext.run(req, res, next)
191
+ }
143
192
  }
144
193
  },
145
194
 
@@ -152,14 +201,17 @@ module.exports = function defineInertiaHook(sails) {
152
201
  * @returns {*} - The value that was shared
153
202
  */
154
203
  share(key, value = null) {
155
- // If we're in a request context, share for this request only
156
204
  const context = requestContext.getContext()
157
205
  if (context) {
158
206
  requestContext.setSharedProp(key, value)
159
207
  return value
160
208
  }
161
- // Fallback to global share if called outside request (e.g., in hooks)
162
- sails.inertia.globalSharedProps[key] = value
209
+ // Never fall back to global that causes data to leak across requests.
210
+ // Use shareGlobally() for truly global data like app name.
211
+ sails.log.warn(
212
+ `sails.inertia.share('${key}') called outside request context. ` +
213
+ 'Value was not stored. Use shareGlobally() for global data.'
214
+ )
163
215
  return value
164
216
  },
165
217
 
@@ -190,67 +242,66 @@ module.exports = function defineInertiaHook(sails) {
190
242
 
191
243
  /**
192
244
  * Flush shared properties for the current request.
193
- * Since context is set up early in routes.before, this always works
194
- * in hooks and middleware.
245
+ * Always flushes from both request-scoped AND global storage to prevent
246
+ * stale data from leaking across requests.
195
247
  * @param {string|null} key - The key of the property to flush, or null to flush all
196
- * @param {boolean} [global=false] - Whether to also flush global props (rarely needed)
197
248
  */
198
- flushShared(key, global = false) {
249
+ flushShared(key) {
199
250
  const context = requestContext.getContext()
200
251
  if (key) {
201
252
  if (context) {
202
253
  delete context.sharedProps[key]
203
254
  }
204
- if (global) {
205
- delete sails.inertia.globalSharedProps[key]
206
- }
255
+ // Always clean global too — prevents stale data from a previous
256
+ // request (or a share() that fell through before context was ready)
257
+ delete sails.inertia.globalSharedProps[key]
207
258
  } else {
208
259
  if (context) {
209
260
  context.sharedProps = {}
210
261
  }
211
- if (global) {
212
- sails.inertia.globalSharedProps = {}
213
- }
262
+ sails.inertia.globalSharedProps = {}
214
263
  }
215
264
  },
216
265
 
217
266
  /**
218
- * Add view data for the current request.
267
+ * Set a local for the current request's root EJS template.
219
268
  * Uses AsyncLocalStorage to ensure data doesn't leak between concurrent requests.
220
- * @param {string} key - The key of the view data
221
- * @param {*} value - The value of the view data
269
+ * @param {string} key - The local variable name
270
+ * @param {*} value - The value
222
271
  * @returns {*} - The value that was set
223
272
  */
224
- viewData(key, value) {
273
+ local(key, value) {
225
274
  const context = requestContext.getContext()
226
275
  if (context) {
227
- requestContext.setSharedViewData(key, value)
276
+ requestContext.setSharedLocal(key, value)
228
277
  return value
229
278
  }
230
- // Fallback to global if called outside request
231
- sails.inertia.globalSharedViewData[key] = value
279
+ sails.log.warn(
280
+ `sails.inertia.local('${key}') called outside request context. ` +
281
+ 'Value was not stored. Use localGlobally() for global data.'
282
+ )
232
283
  return value
233
284
  },
234
285
 
235
286
  /**
236
- * Add view data globally across all requests.
237
- * @param {string} key - The key of the view data
238
- * @param {*} value - The value of the view data
287
+ * Set a local globally across all requests.
288
+ * @param {string} key - The local variable name
289
+ * @param {*} value - The value
239
290
  * @returns {*} - The value that was set
240
291
  */
241
- viewDataGlobally(key, value) {
242
- sails.inertia.globalSharedViewData[key] = value
292
+ localGlobally(key, value) {
293
+ sails.inertia.globalSharedLocals[key] = value
243
294
  return value
244
295
  },
245
296
 
246
297
  /**
247
- * Get view data (merges global + request-scoped)
248
- * @param {string} key - The key of the view data to get
249
- * @returns {*} - The view data
298
+ * Get locals (merges global + request-scoped)
299
+ * @param {string} key - The local variable name to get
300
+ * @returns {*} - The locals
250
301
  */
251
- getViewData(key) {
252
- const globalData = sails.inertia.globalSharedViewData
253
- const requestData = requestContext.getSharedViewData()
302
+ getLocals(key) {
303
+ const globalData = sails.inertia.globalSharedLocals
304
+ const requestData = requestContext.getSharedLocals()
254
305
  const merged = { ...globalData, ...requestData }
255
306
  return key ? merged[key] : merged
256
307
  },
@@ -11,7 +11,7 @@
11
11
  * req: Request,
12
12
  * res: Response,
13
13
  * sharedProps: {}, // Request-scoped shared props
14
- * sharedViewData: {}, // Request-scoped view data
14
+ * sharedLocals: {}, // Request-scoped locals for root EJS template
15
15
  * encryptHistory: null, // Request-scoped history encryption (null = use default)
16
16
  * clearHistory: false, // Request-scoped clear history flag
17
17
  * refreshOnceProps: [], // Props to force-refresh for this request
@@ -40,13 +40,13 @@ module.exports = {
40
40
  req,
41
41
  res,
42
42
  sharedProps: {},
43
- sharedViewData: {},
43
+ sharedLocals: {},
44
44
  encryptHistory: null,
45
45
  clearHistory: false,
46
46
  refreshOnceProps: [], // Props to force-refresh for this request
47
47
  rootView: null // Request-scoped root view template
48
48
  }
49
- return requestContext.run(context, callback)
49
+ return requestContext.run(context, () => callback())
50
50
  },
51
51
 
52
52
  /**
@@ -97,23 +97,23 @@ module.exports = {
97
97
  },
98
98
 
99
99
  /**
100
- * Get request-scoped shared view data
101
- * @returns {Object} - The shared view data for this request
100
+ * Get request-scoped shared locals
101
+ * @returns {Object} - The shared locals for this request
102
102
  */
103
- getSharedViewData() {
103
+ getSharedLocals() {
104
104
  const context = requestContext.getStore()
105
- return context?.sharedViewData || {}
105
+ return context?.sharedLocals || {}
106
106
  },
107
107
 
108
108
  /**
109
- * Set request-scoped shared view data
109
+ * Set a request-scoped shared local
110
110
  * @param {string} key - The key
111
111
  * @param {*} value - The value
112
112
  */
113
- setSharedViewData(key, value) {
113
+ setSharedLocal(key, value) {
114
114
  const context = requestContext.getStore()
115
115
  if (context) {
116
- context.sharedViewData[key] = value
116
+ context.sharedLocals[key] = value
117
117
  }
118
118
  },
119
119
 
package/lib/render.js CHANGED
@@ -8,11 +8,11 @@ module.exports = async function render(req, res, data) {
8
8
  // Use request-scoped rootView if set, otherwise fall back to config
9
9
  const rootView = requestContext.getRootView() || sails.config.inertia.rootView
10
10
 
11
- // Use request-scoped view data merged with global view data
12
- // This prevents view data from leaking between concurrent requests
13
- const allViewData = {
14
- ...sails.inertia.getViewData(), // Merges global + request-scoped
15
- ...data.viewData
11
+ // Use request-scoped locals merged with global locals
12
+ // This prevents locals from leaking between concurrent requests
13
+ const allLocals = {
14
+ ...sails.inertia.getLocals(), // Merges global + request-scoped
15
+ ...data.locals
16
16
  }
17
17
 
18
18
  let page = await buildPageObject(req, data.page, data.props)
@@ -32,9 +32,6 @@ module.exports = async function render(req, res, data) {
32
32
  return res.json(page)
33
33
  } else {
34
34
  // Implements full page reload
35
- return res.view(rootView, {
36
- page,
37
- viewData: allViewData
38
- })
35
+ return res.view(rootView, { page, ...allLocals })
39
36
  }
40
37
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "inertia-sails",
3
- "version": "1.2.0",
3
+ "version": "1.3.1",
4
4
  "description": "The Sails adapter for Inertia.",
5
5
  "main": "index.js",
6
6
  "sails": {