velocious 1.0.445 → 1.0.447

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.
Files changed (89) hide show
  1. package/README.md +1 -1
  2. package/build/configuration-types.js +2 -2
  3. package/build/database/pool/async-tracked-multi-connection.js +3 -1
  4. package/build/database/record/index.js +38 -38
  5. package/build/environment-handlers/node/cli/commands/generate/base-models.js +67 -1
  6. package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +169 -0
  7. package/build/frontend-model-controller.js +44 -12
  8. package/build/frontend-model-resource/base-resource.js +519 -129
  9. package/build/frontend-models/base.js +417 -203
  10. package/build/frontend-models/preloader.js +7 -7
  11. package/build/frontend-models/query.js +18 -18
  12. package/build/frontend-models/use-created-event.js +1 -1
  13. package/build/frontend-models/use-destroyed-event.js +1 -1
  14. package/build/frontend-models/use-model-class-event.js +1 -1
  15. package/build/frontend-models/use-updated-event.js +1 -1
  16. package/build/frontend-models/websocket-channel.js +39 -3
  17. package/build/routes/resolver.js +17 -14
  18. package/build/src/configuration-types.d.ts +6 -6
  19. package/build/src/configuration-types.js +3 -3
  20. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  21. package/build/src/database/pool/async-tracked-multi-connection.js +5 -2
  22. package/build/src/database/record/index.d.ts +38 -38
  23. package/build/src/database/record/index.d.ts.map +1 -1
  24. package/build/src/database/record/index.js +39 -39
  25. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts +13 -0
  26. package/build/src/environment-handlers/node/cli/commands/generate/base-models.d.ts.map +1 -1
  27. package/build/src/environment-handlers/node/cli/commands/generate/base-models.js +59 -2
  28. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +74 -0
  29. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
  30. package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +155 -1
  31. package/build/src/frontend-model-controller.d.ts +2 -1
  32. package/build/src/frontend-model-controller.d.ts.map +1 -1
  33. package/build/src/frontend-model-controller.js +38 -14
  34. package/build/src/frontend-model-resource/base-resource.d.ts +196 -21
  35. package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
  36. package/build/src/frontend-model-resource/base-resource.js +467 -112
  37. package/build/src/frontend-models/base.d.ts +232 -149
  38. package/build/src/frontend-models/base.d.ts.map +1 -1
  39. package/build/src/frontend-models/base.js +371 -201
  40. package/build/src/frontend-models/preloader.d.ts +10 -10
  41. package/build/src/frontend-models/preloader.d.ts.map +1 -1
  42. package/build/src/frontend-models/preloader.js +8 -8
  43. package/build/src/frontend-models/query.d.ts +8 -8
  44. package/build/src/frontend-models/query.d.ts.map +1 -1
  45. package/build/src/frontend-models/query.js +19 -19
  46. package/build/src/frontend-models/use-created-event.d.ts +2 -2
  47. package/build/src/frontend-models/use-created-event.d.ts.map +1 -1
  48. package/build/src/frontend-models/use-created-event.js +2 -2
  49. package/build/src/frontend-models/use-destroyed-event.d.ts +1 -1
  50. package/build/src/frontend-models/use-destroyed-event.d.ts.map +1 -1
  51. package/build/src/frontend-models/use-destroyed-event.js +2 -2
  52. package/build/src/frontend-models/use-model-class-event.d.ts +1 -1
  53. package/build/src/frontend-models/use-model-class-event.d.ts.map +1 -1
  54. package/build/src/frontend-models/use-model-class-event.js +2 -2
  55. package/build/src/frontend-models/use-updated-event.d.ts +1 -1
  56. package/build/src/frontend-models/use-updated-event.d.ts.map +1 -1
  57. package/build/src/frontend-models/use-updated-event.js +2 -2
  58. package/build/src/frontend-models/websocket-channel.d.ts +8 -0
  59. package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
  60. package/build/src/frontend-models/websocket-channel.js +35 -4
  61. package/build/src/routes/resolver.d.ts.map +1 -1
  62. package/build/src/routes/resolver.js +7 -4
  63. package/build/src/utils/model-scope.d.ts +4 -4
  64. package/build/src/utils/model-scope.d.ts.map +1 -1
  65. package/build/src/utils/model-scope.js +3 -3
  66. package/build/src/utils/ransack.d.ts +1 -1
  67. package/build/src/utils/ransack.d.ts.map +1 -1
  68. package/build/src/utils/ransack.js +2 -2
  69. package/build/utils/model-scope.js +2 -2
  70. package/build/utils/ransack.js +1 -1
  71. package/package.json +1 -1
  72. package/src/configuration-types.js +2 -2
  73. package/src/database/pool/async-tracked-multi-connection.js +3 -1
  74. package/src/database/record/index.js +38 -38
  75. package/src/environment-handlers/node/cli/commands/generate/base-models.js +67 -1
  76. package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +169 -0
  77. package/src/frontend-model-controller.js +44 -12
  78. package/src/frontend-model-resource/base-resource.js +519 -129
  79. package/src/frontend-models/base.js +417 -203
  80. package/src/frontend-models/preloader.js +7 -7
  81. package/src/frontend-models/query.js +18 -18
  82. package/src/frontend-models/use-created-event.js +1 -1
  83. package/src/frontend-models/use-destroyed-event.js +1 -1
  84. package/src/frontend-models/use-model-class-event.js +1 -1
  85. package/src/frontend-models/use-updated-event.js +1 -1
  86. package/src/frontend-models/websocket-channel.js +39 -3
  87. package/src/routes/resolver.js +17 -14
  88. package/src/utils/model-scope.js +2 -2
  89. package/src/utils/ransack.js +1 -1
