json-rest-api 1.0.24 → 1.0.26
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/docs/API.md +12 -9
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +5 -2
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +3 -3
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +2 -0
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1 -1
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +63 -0
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +17 -12
- package/docs/GUIDE/GUIDE_X_Row_Policies.md +605 -0
- package/docs/GUIDE/GUIDE_X_SocketIO.md +15 -10
- package/docs/GUIDE/GUIDE_X_URL_Management.md +93 -254
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +2 -0
- package/docs/GUIDE/index.md +4 -1
- package/fixing_plan.md +112 -0
- package/index.js +2 -0
- package/package.json +1 -1
- package/plugins/core/bulk-operations-plugin.js +5 -4
- package/plugins/core/connectors/express-plugin.js +6 -1
- package/plugins/core/connectors/fastify-plugin.js +5 -1
- package/plugins/core/connectors/lib/connector-core.js +17 -11
- package/plugins/core/connectors/lib/transport-route-schemas.js +12 -60
- package/plugins/core/file-handling-plugin.js +126 -96
- package/plugins/core/lib/anyapi/anyapi-registry.js +21 -3
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +36 -26
- package/plugins/core/lib/anyapi/schema-utils.js +1 -0
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +7 -1
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +20 -15
- package/plugins/core/lib/querying/knex-pagination-helpers.js +38 -84
- package/plugins/core/lib/querying/knex-relationship-includes.js +29 -28
- package/plugins/core/lib/querying/knex-window-queries.js +10 -77
- package/plugins/core/lib/querying/storage-adapter-utils.js +8 -8
- package/plugins/core/lib/querying/url-helpers.js +9 -21
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +51 -0
- package/plugins/core/lib/querying-writing/database-capabilities.js +1 -1
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +18 -9
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +6 -1
- package/plugins/core/lib/querying-writing/relationship-contracts.js +56 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +53 -4
- package/plugins/core/lib/querying-writing/return-record-settings.js +47 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +34 -41
- package/plugins/core/lib/storage/canonical-storage-mapping.js +2 -1
- package/plugins/core/lib/storage/storage-adapter.js +74 -13
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -3
- package/plugins/core/lib/writing/relationship-processor.js +10 -1
- package/plugins/core/rest-api-anyapi-knex-plugin.js +210 -97
- package/plugins/core/rest-api-knex-plugin.js +70 -60
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +8 -2
- package/plugins/core/rest-api-plugin-methods/common.js +85 -63
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +23 -13
- package/plugins/core/rest-api-plugin-methods/delete.js +2 -6
- package/plugins/core/rest-api-plugin-methods/get-related.js +15 -13
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +1 -1
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +17 -6
- package/plugins/core/rest-api-plugin-methods/patch.js +3 -6
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +24 -14
- package/plugins/core/rest-api-plugin-methods/post.js +4 -7
- package/plugins/core/rest-api-plugin-methods/put.js +4 -7
- package/plugins/core/rest-api-plugin.js +6 -36
- package/plugins/core/rest-api-row-policy-plugin.js +157 -0
- package/plugins/storage/local-storage.js +44 -19
- package/plugins/storage/s3-storage.js +21 -55
- package/tests/README.md +1 -1
- package/tests/anyapi-cursor-pagination.test.js +7 -1
- package/tests/anyapi-custom-idproperty.test.js +163 -0
- package/tests/fastify-plugin.test.js +28 -0
- package/tests/field-setters.test.js +10 -10
- package/tests/file-handling.test.js +118 -0
- package/tests/fixtures/api-configs.js +383 -4
- package/tests/full-jsonapi-belongsto-linkage.test.js +150 -0
- package/tests/http-connectors-parity.test.js +165 -5
- package/tests/local-storage.test.js +96 -0
- package/tests/pagination-cursor-multifield.test.js +121 -0
- package/tests/pagination-enhanced.test.js +66 -15
- package/tests/post-commit-hooks.test.js +120 -0
- package/tests/public-exports.test.js +15 -0
- package/tests/put-create-parity.test.js +216 -0
- package/tests/relationship-endpoints.test.js +259 -0
- package/tests/return-record-settings.test.js +184 -0
- package/tests/row-policy.test.js +489 -0
- package/tests/s3-storage.test.js +38 -0
- package/tests/searchschema-merge.test.js +17 -0
- package/tests/socketio.test.js +62 -0
- package/tests/storage-mapping.test.js +1 -0
- package/tests/test-development-guide.md +5 -5
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
parseSortEntry
|
|
55
55
|
} from './lib/querying/query-field-sort-helpers.js'
|
|
56
56
|
import { unwrapQueryBuilderState } from './lib/querying/query-builder-utils.js'
|
|
57
|
+
import { serializeJsonApiQuery } from './lib/querying-writing/connectors-query-parser.js'
|
|
57
58
|
|
|
58
59
|
const DEFAULT_TENANT = 'default'
|
|
59
60
|
const LINKS_TABLE = 'any_links'
|
|
@@ -122,6 +123,14 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
122
123
|
if (!descriptor) {
|
|
123
124
|
throw new Error(`Descriptor not found for resource '${scopeName}'`)
|
|
124
125
|
}
|
|
126
|
+
const scope = api.resources?.[scopeName] || scopes?.[scopeName]
|
|
127
|
+
const stored = scopeOptionsRegistry.get(scopeName) || {}
|
|
128
|
+
descriptor.idProperty = stored.idProperty ||
|
|
129
|
+
scope?.vars?.schemaInfo?.idProperty ||
|
|
130
|
+
scope?.scopeOptions?.idProperty ||
|
|
131
|
+
descriptor.idProperty ||
|
|
132
|
+
vars.idProperty ||
|
|
133
|
+
'id'
|
|
125
134
|
return descriptor
|
|
126
135
|
}
|
|
127
136
|
|
|
@@ -275,34 +284,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
275
284
|
return descriptors
|
|
276
285
|
}
|
|
277
286
|
|
|
278
|
-
const buildQueryString = (queryParams = {}) => {
|
|
279
|
-
const buildParts = (prefix, value) => {
|
|
280
|
-
const parts = []
|
|
281
|
-
if (Array.isArray(value)) {
|
|
282
|
-
if (value.length === 0) return parts
|
|
283
|
-
parts.push(`${prefix}=${value.map((item) => encodeURIComponent(item)).join(',')}`)
|
|
284
|
-
return parts
|
|
285
|
-
}
|
|
286
|
-
if (value && typeof value === 'object') {
|
|
287
|
-
for (const [key, subValue] of Object.entries(value)) {
|
|
288
|
-
if (subValue === undefined || subValue === null) continue
|
|
289
|
-
const newPrefix = `${prefix}[${key}]`
|
|
290
|
-
parts.push(...buildParts(newPrefix, subValue))
|
|
291
|
-
}
|
|
292
|
-
return parts
|
|
293
|
-
}
|
|
294
|
-
if (value === undefined || value === null) return parts
|
|
295
|
-
parts.push(`${prefix}=${encodeURIComponent(value)}`)
|
|
296
|
-
return parts
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
const parts = []
|
|
300
|
-
for (const [key, value] of Object.entries(queryParams)) {
|
|
301
|
-
parts.push(...buildParts(key, value))
|
|
302
|
-
}
|
|
303
|
-
return parts.length > 0 ? `?${parts.join('&')}` : ''
|
|
304
|
-
}
|
|
305
|
-
|
|
306
287
|
const applyPaginationToQuery = ({
|
|
307
288
|
query,
|
|
308
289
|
scope,
|
|
@@ -379,13 +360,16 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
379
360
|
|
|
380
361
|
predicateChains.forEach((chain) => {
|
|
381
362
|
const chainParts = chain.map(({ descriptor, operator, value }) => {
|
|
363
|
+
const normalizedValue = adapter?.translateFilterValue
|
|
364
|
+
? adapter.translateFilterValue(descriptor.field, value)
|
|
365
|
+
: value
|
|
382
366
|
if (descriptor.queryFieldRuntime) {
|
|
383
|
-
predicateBindings.push(...(descriptor.queryFieldRuntime.bindings || []),
|
|
367
|
+
predicateBindings.push(...(descriptor.queryFieldRuntime.bindings || []), normalizedValue)
|
|
384
368
|
return `(${descriptor.queryFieldRuntime.sql}) ${operator} ?`
|
|
385
369
|
}
|
|
386
370
|
|
|
387
371
|
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
388
|
-
predicateBindings.push(
|
|
372
|
+
predicateBindings.push(normalizedValue)
|
|
389
373
|
return `${columnRef} ${operator} ?`
|
|
390
374
|
})
|
|
391
375
|
|
|
@@ -686,7 +670,88 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
686
670
|
return results
|
|
687
671
|
}
|
|
688
672
|
|
|
689
|
-
const
|
|
673
|
+
const applyTargetScopeFilters = async ({
|
|
674
|
+
query,
|
|
675
|
+
resourceName,
|
|
676
|
+
tableName,
|
|
677
|
+
context,
|
|
678
|
+
filters,
|
|
679
|
+
queryPurpose = 'include'
|
|
680
|
+
}) => {
|
|
681
|
+
const targetScope = api.resources?.[resourceName]
|
|
682
|
+
if (!targetScope?.applyQueryFilters) {
|
|
683
|
+
return { query }
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
const storageAdapter = getScopeStorageAdapter(resourceName)
|
|
687
|
+
const queryState = await targetScope.applyQueryFilters({
|
|
688
|
+
query,
|
|
689
|
+
filters,
|
|
690
|
+
schemaInfo: targetScope.vars?.schemaInfo,
|
|
691
|
+
scopeName: resourceName,
|
|
692
|
+
tableName,
|
|
693
|
+
db: context.db || context.transaction || api.knex.instance,
|
|
694
|
+
isAnyApi: true,
|
|
695
|
+
queryPurpose,
|
|
696
|
+
storageAdapter
|
|
697
|
+
}, {
|
|
698
|
+
...context,
|
|
699
|
+
scopeName: resourceName,
|
|
700
|
+
schemaInfo: targetScope.vars?.schemaInfo,
|
|
701
|
+
storageAdapter
|
|
702
|
+
})
|
|
703
|
+
|
|
704
|
+
return {
|
|
705
|
+
query: unwrapQueryBuilderState(queryState, query)
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
const filterVisibleIdentifiers = async ({ identifiers, context }) => {
|
|
710
|
+
if (!identifiers || identifiers.length === 0) return []
|
|
711
|
+
|
|
712
|
+
const db = context.db || context.transaction || api.knex.instance
|
|
713
|
+
const identifiersByType = new Map()
|
|
714
|
+
|
|
715
|
+
for (const identifier of identifiers) {
|
|
716
|
+
if (!identifier?.type || identifier.id === undefined || identifier.id === null) continue
|
|
717
|
+
if (!identifiersByType.has(identifier.type)) {
|
|
718
|
+
identifiersByType.set(identifier.type, new Set())
|
|
719
|
+
}
|
|
720
|
+
identifiersByType.get(identifier.type).add(normalizeId(identifier.id))
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
const visibleKeys = new Set()
|
|
724
|
+
|
|
725
|
+
for (const [resourceName, idSet] of identifiersByType.entries()) {
|
|
726
|
+
const descriptor = await getDescriptor(resourceName)
|
|
727
|
+
const idColumn = getLogicalResourceIdColumn(descriptor)
|
|
728
|
+
let query = db(descriptor.canonical.tableName)
|
|
729
|
+
.where(descriptor.canonical.tenantColumn, descriptor.tenant)
|
|
730
|
+
.where(descriptor.canonical.resourceColumn, descriptor.resource)
|
|
731
|
+
.whereIn(idColumn, [...idSet])
|
|
732
|
+
.select(idColumn)
|
|
733
|
+
|
|
734
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
735
|
+
query,
|
|
736
|
+
resourceName,
|
|
737
|
+
tableName: descriptor.canonical.tableName,
|
|
738
|
+
context,
|
|
739
|
+
queryPurpose: 'relationship-identifiers'
|
|
740
|
+
})
|
|
741
|
+
query = scopedQueryState.query
|
|
742
|
+
|
|
743
|
+
const rows = await query
|
|
744
|
+
for (const row of rows) {
|
|
745
|
+
visibleKeys.add(`${resourceName}:${String(getLogicalResourceId(row, descriptor))}`)
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return identifiers.filter((identifier) => (
|
|
750
|
+
visibleKeys.has(`${identifier.type}:${String(identifier.id)}`)
|
|
751
|
+
))
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const ensureTargetsExist = async ({ relInfo, relData, context }) => {
|
|
690
755
|
const relArray = Array.isArray(relData) ? relData : []
|
|
691
756
|
if (relArray.length === 0) return
|
|
692
757
|
const targetScope = api.resources[relInfo.target]
|
|
@@ -694,13 +759,17 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
694
759
|
throw new Error(`Target resource '${relInfo.target}' not found`)
|
|
695
760
|
}
|
|
696
761
|
for (const identifier of relArray) {
|
|
697
|
-
if (
|
|
762
|
+
if (identifier?.id === undefined || identifier.id === null) {
|
|
698
763
|
throw new Error('Relationship data requires resource identifier with id')
|
|
699
764
|
}
|
|
700
765
|
if (identifier.type && identifier.type !== relInfo.target) {
|
|
701
766
|
throw new Error(`Relationship expects type '${relInfo.target}' but received '${identifier.type}'`)
|
|
702
767
|
}
|
|
703
|
-
await targetScope.get({
|
|
768
|
+
await targetScope.get({
|
|
769
|
+
id: identifier.id,
|
|
770
|
+
transaction: context.transaction,
|
|
771
|
+
simplified: false
|
|
772
|
+
}, { ...context })
|
|
704
773
|
}
|
|
705
774
|
}
|
|
706
775
|
|
|
@@ -759,7 +828,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
759
828
|
}
|
|
760
829
|
const db = context.transaction || context.db || api.knex.instance
|
|
761
830
|
const leftId = normalizeId(context.id)
|
|
762
|
-
await ensureTargetsExist({ relInfo: info.relInfo, relData,
|
|
831
|
+
await ensureTargetsExist({ relInfo: info.relInfo, relData, context })
|
|
763
832
|
await attachLinks({
|
|
764
833
|
descriptor: info.descriptor,
|
|
765
834
|
relInfo: info.relInfo,
|
|
@@ -777,7 +846,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
777
846
|
}
|
|
778
847
|
const db = context.transaction || context.db || api.knex.instance
|
|
779
848
|
const leftId = normalizeId(context.id)
|
|
780
|
-
await ensureTargetsExist({ relInfo: info.relInfo, relData,
|
|
849
|
+
await ensureTargetsExist({ relInfo: info.relInfo, relData, context })
|
|
781
850
|
if (isUpdate) {
|
|
782
851
|
await syncLinks({
|
|
783
852
|
descriptor: info.descriptor,
|
|
@@ -824,7 +893,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
824
893
|
}
|
|
825
894
|
const db = context.transaction || context.db || api.knex.instance
|
|
826
895
|
const leftId = normalizeId(context.id)
|
|
827
|
-
|
|
896
|
+
const identifiers = await listLinks({
|
|
828
897
|
descriptor: info.descriptor,
|
|
829
898
|
relInfo: info.relInfo,
|
|
830
899
|
relationshipKey: info.relationshipKey,
|
|
@@ -832,6 +901,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
832
901
|
leftId,
|
|
833
902
|
db,
|
|
834
903
|
})
|
|
904
|
+
return filterVisibleIdentifiers({
|
|
905
|
+
identifiers,
|
|
906
|
+
context
|
|
907
|
+
})
|
|
835
908
|
},
|
|
836
909
|
fetchManyToManyRows: async ({ scopeName, relName, parentIds, context }) => {
|
|
837
910
|
const info = await getManyToManyInfo(scopeName, relName)
|
|
@@ -1029,11 +1102,19 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1029
1102
|
row[canonical.resourceColumn] = descriptor.resource
|
|
1030
1103
|
row[canonical.tenantColumn] = descriptor.tenant
|
|
1031
1104
|
|
|
1032
|
-
const
|
|
1105
|
+
const writeRow = Object.fromEntries(
|
|
1033
1106
|
Object.entries(row).filter(([, value]) => value !== undefined)
|
|
1034
1107
|
)
|
|
1035
1108
|
|
|
1036
|
-
if (
|
|
1109
|
+
if (context.isCreate) {
|
|
1110
|
+
await context.db(canonical.tableName).insert({
|
|
1111
|
+
...writeRow,
|
|
1112
|
+
[logicalIdColumn]: id,
|
|
1113
|
+
})
|
|
1114
|
+
return 1
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
if (Object.keys(writeRow).length === 0) {
|
|
1037
1118
|
return 0
|
|
1038
1119
|
}
|
|
1039
1120
|
|
|
@@ -1041,7 +1122,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1041
1122
|
.where(logicalIdColumn, id)
|
|
1042
1123
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1043
1124
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1044
|
-
.update(
|
|
1125
|
+
.update(writeRow)
|
|
1045
1126
|
|
|
1046
1127
|
return result
|
|
1047
1128
|
}
|
|
@@ -1096,7 +1177,14 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1096
1177
|
return result
|
|
1097
1178
|
}
|
|
1098
1179
|
|
|
1099
|
-
helpers.dataGetMinimal = async ({
|
|
1180
|
+
helpers.dataGetMinimal = async ({
|
|
1181
|
+
scopeName,
|
|
1182
|
+
context,
|
|
1183
|
+
runHooks,
|
|
1184
|
+
applyQueryFilters,
|
|
1185
|
+
filters = context.queryParams?.filters,
|
|
1186
|
+
queryPurpose = 'single'
|
|
1187
|
+
}) => {
|
|
1100
1188
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
1101
1189
|
if (storageAdapter) {
|
|
1102
1190
|
context.storageAdapter = storageAdapter
|
|
@@ -1115,33 +1203,40 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1115
1203
|
if (typeof applyQueryFilters === 'function') {
|
|
1116
1204
|
const scopedQueryState = await applyQueryFilters({
|
|
1117
1205
|
query,
|
|
1118
|
-
filters
|
|
1206
|
+
filters,
|
|
1119
1207
|
tableName: canonical.tableName,
|
|
1120
1208
|
db: context.db,
|
|
1121
1209
|
scopeName,
|
|
1210
|
+
queryPurpose,
|
|
1122
1211
|
storageAdapter,
|
|
1123
1212
|
isAnyApi: true
|
|
1124
1213
|
})
|
|
1125
1214
|
query = unwrapQueryBuilderState(scopedQueryState, query)
|
|
1126
1215
|
} else if (runHooks) {
|
|
1216
|
+
const previousKnexQuery = context.knexQuery
|
|
1127
1217
|
context.knexQuery = {
|
|
1128
1218
|
query,
|
|
1129
|
-
filters
|
|
1219
|
+
filters,
|
|
1130
1220
|
schemaInfo: context.schemaInfo,
|
|
1131
1221
|
scopeName,
|
|
1132
1222
|
tableName: canonical.tableName,
|
|
1133
1223
|
db: context.db,
|
|
1224
|
+
queryPurpose,
|
|
1134
1225
|
isAnyApi: true,
|
|
1135
1226
|
adapter: storageAdapter,
|
|
1136
1227
|
storageAdapter,
|
|
1137
1228
|
}
|
|
1138
1229
|
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1230
|
+
try {
|
|
1231
|
+
await applyBuiltInAnyApiQueryFilters(context)
|
|
1232
|
+
await runHooks('knexQueryFiltering')
|
|
1233
|
+
query = context.knexQuery?.query || query
|
|
1234
|
+
} finally {
|
|
1235
|
+
if (previousKnexQuery === undefined) {
|
|
1236
|
+
delete context.knexQuery
|
|
1237
|
+
} else {
|
|
1238
|
+
context.knexQuery = previousKnexQuery
|
|
1239
|
+
}
|
|
1145
1240
|
}
|
|
1146
1241
|
}
|
|
1147
1242
|
|
|
@@ -1226,34 +1321,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1226
1321
|
}
|
|
1227
1322
|
}
|
|
1228
1323
|
|
|
1229
|
-
const applyTargetScopeFilters = async ({ query, resourceName, tableName, context, filters }) => {
|
|
1230
|
-
const targetScope = api.resources?.[resourceName]
|
|
1231
|
-
if (!targetScope?.applyQueryFilters) {
|
|
1232
|
-
return { query }
|
|
1233
|
-
}
|
|
1234
|
-
|
|
1235
|
-
const storageAdapter = getScopeStorageAdapter(resourceName)
|
|
1236
|
-
const queryState = await targetScope.applyQueryFilters({
|
|
1237
|
-
query,
|
|
1238
|
-
filters,
|
|
1239
|
-
schemaInfo: targetScope.vars?.schemaInfo,
|
|
1240
|
-
scopeName: resourceName,
|
|
1241
|
-
tableName,
|
|
1242
|
-
db: context.db || context.transaction || api.knex.instance,
|
|
1243
|
-
isAnyApi: true,
|
|
1244
|
-
storageAdapter
|
|
1245
|
-
}, {
|
|
1246
|
-
...context,
|
|
1247
|
-
scopeName: resourceName,
|
|
1248
|
-
schemaInfo: targetScope.vars?.schemaInfo,
|
|
1249
|
-
storageAdapter
|
|
1250
|
-
})
|
|
1251
|
-
|
|
1252
|
-
return {
|
|
1253
|
-
query: unwrapQueryBuilderState(queryState, query)
|
|
1254
|
-
}
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
1324
|
const buildIncludedResource = ({ row, descriptor, context, fieldSelectionInfo = null }) => {
|
|
1258
1325
|
const translated = translateCanonicalRecordFromStorage(
|
|
1259
1326
|
row,
|
|
@@ -1319,6 +1386,13 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1319
1386
|
context
|
|
1320
1387
|
})
|
|
1321
1388
|
query = selectionState.query
|
|
1389
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1390
|
+
query,
|
|
1391
|
+
resourceName: targetDescriptor.resource,
|
|
1392
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1393
|
+
context
|
|
1394
|
+
})
|
|
1395
|
+
query = scopedQueryState.query
|
|
1322
1396
|
const rows = await query
|
|
1323
1397
|
|
|
1324
1398
|
const newResources = []
|
|
@@ -1388,6 +1462,13 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1388
1462
|
context
|
|
1389
1463
|
})
|
|
1390
1464
|
query = selectionState.query
|
|
1465
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1466
|
+
query,
|
|
1467
|
+
resourceName: targetDescriptor.resource,
|
|
1468
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1469
|
+
context
|
|
1470
|
+
})
|
|
1471
|
+
query = scopedQueryState.query
|
|
1391
1472
|
const rows = await query
|
|
1392
1473
|
|
|
1393
1474
|
for (const row of rows) {
|
|
@@ -1587,6 +1668,13 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1587
1668
|
context
|
|
1588
1669
|
})
|
|
1589
1670
|
query = selectionState.query
|
|
1671
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1672
|
+
query,
|
|
1673
|
+
resourceName: targetDescriptor.resource,
|
|
1674
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1675
|
+
context
|
|
1676
|
+
})
|
|
1677
|
+
query = scopedQueryState.query
|
|
1590
1678
|
const rows = await query
|
|
1591
1679
|
|
|
1592
1680
|
const newResources = []
|
|
@@ -1676,7 +1764,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1676
1764
|
query,
|
|
1677
1765
|
resourceName: targetDescriptor.resource,
|
|
1678
1766
|
tableName: targetDescriptor.canonical.tableName,
|
|
1679
|
-
context
|
|
1767
|
+
context,
|
|
1768
|
+
queryPurpose: 'relationship-identifiers'
|
|
1680
1769
|
})
|
|
1681
1770
|
query = scopedQueryState.query
|
|
1682
1771
|
const rows = await query
|
|
@@ -1722,7 +1811,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1722
1811
|
query,
|
|
1723
1812
|
resourceName: targetDescriptor.resource,
|
|
1724
1813
|
tableName: targetDescriptor.canonical.tableName,
|
|
1725
|
-
context
|
|
1814
|
+
context,
|
|
1815
|
+
queryPurpose: 'relationship-identifiers'
|
|
1726
1816
|
})
|
|
1727
1817
|
query = scopedQueryState.query
|
|
1728
1818
|
const rows = await query
|
|
@@ -1756,13 +1846,22 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1756
1846
|
for (const [relName] of manyEntries) {
|
|
1757
1847
|
const info = await getManyToManyInfo(descriptor.resource, relName)
|
|
1758
1848
|
if (!info) continue
|
|
1759
|
-
|
|
1849
|
+
let rows = await fetchLinksForParents({
|
|
1760
1850
|
descriptor: info.descriptor,
|
|
1761
1851
|
relationshipKey: info.relationshipKey,
|
|
1762
1852
|
parentIds,
|
|
1763
1853
|
db,
|
|
1764
1854
|
})
|
|
1765
1855
|
|
|
1856
|
+
const visibleIdentifiers = await filterVisibleIdentifiers({
|
|
1857
|
+
identifiers: rows.map((row) => ({ type: row.childType, id: row.childId })),
|
|
1858
|
+
context
|
|
1859
|
+
})
|
|
1860
|
+
const visibleKeys = new Set(
|
|
1861
|
+
visibleIdentifiers.map((identifier) => `${identifier.type}:${String(identifier.id)}`)
|
|
1862
|
+
)
|
|
1863
|
+
rows = rows.filter((row) => visibleKeys.has(`${row.childType}:${String(row.childId)}`))
|
|
1864
|
+
|
|
1766
1865
|
const grouped = rows.reduce((acc, row) => {
|
|
1767
1866
|
const key = String(row.parentId)
|
|
1768
1867
|
acc[key] = acc[key] || []
|
|
@@ -1943,6 +2042,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1943
2042
|
scope.vars?.idProperty ||
|
|
1944
2043
|
vars.idProperty ||
|
|
1945
2044
|
'id'
|
|
2045
|
+
descriptor.idProperty = idProperty
|
|
1946
2046
|
|
|
1947
2047
|
scope.vars.schemaInfo = {
|
|
1948
2048
|
...existingInfo,
|
|
@@ -2050,6 +2150,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2050
2150
|
scopeName,
|
|
2051
2151
|
tableName: tableNameForHooks,
|
|
2052
2152
|
db,
|
|
2153
|
+
queryPurpose: 'collection',
|
|
2053
2154
|
isAnyApi: true,
|
|
2054
2155
|
adapter,
|
|
2055
2156
|
storageAdapter,
|
|
@@ -2185,7 +2286,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2185
2286
|
}
|
|
2186
2287
|
|
|
2187
2288
|
context.returnMeta = context.returnMeta || {}
|
|
2188
|
-
|
|
2289
|
+
const queryString = serializeJsonApiQuery(queryParams)
|
|
2290
|
+
context.returnMeta.queryString = queryString ? `?${queryString}` : ''
|
|
2189
2291
|
delete context.returnMeta.paginationMeta
|
|
2190
2292
|
delete context.returnMeta.paginationLinks
|
|
2191
2293
|
|
|
@@ -2215,7 +2317,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2215
2317
|
cursorRecords,
|
|
2216
2318
|
paginationInfo.pageSize,
|
|
2217
2319
|
hasMore,
|
|
2218
|
-
cursorFields
|
|
2320
|
+
cursorFields,
|
|
2321
|
+
{
|
|
2322
|
+
schemaInfo
|
|
2323
|
+
}
|
|
2219
2324
|
)
|
|
2220
2325
|
context.returnMeta.paginationMeta = paginationMeta
|
|
2221
2326
|
const urlPrefix = getUrlPrefix(context, scope)
|
|
@@ -2226,7 +2331,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2226
2331
|
cursorRecords,
|
|
2227
2332
|
paginationInfo.pageSize,
|
|
2228
2333
|
hasMore,
|
|
2229
|
-
cursorFields
|
|
2334
|
+
cursorFields,
|
|
2335
|
+
{
|
|
2336
|
+
schemaInfo
|
|
2337
|
+
}
|
|
2230
2338
|
)
|
|
2231
2339
|
}
|
|
2232
2340
|
|
|
@@ -2272,27 +2380,30 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2272
2380
|
const schemaInfo = scope?.vars?.schemaInfo
|
|
2273
2381
|
const tableNameForHooks = schemaInfo?.tableName || adapter.tableAlias
|
|
2274
2382
|
|
|
2275
|
-
const
|
|
2276
|
-
context
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
}
|
|
2383
|
+
const countContext = {
|
|
2384
|
+
...context,
|
|
2385
|
+
knexQuery: {
|
|
2386
|
+
query: adapter.query,
|
|
2387
|
+
filters: context.queryParams?.filters,
|
|
2388
|
+
schemaInfo,
|
|
2389
|
+
scopeName,
|
|
2390
|
+
tableName: tableNameForHooks,
|
|
2391
|
+
db,
|
|
2392
|
+
queryPurpose: 'count',
|
|
2393
|
+
isAnyApi: true,
|
|
2394
|
+
adapter,
|
|
2395
|
+
storageAdapter,
|
|
2396
|
+
}
|
|
2289
2397
|
}
|
|
2290
2398
|
|
|
2399
|
+
const hookParams = { context: countContext }
|
|
2291
2400
|
await polymorphicFiltersHook(hookParams, queryHookDependencies)
|
|
2292
2401
|
await crossTableFiltersHook(hookParams, queryHookDependencies)
|
|
2293
2402
|
await basicFiltersHook(hookParams, queryHookDependencies)
|
|
2403
|
+
await api.runHooks('knexQueryFiltering', countContext)
|
|
2294
2404
|
|
|
2295
|
-
const
|
|
2405
|
+
const countQuery = countContext.knexQuery?.query || adapter.query
|
|
2406
|
+
const [{ count }] = await countQuery.count({ count: '*' })
|
|
2296
2407
|
return Number(count)
|
|
2297
2408
|
}
|
|
2298
2409
|
|
|
@@ -2328,6 +2439,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2328
2439
|
schema,
|
|
2329
2440
|
relationships,
|
|
2330
2441
|
canonicalFieldMap: canonicalFieldsMap,
|
|
2442
|
+
idProperty,
|
|
2331
2443
|
})
|
|
2332
2444
|
|
|
2333
2445
|
const updatedStored = scopeOptionsRegistry.get(scopeName) || {}
|
|
@@ -2398,6 +2510,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2398
2510
|
canonicalFieldMap: scopeOptions?.canonicalFieldsMap ||
|
|
2399
2511
|
scope?.scopeOptions?.canonicalFieldsMap ||
|
|
2400
2512
|
null,
|
|
2513
|
+
idProperty,
|
|
2401
2514
|
})
|
|
2402
2515
|
if (scope) {
|
|
2403
2516
|
scope.scopeOptions = {
|