json-rest-api 1.0.23 → 1.0.24
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 +22 -1
- package/docs/API.md +145 -24
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +83 -4
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +122 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +0 -6
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1 -1
- package/plugins/core/lib/dbTablesOperations.js +10 -3
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +0 -1
- package/plugins/core/lib/querying/knex-query-helpers.js +5 -6
- package/plugins/core/lib/querying/knex-relationship-includes.js +11 -18
- package/plugins/core/lib/querying-writing/compile-schemas.js +10 -6
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +1 -2
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +22 -2
- package/plugins/core/lib/storage/storage-adapter.js +3 -2
- package/plugins/core/lib/storage/storage-mapping.js +86 -16
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +194 -102
- package/plugins/core/rest-api-knex-plugin.js +23 -16
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/common.js +27 -9
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +7 -1
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +31 -3
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +8 -0
- package/plugins/core/rest-api-plugin-methods/put.js +31 -3
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +4 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -4
- package/plugins/core/socketio-plugin.js +0 -1
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/db-introspection.test.js +4 -1
- package/tests/db-table-operations.test.js +25 -20
- package/tests/fastify-plugin.test.js +21 -6
- package/tests/fixtures/api-configs.js +133 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/storage-mapping.test.js +55 -6
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
buildCursorMeta,
|
|
15
15
|
parseCursor,
|
|
16
16
|
} from './lib/querying/knex-pagination-helpers.js'
|
|
17
|
-
import { getUrlPrefix, buildResourceUrl } from './lib/querying/url-helpers.js'
|
|
17
|
+
import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './lib/querying/url-helpers.js'
|
|
18
18
|
import {
|
|
19
19
|
normalizeId,
|
|
20
20
|
resolveFieldInfo,
|
|
@@ -35,12 +35,16 @@ import {
|
|
|
35
35
|
polymorphicFiltersHook,
|
|
36
36
|
crossTableFiltersHook,
|
|
37
37
|
basicFiltersHook,
|
|
38
|
+
resolveSearchOperator,
|
|
39
|
+
applyWhereForOperator,
|
|
38
40
|
} from './lib/querying/knex-query-helpers.js'
|
|
39
41
|
import {
|
|
40
42
|
createStorageAdapter,
|
|
41
43
|
} from './lib/storage/storage-adapter.js'
|
|
42
44
|
import {
|
|
43
45
|
translateCanonicalAttributesForStorage,
|
|
46
|
+
getCanonicalResourceId,
|
|
47
|
+
getCanonicalResourceIdColumn,
|
|
44
48
|
translateCanonicalRecordFromStorage,
|
|
45
49
|
} from './lib/storage/canonical-storage-mapping.js'
|
|
46
50
|
import {
|
|
@@ -121,46 +125,104 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
121
125
|
return descriptor
|
|
122
126
|
}
|
|
123
127
|
|
|
128
|
+
const decorateResourceLinks = ({ resource, scope, scopeName, context }) => {
|
|
129
|
+
if (!resource || !scope || !scopeName || !context) return
|
|
130
|
+
|
|
131
|
+
resource.links = resource.links || {}
|
|
132
|
+
resource.links.self = buildResourceUrl(context, scope, scopeName, resource.id)
|
|
133
|
+
|
|
134
|
+
if (!resource.relationships || typeof resource.relationships !== 'object') {
|
|
135
|
+
return
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const [relationshipName, relationshipObject] of Object.entries(resource.relationships)) {
|
|
139
|
+
if (!relationshipObject || typeof relationshipObject !== 'object') {
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
relationshipObject.links = {
|
|
144
|
+
self: buildRelationshipUrl(context, scope, scopeName, resource.id, relationshipName, true),
|
|
145
|
+
related: buildRelationshipUrl(context, scope, scopeName, resource.id, relationshipName, false),
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
124
150
|
const getAllowedQueryFieldNames = (scopeName) => (
|
|
125
151
|
Object.keys(api.resources?.[scopeName]?.vars?.queryFields || {})
|
|
126
152
|
)
|
|
127
153
|
|
|
154
|
+
const getLogicalResourceIdColumn = (descriptor) => getCanonicalResourceIdColumn(descriptor)
|
|
155
|
+
|
|
156
|
+
const getLogicalResourceId = (row, descriptor) => getCanonicalResourceId(row, descriptor)
|
|
157
|
+
|
|
128
158
|
const applyFiltersToQuery = ({ query, filters, descriptor, searchSchema, adapter }) => {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
159
|
+
if (!filters || Object.keys(filters).length === 0) return
|
|
160
|
+
|
|
161
|
+
const normalizeValue = (field, value) => {
|
|
162
|
+
if (!adapter?.translateFilterValue) {
|
|
163
|
+
return value
|
|
164
|
+
}
|
|
165
|
+
if (Array.isArray(value)) {
|
|
166
|
+
return value.map((entry) => adapter.translateFilterValue(field, entry))
|
|
167
|
+
}
|
|
168
|
+
return adapter.translateFilterValue(field, value)
|
|
134
169
|
}
|
|
135
170
|
|
|
136
|
-
|
|
137
|
-
|
|
171
|
+
for (const [filterKey, rawValue] of Object.entries(filters)) {
|
|
172
|
+
const fieldDef = searchSchema?.[filterKey]
|
|
173
|
+
|
|
174
|
+
if (fieldDef?.polymorphicField) {
|
|
175
|
+
continue
|
|
176
|
+
}
|
|
138
177
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
178
|
+
if (fieldDef?.actualField?.includes('.')) {
|
|
179
|
+
continue
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (fieldDef?.oneOf?.some((field) => field.includes('.'))) {
|
|
183
|
+
continue
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
if (fieldDef?.oneOf && Array.isArray(fieldDef.oneOf)) {
|
|
187
|
+
continue
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (fieldDef?.applyFilter && typeof fieldDef.applyFilter === 'function') {
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const actualField = fieldDef?.actualField || filterKey
|
|
195
|
+
const fieldInfo = ensureFilterableField(descriptor, filterKey)
|
|
196
|
+
const columnRef = adapter ? adapter.translateColumn(actualField) : fieldInfo.column
|
|
197
|
+
const operator = resolveSearchOperator(fieldDef || fieldInfo.definition)
|
|
142
198
|
const values = normalizeFilterValues(rawValue, fieldInfo.definition, {
|
|
143
199
|
isRelationship: fieldInfo.isRelationship,
|
|
144
200
|
})
|
|
145
|
-
|
|
146
|
-
const nonNullValues =
|
|
147
|
-
const hasNull =
|
|
201
|
+
const normalizedValues = values.map((value) => normalizeValue(actualField, value))
|
|
202
|
+
const nonNullValues = normalizedValues.filter((value) => value !== null)
|
|
203
|
+
const hasNull = normalizedValues.length !== nonNullValues.length
|
|
148
204
|
|
|
149
205
|
if (nonNullValues.length === 0 && hasNull) {
|
|
150
|
-
query.whereNull(
|
|
206
|
+
query.whereNull(columnRef)
|
|
151
207
|
continue
|
|
152
208
|
}
|
|
153
209
|
if (nonNullValues.length === 1 && !hasNull) {
|
|
154
|
-
|
|
210
|
+
applyWhereForOperator({
|
|
211
|
+
builder: query,
|
|
212
|
+
columnRef,
|
|
213
|
+
operator,
|
|
214
|
+
value: nonNullValues[0],
|
|
215
|
+
knex,
|
|
216
|
+
})
|
|
155
217
|
continue
|
|
156
218
|
}
|
|
157
219
|
if (nonNullValues.length > 1 && !hasNull) {
|
|
158
|
-
query.whereIn(
|
|
220
|
+
query.whereIn(columnRef, nonNullValues)
|
|
159
221
|
continue
|
|
160
222
|
}
|
|
161
223
|
if (nonNullValues.length > 0 && hasNull) {
|
|
162
|
-
query.where(function
|
|
163
|
-
this.whereIn(
|
|
224
|
+
query.where(function anyApiNullFilterGroup () {
|
|
225
|
+
this.whereIn(columnRef, nonNullValues).orWhereNull(columnRef)
|
|
164
226
|
})
|
|
165
227
|
}
|
|
166
228
|
}
|
|
@@ -891,12 +953,13 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
891
953
|
const descriptor = await getDescriptor(scopeName)
|
|
892
954
|
const { canonical } = descriptor
|
|
893
955
|
const id = context.id
|
|
956
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
894
957
|
|
|
895
958
|
const row = await context.db(canonical.tableName)
|
|
896
959
|
.select('id')
|
|
897
960
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
898
961
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
899
|
-
.where(
|
|
962
|
+
.where(logicalIdColumn, id)
|
|
900
963
|
.first()
|
|
901
964
|
|
|
902
965
|
return !!row
|
|
@@ -909,10 +972,17 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
909
972
|
}
|
|
910
973
|
const descriptor = await getDescriptor(scopeName)
|
|
911
974
|
const { canonical } = descriptor
|
|
975
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
976
|
+
const explicitId = context.inputRecord?.data?.id
|
|
912
977
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
913
978
|
const row = storageAdapter?.toStorageRow
|
|
914
979
|
? storageAdapter.toStorageRow(attributes)
|
|
915
980
|
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
981
|
+
|
|
982
|
+
if (explicitId !== undefined && explicitId !== null) {
|
|
983
|
+
row[logicalIdColumn] = explicitId
|
|
984
|
+
}
|
|
985
|
+
|
|
916
986
|
row[canonical.tenantColumn] = descriptor.tenant
|
|
917
987
|
row[canonical.resourceColumn] = descriptor.resource
|
|
918
988
|
|
|
@@ -921,10 +991,25 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
921
991
|
.returning('id')
|
|
922
992
|
|
|
923
993
|
const inserted = Array.isArray(result) ? result[0] : result
|
|
924
|
-
|
|
925
|
-
|
|
994
|
+
const insertedRowId = inserted && typeof inserted === 'object' && 'id' in inserted
|
|
995
|
+
? inserted.id
|
|
996
|
+
: inserted
|
|
997
|
+
|
|
998
|
+
if (explicitId !== undefined && explicitId !== null) {
|
|
999
|
+
return explicitId
|
|
926
1000
|
}
|
|
927
|
-
|
|
1001
|
+
|
|
1002
|
+
if (insertedRowId === undefined || insertedRowId === null) {
|
|
1003
|
+
return insertedRowId
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const generatedLogicalId = String(insertedRowId)
|
|
1007
|
+
|
|
1008
|
+
await context.db(canonical.tableName)
|
|
1009
|
+
.where({ id: insertedRowId })
|
|
1010
|
+
.update({ [logicalIdColumn]: generatedLogicalId })
|
|
1011
|
+
|
|
1012
|
+
return generatedLogicalId
|
|
928
1013
|
}
|
|
929
1014
|
|
|
930
1015
|
helpers.dataPut = async ({ scopeName, context }) => {
|
|
@@ -935,6 +1020,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
935
1020
|
const descriptor = await getDescriptor(scopeName)
|
|
936
1021
|
const { canonical } = descriptor
|
|
937
1022
|
const id = context.id
|
|
1023
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
938
1024
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
939
1025
|
const row = storageAdapter?.toStorageRow
|
|
940
1026
|
? storageAdapter.toStorageRow(attributes)
|
|
@@ -952,7 +1038,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
952
1038
|
}
|
|
953
1039
|
|
|
954
1040
|
const result = await context.db(canonical.tableName)
|
|
955
|
-
.where(
|
|
1041
|
+
.where(logicalIdColumn, id)
|
|
956
1042
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
957
1043
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
958
1044
|
.update(updateRow)
|
|
@@ -968,6 +1054,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
968
1054
|
const descriptor = await getDescriptor(scopeName)
|
|
969
1055
|
const { canonical } = descriptor
|
|
970
1056
|
const id = context.id
|
|
1057
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
971
1058
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
972
1059
|
const row = storageAdapter?.toStorageRow
|
|
973
1060
|
? storageAdapter.toStorageRow(attributes)
|
|
@@ -982,7 +1069,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
982
1069
|
}
|
|
983
1070
|
|
|
984
1071
|
const result = await context.db(canonical.tableName)
|
|
985
|
-
.where(
|
|
1072
|
+
.where(logicalIdColumn, id)
|
|
986
1073
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
987
1074
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
988
1075
|
.update(updateRow)
|
|
@@ -998,9 +1085,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
998
1085
|
const descriptor = await getDescriptor(scopeName)
|
|
999
1086
|
const { canonical } = descriptor
|
|
1000
1087
|
const id = context.id
|
|
1088
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
1001
1089
|
|
|
1002
1090
|
const result = await context.db(canonical.tableName)
|
|
1003
|
-
.where(
|
|
1091
|
+
.where(logicalIdColumn, id)
|
|
1004
1092
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1005
1093
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1006
1094
|
.delete()
|
|
@@ -1016,10 +1104,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1016
1104
|
const descriptor = await getDescriptor(scopeName)
|
|
1017
1105
|
const { canonical } = descriptor
|
|
1018
1106
|
const id = context.id
|
|
1107
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
1019
1108
|
const scope = api.resources?.[scopeName]
|
|
1020
1109
|
|
|
1021
1110
|
let query = context.db(canonical.tableName)
|
|
1022
|
-
.where(
|
|
1111
|
+
.where(logicalIdColumn, id)
|
|
1023
1112
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1024
1113
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1025
1114
|
|
|
@@ -1047,6 +1136,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1047
1136
|
storageAdapter,
|
|
1048
1137
|
}
|
|
1049
1138
|
|
|
1139
|
+
await applyBuiltInAnyApiQueryFilters(context)
|
|
1140
|
+
|
|
1050
1141
|
await runHooks('knexQueryFiltering')
|
|
1051
1142
|
|
|
1052
1143
|
if (context.knexQuery) {
|
|
@@ -1062,16 +1153,15 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1062
1153
|
descriptor,
|
|
1063
1154
|
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
1064
1155
|
)
|
|
1156
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1065
1157
|
const minimal = {
|
|
1066
1158
|
type: descriptor.resource,
|
|
1067
|
-
id:
|
|
1159
|
+
id: resourceId,
|
|
1068
1160
|
attributes: translated.attributes,
|
|
1069
1161
|
relationships: translated.relationships,
|
|
1070
1162
|
}
|
|
1071
1163
|
if (scope) {
|
|
1072
|
-
minimal
|
|
1073
|
-
self: buildResourceUrl(context, scope, scopeName, minimal.id),
|
|
1074
|
-
}
|
|
1164
|
+
decorateResourceLinks({ resource: minimal, scope, scopeName, context })
|
|
1075
1165
|
}
|
|
1076
1166
|
return minimal
|
|
1077
1167
|
}
|
|
@@ -1170,9 +1260,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1170
1260
|
descriptor,
|
|
1171
1261
|
{ allowedExtraFields: getAllowedQueryFieldNames(descriptor.resource) }
|
|
1172
1262
|
)
|
|
1263
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1173
1264
|
const includeResource = {
|
|
1174
1265
|
type: descriptor.resource,
|
|
1175
|
-
id:
|
|
1266
|
+
id: resourceId,
|
|
1176
1267
|
attributes: translated.attributes,
|
|
1177
1268
|
}
|
|
1178
1269
|
|
|
@@ -1186,8 +1277,12 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1186
1277
|
|
|
1187
1278
|
const targetScope = api.resources?.[descriptor.resource]
|
|
1188
1279
|
if (targetScope) {
|
|
1189
|
-
|
|
1190
|
-
|
|
1280
|
+
decorateResourceLinks({
|
|
1281
|
+
resource: includeResource,
|
|
1282
|
+
scope: targetScope,
|
|
1283
|
+
scopeName: descriptor.resource,
|
|
1284
|
+
context,
|
|
1285
|
+
})
|
|
1191
1286
|
}
|
|
1192
1287
|
|
|
1193
1288
|
return includeResource
|
|
@@ -1216,7 +1311,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1216
1311
|
let query = db(targetDescriptor.canonical.tableName)
|
|
1217
1312
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1218
1313
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1219
|
-
.whereIn(
|
|
1314
|
+
.whereIn(getLogicalResourceIdColumn(targetDescriptor), ids)
|
|
1220
1315
|
const selectionState = await applyIncludeFieldSelection({
|
|
1221
1316
|
query,
|
|
1222
1317
|
resourceName: targetDescriptor.resource,
|
|
@@ -1228,7 +1323,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1228
1323
|
|
|
1229
1324
|
const newResources = []
|
|
1230
1325
|
for (const row of rows) {
|
|
1231
|
-
const
|
|
1326
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1327
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1232
1328
|
if (seen.has(includeKey)) continue
|
|
1233
1329
|
seen.add(includeKey)
|
|
1234
1330
|
const includeResource = buildIncludedResource({
|
|
@@ -1284,7 +1380,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1284
1380
|
let query = db(targetDescriptor.canonical.tableName)
|
|
1285
1381
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1286
1382
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1287
|
-
.whereIn(
|
|
1383
|
+
.whereIn(getLogicalResourceIdColumn(targetDescriptor), [...idSet])
|
|
1288
1384
|
const selectionState = await applyIncludeFieldSelection({
|
|
1289
1385
|
query,
|
|
1290
1386
|
resourceName: targetDescriptor.resource,
|
|
@@ -1295,7 +1391,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1295
1391
|
const rows = await query
|
|
1296
1392
|
|
|
1297
1393
|
for (const row of rows) {
|
|
1298
|
-
const
|
|
1394
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1395
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1299
1396
|
if (seen.has(includeKey)) continue
|
|
1300
1397
|
seen.add(includeKey)
|
|
1301
1398
|
const includeResource = buildIncludedResource({
|
|
@@ -1432,7 +1529,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1432
1529
|
|
|
1433
1530
|
const newResources = []
|
|
1434
1531
|
for (const row of rows) {
|
|
1435
|
-
const
|
|
1532
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1533
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1436
1534
|
if (seen.has(includeKey)) continue
|
|
1437
1535
|
seen.add(includeKey)
|
|
1438
1536
|
const includeResource = buildIncludedResource({
|
|
@@ -1481,7 +1579,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1481
1579
|
let query = db(targetDescriptor.canonical.tableName)
|
|
1482
1580
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1483
1581
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1484
|
-
.whereIn(
|
|
1582
|
+
.whereIn(getLogicalResourceIdColumn(targetDescriptor), childIds)
|
|
1485
1583
|
const selectionState = await applyIncludeFieldSelection({
|
|
1486
1584
|
query,
|
|
1487
1585
|
resourceName: targetDescriptor.resource,
|
|
@@ -1493,7 +1591,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1493
1591
|
|
|
1494
1592
|
const newResources = []
|
|
1495
1593
|
for (const row of rows) {
|
|
1496
|
-
const
|
|
1594
|
+
const includeId = getLogicalResourceId(row, targetDescriptor)
|
|
1595
|
+
const includeKey = `${targetDescriptor.resource}:${includeId}`
|
|
1497
1596
|
if (seen.has(includeKey)) continue
|
|
1498
1597
|
seen.add(includeKey)
|
|
1499
1598
|
const includeResource = buildIncludedResource({
|
|
@@ -1587,7 +1686,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1587
1686
|
if (parentId == null) return acc
|
|
1588
1687
|
const key = String(parentId)
|
|
1589
1688
|
acc[key] = acc[key] || []
|
|
1590
|
-
acc[key].push({ type: targetDescriptor.resource, id:
|
|
1689
|
+
acc[key].push({ type: targetDescriptor.resource, id: getLogicalResourceId(row, targetDescriptor) })
|
|
1591
1690
|
return acc
|
|
1592
1691
|
}, {})
|
|
1593
1692
|
|
|
@@ -1633,7 +1732,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1633
1732
|
if (parentId == null) return acc
|
|
1634
1733
|
const key = String(parentId)
|
|
1635
1734
|
acc[key] = acc[key] || []
|
|
1636
|
-
acc[key].push({ type: targetDescriptor.resource, id:
|
|
1735
|
+
acc[key].push({ type: targetDescriptor.resource, id: getLogicalResourceId(row, targetDescriptor) })
|
|
1637
1736
|
return acc
|
|
1638
1737
|
}, {})
|
|
1639
1738
|
|
|
@@ -1654,7 +1753,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1654
1753
|
const db = context.db || context.transaction || api.knex.instance
|
|
1655
1754
|
const parentIds = resources.map((resource) => resource.id)
|
|
1656
1755
|
|
|
1657
|
-
for (const [relName
|
|
1756
|
+
for (const [relName] of manyEntries) {
|
|
1658
1757
|
const info = await getManyToManyInfo(descriptor.resource, relName)
|
|
1659
1758
|
if (!info) continue
|
|
1660
1759
|
const rows = await fetchLinksForParents({
|
|
@@ -1687,6 +1786,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1687
1786
|
const descriptor = await getDescriptor(scopeName)
|
|
1688
1787
|
const { canonical } = descriptor
|
|
1689
1788
|
const id = context.id
|
|
1789
|
+
const logicalIdColumn = getLogicalResourceIdColumn(descriptor)
|
|
1690
1790
|
const scope = api.resources?.[scopeName]
|
|
1691
1791
|
|
|
1692
1792
|
let fieldSelectionInfo = null
|
|
@@ -1698,7 +1798,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1698
1798
|
}
|
|
1699
1799
|
|
|
1700
1800
|
let query = context.db(canonical.tableName)
|
|
1701
|
-
.where(
|
|
1801
|
+
.where(logicalIdColumn, id)
|
|
1702
1802
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1703
1803
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1704
1804
|
|
|
@@ -1726,9 +1826,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1726
1826
|
descriptor,
|
|
1727
1827
|
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
1728
1828
|
)
|
|
1829
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
1729
1830
|
const data = {
|
|
1730
1831
|
type: descriptor.resource,
|
|
1731
|
-
id:
|
|
1832
|
+
id: resourceId,
|
|
1732
1833
|
attributes: record.attributes,
|
|
1733
1834
|
}
|
|
1734
1835
|
|
|
@@ -1760,24 +1861,20 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1760
1861
|
}
|
|
1761
1862
|
|
|
1762
1863
|
if (scope) {
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
response.links = {
|
|
1766
|
-
self: data.links.self,
|
|
1767
|
-
}
|
|
1864
|
+
decorateResourceLinks({ resource: data, scope, scopeName, context })
|
|
1865
|
+
response.links = { self: data.links.self }
|
|
1768
1866
|
}
|
|
1769
1867
|
|
|
1770
1868
|
if (included.length > 0) {
|
|
1771
1869
|
for (const includeResource of included) {
|
|
1772
1870
|
const targetScope = api.resources?.[includeResource.type]
|
|
1773
1871
|
if (!targetScope) continue
|
|
1774
|
-
|
|
1775
|
-
|
|
1872
|
+
decorateResourceLinks({
|
|
1873
|
+
resource: includeResource,
|
|
1874
|
+
scope: targetScope,
|
|
1875
|
+
scopeName: includeResource.type,
|
|
1776
1876
|
context,
|
|
1777
|
-
|
|
1778
|
-
includeResource.type,
|
|
1779
|
-
includeResource.id
|
|
1780
|
-
)
|
|
1877
|
+
})
|
|
1781
1878
|
}
|
|
1782
1879
|
}
|
|
1783
1880
|
|
|
@@ -1894,32 +1991,14 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1894
1991
|
return { resourceToTableName, tableNameToResource }
|
|
1895
1992
|
}
|
|
1896
1993
|
|
|
1897
|
-
const
|
|
1898
|
-
get (_target, prop) {
|
|
1899
|
-
if (typeof prop !== 'string') {
|
|
1900
|
-
return undefined
|
|
1901
|
-
}
|
|
1902
|
-
const resource = api.resources?.[prop]
|
|
1903
|
-
if (resource?.vars?.schemaInfo) {
|
|
1904
|
-
return resource
|
|
1905
|
-
}
|
|
1906
|
-
return scopes?.[prop]
|
|
1907
|
-
},
|
|
1908
|
-
})
|
|
1909
|
-
|
|
1910
|
-
const queryHookDependencies = { log, scopes: scopesProxy, knex, getStorageAdapter: getScopeStorageAdapter }
|
|
1994
|
+
const queryHookDependencies = { log, scopes: api.resources, knex, getStorageAdapter: getScopeStorageAdapter }
|
|
1911
1995
|
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
)
|
|
1919
|
-
|
|
1920
|
-
addHook('knexQueryFiltering', 'basicFiltersHook', {}, async (hookParams) =>
|
|
1921
|
-
basicFiltersHook(hookParams, queryHookDependencies)
|
|
1922
|
-
)
|
|
1996
|
+
const applyBuiltInAnyApiQueryFilters = async (context) => {
|
|
1997
|
+
const hookParams = { context }
|
|
1998
|
+
await polymorphicFiltersHook(hookParams, queryHookDependencies)
|
|
1999
|
+
await crossTableFiltersHook(hookParams, queryHookDependencies)
|
|
2000
|
+
await basicFiltersHook(hookParams, queryHookDependencies)
|
|
2001
|
+
}
|
|
1923
2002
|
|
|
1924
2003
|
helpers.dataQuery = async ({ scopeName, context, runHooks }) => {
|
|
1925
2004
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
@@ -1976,6 +2055,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1976
2055
|
storageAdapter,
|
|
1977
2056
|
}
|
|
1978
2057
|
|
|
2058
|
+
await applyBuiltInAnyApiQueryFilters(context)
|
|
2059
|
+
|
|
1979
2060
|
if (runHooks) {
|
|
1980
2061
|
await runHooks('knexQueryFiltering')
|
|
1981
2062
|
}
|
|
@@ -1984,19 +2065,20 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1984
2065
|
|
|
1985
2066
|
const countQuery = queryBuilder.clone()
|
|
1986
2067
|
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2068
|
+
let selectionState = { query: queryBuilder, queryFieldRuntimeByField: new Map() }
|
|
2069
|
+
if (scope && fieldSelectionInfo) {
|
|
2070
|
+
selectionState = await applyFieldSelectionToQuery({
|
|
2071
|
+
query: queryBuilder,
|
|
2072
|
+
scope,
|
|
2073
|
+
fieldSelectionInfo,
|
|
2074
|
+
tableName: adapter.tableAlias,
|
|
2075
|
+
useTablePrefix: false,
|
|
2076
|
+
storageAdapter,
|
|
2077
|
+
db,
|
|
2078
|
+
context,
|
|
2079
|
+
scopeName
|
|
2080
|
+
})
|
|
2081
|
+
}
|
|
2000
2082
|
|
|
2001
2083
|
const sortDescriptors = applySortingToQuery({
|
|
2002
2084
|
query: selectionState.query,
|
|
@@ -2006,9 +2088,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2006
2088
|
queryFieldRuntimeByField: selectionState.queryFieldRuntimeByField
|
|
2007
2089
|
})
|
|
2008
2090
|
|
|
2009
|
-
if (queryParams?.page?.after) {
|
|
2010
|
-
}
|
|
2011
|
-
|
|
2012
2091
|
const paginationInfo = applyPaginationToQuery({
|
|
2013
2092
|
query: queryBuilder,
|
|
2014
2093
|
scope,
|
|
@@ -2027,7 +2106,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2027
2106
|
const record = {}
|
|
2028
2107
|
for (const descriptorEntry of cursorDescriptors) {
|
|
2029
2108
|
if (descriptorEntry.field === 'id') {
|
|
2030
|
-
record.id = row
|
|
2109
|
+
record.id = getLogicalResourceId(row, descriptor)
|
|
2031
2110
|
} else if (descriptorEntry.queryFieldRuntime) {
|
|
2032
2111
|
record[descriptorEntry.field] = row[descriptorEntry.field]
|
|
2033
2112
|
} else if (descriptorEntry.column) {
|
|
@@ -2050,9 +2129,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2050
2129
|
descriptor,
|
|
2051
2130
|
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
2052
2131
|
)
|
|
2132
|
+
const resourceId = getLogicalResourceId(row, descriptor)
|
|
2053
2133
|
const resource = {
|
|
2054
2134
|
type: descriptor.resource,
|
|
2055
|
-
id:
|
|
2135
|
+
id: resourceId,
|
|
2056
2136
|
attributes: translated.attributes,
|
|
2057
2137
|
}
|
|
2058
2138
|
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
@@ -2060,8 +2140,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2060
2140
|
}
|
|
2061
2141
|
const resourceScope = scope
|
|
2062
2142
|
if (resourceScope) {
|
|
2063
|
-
resource
|
|
2064
|
-
resource.links.self = buildResourceUrl(context, resourceScope, scopeName, resource.id)
|
|
2143
|
+
decorateResourceLinks({ resource, scope: resourceScope, scopeName, context })
|
|
2065
2144
|
}
|
|
2066
2145
|
if (fieldSelectionInfo?.computedDependencies?.length) {
|
|
2067
2146
|
resource.__$jsonrestapi_computed_deps$__ = fieldSelectionInfo.computedDependencies
|
|
@@ -2092,6 +2171,19 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2092
2171
|
response.included = included
|
|
2093
2172
|
}
|
|
2094
2173
|
|
|
2174
|
+
if (included.length > 0) {
|
|
2175
|
+
for (const includeResource of included) {
|
|
2176
|
+
const targetScope = api.resources?.[includeResource.type]
|
|
2177
|
+
if (!targetScope) continue
|
|
2178
|
+
decorateResourceLinks({
|
|
2179
|
+
resource: includeResource,
|
|
2180
|
+
scope: targetScope,
|
|
2181
|
+
scopeName: includeResource.type,
|
|
2182
|
+
context,
|
|
2183
|
+
})
|
|
2184
|
+
}
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2095
2187
|
context.returnMeta = context.returnMeta || {}
|
|
2096
2188
|
context.returnMeta.queryString = buildQueryString(queryParams)
|
|
2097
2189
|
delete context.returnMeta.paginationMeta
|