@@ -15,7 +15,7 @@ export default class FrontendModelPreloader {
15
15
  /**
16
16
  * Runs preload.
17
17
  * @param {Array<import("./base.js").default>} models - Frontend model instances to preload onto.
18
- * @param {import("./query.js").default<typeof import("./base.js").default> | import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} queryOrSpec - A query built via `Model.preload(...).select(...)`, or a raw preload spec.
18
+ * @param {import("./query.js").default<import("./base.js").FrontendModelClass> | import("../database/query/index.js").NestedPreloadRecord | string | Array<string | import("../database/query/index.js").NestedPreloadRecord>} queryOrSpec - A query built via `Model.preload(...).select(...)`, or a raw preload spec.
19
19
  * @param {{force?: boolean}} [options] - Options.
20
20
  * @returns {Promise<void>} - Resolves when preloading completes.
21
21
  */
@@ -24,12 +24,12 @@ export default class FrontendModelPreloader {
24
24
 
25
25
  const modelClass = /**
26
26
  * Narrows the runtime value to the documented type.
27
- @type {typeof import("./base.js").default} */ (models[0].constructor)
27
+ @type {import("./base.js").FrontendModelClass} */ (models[0].constructor)
28
28
  const isQuery = Boolean(queryOrSpec) && typeof queryOrSpec === "object" && "_preload" in queryOrSpec
29
29
  const query = isQuery
30
30
  ? /**
31
31
  * Narrows the runtime value to the documented type.
32
- @type {import("./query.js").default<typeof import("./base.js").default>} */ (queryOrSpec)
32
+ @type {import("./query.js").default<import("./base.js").FrontendModelClass>} */ (queryOrSpec)
33
33
  : modelClass.preload(/**
34
34
  * Narrows the runtime value to the documented type.
35
35
  @type {?} */ (queryOrSpec))
@@ -85,10 +85,10 @@ export default class FrontendModelPreloader {
85
85
  /**
86
86
  * Runs model needs reload.
87
87
  * @param {object} args - Options object.
88
- * @param {typeof import("./base.js").default} args.modelClass - Model class the preload graph is rooted at.
88
+ * @param {import("./base.js").FrontendModelClass} args.modelClass - Model class the preload graph is rooted at.
89
89
  * @param {import("./base.js").default} args.model - Model instance.
90
90
  * @param {import("../database/query/index.js").NestedPreloadRecord} args.preload - Preload sub-graph to satisfy.
91
- * @param {import("./query.js").default<typeof import("./base.js").default>} args.query - Source query carrying select/selectsExtra.
91
+ * @param {import("./query.js").default<import("./base.js").FrontendModelClass>} args.query - Source query carrying select/selectsExtra.
92
92
  * @param {boolean} args.force - Whether to reload regardless of cached state.
93
93
  * @returns {boolean} - Whether the model needs a reload request.
94
94
  */
@@ -110,11 +110,11 @@ export default class FrontendModelPreloader {
110
110
  * client-unknown default attributes plus the extras), so it always reloads.
111
111
  * With no select and no nested preload, being preloaded is enough.
112
112
  * @param {object} args - Options object.
113
- * @param {typeof import("./base.js").default} args.modelClass - Model class owning the relationship.
113
+ * @param {import("./base.js").FrontendModelClass} args.modelClass - Model class owning the relationship.
114
114
  * @param {import("./base.js").default} args.model - Model instance.
115
115
  * @param {string} args.relationshipName - Relationship name.
116
116
  * @param {import("../database/query/index.js").NestedPreloadRecord[string]} args.subPreload - Preload value for this relationship (`true` or a nested record).
117
- * @param {import("./query.js").default<typeof import("./base.js").default>} args.query - Source query carrying select/selectsExtra.
117
+ * @param {import("./query.js").default<import("./base.js").FrontendModelClass>} args.query - Source query carrying select/selectsExtra.
118
118
  * @returns {boolean} - Whether the relationship is already satisfied.
119
119
  */
120
120
  static _relationshipSatisfied({modelClass, model, relationshipName, subPreload, query}) {
@@ -37,11 +37,11 @@ import isPlainObject from "../utils/plain-object.js"
37
37
  */
38
38
  /**
39
39
  * Defines this typedef.
40
- * @typedef {FrontendModelProjectionOptions & {query?: FrontendModelQuery<typeof import("./base.js").default>}} FrontendModelEventOptionsObject
40
+ * @typedef {FrontendModelProjectionOptions & {query?: FrontendModelQuery<import("./base.js").FrontendModelClass>}} FrontendModelEventOptionsObject
41
41
  */
42
42
  /**
43
43
  * FrontendModelEventOptions type.
44
- * @typedef {FrontendModelEventOptionsObject | FrontendModelQuery<typeof import("./base.js").default>} FrontendModelEventOptions
44
+ * @typedef {FrontendModelEventOptionsObject | FrontendModelQuery<import("./base.js").FrontendModelClass>} FrontendModelEventOptions
45
45
  */
46
46
  /**
47
47
  * FrontendModelProjectionPayload type.
@@ -955,7 +955,7 @@ export function normalizePluck(pluck) {
955
955
 
956
956
  /**
957
957
  * Runs frontend model resource attributes.
958
- * @param {typeof import("./base.js").default} modelClass - Model class.
958
+ * @param {import("./base.js").FrontendModelClass} modelClass - Model class.
959
959
  * @returns {Set<string>} - Resource attribute names.
960
960
  */
961
961
  function frontendModelResourceAttributes(modelClass) {
@@ -977,9 +977,9 @@ function frontendModelResourceAttributes(modelClass) {
977
977
 
978
978
  /**
979
979
  * Runs frontend model pluck target model class.
980
- * @param {typeof import("./base.js").default} modelClass - Root model class.
980
+ * @param {import("./base.js").FrontendModelClass} modelClass - Root model class.
981
981
  * @param {string[]} path - Relationship path.
982
- * @returns {typeof import("./base.js").default} - Target model class for path.
982
+ * @returns {import("./base.js").FrontendModelClass} - Target model class for path.
983
983
  */
984
984
  function frontendModelPluckTargetModelClass(modelClass, path) {
985
985
  let targetModelClass = modelClass
@@ -1011,7 +1011,7 @@ function frontendModelPluckTargetModelClass(modelClass, path) {
1011
1011
  /**
1012
1012
  * Runs validate pluck definitions.
1013
1013
  * @param {object} args - Pluck validation args.
1014
- * @param {typeof import("./base.js").default} args.modelClass - Root model class.
1014
+ * @param {import("./base.js").FrontendModelClass} args.modelClass - Root model class.
1015
1015
  * @param {FrontendModelPluck[]} args.pluck - Pluck descriptors.
1016
1016
  * @returns {FrontendModelPluck[]} - Validated pluck descriptors.
1017
1017
  */
@@ -1075,7 +1075,7 @@ function reverseSortDirection(direction) {
1075
1075
 
1076
1076
  /**
1077
1077
  * Query wrapper for frontend model commands.
1078
- * @template {typeof import("./base.js").default} T
1078
+ * @template {import("./base.js").FrontendModelClass} T
1079
1079
  */
1080
1080
  export default class FrontendModelQuery {
1081
1081
  /**
@@ -2145,7 +2145,7 @@ function frontendModelEventFilterKey(payload) {
2145
2145
 
2146
2146
  /**
2147
2147
  * Runs apply frontend model projection options.
2148
- * @param {FrontendModelQuery<typeof import("./base.js").default>} query - Query receiving projection options.
2148
+ * @param {FrontendModelQuery<import("./base.js").FrontendModelClass>} query - Query receiving projection options.
2149
2149
  * @param {FrontendModelProjectionOptions} options - Projection options.
2150
2150
  * @returns {void}
2151
2151
  */
@@ -2160,8 +2160,8 @@ function applyFrontendModelProjectionOptions(query, options) {
2160
2160
 
2161
2161
  /**
2162
2162
  * Runs assert frontend model event query class.
2163
- * @param {typeof import("./base.js").default} modelClass - Expected frontend model class.
2164
- * @param {FrontendModelQuery<typeof import("./base.js").default>} query - Event query.
2163
+ * @param {import("./base.js").FrontendModelClass} modelClass - Expected frontend model class.
2164
+ * @param {FrontendModelQuery<import("./base.js").FrontendModelClass>} query - Event query.
2165
2165
  * @returns {void}
2166
2166
  */
2167
2167
  function assertFrontendModelEventQueryClass(modelClass, query) {
@@ -2183,9 +2183,9 @@ function assertFrontendModelEventOptionsObject(options) {
2183
2183
 
2184
2184
  /**
2185
2185
  * Runs cloned frontend model event query.
2186
- * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2187
- * @param {FrontendModelQuery<typeof import("./base.js").default>} query - Event query.
2188
- * @returns {FrontendModelQuery<typeof import("./base.js").default>} - Cloned query used by event subscriptions.
2186
+ * @param {import("./base.js").FrontendModelClass} modelClass - Frontend model class.
2187
+ * @param {FrontendModelQuery<import("./base.js").FrontendModelClass>} query - Event query.
2188
+ * @returns {FrontendModelQuery<import("./base.js").FrontendModelClass>} - Cloned query used by event subscriptions.
2189
2189
  */
2190
2190
  function clonedFrontendModelEventQuery(modelClass, query) {
2191
2191
  assertFrontendModelEventQueryClass(modelClass, query)
@@ -2195,9 +2195,9 @@ function clonedFrontendModelEventQuery(modelClass, query) {
2195
2195
 
2196
2196
  /**
2197
2197
  * Runs frontend model event query from options object.
2198
- * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2198
+ * @param {import("./base.js").FrontendModelClass} modelClass - Frontend model class.
2199
2199
  * @param {FrontendModelEventOptionsObject} options - Event options object.
2200
- * @returns {FrontendModelQuery<typeof import("./base.js").default>} - Query used by event subscriptions.
2200
+ * @returns {FrontendModelQuery<import("./base.js").FrontendModelClass>} - Query used by event subscriptions.
2201
2201
  */
2202
2202
  function frontendModelEventQueryFromOptionsObject(modelClass, options) {
2203
2203
  if (options.query !== undefined && !(options.query instanceof FrontendModelQuery)) {
@@ -2215,9 +2215,9 @@ function frontendModelEventQueryFromOptionsObject(modelClass, options) {
2215
2215
 
2216
2216
  /**
2217
2217
  * Runs frontend model event query.
2218
- * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2218
+ * @param {import("./base.js").FrontendModelClass} modelClass - Frontend model class.
2219
2219
  * @param {FrontendModelEventOptions} [options] - Event query or projection options.
2220
- * @returns {FrontendModelQuery<typeof import("./base.js").default>} - Normalized query used by event subscriptions.
2220
+ * @returns {FrontendModelQuery<import("./base.js").FrontendModelClass>} - Normalized query used by event subscriptions.
2221
2221
  */
2222
2222
  function frontendModelEventQuery(modelClass, options = {}) {
2223
2223
  if (options instanceof FrontendModelQuery) return clonedFrontendModelEventQuery(modelClass, options)
@@ -2236,7 +2236,7 @@ function frontendModelEventQuery(modelClass, options = {}) {
2236
2236
 
2237
2237
  /**
2238
2238
  * Runs the frontendModelEventOptionsPayload helper.
2239
- * @param {typeof import("./base.js").default} modelClass - Frontend model class.
2239
+ * @param {import("./base.js").FrontendModelClass} modelClass - Frontend model class.
2240
2240
  * @param {FrontendModelEventOptions} [options] - Event query or projection options.
2241
2241
  * @returns {FrontendModelEventOptionsPayload} - Normalized event subscription payload.
2242
2242
  */
@@ -2,7 +2,7 @@
2
2
 
3
3
  import useModelClassEvent from "./use-model-class-event.js"
4
4
 
5
- /** @typedef {typeof import("./base.js").default} FrontendModelClass */
5
+ /** @typedef {import("./base.js").FrontendModelClass} FrontendModelClass */
6
6
  /** @typedef {import("./use-model-class-event.js").FrontendModelCreateUpdateEventPayload} FrontendModelCreateEventPayload */
7
7
  /** @typedef {import("./use-model-class-event.js").UseModelClassEventOptions} UseCreatedEventOptions */
8
8
  /** @typedef {(payload: FrontendModelCreateEventPayload) => void} FrontendModelCreateEventCallback */
@@ -9,7 +9,7 @@ import useModelClassEvent from "./use-model-class-event.js"
9
9
 
10
10
  /**
11
11
  * FrontendModelClass type.
12
- @typedef {typeof import("./base.js").default} FrontendModelClass */
12
+ @typedef {import("./base.js").FrontendModelClass} FrontendModelClass */
13
13
  /**
14
14
  * FrontendModelInstance type.
15
15
  @typedef {import("./base.js").default} FrontendModelInstance */
@@ -7,7 +7,7 @@ import clearPendingDebouncedCallback from "./clear-pending-debounced-callback.js
7
7
 
8
8
  /**
9
9
  * FrontendModelClass type.
10
- @typedef {typeof import("./base.js").default} FrontendModelClass */
10
+ @typedef {import("./base.js").FrontendModelClass} FrontendModelClass */
11
11
  /**
12
12
  * FrontendModelInstance type.
13
13
  @typedef {InstanceType<FrontendModelClass>} FrontendModelInstance */
@@ -9,7 +9,7 @@ import useModelClassEvent from "./use-model-class-event.js"
9
9
 
10
10
  /**
11
11
  * FrontendModelClass type.
12
- @typedef {typeof import("./base.js").default} FrontendModelClass */
12
+ @typedef {import("./base.js").FrontendModelClass} FrontendModelClass */
13
13
  /**
14
14
  * FrontendModelInstance type.
15
15
  @typedef {import("./base.js").default} FrontendModelInstance */
@@ -61,7 +61,11 @@ export default class FrontendModelWebsocketChannel extends VelociousWebsocketCha
61
61
  if (!ModelClass) return false
62
62
 
63
63
  const ability = await configuration.resolveAbility?.({
64
- params: {model: modelName},
64
+ // Forward the subscriber's params (e.g. authenticationToken) so token-authenticated clients
65
+ // resolve the same ability they would over HTTP. Without this only session/cookie auth on the
66
+ // upgrade request works, and param-based auth (like a scanner passing an authenticationToken)
67
+ // is dropped — leaving such subscribers with a guest ability and no read rule.
68
+ params: {...this.params, model: modelName},
65
69
  request: /**
66
70
  * Narrows the runtime value to the documented type.
67
71
  @type {import("../http-server/client/request.js").default} */ (this._syntheticRequest()),
@@ -116,6 +120,15 @@ export default class FrontendModelWebsocketChannel extends VelociousWebsocketCha
116
120
  const hasEventFilters = this._hasEventFilterParams()
117
121
 
118
122
  if (!this._hasProjectionParams() && !hasEventFilters) {
123
+ // Even unfiltered subscriptions must respect the subscriber's ability. A create/update carries
124
+ // the record, so only deliver it when the record is within the authenticated ability's scope.
125
+ // Destroys (and bodies without a usable id) carry no record, so pass them through unchanged.
126
+ if (body && typeof body === "object" && (body.action === "create" || body.action === "update") && body.id !== undefined && body.id !== null) {
127
+ const FrontendModelController = await this._frontendModelControllerClass()
128
+
129
+ if (!await this._eventIsAccessible(body.id, FrontendModelController)) return
130
+ }
131
+
119
132
  this.sendMessage(body, meta)
120
133
  return
121
134
  }
@@ -343,6 +356,25 @@ export default class FrontendModelWebsocketChannel extends VelociousWebsocketCha
343
356
  return controller
344
357
  }
345
358
 
359
+ /**
360
+ * Whether the broadcast record is within the subscriber's authenticated ability scope. Used to gate
361
+ * unfiltered/unprojected create/update delivery so a scoped token never receives a record it cannot read.
362
+ * @param {string | number} id - Event record id.
363
+ * @param {typeof import("../frontend-model-controller.js").default} FrontendModelController - Server-side frontend-model controller class.
364
+ * @returns {Promise<boolean>} True when the record is readable by this subscription.
365
+ */
366
+ async _eventIsAccessible(id, FrontendModelController) {
367
+ const controller = this._frontendModelController(FrontendModelController)
368
+
369
+ await controller.ensureFrontendModelClassInitialized()
370
+
371
+ const ModelClass = controller.frontendModelClass()
372
+ const primaryKey = ModelClass.primaryKey()
373
+ const query = controller.frontendModelAuthorizedQuery("find").where({[ModelClass.tableName()]: {[primaryKey]: id}})
374
+
375
+ return Boolean(await query.first())
376
+ }
377
+
346
378
  /**
347
379
  * Runs matched event filter keys for event id.
348
380
  * @param {string | number} id - Event record id.
@@ -389,7 +421,9 @@ export default class FrontendModelWebsocketChannel extends VelociousWebsocketCha
389
421
  const primaryKey = ModelClass.primaryKey()
390
422
  const where = controller.frontendModelWhere()
391
423
  const joins = controller.frontendModelJoins()
392
- let query = ModelClass.where({[ModelClass.tableName()]: {[primaryKey]: id}})
424
+ // Start from the subscriber's authorized scope so a filter can only ever match records the
425
+ // subscription's ability permits to read.
426
+ let query = controller.frontendModelAuthorizedQuery("find").where({[ModelClass.tableName()]: {[primaryKey]: id}})
393
427
 
394
428
  if (where) controller.applyFrontendModelWhere({query, where})
395
429
  if (joins) controller.applyFrontendModelJoins({joins, query})
@@ -414,7 +448,9 @@ export default class FrontendModelWebsocketChannel extends VelociousWebsocketCha
414
448
 
415
449
  const ModelClass = controller.frontendModelClass()
416
450
  const primaryKey = ModelClass.primaryKey()
417
- let query = ModelClass.where({[ModelClass.tableName()]: {[primaryKey]: id}})
451
+ // Reload through the subscriber's authorized scope so projected records are only ever sent for
452
+ // rows the subscription's ability permits to read.
453
+ let query = controller.frontendModelAuthorizedQuery("find").where({[ModelClass.tableName()]: {[primaryKey]: id}})
418
454
  const preload = controller.frontendModelPreload()
419
455
 
420
456
  if (preload) query = query.preload(preload)
@@ -120,9 +120,9 @@ export default class VelociousRoutesResolver {
120
120
  }
121
121
 
122
122
  const routeResolverHookMatch = await this.resolveRouteResolverHooks(currentPath, {hasMatchingCustomRoute})
123
- const skipControllerConnections = routeResolverHookMatch?.skipControllerConnections === true
124
- const skipAbilityResolution = routeResolverHookMatch?.skipAbilityResolution === true
125
- const skipTenantResolution = routeResolverHookMatch?.skipTenantResolution === true
123
+ let skipControllerConnections = routeResolverHookMatch?.skipControllerConnections === true
124
+ let skipAbilityResolution = routeResolverHookMatch?.skipAbilityResolution === true
125
+ let skipTenantResolution = routeResolverHookMatch?.skipTenantResolution === true
126
126
  const matchResult = routeResolverHookMatch || !currentRoute ? undefined : this.matchPathWithRoutes(currentRoute, currentPath)
127
127
  const actionParam = this.params.action
128
128
  const controllerParam = this.params.controller
@@ -151,21 +151,24 @@ export default class VelociousRoutesResolver {
151
151
  viewPath = routeHookViewPath || `${this.configuration.getDirectory()}/src/routes/${controller}`
152
152
  this.routeHookControllerClass = routeHookControllerClass
153
153
  } else if (!matchResult) {
154
- const __filename = fileURLToPath(import.meta.url)
155
- const __dirname = dirname(__filename)
156
- const requestedPath = currentPath.replace(/^\//, "") || "_root"
157
- const attemptedControllerPath = `${this.configuration.getDirectory()}/src/routes/${requestedPath}/controller.js`
154
+ const __filename = fileURLToPath(import.meta.url)
155
+ const __dirname = dirname(__filename)
156
+ const requestedPath = currentPath.replace(/^\//, "") || "_root"
157
+ const attemptedControllerPath = `${this.configuration.getDirectory()}/src/routes/${requestedPath}/controller.js`
158
158
 
159
- const logger = this.logger
159
+ const logger = this.logger
160
160
 
161
- if (!logger) throw new Error("Logger not initialized")
161
+ if (!logger) throw new Error("Logger not initialized")
162
162
 
163
- await logger.warn(`No route matched for ${rawPath}. Tried controller at ${attemptedControllerPath}`)
163
+ await logger.warn(`No route matched for ${rawPath}. Tried controller at ${attemptedControllerPath}`)
164
164
 
165
- controller = "errors"
166
- controllerPath = "./built-in/errors/controller.js"
167
- action = "notFound"
168
- viewPath = await fs.realpath(`${__dirname}/built-in/errors`)
165
+ controller = "errors"
166
+ controllerPath = "./built-in/errors/controller.js"
167
+ action = "notFound"
168
+ skipAbilityResolution = true
169
+ skipControllerConnections = true
170
+ skipTenantResolution = true
171
+ viewPath = await fs.realpath(`${__dirname}/built-in/errors`)
169
172
  } else if (action) {
170
173
  if (!controller) controller = "_root"
171
174
 
@@ -7,7 +7,7 @@ const MODEL_SCOPE_DESCRIPTOR_MARKER = "velociousModelScopeDescriptor"
7
7
  * @typedef {object} ModelScopeDescriptor
8
8
  * @property {true} [velociousModelScopeDescriptor] - Internal marker.
9
9
  * @property {(...args: Array<?>) => ?} callback - Scope callback.
10
- * @property {typeof import("../database/record/index.js").default | typeof import("../frontend-models/base.js").default} modelClass - Owning model class.
10
+ * @property {typeof import("../database/record/index.js").default | import("../frontend-models/base.js").FrontendModelClass} modelClass - Owning model class.
11
11
  * @property {Array<?>} scopeArgs - Scope arguments.
12
12
  */
13
13
 
@@ -15,7 +15,7 @@ const MODEL_SCOPE_DESCRIPTOR_MARKER = "velociousModelScopeDescriptor"
15
15
  * Runs the defineModelScope helper.
16
16
  * @param {object} args - Definition arguments.
17
17
  * @param {(...args: Array<?>) => ?} args.callback - Scope callback.
18
- * @param {typeof import("../database/record/index.js").default | typeof import("../frontend-models/base.js").default} args.modelClass - Owning model class.
18
+ * @param {typeof import("../database/record/index.js").default | import("../frontend-models/base.js").FrontendModelClass} args.modelClass - Owning model class.
19
19
  * @param {() => ?} args.startQuery - Factory that returns a fresh query for the owning model class.
20
20
  * @returns {((...args: Array<?>) => ?) & {scope: (...args: Array<?>) => ModelScopeDescriptor}} - Scope helper.
21
21
  */
@@ -16,7 +16,7 @@ import {resolveFrontendModelClass} from "../frontend-models/model-registry.js"
16
16
 
17
17
  /**
18
18
  * RansackModelClass type.
19
- * @typedef {typeof import("../database/record/index.js").default | typeof import("../frontend-models/base.js").default} RansackModelClass
19
+ * @typedef {typeof import("../database/record/index.js").default | import("../frontend-models/base.js").FrontendModelClass} RansackModelClass
20
20
  */
21
21
 
22
22
  /**