velocious 1.0.465 → 1.0.467

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 (35) hide show
  1. package/README.md +2 -1
  2. package/build/configuration.js +2 -2
  3. package/build/database/pool/async-tracked-multi-connection.js +7 -2
  4. package/build/database/pool/single-multi-use.js +38 -0
  5. package/build/environment-handlers/node/cli/commands/lint/relationships.js +95 -2
  6. package/build/frontend-model-controller.js +162 -56
  7. package/build/frontend-models/query.js +55 -33
  8. package/build/src/configuration.js +3 -3
  9. package/build/src/database/pool/async-tracked-multi-connection.d.ts.map +1 -1
  10. package/build/src/database/pool/async-tracked-multi-connection.js +9 -3
  11. package/build/src/database/pool/single-multi-use.d.ts +1 -0
  12. package/build/src/database/pool/single-multi-use.d.ts.map +1 -1
  13. package/build/src/database/pool/single-multi-use.js +34 -1
  14. package/build/src/environment-handlers/node/cli/commands/lint/relationships.d.ts +29 -0
  15. package/build/src/environment-handlers/node/cli/commands/lint/relationships.d.ts.map +1 -1
  16. package/build/src/environment-handlers/node/cli/commands/lint/relationships.js +83 -3
  17. package/build/src/frontend-model-controller.d.ts +45 -5
  18. package/build/src/frontend-model-controller.d.ts.map +1 -1
  19. package/build/src/frontend-model-controller.js +155 -57
  20. package/build/src/frontend-models/query.d.ts +8 -0
  21. package/build/src/frontend-models/query.d.ts.map +1 -1
  22. package/build/src/frontend-models/query.js +53 -34
  23. package/build/src/utils/ransack.d.ts +11 -3
  24. package/build/src/utils/ransack.d.ts.map +1 -1
  25. package/build/src/utils/ransack.js +42 -23
  26. package/build/utils/ransack.js +44 -22
  27. package/package.json +1 -1
  28. package/src/configuration.js +2 -2
  29. package/src/database/pool/async-tracked-multi-connection.js +7 -2
  30. package/src/database/pool/single-multi-use.js +38 -0
  31. package/src/environment-handlers/node/cli/commands/lint/relationships.js +95 -2
  32. package/src/frontend-model-controller.js +162 -56
  33. package/src/frontend-models/query.js +55 -33
  34. package/src/types/external-modules.d.ts +13 -0
  35. package/src/utils/ransack.js +44 -22
@@ -6,7 +6,7 @@ import FrontendModelBaseResource from "./frontend-model-resource/base-resource.j
6
6
  import Response from "./http-server/client/response.js"
7
7
  import {frontendModelResourcesWithBuiltInsForBackendProject} from "./frontend-models/built-in-resources.js"
8
8
  import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcePath, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
9
- import {normalizeGroup as normalizeQueryGroup, normalizeJoins as normalizeQueryJoins, normalizePluck as normalizeQueryPluck, normalizePreload as normalizeQueryPreload, normalizeSearchOperator as normalizeQuerySearchOperator, normalizeSort as normalizeQuerySort} from "./frontend-models/query.js"
9
+ import {FrontendModelQueryError, normalizeGroup as normalizeQueryGroup, normalizeJoins as normalizeQueryJoins, normalizePluck as normalizeQueryPluck, normalizePreload as normalizeQueryPreload, normalizeSearchOperator as normalizeQuerySearchOperator, normalizeSort as normalizeQuerySort} from "./frontend-models/query.js"
10
10
  import {assignSafeProperty, deserializeFrontendModelTransportValue, isBackendModelInstance, serializeFrontendModelTransportValue} from "./frontend-models/transport-serialization.js"
11
11
  import RoutesResolver from "./routes/resolver.js"
12
12
  import {ValidationError} from "./database/record/index.js"
@@ -14,6 +14,7 @@ import { normalizeDateStringForWrite } from "./database/datetime-storage.js"
14
14
  import VelociousError from "./velocious-error.js"
