velocious 1.0.454 → 1.0.455
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 +2 -1
- package/build/authorization/ability.js +3 -6
- package/build/authorization/base-resource.js +7 -9
- package/build/configuration-types.js +3 -3
- package/build/configuration.js +12 -17
- package/build/database/drivers/base.js +3 -3
- package/build/database/pool/base.js +2 -1
- package/build/database/query/preloader/ensure-model-class-initialized.js +1 -6
- package/build/database/record/attachments/handle.js +32 -0
- package/build/environment-handlers/node/cli/commands/generate/frontend-models.js +25 -26
- package/build/frontend-model-controller.js +167 -88
- package/build/frontend-model-resource/base-resource.js +133 -31
- package/build/frontend-models/base.js +30 -6
- package/build/frontend-models/model-registry.js +1 -1
- package/build/frontend-models/query.js +3 -9
- package/build/frontend-models/resource-definition.js +1 -0
- package/build/frontend-models/transport-serialization.js +2 -3
- package/build/frontend-models/websocket-channel.js +7 -12
- package/build/frontend-models/websocket-publishers.js +11 -67
- package/build/routes/hooks/frontend-model-command-route-hook.js +1 -1
- package/build/src/authorization/ability.d.ts.map +1 -1
- package/build/src/authorization/ability.js +4 -8
- package/build/src/authorization/base-resource.d.ts +2 -2
- package/build/src/authorization/base-resource.d.ts.map +1 -1
- package/build/src/authorization/base-resource.js +7 -9
- package/build/src/configuration-types.d.ts +6 -6
- package/build/src/configuration-types.d.ts.map +1 -1
- package/build/src/configuration-types.js +4 -4
- package/build/src/configuration.d.ts.map +1 -1
- package/build/src/configuration.js +13 -18
- package/build/src/database/drivers/base.js +4 -4
- package/build/src/database/pool/base.d.ts +2 -1
- package/build/src/database/pool/base.d.ts.map +1 -1
- package/build/src/database/pool/base.js +3 -2
- package/build/src/database/query/preloader/ensure-model-class-initialized.d.ts.map +1 -1
- package/build/src/database/query/preloader/ensure-model-class-initialized.js +2 -6
- package/build/src/database/record/attachments/handle.d.ts +13 -0
- package/build/src/database/record/attachments/handle.d.ts.map +1 -1
- package/build/src/database/record/attachments/handle.js +29 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts +6 -0
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/generate/frontend-models.js +25 -24
- package/build/src/frontend-model-controller.d.ts +70 -26
- package/build/src/frontend-model-controller.d.ts.map +1 -1
- package/build/src/frontend-model-controller.js +144 -87
- package/build/src/frontend-model-resource/base-resource.d.ts +192 -16
- package/build/src/frontend-model-resource/base-resource.d.ts.map +1 -1
- package/build/src/frontend-model-resource/base-resource.js +124 -33
- package/build/src/frontend-models/base.d.ts +14 -1
- package/build/src/frontend-models/base.d.ts.map +1 -1
- package/build/src/frontend-models/base.js +28 -7
- package/build/src/frontend-models/model-registry.js +2 -2
- package/build/src/frontend-models/query.d.ts.map +1 -1
- package/build/src/frontend-models/query.js +4 -10
- package/build/src/frontend-models/resource-definition.d.ts.map +1 -1
- package/build/src/frontend-models/resource-definition.js +2 -1
- package/build/src/frontend-models/transport-serialization.d.ts +2 -4
- package/build/src/frontend-models/transport-serialization.d.ts.map +1 -1
- package/build/src/frontend-models/transport-serialization.js +3 -4
- package/build/src/frontend-models/websocket-channel.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-channel.js +8 -13
- package/build/src/frontend-models/websocket-publishers.d.ts.map +1 -1
- package/build/src/frontend-models/websocket-publishers.js +12 -60
- package/build/src/routes/hooks/frontend-model-command-route-hook.js +2 -2
- package/package.json +1 -1
- package/scripts/test-browser.js +2 -2
- package/src/authorization/ability.js +3 -6
- package/src/authorization/base-resource.js +7 -9
- package/src/configuration-types.js +3 -3
- package/src/configuration.js +12 -17
- package/src/database/drivers/base.js +3 -3
- package/src/database/pool/base.js +2 -1
- package/src/database/query/preloader/ensure-model-class-initialized.js +1 -6
- package/src/database/record/attachments/handle.js +32 -0
- package/src/environment-handlers/node/cli/commands/generate/frontend-models.js +25 -26
- package/src/frontend-model-controller.js +167 -88
- package/src/frontend-model-resource/base-resource.js +133 -31
- package/src/frontend-models/base.js +30 -6
- package/src/frontend-models/model-registry.js +1 -1
- package/src/frontend-models/query.js +3 -9
- package/src/frontend-models/resource-definition.js +1 -0
- package/src/frontend-models/transport-serialization.js +2 -3
- package/src/frontend-models/websocket-channel.js +7 -12
- package/src/frontend-models/websocket-publishers.js +11 -67
- package/src/routes/hooks/frontend-model-command-route-hook.js +1 -1
|
@@ -5,7 +5,7 @@ import Controller from "./controller.js"
|
|
|
5
5
|
import FrontendModelBaseResource from "./frontend-model-resource/base-resource.js"
|
|
6
6
|
import Response from "./http-server/client/response.js"
|
|
7
7
|
import {frontendModelResourcesWithBuiltInsForBackendProject} from "./frontend-models/built-in-resources.js"
|
|
8
|
-
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcePath} from "./frontend-models/resource-definition.js"
|
|
8
|
+
import {frontendModelResourceClassFromDefinition, frontendModelResourceConfigurationFromDefinition, frontendModelResourcePath, frontendModelResourcesForBackendProject} from "./frontend-models/resource-definition.js"
|
|
9
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"
|
|
10
10
|
import {assignSafeProperty, deserializeFrontendModelTransportValue, isBackendModelInstance, serializeFrontendModelTransportValue} from "./frontend-models/transport-serialization.js"
|
|
11
11
|
import RoutesResolver from "./routes/resolver.js"
|
|
@@ -149,6 +149,14 @@ function normalizeFrontendModelSelect(select, rootModelName = null) {
|
|
|
149
149
|
* }} FrontendModelEndpointErrorContext
|
|
150
150
|
*/
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* FrontendModelIndexQueryOptions type.
|
|
154
|
+
* @typedef {object} FrontendModelIndexQueryOptions
|
|
155
|
+
* @property {boolean} [includePagination] - Whether frontend-model pagination params should be applied.
|
|
156
|
+
* @property {boolean} [includeSort] - Whether frontend-model sort params should be applied.
|
|
157
|
+
* @property {import("./frontend-model-resource/base-resource.js").default} [resource] - Resource providing query hooks.
|
|
158
|
+
*/
|
|
159
|
+
|
|
152
160
|
const frontendModelJoinedPathsSymbol = Symbol("frontendModelJoinedPaths")
|
|
153
161
|
const frontendModelGroupedColumnsSymbol = Symbol("frontendModelGroupedColumns")
|
|
154
162
|
const frontendModelWhereNoMatchSymbol = Symbol("frontendModelWhereNoMatch")
|
|
@@ -772,14 +780,10 @@ export default class FrontendModelController extends Controller {
|
|
|
772
780
|
/**
|
|
773
781
|
* Runs frontend model resource model class.
|
|
774
782
|
* @param {{modelName: string, resourceClass: import("./configuration-types.js").FrontendModelResourceClassType}} frontendModelResource - Frontend model resource configuration.
|
|
775
|
-
* @returns {typeof import("./database/record/index.js").default
|
|
783
|
+
* @returns {typeof import("./database/record/index.js").default} - Backing record class.
|
|
776
784
|
*/
|
|
777
785
|
frontendModelResourceModelClass(frontendModelResource) {
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
if (resourceModelClass) return resourceModelClass
|
|
781
|
-
|
|
782
|
-
return this.getConfiguration().getModelClasses()[frontendModelResource.modelName] || null
|
|
786
|
+
return frontendModelResource.resourceClass.modelClass()
|
|
783
787
|
}
|
|
784
788
|
|
|
785
789
|
/**
|
|
@@ -791,18 +795,7 @@ export default class FrontendModelController extends Controller {
|
|
|
791
795
|
|
|
792
796
|
if (!frontendModelResource) return null
|
|
793
797
|
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
if (resourceModelClass) return resourceModelClass
|
|
797
|
-
|
|
798
|
-
const modelClasses = this.getConfiguration().getModelClasses()
|
|
799
|
-
const modelClass = modelClasses[frontendModelResource.modelName]
|
|
800
|
-
|
|
801
|
-
if (!modelClass) {
|
|
802
|
-
throw new Error(`Frontend model '${frontendModelResource.modelName}' is configured for '${this.frontendModelParams().controller}', but no model class was registered. Registered models: ${Object.keys(modelClasses).join(", ")}`)
|
|
803
|
-
}
|
|
804
|
-
|
|
805
|
-
return modelClass
|
|
798
|
+
return this.frontendModelResourceModelClass(frontendModelResource)
|
|
806
799
|
}
|
|
807
800
|
|
|
808
801
|
/**
|
|
@@ -835,13 +828,7 @@ export default class FrontendModelController extends Controller {
|
|
|
835
828
|
async ensureFrontendModelRecordClassInitialized(modelClass) {
|
|
836
829
|
if (!modelClass || modelClass.isInitialized()) return
|
|
837
830
|
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
if (typeof modelClass.ensureInitialized === "function") {
|
|
841
|
-
await modelClass.ensureInitialized({configuration})
|
|
842
|
-
} else if (typeof modelClass.initializeRecord === "function") {
|
|
843
|
-
await modelClass.initializeRecord({configuration})
|
|
844
|
-
}
|
|
831
|
+
await modelClass.ensureInitialized({configuration: this.getConfiguration()})
|
|
845
832
|
}
|
|
846
833
|
|
|
847
834
|
/**
|
|
@@ -1000,7 +987,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1000
987
|
|
|
1001
988
|
/**
|
|
1002
989
|
* Runs frontend model ability action.
|
|
1003
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
990
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
1004
991
|
* @returns {string} - Ability action configured for the frontend action.
|
|
1005
992
|
*/
|
|
1006
993
|
frontendModelAbilityAction(action) {
|
|
@@ -1018,7 +1005,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1018
1005
|
|
|
1019
1006
|
const abilityKey = action === "attach"
|
|
1020
1007
|
? "update"
|
|
1021
|
-
: ((action === "download" || action === "url") ? "find" : action)
|
|
1008
|
+
: ((action === "download" || action === "url" || action === "attachmentList") ? "find" : action)
|
|
1022
1009
|
const abilityAction = abilities[abilityKey]
|
|
1023
1010
|
|
|
1024
1011
|
if (typeof abilityAction !== "string" || abilityAction.length < 1) {
|
|
@@ -1030,7 +1017,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1030
1017
|
|
|
1031
1018
|
/**
|
|
1032
1019
|
* Runs frontend model ability authorized query.
|
|
1033
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
1020
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
1034
1021
|
* @returns {import("./database/query/model-class-query.js").default<typeof import("./database/record/index.js").default>} - Authorized query for the action.
|
|
1035
1022
|
*/
|
|
1036
1023
|
frontendModelAbilityAuthorizedQuery(action) {
|
|
@@ -1041,7 +1028,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1041
1028
|
|
|
1042
1029
|
/**
|
|
1043
1030
|
* Runs frontend model authorized query.
|
|
1044
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
1031
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
1045
1032
|
* @returns {import("./database/query/model-class-query.js").default<typeof import("./database/record/index.js").default>} - Authorized query for the action.
|
|
1046
1033
|
*/
|
|
1047
1034
|
frontendModelAuthorizedQuery(action) {
|
|
@@ -1071,7 +1058,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1071
1058
|
/**
|
|
1072
1059
|
* Runs frontend model filter authorized models.
|
|
1073
1060
|
* @param {object} args - Arguments.
|
|
1074
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} args.action - Frontend action.
|
|
1061
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} args.action - Frontend action.
|
|
1075
1062
|
* @param {import("./database/record/index.js").default[]} args.models - Candidate models.
|
|
1076
1063
|
* @returns {Promise<import("./database/record/index.js").default[]>} - Authorized models.
|
|
1077
1064
|
*/
|
|
@@ -1091,7 +1078,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1091
1078
|
|
|
1092
1079
|
/**
|
|
1093
1080
|
* Runs run frontend model before action.
|
|
1094
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
1081
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
1095
1082
|
* @returns {Promise<boolean>} - Whether action should continue.
|
|
1096
1083
|
*/
|
|
1097
1084
|
async runFrontendModelBeforeAction(action) {
|
|
@@ -1102,7 +1089,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1102
1089
|
|
|
1103
1090
|
/**
|
|
1104
1091
|
* Runs frontend model find record.
|
|
1105
|
-
* @param {"find" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
1092
|
+
* @param {"find" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
1106
1093
|
* @param {string | number} id - Record id.
|
|
1107
1094
|
* @returns {Promise<import("./database/record/index.js").default | null>} - Located model record.
|
|
1108
1095
|
*/
|
|
@@ -1253,7 +1240,11 @@ export default class FrontendModelController extends Controller {
|
|
|
1253
1240
|
*/
|
|
1254
1241
|
frontendModelPluck() {
|
|
1255
1242
|
try {
|
|
1256
|
-
|
|
1243
|
+
const pluck = normalizeQueryPluck(this.frontendModelParams().pluck)
|
|
1244
|
+
|
|
1245
|
+
this.validateFrontendModelPluckDefinitions(pluck)
|
|
1246
|
+
|
|
1247
|
+
return pluck
|
|
1257
1248
|
} catch (error) {
|
|
1258
1249
|
throw frontendModelValidationErrorForError(error)
|
|
1259
1250
|
}
|
|
@@ -1301,9 +1292,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1301
1292
|
* Resolve an entry from the frontend-model `abilities` payload to
|
|
1302
1293
|
* its backend model class by looking up the resource by modelName
|
|
1303
1294
|
* across all configured backend projects. Returns null when no
|
|
1304
|
-
* resource matches
|
|
1305
|
-
* caller requesting abilities for a model they cannot resolve does
|
|
1306
|
-
* not crash the request.
|
|
1295
|
+
* resource matches the user-provided ability entry.
|
|
1307
1296
|
* @param {string} modelName
|
|
1308
1297
|
* @returns {typeof import("./database/record/index.js").default | null}
|
|
1309
1298
|
*/
|
|
@@ -1311,23 +1300,20 @@ export default class FrontendModelController extends Controller {
|
|
|
1311
1300
|
if (typeof modelName !== "string" || modelName.length === 0) return null
|
|
1312
1301
|
|
|
1313
1302
|
const configuration = this.getConfiguration()
|
|
1314
|
-
const backendProjects = configuration
|
|
1303
|
+
const backendProjects = configuration.getBackendProjects()
|
|
1315
1304
|
|
|
1316
1305
|
for (const backendProject of backendProjects) {
|
|
1317
|
-
const frontendModels = backendProject
|
|
1318
|
-
if (!frontendModels || typeof frontendModels !== "object") continue
|
|
1319
|
-
|
|
1306
|
+
const frontendModels = frontendModelResourcesForBackendProject(backendProject)
|
|
1320
1307
|
const resourceDefinition = frontendModels[modelName]
|
|
1308
|
+
|
|
1321
1309
|
if (!resourceDefinition) continue
|
|
1322
1310
|
|
|
1323
1311
|
const resourceClass = frontendModelResourceClassFromDefinition(resourceDefinition)
|
|
1324
|
-
if (!resourceClass)
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
? resourceClass.modelClass()
|
|
1328
|
-
: resourceClass.ModelClass
|
|
1312
|
+
if (!resourceClass) {
|
|
1313
|
+
throw new Error(`Frontend model '${modelName}' resource definition must be a FrontendModelBaseResource subclass.`)
|
|
1314
|
+
}
|
|
1329
1315
|
|
|
1330
|
-
|
|
1316
|
+
return resourceClass.modelClass()
|
|
1331
1317
|
}
|
|
1332
1318
|
|
|
1333
1319
|
return null
|
|
@@ -1361,24 +1347,15 @@ export default class FrontendModelController extends Controller {
|
|
|
1361
1347
|
if (seen.has(record)) return
|
|
1362
1348
|
seen.add(record)
|
|
1363
1349
|
|
|
1364
|
-
const ModelClass =
|
|
1365
|
-
|
|
1366
|
-
: null
|
|
1367
|
-
if (ModelClass && typeof ModelClass.getModelName === "function" && ModelClass.getModelName() === modelName) {
|
|
1350
|
+
const ModelClass = record.getModelClass()
|
|
1351
|
+
if (ModelClass.getModelName() === modelName) {
|
|
1368
1352
|
out.push(record)
|
|
1369
1353
|
}
|
|
1370
1354
|
|
|
1371
|
-
const relationshipsMap =
|
|
1372
|
-
? ModelClass.getRelationshipsMap()
|
|
1373
|
-
: null
|
|
1374
|
-
if (!relationshipsMap) return
|
|
1355
|
+
const relationshipsMap = ModelClass.getRelationshipsMap()
|
|
1375
1356
|
|
|
1376
1357
|
for (const relationshipName of Object.keys(relationshipsMap)) {
|
|
1377
|
-
const relationship =
|
|
1378
|
-
? record.getRelationshipByName(relationshipName)
|
|
1379
|
-
: null
|
|
1380
|
-
if (!relationship || typeof relationship.getLoadedOrUndefined !== "function") continue
|
|
1381
|
-
|
|
1358
|
+
const relationship = record.getRelationshipByName(relationshipName)
|
|
1382
1359
|
const loaded = relationship.getLoadedOrUndefined()
|
|
1383
1360
|
if (loaded === undefined) continue
|
|
1384
1361
|
|
|
@@ -1512,9 +1489,11 @@ export default class FrontendModelController extends Controller {
|
|
|
1512
1489
|
|
|
1513
1490
|
/**
|
|
1514
1491
|
* Runs frontend model index query.
|
|
1492
|
+
* @param {FrontendModelIndexQueryOptions} [options] - Index query options.
|
|
1515
1493
|
* @returns {import("./database/query/model-class-query.js").default} - Frontend index query with normalized params applied.
|
|
1516
1494
|
*/
|
|
1517
|
-
frontendModelIndexQuery() {
|
|
1495
|
+
frontendModelIndexQuery(options = {}) {
|
|
1496
|
+
const {includePagination = true, includeSort = true, resource = this.frontendModelResourceInstance()} = options
|
|
1518
1497
|
let query = this.frontendModelAuthorizedQuery("index")
|
|
1519
1498
|
const preload = this.frontendModelPreload()
|
|
1520
1499
|
|
|
@@ -1527,7 +1506,9 @@ export default class FrontendModelController extends Controller {
|
|
|
1527
1506
|
const pagination = this.frontendModelPagination()
|
|
1528
1507
|
const distinct = this.frontendModelDistinct()
|
|
1529
1508
|
|
|
1530
|
-
|
|
1509
|
+
if (includePagination) {
|
|
1510
|
+
resource.applyFrontendModelIndexPagination({controller: this, pagination, query})
|
|
1511
|
+
}
|
|
1531
1512
|
|
|
1532
1513
|
if (distinct !== null) {
|
|
1533
1514
|
query.distinct(distinct)
|
|
@@ -1550,7 +1531,7 @@ export default class FrontendModelController extends Controller {
|
|
|
1550
1531
|
const searches = this.frontendModelSearches()
|
|
1551
1532
|
|
|
1552
1533
|
for (const search of searches) {
|
|
1553
|
-
|
|
1534
|
+
resource.applyFrontendModelIndexSearch({controller: this, query, search})
|
|
1554
1535
|
}
|
|
1555
1536
|
|
|
1556
1537
|
const groups = this.frontendModelGroup()
|
|
@@ -1565,9 +1546,9 @@ export default class FrontendModelController extends Controller {
|
|
|
1565
1546
|
|
|
1566
1547
|
const sorts = this.frontendModelSort()
|
|
1567
1548
|
|
|
1568
|
-
if (sorts.length > 0) {
|
|
1549
|
+
if (includeSort && sorts.length > 0) {
|
|
1569
1550
|
for (const sort of sorts) {
|
|
1570
|
-
|
|
1551
|
+
resource.applyFrontendModelIndexSort({controller: this, query, sort})
|
|
1571
1552
|
}
|
|
1572
1553
|
}
|
|
1573
1554
|
|
|
@@ -1696,6 +1677,95 @@ export default class FrontendModelController extends Controller {
|
|
|
1696
1677
|
})
|
|
1697
1678
|
}
|
|
1698
1679
|
|
|
1680
|
+
/**
|
|
1681
|
+
* Resolves a frontend-model pluck attribute to a database column.
|
|
1682
|
+
* @param {{attributeName: string, modelClass: typeof import("./database/record/index.js").default}} args - Arguments.
|
|
1683
|
+
* @returns {string | undefined} Resolved DB column name.
|
|
1684
|
+
*/
|
|
1685
|
+
resolveFrontendModelPluckColumnName({attributeName, modelClass}) {
|
|
1686
|
+
const attributeNames = this.frontendModelResourceAttributeNamesForModelClass(modelClass)
|
|
1687
|
+
|
|
1688
|
+
if (attributeNames && !attributeNames.has(attributeName)) return undefined
|
|
1689
|
+
|
|
1690
|
+
return this.resolveFrontendModelColumnName(modelClass, attributeName)
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
/**
|
|
1694
|
+
* Runs exposed frontend-model resource attribute names for a model class.
|
|
1695
|
+
* @param {typeof import("./database/record/index.js").default} modelClass - Model class.
|
|
1696
|
+
* @returns {Set<string> | null} Exposed resource attribute names, or null when the resource exposes all DB-backed model attributes.
|
|
1697
|
+
*/
|
|
1698
|
+
frontendModelResourceAttributeNamesForModelClass(modelClass) {
|
|
1699
|
+
const frontendModelResource = this.frontendModelResourceConfigurationForModelClass(modelClass)
|
|
1700
|
+
|
|
1701
|
+
if (!frontendModelResource) return new Set()
|
|
1702
|
+
|
|
1703
|
+
const attributes = frontendModelResource.resourceConfiguration.attributes
|
|
1704
|
+
|
|
1705
|
+
if (!attributes) return null
|
|
1706
|
+
|
|
1707
|
+
const attributeNames = this.frontendModelResourceAttributeNames(attributes)
|
|
1708
|
+
|
|
1709
|
+
if (attributeNames.size < 1) return null
|
|
1710
|
+
|
|
1711
|
+
return attributeNames
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
/**
|
|
1715
|
+
* Runs exposed frontend-model resource attribute names.
|
|
1716
|
+
* @param {import("./configuration-types.js").FrontendModelResourceConfiguration["attributes"]} attributes - Resource attributes.
|
|
1717
|
+
* @returns {Set<string>} Exposed resource attribute names.
|
|
1718
|
+
*/
|
|
1719
|
+
frontendModelResourceAttributeNames(attributes) {
|
|
1720
|
+
/** @type {Set<string>} */
|
|
1721
|
+
const attributeNames = new Set()
|
|
1722
|
+
|
|
1723
|
+
if (Array.isArray(attributes)) {
|
|
1724
|
+
for (const attribute of attributes) {
|
|
1725
|
+
if (typeof attribute === "string") {
|
|
1726
|
+
attributeNames.add(attribute)
|
|
1727
|
+
continue
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
const attributeConfig = /** @type {import("./configuration-types.js").FrontendModelAttributeConfiguration} */ (attribute)
|
|
1731
|
+
|
|
1732
|
+
if (typeof attributeConfig.name !== "string" || attributeConfig.name.length < 1) {
|
|
1733
|
+
throw new Error("Frontend-model resource attribute array entries must be strings or configs with a name.")
|
|
1734
|
+
}
|
|
1735
|
+
|
|
1736
|
+
attributeNames.add(attributeConfig.name)
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
return attributeNames
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
return new Set(Object.keys(attributes))
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
/**
|
|
1746
|
+
* Validates frontend-model pluck definitions against exposed resource attributes.
|
|
1747
|
+
* @param {FrontendModelPluck[]} pluck - Pluck descriptors.
|
|
1748
|
+
* @returns {void}
|
|
1749
|
+
*/
|
|
1750
|
+
validateFrontendModelPluckDefinitions(pluck) {
|
|
1751
|
+
const modelClass = this.frontendModelClass()
|
|
1752
|
+
|
|
1753
|
+
for (const pluckEntry of pluck) {
|
|
1754
|
+
const targetModelClass = this.frontendModelSearchTargetModelClass({
|
|
1755
|
+
modelClass,
|
|
1756
|
+
path: pluckEntry.path
|
|
1757
|
+
})
|
|
1758
|
+
const columnName = this.resolveFrontendModelPluckColumnName({
|
|
1759
|
+
attributeName: pluckEntry.column,
|
|
1760
|
+
modelClass: targetModelClass
|
|
1761
|
+
})
|
|
1762
|
+
|
|
1763
|
+
if (!columnName) {
|
|
1764
|
+
throw new Error(`Unknown pluck column "${pluckEntry.column}" for ${targetModelClass.name}`)
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
}
|
|
1768
|
+
|
|
1699
1769
|
/**
|
|
1700
1770
|
* Runs frontend model search target model class.
|
|
1701
1771
|
* @param {object} args - Search args.
|
|
@@ -1945,6 +2015,8 @@ export default class FrontendModelController extends Controller {
|
|
|
1945
2015
|
})
|
|
1946
2016
|
.filter((entry) => typeof entry === "string")
|
|
1947
2017
|
|
|
2018
|
+
if (attributeNames.length === 0) return null
|
|
2019
|
+
|
|
1948
2020
|
return new Set(attributeNames)
|
|
1949
2021
|
}
|
|
1950
2022
|
|
|
@@ -2612,7 +2684,7 @@ export default class FrontendModelController extends Controller {
|
|
|
2612
2684
|
}
|
|
2613
2685
|
|
|
2614
2686
|
const configuration = this.getConfiguration()
|
|
2615
|
-
const backendProjects = configuration.getBackendProjects
|
|
2687
|
+
const backendProjects = configuration.getBackendProjects()
|
|
2616
2688
|
const modelClassName = /**
|
|
2617
2689
|
* Types the following value.
|
|
2618
2690
|
* @type {typeof import("./database/record/index.js").default} */ (model.constructor).getModelName()
|
|
@@ -2632,9 +2704,7 @@ export default class FrontendModelController extends Controller {
|
|
|
2632
2704
|
* @type {typeof import("./database/record/index.js").default} */ (model.constructor),
|
|
2633
2705
|
modelName: modelClassName,
|
|
2634
2706
|
params: {},
|
|
2635
|
-
resourceConfiguration:
|
|
2636
|
-
* Types the following value.
|
|
2637
|
-
* @type {import("./configuration-types.js").FrontendModelResourceConfiguration | undefined} */ (typeof resourceClass.resourceConfig === "function" ? resourceClass.resourceConfig() : undefined)
|
|
2707
|
+
resourceConfiguration: resourceClass.resourceConfig()
|
|
2638
2708
|
})
|
|
2639
2709
|
}
|
|
2640
2710
|
}
|
|
@@ -2842,15 +2912,9 @@ export default class FrontendModelController extends Controller {
|
|
|
2842
2912
|
for (const [modelIndex, model] of models.entries()) {
|
|
2843
2913
|
const serializedAttributes = await this.serializeFrontendModelAttributes(model)
|
|
2844
2914
|
const preloadedRelationships = preloadedRelationshipsPerModel[modelIndex]
|
|
2845
|
-
const associationCounts =
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
const queryDataValues = typeof model.queryDataValues === "function"
|
|
2849
|
-
? model.queryDataValues()
|
|
2850
|
-
: {}
|
|
2851
|
-
const computedAbilities = typeof model.computedAbilities === "function"
|
|
2852
|
-
? model.computedAbilities()
|
|
2853
|
-
: {}
|
|
2915
|
+
const associationCounts = model.associationCounts()
|
|
2916
|
+
const queryDataValues = model.queryDataValues()
|
|
2917
|
+
const computedAbilities = model.computedAbilities()
|
|
2854
2918
|
const hasCounts = Object.keys(associationCounts).length > 0
|
|
2855
2919
|
const hasQueryData = Object.keys(queryDataValues).length > 0
|
|
2856
2920
|
const hasAbilities = Object.keys(computedAbilities).length > 0
|
|
@@ -2944,7 +3008,7 @@ export default class FrontendModelController extends Controller {
|
|
|
2944
3008
|
* @param {object} args - Error context args.
|
|
2945
3009
|
* @param {string} args.action - Endpoint/action label.
|
|
2946
3010
|
* @param {unknown} args.error - Caught error.
|
|
2947
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url" | "custom-command"} [args.commandType] - Frontend-model command type.
|
|
3011
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url" | "custom-command"} [args.commandType] - Frontend-model command type.
|
|
2948
3012
|
* @param {string | undefined} [args.model] - Request model name when available.
|
|
2949
3013
|
* @param {string | undefined} [args.requestId] - Batch request id when available.
|
|
2950
3014
|
* @returns {FrontendModelEndpointErrorContext} Frontend-model endpoint error context.
|
|
@@ -3025,7 +3089,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3025
3089
|
* @param {object} args - Error log args.
|
|
3026
3090
|
* @param {string} args.action - Endpoint/action label.
|
|
3027
3091
|
* @param {?} args.error - Caught error.
|
|
3028
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url" | "custom-command"} [args.commandType] - Frontend-model command type.
|
|
3092
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url" | "custom-command"} [args.commandType] - Frontend-model command type.
|
|
3029
3093
|
* @param {string | undefined} [args.model] - Request model name when available.
|
|
3030
3094
|
* @param {string | undefined} [args.requestId] - Batch request id when available.
|
|
3031
3095
|
* @returns {Promise<void>} - Resolves after logging.
|
|
@@ -3066,7 +3130,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3066
3130
|
|
|
3067
3131
|
/**
|
|
3068
3132
|
* Runs frontend model render command response.
|
|
3069
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
3133
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
3070
3134
|
* @returns {Promise<void>} - Resolves when response has been rendered.
|
|
3071
3135
|
*/
|
|
3072
3136
|
async frontendModelRenderCommandResponse(action) {
|
|
@@ -3094,7 +3158,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3094
3158
|
|
|
3095
3159
|
/**
|
|
3096
3160
|
* Runs frontend model command payload.
|
|
3097
|
-
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "download" | "url"} action - Frontend action.
|
|
3161
|
+
* @param {"index" | "find" | "create" | "update" | "destroy" | "attach" | "attachmentList" | "download" | "url"} action - Frontend action.
|
|
3098
3162
|
* @returns {Promise<Record<string, ?> | null>} - Response payload.
|
|
3099
3163
|
*/
|
|
3100
3164
|
async frontendModelCommandPayload(action) {
|
|
@@ -3127,7 +3191,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3127
3191
|
|
|
3128
3192
|
const values = await this.frontendModelPluckValues({
|
|
3129
3193
|
pluck,
|
|
3130
|
-
query:
|
|
3194
|
+
query: resource.indexQuery()
|
|
3131
3195
|
})
|
|
3132
3196
|
|
|
3133
3197
|
return {
|
|
@@ -3248,6 +3312,24 @@ export default class FrontendModelController extends Controller {
|
|
|
3248
3312
|
}
|
|
3249
3313
|
}
|
|
3250
3314
|
|
|
3315
|
+
if (action === "attachmentList") {
|
|
3316
|
+
const attachmentParams = frontendModelAttachmentParams(params)
|
|
3317
|
+
if (typeof attachmentParams === "string") return this.frontendModelErrorPayload(attachmentParams)
|
|
3318
|
+
|
|
3319
|
+
const model = await this.frontendModelFindRecord("attachmentList", id)
|
|
3320
|
+
|
|
3321
|
+
if (!model) {
|
|
3322
|
+
return this.frontendModelErrorPayload(`${modelClass.name} not found.`)
|
|
3323
|
+
}
|
|
3324
|
+
|
|
3325
|
+
const attachments = await model.getAttachmentByName(attachmentParams.attachmentName).listMetadata()
|
|
3326
|
+
|
|
3327
|
+
return {
|
|
3328
|
+
attachments,
|
|
3329
|
+
status: "success"
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
|
|
3251
3333
|
if (action === "find") {
|
|
3252
3334
|
const model = await this.frontendModelFindRecord("find", id)
|
|
3253
3335
|
|
|
@@ -3326,7 +3408,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3326
3408
|
continue
|
|
3327
3409
|
}
|
|
3328
3410
|
|
|
3329
|
-
const isBuiltInCommand = ["index", "find", "create", "update", "destroy", "attach", "download", "url"].includes(commandType)
|
|
3411
|
+
const isBuiltInCommand = ["index", "find", "create", "update", "destroy", "attach", "download", "url", "attachmentList"].includes(commandType)
|
|
3330
3412
|
|
|
3331
3413
|
if (!isBuiltInCommand && (typeof customPath !== "string" || !customPath.startsWith("/"))) {
|
|
3332
3414
|
responses.push({
|
|
@@ -3676,10 +3758,7 @@ export default class FrontendModelController extends Controller {
|
|
|
3676
3758
|
|
|
3677
3759
|
if (isBackendModelInstance(value)) {
|
|
3678
3760
|
const richSerialized = await resource.serialize(value, action)
|
|
3679
|
-
const
|
|
3680
|
-
* Types the following value.
|
|
3681
|
-
* @type {{constructor: {getModelName?: () => string, name?: string}}} */ (value).constructor
|
|
3682
|
-
const modelName = typeof modelClass.getModelName === "function" ? modelClass.getModelName() : (modelClass.name || "")
|
|
3761
|
+
const modelName = value.getModelClass().getModelName()
|
|
3683
3762
|
|
|
3684
3763
|
// Wrap the resource-serialized payload in the frontend_model transport
|
|
3685
3764
|
// marker. Marker-based decoding routes through `instantiateFromResponse`,
|