15
15
  import isDate from "./utils/is-date.js"
16
16
  import isPlainObject from "./utils/plain-object.js"
17
+ import {RansackQueryError, normalizeRansackGroup, parseRansackSort} from "./utils/ransack.js"
17
18
 
18
19
  /**
19
20
  * Runs normalize frontend model preload.
@@ -37,7 +38,7 @@ function normalizeFrontendModelJoins(joins) {
37
38
  try {
38
39
  return normalizeQueryJoins(joins)
39
40
  } catch (error) {
40
- throw frontendModelValidationErrorForError(error)
41
+ throwFrontendModelQueryErrorForParserError(error)
41
42
  }
42
43
  }
43
44
 
@@ -52,7 +53,7 @@ function normalizeFrontendModelSelect(select, rootModelName = null) {
52
53
 
53
54
  if (typeof select === "string") {
54
55
  if (!rootModelName) {
55
- throw frontendModelValidationError("Invalid select shorthand without root model name")
56
+ throw frontendModelQueryError("Invalid select shorthand without root model name")
56
57
  }
57
58
 
58
59
  return {[rootModelName]: [select]}
@@ -60,12 +61,12 @@ function normalizeFrontendModelSelect(select, rootModelName = null) {
60
61
 
61
62
  if (Array.isArray(select)) {
62
63
  if (!rootModelName) {
63
- throw frontendModelValidationError("Invalid select shorthand without root model name")
64
+ throw frontendModelQueryError("Invalid select shorthand without root model name")
64
65
  }
65
66
 
66
67
  for (const attributeName of select) {
67
68
  if (typeof attributeName !== "string") {
68
- throw frontendModelValidationError(`Invalid select attribute for ${rootModelName}: ${typeof attributeName}`)
69
+ throw frontendModelQueryError(`Invalid select attribute for ${rootModelName}: ${typeof attributeName}`)
69
70
  }
70
71
  }
71
72
 
@@ -73,7 +74,7 @@ function normalizeFrontendModelSelect(select, rootModelName = null) {
73
74
  }
74
75
 
75
76
  if (!isPlainObject(select)) {
76
- throw frontendModelValidationError(`Invalid select type: ${typeof select}`)
77
+ throw frontendModelQueryError(`Invalid select type: ${typeof select}`)
77
78
  }
78
79
 
79
80
  /**
@@ -88,12 +89,12 @@ function normalizeFrontendModelSelect(select, rootModelName = null) {
88
89
  }
89
90
 
90
91
  if (!Array.isArray(selectValue)) {
91
- throw frontendModelValidationError(`Invalid select value for ${modelName}: ${typeof selectValue}`)
92
+ throw frontendModelQueryError(`Invalid select value for ${modelName}: ${typeof selectValue}`)
92
93
  }
93
94
 
94
95
  for (const attributeName of selectValue) {
95
96
  if (typeof attributeName !== "string") {
96
- throw frontendModelValidationError(`Invalid select attribute for ${modelName}: ${typeof attributeName}`)
97
+ throw frontendModelQueryError(`Invalid select attribute for ${modelName}: ${typeof attributeName}`)
97
98
  }
98
99
  }
99
100
 
@@ -175,27 +176,25 @@ function frontendModelQueryMetadata(query) {
175
176
  }
176
177
 
177
178
  /**
178
- * Runs frontend model validation error.
179
- * @param {string} message - Validation error message.
180
- * @returns {VelociousError} - Client-safe validation error.
179
+ * Builds a client-safe frontend-model query error.
180
+ * @param {string} message - Error message.
181
+ * @returns {VelociousError} Client-safe query error.
181
182
  */
182
- function frontendModelValidationError(message) {
183
- return VelociousError.safe(message, {code: "frontend-model-validation"})
183
+ function frontendModelQueryError(message) {
184
+ return VelociousError.safe(message, {code: "frontend-model-query-error"})
184
185
  }
185
186
 
186
187
  /**
187
- * Runs frontend model validation error for error.
188
+ * Throws a client-safe frontend-model query error for typed query parser errors.
188
189
  * @param {?} error - Error raised while normalizing client query params.
189
- * @returns {VelociousError} - Client-safe validation error preserving the normalizer message.
190
+ * @returns {never} Always throws.
190
191
  */
191
- function frontendModelValidationErrorForError(error) {
192
- if (error instanceof VelociousError && error.safeToExpose) return error
193
-
194
- const message = error instanceof Error
195
- ? error.message
196
- : String(error)
192
+ function throwFrontendModelQueryErrorForParserError(error) {
193
+ if (error instanceof FrontendModelQueryError || error instanceof RansackQueryError) {
194
+ throw frontendModelQueryError(error.message)
195
+ }
197
196
 
198
- return frontendModelValidationError(message)
197
+ throw error
199
198
  }
200
199
 
201
200
  /**
@@ -338,7 +337,7 @@ function normalizeFrontendModelSearches(searches) {
338
337
  if (!searches) return []
339
338
 
340
339
  if (!Array.isArray(searches)) {
341
- throw frontendModelValidationError(`Invalid searches type: ${typeof searches}`)
340
+ throw frontendModelQueryError(`Invalid searches type: ${typeof searches}`)
342
341
  }
343
342
 
344
343
  /**
@@ -348,7 +347,7 @@ function normalizeFrontendModelSearches(searches) {
348
347
 
349
348
  for (const search of searches) {
350
349
  if (!isPlainObject(search)) {
351
- throw frontendModelValidationError(`Invalid search entry type: ${typeof search}`)
350
+ throw frontendModelQueryError(`Invalid search entry type: ${typeof search}`)
352
351
  }
353
352
 
354
353
  const path = search.path
@@ -356,26 +355,34 @@ function normalizeFrontendModelSearches(searches) {
356
355
  const operator = search.operator
357
356
 
358
357
  if (!Array.isArray(path)) {
359
- throw frontendModelValidationError("Invalid search path: expected an array")
358
+ throw frontendModelQueryError("Invalid search path: expected an array")
360
359
  }
361
360
 
362
361
  for (const pathEntry of path) {
363
362
  if (typeof pathEntry !== "string" || pathEntry.length < 1) {
364
- throw frontendModelValidationError("Invalid search path entry: expected non-empty string")
363
+ throw frontendModelQueryError("Invalid search path entry: expected non-empty string")
365
364
  }
366
365
  }
367
366
 
368
367
  if (typeof column !== "string" || column.length < 1) {
369
- throw frontendModelValidationError("Invalid search column: expected non-empty string")
368
+ throw frontendModelQueryError("Invalid search column: expected non-empty string")
370
369
  }
371
370
 
372
371
  if (typeof operator !== "string") {
373
- throw frontendModelValidationError(`Invalid search operator: ${operator}`)
372
+ throw frontendModelQueryError(`Invalid search operator: ${operator}`)
373
+ }
374
+
375
+ let normalizedOperator
376
+
377
+ try {
378
+ normalizedOperator = normalizeQuerySearchOperator(operator)
379
+ } catch (error) {
380
+ throwFrontendModelQueryErrorForParserError(error)
374
381
  }
375
382
 
376
383
  normalized.push({
377
384
  column,
378
- operator: normalizeQuerySearchOperator(operator),
385
+ operator: normalizedOperator,
379
386
  path: [...path],
380
387
  value: search.value
381
388
  })
@@ -393,7 +400,7 @@ function normalizeFrontendModelWhere(where) {
393
400
  if (!where) return null
394
401
 
395
402
  if (!isPlainObject(where)) {
396
- throw frontendModelValidationError(`Invalid where type: ${typeof where}`)
403
+ throw frontendModelQueryError(`Invalid where type: ${typeof where}`)
397
404
  }
398
405
 
399
406
  return where
@@ -408,7 +415,7 @@ function normalizeFrontendModelRansack(ransack) {
408
415
  if (!ransack) return null
409
416
 
410
417
  if (!isPlainObject(ransack)) {
411
- throw frontendModelValidationError(`Invalid ransack type: ${typeof ransack}`)
418
+ throw frontendModelQueryError(`Invalid ransack type: ${typeof ransack}`)
412
419
  }
413
420
 
414
421
  return ransack
@@ -425,11 +432,11 @@ function normalizeFrontendModelIntegerParam(value, name, min) {
425
432
  if (value == null) return null
426
433
 
427
434
  if (typeof value !== "number" || !Number.isInteger(value)) {
428
- throw frontendModelValidationError(`Invalid ${name}: expected integer number`)
435
+ throw frontendModelQueryError(`Invalid ${name}: expected integer number`)
429
436
  }
430
437
 
431
438
  if (value < min) {
432
- throw frontendModelValidationError(`Invalid ${name}: expected value >= ${min}`)
439
+ throw frontendModelQueryError(`Invalid ${name}: expected value >= ${min}`)
433
440
  }
434
441
 
435
442
  return value
@@ -462,7 +469,7 @@ function normalizeFrontendModelDistinct(distinct) {
462
469
  if (distinct == null) return null
463
470
 
464
471
  if (typeof distinct !== "boolean") {
465
- throw frontendModelValidationError(`Invalid distinct: expected boolean`)
472
+ throw frontendModelQueryError(`Invalid distinct: expected boolean`)
466
473
  }
467
474
 
468
475
  return distinct
@@ -1194,7 +1201,11 @@ export default class FrontendModelController extends Controller {
1194
1201
  * @returns {FrontendModelSort[]} - Frontend sort definitions.
1195
1202
  */
1196
1203
  frontendModelSort() {
1197
- return normalizeQuerySort(this.frontendModelParams().sort)
1204
+ try {
1205
+ return normalizeQuerySort(this.frontendModelParams().sort)
1206
+ } catch (error) {
1207
+ throwFrontendModelQueryErrorForParserError(error)
1208
+ }
1198
1209
  }
1199
1210
 
1200
1211
  /**
@@ -1205,7 +1216,7 @@ export default class FrontendModelController extends Controller {
1205
1216
  try {
1206
1217
  return normalizeQueryGroup(this.frontendModelParams().group)
1207
1218
  } catch (error) {
1208
- throw frontendModelValidationErrorForError(error)
1219
+ throwFrontendModelQueryErrorForParserError(error)
1209
1220
  }
1210
1221
  }
1211
1222
 
@@ -1240,11 +1251,11 @@ export default class FrontendModelController extends Controller {
1240
1251
  try {
1241
1252
  const pluck = normalizeQueryPluck(this.frontendModelParams().pluck)
1242
1253
 
1243
- this.validateFrontendModelPluckDefinitions(pluck)
1254
+ this.assertFrontendModelPluckDefinitionsAllowed(pluck)
1244
1255
 
1245
1256
  return pluck
1246
1257
  } catch (error) {
1247
- throw frontendModelValidationErrorForError(error)
1258
+ throwFrontendModelQueryErrorForParserError(error)
1248
1259
  }
1249
1260
  }
1250
1261
 
@@ -1517,6 +1528,7 @@ export default class FrontendModelController extends Controller {
1517
1528
  const ransack = this.frontendModelRansack()
1518
1529
 
1519
1530
  if (ransack) {
1531
+ this.assertFrontendModelRansackAllowed(ransack)
1520
1532
  query.ransack(ransack)
1521
1533
  }
1522
1534
 
@@ -1639,7 +1651,7 @@ export default class FrontendModelController extends Controller {
1639
1651
  })
1640
1652
 
1641
1653
  if (!columnName) {
1642
- throw new Error(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
1654
+ throw frontendModelQueryError(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
1643
1655
  }
1644
1656
 
1645
1657
  if (pluckEntry.path.length > 0) {
@@ -1735,11 +1747,11 @@ export default class FrontendModelController extends Controller {
1735
1747
  }
1736
1748
 
1737
1749
  /**
1738
- * Validates frontend-model pluck definitions against exposed resource attributes.
1750
+ * Asserts frontend-model pluck definitions only reference exposed resource attributes.
1739
1751
  * @param {FrontendModelPluck[]} pluck - Pluck descriptors.
1740
1752
  * @returns {void}
1741
1753
  */
1742
- validateFrontendModelPluckDefinitions(pluck) {
1754
+ assertFrontendModelPluckDefinitionsAllowed(pluck) {
1743
1755
  const modelClass = this.frontendModelClass()
1744
1756
 
1745
1757
  for (const pluckEntry of pluck) {
@@ -1753,11 +1765,105 @@ export default class FrontendModelController extends Controller {
1753
1765
  })
1754
1766
 
1755
1767
  if (!columnName) {
1756
- throw new Error(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
1768
+ throw frontendModelQueryError(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
1769
+ }
1770
+ }
1771
+ }
1772
+
1773
+ /**
1774
+ * Asserts frontend-model Ransack definitions only reference exposed resource attributes.
1775
+ * @param {Record<string, ?>} ransack - Ransack descriptor.
1776
+ * @returns {void}
1777
+ */
1778
+ assertFrontendModelRansackAllowed(ransack) {
1779
+ const {s, ...filterParams} = ransack
1780
+
1781
+ if (Object.keys(filterParams).length > 0) {
1782
+ this.assertFrontendModelRansackGroupAllowed({
1783
+ group: this.frontendModelRansackGroup(filterParams)
1784
+ })
1785
+ }
1786
+
1787
+ if (typeof s === "string" && s.trim().length > 0) {
1788
+ for (const sort of this.frontendModelRansackSorts(s)) {
1789
+ this.assertFrontendModelRansackAttributeAllowed({
1790
+ attributeName: sort.attribute,
1791
+ modelClass: this.frontendModelClass(),
1792
+ operationName: "ransack sort"
1793
+ })
1757
1794
  }
1758
1795
  }
1759
1796
  }
1760
1797
 
1798
+ /**
1799
+ * Runs normalized frontend-model Ransack group.
1800
+ * @param {Record<string, ?>} filterParams - Ransack filter params.
1801
+ * @returns {import("./utils/ransack.js").RansackGroup} Normalized Ransack group.
1802
+ */
1803
+ frontendModelRansackGroup(filterParams) {
1804
+ try {
1805
+ return normalizeRansackGroup(this.frontendModelClass(), filterParams)
1806
+ } catch (error) {
1807
+ throwFrontendModelQueryErrorForParserError(error)
1808
+ }
1809
+ }
1810
+
1811
+ /**
1812
+ * Runs normalized frontend-model Ransack sorts.
1813
+ * @param {string} sortString - Ransack sort string.
1814
+ * @returns {import("./utils/ransack.js").RansackSort[]} Normalized Ransack sorts.
1815
+ */
1816
+ frontendModelRansackSorts(sortString) {
1817
+ try {
1818
+ return parseRansackSort(this.frontendModelClass(), sortString)
1819
+ } catch (error) {
1820
+ throwFrontendModelQueryErrorForParserError(error)
1821
+ }
1822
+ }
1823
+
1824
+ /**
1825
+ * Asserts a normalized frontend-model Ransack group only references exposed attributes.
1826
+ * @param {object} args - Assertion args.
1827
+ * @param {import("./utils/ransack.js").RansackGroup} args.group - Ransack group.
1828
+ * @returns {void}
1829
+ */
1830
+ assertFrontendModelRansackGroupAllowed({group}) {
1831
+ for (const condition of group.conditions) {
1832
+ for (const attribute of condition.attributes) {
1833
+ const targetModelClass = this.frontendModelSearchTargetModelClass({
1834
+ modelClass: this.frontendModelClass(),
1835
+ path: attribute.path
1836
+ })
1837
+
1838
+ this.assertFrontendModelRansackAttributeAllowed({
1839
+ attributeName: attribute.attributeName,
1840
+ modelClass: targetModelClass,
1841
+ operationName: "ransack"
1842
+ })
1843
+ }
1844
+ }
1845
+
1846
+ for (const grouping of group.groupings) {
1847
+ this.assertFrontendModelRansackGroupAllowed({group: grouping})
1848
+ }
1849
+ }
1850
+
1851
+ /**
1852
+ * Asserts one normalized frontend-model Ransack attribute is exposed by its resource.
1853
+ * @param {object} args - Assertion args.
1854
+ * @param {string} args.attributeName - Attribute name.
1855
+ * @param {typeof import("./database/record/index.js").default} args.modelClass - Target model class.
1856
+ * @param {string} args.operationName - Operation name for errors.
1857
+ * @returns {void}
1858
+ */
1859
+ assertFrontendModelRansackAttributeAllowed({attributeName, modelClass, operationName}) {
1860
+ const attributeNames = this.frontendModelResourceAttributeNamesForModelClass(modelClass)
1861
+
1862
+ if (attributeNames && !attributeNames.has(attributeName)) {
1863
+ throw frontendModelQueryError(`Unknown ${operationName} attribute "${attributeName}" for ${modelClass.name}`)
1864
+ }
1865
+ }
1866
+
1761
1867
  /**
1762
1868
  * Runs frontend model search target model class.
1763
1869
  * @param {object} args - Search args.
@@ -1772,7 +1878,7 @@ export default class FrontendModelController extends Controller {
1772
1878
  const relationship = targetModelClass.getRelationshipsMap()[relationshipName]
1773
1879
 
1774
1880
  if (!relationship) {
1775
- throw new Error(`Unknown search relationship "${relationshipName}" for ${targetModelClass.name}`)
1881
+ throw frontendModelQueryError(`Unknown search relationship "${relationshipName}" for ${targetModelClass.name}`)
1776
1882
  }
1777
1883
 
1778
1884
  const relationshipTargetModelClass = relationship.getTargetModelClass()
@@ -1807,7 +1913,7 @@ export default class FrontendModelController extends Controller {
1807
1913
  })
1808
1914
 
1809
1915
  if (!columnName) {
1810
- throw new Error(`Unknown search column "${search.column}" for ${targetModelClass.name}`)
1916
+ throw frontendModelQueryError(`Unknown search column "${search.column}" for ${targetModelClass.name}`)
1811
1917
  }
1812
1918
 
1813
1919
  if (search.path.length > 0) {
@@ -1958,7 +2064,7 @@ export default class FrontendModelController extends Controller {
1958
2064
  const relationship = modelClass.getRelationshipsMap()[relationshipName]
1959
2065
 
1960
2066
  if (!relationship) {
1961
- throw new Error(`Unknown join relationship "${relationshipName}" for ${modelClass.name}`)
2067
+ throw frontendModelQueryError(`Unknown join relationship "${relationshipName}" for ${modelClass.name}`)
1962
2068
  }
1963
2069
 
1964
2070
  const targetModelClass = relationship.getTargetModelClass()
@@ -2049,18 +2155,18 @@ export default class FrontendModelController extends Controller {
2049
2155
  }
2050
2156
 
2051
2157
  /**
2052
- * Validates a selected frontend-model attribute list against resource metadata.
2158
+ * Asserts a selected frontend-model attribute list only references exposed attributes.
2053
2159
  * @param {object} args - Args.
2054
2160
  * @param {string[]} args.attributeNames - Selected attribute names.
2055
2161
  * @param {typeof import("./database/record/index.js").default} args.modelClass - Model class.
2056
2162
  * @param {"select" | "selectsExtra"} args.operationName - Selection operation.
2057
- * @returns {string[]} - Validated selected attribute names.
2163
+ * @returns {string[]} - Allowed selected attribute names.
2058
2164
  */
2059
- validateFrontendModelSelectedAttributes({attributeNames, modelClass, operationName}) {
2165
+ assertFrontendModelSelectedAttributesAllowed({attributeNames, modelClass, operationName}) {
2060
2166
  for (const attributeName of attributeNames) {
2061
2167
  if (this.frontendModelAttributeIsExposed({attributeName, modelClass})) continue
2062
2168
 
2063
- throw new Error(`Unknown ${operationName} attribute "${attributeName}" for ${modelClass.name}`)
2169
+ throw frontendModelQueryError(`Unknown ${operationName} attribute "${attributeName}" for ${modelClass.name}`)
2064
2170
  }
2065
2171
 
2066
2172
  return attributeNames
@@ -2163,7 +2269,7 @@ export default class FrontendModelController extends Controller {
2163
2269
  const relationship = modelClass.getRelationshipsMap()[attributeName]
2164
2270
 
2165
2271
  if (!relationship) {
2166
- throw new Error(`Unknown where relationship "${attributeName}" for ${modelClass.name}`)
2272
+ throw frontendModelQueryError(`Unknown where relationship "${attributeName}" for ${modelClass.name}`)
2167
2273
  }
2168
2274
 
2169
2275
  const targetModelClass = relationship.getTargetModelClass()
@@ -2184,7 +2290,7 @@ export default class FrontendModelController extends Controller {
2184
2290
  continue
2185
2291
  }
2186
2292
 
2187
- throw new Error(`Unknown where column "${attributeName}" for ${modelClass.name}`)
2293
+ throw frontendModelQueryError(`Unknown where column "${attributeName}" for ${modelClass.name}`)
2188
2294
  }
2189
2295
  }
2190
2296
 
@@ -2251,7 +2357,7 @@ export default class FrontendModelController extends Controller {
2251
2357
  })
2252
2358
 
2253
2359
  if (!columnName) {
2254
- throw new Error(`Unknown group column "${group.column}" for ${targetModelClass.name}`)
2360
+ throw frontendModelQueryError(`Unknown group column "${group.column}" for ${targetModelClass.name}`)
2255
2361
  }
2256
2362
 
2257
2363
  this.ensureFrontendModelJoinPath({path: group.path, query})
@@ -2369,7 +2475,7 @@ export default class FrontendModelController extends Controller {
2369
2475
  const translationPath = sort.path.concat(["currentTranslation"])
2370
2476
 
2371
2477
  if (!translationColumnName) {
2372
- throw new Error(`Unknown translated sort column "${sort.column}" for ${targetModelClass.name}`)
2478
+ throw frontendModelQueryError(`Unknown translated sort column "${sort.column}" for ${targetModelClass.name}`)
2373
2479
  }
2374
2480
 
2375
2481
  this.ensureFrontendModelSortJoinPath({path: translationPath, query})
@@ -2383,7 +2489,7 @@ export default class FrontendModelController extends Controller {
2383
2489
  }
2384
2490
 
2385
2491
  if (!columnName) {
2386
- throw new Error(`Unknown sort column "${sort.column}" for ${targetModelClass.name}`)
2492
+ throw frontendModelQueryError(`Unknown sort column "${sort.column}" for ${targetModelClass.name}`)
2387
2493
  }
2388
2494
 
2389
2495
  this.ensureFrontendModelSortJoinPath({path: sort.path, query})
@@ -2440,7 +2546,7 @@ export default class FrontendModelController extends Controller {
2440
2546
 
2441
2547
  if (!selectedAttributes) return null
2442
2548
 
2443
- return this.validateFrontendModelSelectedAttributes({
2549
+ return this.assertFrontendModelSelectedAttributesAllowed({
2444
2550
  attributeNames: selectedAttributes,
2445
2551
  modelClass,
2446
2552
  operationName: "select"
@@ -2461,7 +2567,7 @@ export default class FrontendModelController extends Controller {
2461
2567
 
2462
2568
  if (!extraAttributes) return null
2463
2569
 
2464
- return this.validateFrontendModelSelectedAttributes({
2570
+ return this.assertFrontendModelSelectedAttributesAllowed({
2465
2571
  attributeNames: extraAttributes,
2466
2572
  modelClass,
2467
2573
  operationName: "selectsExtra"