json-rest-api 1.0.21 → 1.0.23
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/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +51 -14
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/package.json +4 -4
- package/plugins/core/connectors/express-plugin.js +97 -184
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1104 -160
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
- package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
- package/plugins/core/lib/storage/storage-adapter.js +79 -2
- package/plugins/core/lib/storage/storage-mapping.js +204 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +249 -211
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +121 -83
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get.js +23 -14
- package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
- package/plugins/core/rest-api-plugin-methods/post.js +13 -8
- package/plugins/core/rest-api-plugin-methods/put.js +45 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin.js +2 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -1
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -3
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +518 -0
- package/tests/db-table-operations.test.js +781 -0
- package/tests/fastify-plugin.test.js +141 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +188 -219
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +210 -0
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -5,6 +5,7 @@ import { createSchema } from 'json-rest-schema'
|
|
|
5
5
|
import {
|
|
6
6
|
DEFAULT_QUERY_LIMIT,
|
|
7
7
|
DEFAULT_MAX_QUERY_LIMIT,
|
|
8
|
+
COMPUTED_DEPENDENCIES_KEY,
|
|
8
9
|
} from './lib/querying-writing/knex-constants.js'
|
|
9
10
|
import {
|
|
10
11
|
calculatePaginationMeta,
|
|
@@ -29,13 +30,26 @@ import {
|
|
|
29
30
|
ensureSearchFieldsAreIndexed,
|
|
30
31
|
generateSearchSchemaFromSchema,
|
|
31
32
|
} from './lib/querying-writing/schema-helpers.js'
|
|
32
|
-
import { buildFieldSelection } from './lib/querying-writing/knex-field-helpers.js'
|
|
33
|
+
import { applyFieldSelectionToQuery, buildFieldSelection } from './lib/querying-writing/knex-field-helpers.js'
|
|
33
34
|
import {
|
|
34
35
|
polymorphicFiltersHook,
|
|
35
36
|
crossTableFiltersHook,
|
|
36
37
|
basicFiltersHook,
|
|
37
38
|
} from './lib/querying/knex-query-helpers.js'
|
|
38
|
-
import {
|
|
39
|
+
import {
|
|
40
|
+
createStorageAdapter,
|
|
41
|
+
} from './lib/storage/storage-adapter.js'
|
|
42
|
+
import {
|
|
43
|
+
translateCanonicalAttributesForStorage,
|
|
44
|
+
translateCanonicalRecordFromStorage,
|
|
45
|
+
} from './lib/storage/canonical-storage-mapping.js'
|
|
46
|
+
import {
|
|
47
|
+
buildCursorPredicateChains,
|
|
48
|
+
applyQueryFieldOrder,
|
|
49
|
+
buildEffectiveSortList,
|
|
50
|
+
parseSortEntry
|
|
51
|
+
} from './lib/querying/query-field-sort-helpers.js'
|
|
52
|
+
import { unwrapQueryBuilderState } from './lib/querying/query-builder-utils.js'
|
|
39
53
|
|
|
40
54
|
const DEFAULT_TENANT = 'default'
|
|
41
55
|
const LINKS_TABLE = 'any_links'
|
|
@@ -107,6 +121,10 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
107
121
|
return descriptor
|
|
108
122
|
}
|
|
109
123
|
|
|
124
|
+
const getAllowedQueryFieldNames = (scopeName) => (
|
|
125
|
+
Object.keys(api.resources?.[scopeName]?.vars?.queryFields || {})
|
|
126
|
+
)
|
|
127
|
+
|
|
110
128
|
const applyFiltersToQuery = ({ query, filters, descriptor, searchSchema, adapter }) => {
|
|
111
129
|
// Filtering is handled by the query hooks to keep behavior consistent
|
|
112
130
|
// (operators, null handling, multi-field, joins, polymorphic, etc.).
|
|
@@ -148,35 +166,35 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
148
166
|
}
|
|
149
167
|
}
|
|
150
168
|
|
|
151
|
-
const applySortingToQuery = ({ query, sort, descriptor, scope }) => {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (Array.isArray(defaultSort)) {
|
|
157
|
-
sortList = defaultSort
|
|
158
|
-
} else if (typeof defaultSort === 'string') {
|
|
159
|
-
sortList = [defaultSort]
|
|
160
|
-
} else if (defaultSort && typeof defaultSort === 'object') {
|
|
161
|
-
const field = defaultSort.field || defaultSort.column || 'id'
|
|
162
|
-
const direction = (defaultSort.direction || '').toLowerCase() === 'desc' ? '-' : ''
|
|
163
|
-
sortList = [`${direction}${field}`]
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const effectiveSort = sortList.length > 0 ? sortList : ['id']
|
|
169
|
+
const applySortingToQuery = ({ query, sort, descriptor, scope, queryFieldRuntimeByField = new Map() }) => {
|
|
170
|
+
const effectiveSort = buildEffectiveSortList(sort, {
|
|
171
|
+
defaultSort: scope?.vars?.defaultSort,
|
|
172
|
+
idField: 'id'
|
|
173
|
+
})
|
|
168
174
|
const descriptors = []
|
|
169
175
|
|
|
170
176
|
for (const entry of effectiveSort) {
|
|
171
|
-
const
|
|
172
|
-
const
|
|
177
|
+
const { field, direction, sqlDirection } = parseSortEntry(entry)
|
|
178
|
+
const queryFieldRuntime = queryFieldRuntimeByField.get(field)
|
|
179
|
+
if (queryFieldRuntime) {
|
|
180
|
+
applyQueryFieldOrder(query, queryFieldRuntime, sqlDirection)
|
|
181
|
+
descriptors.push({
|
|
182
|
+
field,
|
|
183
|
+
direction,
|
|
184
|
+
definition: queryFieldRuntime.definition,
|
|
185
|
+
isRelationship: false,
|
|
186
|
+
queryFieldRuntime,
|
|
187
|
+
})
|
|
188
|
+
continue
|
|
189
|
+
}
|
|
190
|
+
|
|
173
191
|
const fieldInfo = resolveFieldInfo(descriptor, field)
|
|
174
192
|
if (!fieldInfo?.column) continue
|
|
175
|
-
query.orderBy(fieldInfo.column,
|
|
193
|
+
query.orderBy(fieldInfo.column, direction)
|
|
176
194
|
descriptors.push({
|
|
177
195
|
field,
|
|
178
196
|
column: fieldInfo.column,
|
|
179
|
-
direction
|
|
197
|
+
direction,
|
|
180
198
|
definition: fieldInfo.definition || null,
|
|
181
199
|
isRelationship: fieldInfo.isRelationship || false,
|
|
182
200
|
})
|
|
@@ -282,64 +300,37 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
282
300
|
return typed
|
|
283
301
|
}
|
|
284
302
|
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const value = cursorValues[descriptor.field]
|
|
293
|
-
if (value === undefined) return
|
|
294
|
-
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
295
|
-
const operator = operatorSelector(descriptor.direction)
|
|
296
|
-
if (adapter) {
|
|
297
|
-
query.whereRaw(`${columnRef} ${operator} ?`, [value])
|
|
298
|
-
} else {
|
|
299
|
-
query.where(columnRef, operator, value)
|
|
300
|
-
}
|
|
301
|
-
return
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
const translatedConditions = []
|
|
305
|
-
|
|
306
|
-
descriptors.forEach((descriptor, index) => {
|
|
307
|
-
const value = cursorValues[descriptor.field]
|
|
308
|
-
if (value === undefined) return
|
|
309
|
-
|
|
310
|
-
const chain = []
|
|
311
|
-
for (let i = 0; i < index; i += 1) {
|
|
312
|
-
const prev = descriptors[i]
|
|
313
|
-
const prevValue = cursorValues[prev.field]
|
|
314
|
-
if (prevValue === undefined) return
|
|
315
|
-
const prevColumn = adapter ? adapter.translateColumn(prev.column) : prev.column
|
|
316
|
-
chain.push({ column: prevColumn, operator: '=', value: prevValue })
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const operator = operatorSelector(descriptor.direction)
|
|
320
|
-
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
321
|
-
chain.push({ column: columnRef, operator, value })
|
|
322
|
-
translatedConditions.push(chain)
|
|
323
|
-
})
|
|
303
|
+
const applyRawCursorPredicate = (cursorValues, operatorSelector) => {
|
|
304
|
+
const predicateChains = buildCursorPredicateChains(
|
|
305
|
+
descriptors,
|
|
306
|
+
cursorValues,
|
|
307
|
+
operatorSelector,
|
|
308
|
+
{ onMissingValue: () => {} }
|
|
309
|
+
)
|
|
324
310
|
|
|
325
|
-
if (
|
|
311
|
+
if (predicateChains.length === 0) {
|
|
326
312
|
return
|
|
327
313
|
}
|
|
328
314
|
|
|
329
315
|
const predicateParts = []
|
|
330
316
|
const predicateBindings = []
|
|
331
|
-
|
|
332
|
-
|
|
317
|
+
|
|
318
|
+
predicateChains.forEach((chain) => {
|
|
319
|
+
const chainParts = chain.map(({ descriptor, operator, value }) => {
|
|
320
|
+
if (descriptor.queryFieldRuntime) {
|
|
321
|
+
predicateBindings.push(...(descriptor.queryFieldRuntime.bindings || []), value)
|
|
322
|
+
return `(${descriptor.queryFieldRuntime.sql}) ${operator} ?`
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
const columnRef = adapter ? adapter.translateColumn(descriptor.column) : descriptor.column
|
|
333
326
|
predicateBindings.push(value)
|
|
334
|
-
return `${
|
|
327
|
+
return `${columnRef} ${operator} ?`
|
|
335
328
|
})
|
|
329
|
+
|
|
336
330
|
predicateParts.push(`(${chainParts.join(' AND ')})`)
|
|
337
331
|
})
|
|
338
332
|
|
|
339
|
-
|
|
340
|
-
const combined = predicateParts.join(' OR ')
|
|
341
|
-
query.whereRaw(`(${combined})`, predicateBindings)
|
|
342
|
-
}
|
|
333
|
+
query.whereRaw(`(${predicateParts.join(' OR ')})`, predicateBindings)
|
|
343
334
|
}
|
|
344
335
|
|
|
345
336
|
const ensurePageSize = () => {
|
|
@@ -378,11 +369,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
378
369
|
if (pageParams.after) {
|
|
379
370
|
const cursorMap = parseCursorOrThrow(pageParams.after, 'after')
|
|
380
371
|
const cursorValues = coerceCursorValues(cursorMap)
|
|
381
|
-
|
|
372
|
+
applyRawCursorPredicate(cursorValues, (direction) => (direction === 'desc' ? '<' : '>'))
|
|
382
373
|
} else if (pageParams.before) {
|
|
383
374
|
const cursorMap = parseCursorOrThrow(pageParams.before, 'before')
|
|
384
375
|
const cursorValues = coerceCursorValues(cursorMap)
|
|
385
|
-
|
|
376
|
+
applyRawCursorPredicate(cursorValues, (direction) => (direction === 'desc' ? '>' : '<'))
|
|
386
377
|
}
|
|
387
378
|
|
|
388
379
|
return {
|
|
@@ -919,7 +910,9 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
919
910
|
const descriptor = await getDescriptor(scopeName)
|
|
920
911
|
const { canonical } = descriptor
|
|
921
912
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
922
|
-
const row =
|
|
913
|
+
const row = storageAdapter?.toStorageRow
|
|
914
|
+
? storageAdapter.toStorageRow(attributes)
|
|
915
|
+
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
923
916
|
row[canonical.tenantColumn] = descriptor.tenant
|
|
924
917
|
row[canonical.resourceColumn] = descriptor.resource
|
|
925
918
|
|
|
@@ -943,7 +936,9 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
943
936
|
const { canonical } = descriptor
|
|
944
937
|
const id = context.id
|
|
945
938
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
946
|
-
const row =
|
|
939
|
+
const row = storageAdapter?.toStorageRow
|
|
940
|
+
? storageAdapter.toStorageRow(attributes)
|
|
941
|
+
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
947
942
|
|
|
948
943
|
row[canonical.resourceColumn] = descriptor.resource
|
|
949
944
|
row[canonical.tenantColumn] = descriptor.tenant
|
|
@@ -974,7 +969,9 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
974
969
|
const { canonical } = descriptor
|
|
975
970
|
const id = context.id
|
|
976
971
|
const attributes = context.inputRecord?.data?.attributes || {}
|
|
977
|
-
const row =
|
|
972
|
+
const row = storageAdapter?.toStorageRow
|
|
973
|
+
? storageAdapter.toStorageRow(attributes)
|
|
974
|
+
: translateCanonicalAttributesForStorage(attributes, descriptor)
|
|
978
975
|
|
|
979
976
|
const updateRow = Object.fromEntries(
|
|
980
977
|
Object.entries(row).filter(([, value]) => value !== undefined)
|
|
@@ -1011,7 +1008,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1011
1008
|
return result
|
|
1012
1009
|
}
|
|
1013
1010
|
|
|
1014
|
-
helpers.dataGetMinimal = async ({ scopeName, context }) => {
|
|
1011
|
+
helpers.dataGetMinimal = async ({ scopeName, context, runHooks, applyQueryFilters }) => {
|
|
1015
1012
|
const storageAdapter = getScopeStorageAdapter(scopeName)
|
|
1016
1013
|
if (storageAdapter) {
|
|
1017
1014
|
context.storageAdapter = storageAdapter
|
|
@@ -1021,14 +1018,50 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1021
1018
|
const id = context.id
|
|
1022
1019
|
const scope = api.resources?.[scopeName]
|
|
1023
1020
|
|
|
1024
|
-
|
|
1021
|
+
let query = context.db(canonical.tableName)
|
|
1025
1022
|
.where('id', id)
|
|
1026
1023
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1027
1024
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1028
|
-
|
|
1025
|
+
|
|
1026
|
+
if (typeof applyQueryFilters === 'function') {
|
|
1027
|
+
const scopedQueryState = await applyQueryFilters({
|
|
1028
|
+
query,
|
|
1029
|
+
filters: context.queryParams?.filters,
|
|
1030
|
+
tableName: canonical.tableName,
|
|
1031
|
+
db: context.db,
|
|
1032
|
+
scopeName,
|
|
1033
|
+
storageAdapter,
|
|
1034
|
+
isAnyApi: true
|
|
1035
|
+
})
|
|
1036
|
+
query = unwrapQueryBuilderState(scopedQueryState, query)
|
|
1037
|
+
} else if (runHooks) {
|
|
1038
|
+
context.knexQuery = {
|
|
1039
|
+
query,
|
|
1040
|
+
filters: context.queryParams?.filters,
|
|
1041
|
+
schemaInfo: context.schemaInfo,
|
|
1042
|
+
scopeName,
|
|
1043
|
+
tableName: canonical.tableName,
|
|
1044
|
+
db: context.db,
|
|
1045
|
+
isAnyApi: true,
|
|
1046
|
+
adapter: storageAdapter,
|
|
1047
|
+
storageAdapter,
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
await runHooks('knexQueryFiltering')
|
|
1051
|
+
|
|
1052
|
+
if (context.knexQuery) {
|
|
1053
|
+
delete context.knexQuery
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const row = await query.first()
|
|
1029
1058
|
|
|
1030
1059
|
if (!row) return null
|
|
1031
|
-
const translated =
|
|
1060
|
+
const translated = translateCanonicalRecordFromStorage(
|
|
1061
|
+
row,
|
|
1062
|
+
descriptor,
|
|
1063
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
1064
|
+
)
|
|
1032
1065
|
const minimal = {
|
|
1033
1066
|
type: descriptor.resource,
|
|
1034
1067
|
id: String(row.id),
|
|
@@ -1069,17 +1102,102 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1069
1102
|
return tree
|
|
1070
1103
|
}
|
|
1071
1104
|
|
|
1105
|
+
const applyIncludeFieldSelection = async ({ query, resourceName, tableName, context }) => {
|
|
1106
|
+
const targetScope = api.resources?.[resourceName]
|
|
1107
|
+
if (!targetScope) {
|
|
1108
|
+
return { query, fieldSelectionInfo: null }
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
const storageAdapter = getScopeStorageAdapter(resourceName)
|
|
1112
|
+
const fieldSelectionInfo = await buildFieldSelection(targetScope, {
|
|
1113
|
+
context: {
|
|
1114
|
+
...context,
|
|
1115
|
+
scopeName: resourceName,
|
|
1116
|
+
schemaInfo: targetScope.vars.schemaInfo,
|
|
1117
|
+
storageAdapter,
|
|
1118
|
+
}
|
|
1119
|
+
})
|
|
1120
|
+
|
|
1121
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
1122
|
+
query,
|
|
1123
|
+
scope: targetScope,
|
|
1124
|
+
fieldSelectionInfo,
|
|
1125
|
+
tableName,
|
|
1126
|
+
useTablePrefix: false,
|
|
1127
|
+
storageAdapter,
|
|
1128
|
+
db: context.db || context.transaction || api.knex.instance,
|
|
1129
|
+
context,
|
|
1130
|
+
scopeName: resourceName
|
|
1131
|
+
})
|
|
1132
|
+
|
|
1133
|
+
return {
|
|
1134
|
+
query: selectionState.query,
|
|
1135
|
+
fieldSelectionInfo
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
const applyTargetScopeFilters = async ({ query, resourceName, tableName, context, filters }) => {
|
|
1140
|
+
const targetScope = api.resources?.[resourceName]
|
|
1141
|
+
if (!targetScope?.applyQueryFilters) {
|
|
1142
|
+
return { query }
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
const storageAdapter = getScopeStorageAdapter(resourceName)
|
|
1146
|
+
const queryState = await targetScope.applyQueryFilters({
|
|
1147
|
+
query,
|
|
1148
|
+
filters,
|
|
1149
|
+
schemaInfo: targetScope.vars?.schemaInfo,
|
|
1150
|
+
scopeName: resourceName,
|
|
1151
|
+
tableName,
|
|
1152
|
+
db: context.db || context.transaction || api.knex.instance,
|
|
1153
|
+
isAnyApi: true,
|
|
1154
|
+
storageAdapter
|
|
1155
|
+
}, {
|
|
1156
|
+
...context,
|
|
1157
|
+
scopeName: resourceName,
|
|
1158
|
+
schemaInfo: targetScope.vars?.schemaInfo,
|
|
1159
|
+
storageAdapter
|
|
1160
|
+
})
|
|
1161
|
+
|
|
1162
|
+
return {
|
|
1163
|
+
query: unwrapQueryBuilderState(queryState, query)
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
|
|
1167
|
+
const buildIncludedResource = ({ row, descriptor, context, fieldSelectionInfo = null }) => {
|
|
1168
|
+
const translated = translateCanonicalRecordFromStorage(
|
|
1169
|
+
row,
|
|
1170
|
+
descriptor,
|
|
1171
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(descriptor.resource) }
|
|
1172
|
+
)
|
|
1173
|
+
const includeResource = {
|
|
1174
|
+
type: descriptor.resource,
|
|
1175
|
+
id: String(row.id),
|
|
1176
|
+
attributes: translated.attributes,
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1180
|
+
includeResource.relationships = translated.relationships
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
if (fieldSelectionInfo?.computedDependencies?.length) {
|
|
1184
|
+
includeResource[COMPUTED_DEPENDENCIES_KEY] = fieldSelectionInfo.computedDependencies
|
|
1185
|
+
}
|
|
1186
|
+
|
|
1187
|
+
const targetScope = api.resources?.[descriptor.resource]
|
|
1188
|
+
if (targetScope) {
|
|
1189
|
+
includeResource.links = includeResource.links || {}
|
|
1190
|
+
includeResource.links.self = buildResourceUrl(context, targetScope, descriptor.resource, includeResource.id)
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
return includeResource
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1072
1196
|
const collectIncludes = async ({ descriptor, resources, includeTree, context, includes, seen }) => {
|
|
1073
1197
|
const entries = Object.entries(includeTree || {})
|
|
1074
1198
|
if (entries.length === 0 || !resources || resources.length === 0) return
|
|
1075
1199
|
|
|
1076
1200
|
const db = context.db || context.transaction || api.knex.instance
|
|
1077
|
-
const addSelfLink = (resource, resourceType) => {
|
|
1078
|
-
const targetScope = api.resources?.[resourceType]
|
|
1079
|
-
if (!targetScope) return
|
|
1080
|
-
resource.links = resource.links || {}
|
|
1081
|
-
resource.links.self = buildResourceUrl(context, targetScope, resourceType, resource.id)
|
|
1082
|
-
}
|
|
1083
1201
|
|
|
1084
1202
|
for (const [relName, childTree] of entries) {
|
|
1085
1203
|
const childKeys = Object.keys(childTree || {})
|
|
@@ -1095,26 +1213,30 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1095
1213
|
const targetDescriptor = await registry.getDescriptor(tenantId, belongsToInfo.target)
|
|
1096
1214
|
if (!targetDescriptor) continue
|
|
1097
1215
|
|
|
1098
|
-
|
|
1216
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1099
1217
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1100
1218
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1101
1219
|
.whereIn('id', ids)
|
|
1220
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1221
|
+
query,
|
|
1222
|
+
resourceName: targetDescriptor.resource,
|
|
1223
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1224
|
+
context
|
|
1225
|
+
})
|
|
1226
|
+
query = selectionState.query
|
|
1227
|
+
const rows = await query
|
|
1102
1228
|
|
|
1103
1229
|
const newResources = []
|
|
1104
1230
|
for (const row of rows) {
|
|
1105
1231
|
const includeKey = `${targetDescriptor.resource}:${row.id}`
|
|
1106
1232
|
if (seen.has(includeKey)) continue
|
|
1107
1233
|
seen.add(includeKey)
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1115
|
-
includeResource.relationships = translated.relationships
|
|
1116
|
-
}
|
|
1117
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1234
|
+
const includeResource = buildIncludedResource({
|
|
1235
|
+
row,
|
|
1236
|
+
descriptor: targetDescriptor,
|
|
1237
|
+
context,
|
|
1238
|
+
fieldSelectionInfo: selectionState.fieldSelectionInfo
|
|
1239
|
+
})
|
|
1118
1240
|
includes.push(includeResource)
|
|
1119
1241
|
newResources.push(includeResource)
|
|
1120
1242
|
}
|
|
@@ -1159,25 +1281,29 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1159
1281
|
continue
|
|
1160
1282
|
}
|
|
1161
1283
|
|
|
1162
|
-
|
|
1284
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1163
1285
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1164
1286
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1165
1287
|
.whereIn('id', [...idSet])
|
|
1288
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1289
|
+
query,
|
|
1290
|
+
resourceName: targetDescriptor.resource,
|
|
1291
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1292
|
+
context
|
|
1293
|
+
})
|
|
1294
|
+
query = selectionState.query
|
|
1295
|
+
const rows = await query
|
|
1166
1296
|
|
|
1167
1297
|
for (const row of rows) {
|
|
1168
1298
|
const includeKey = `${targetDescriptor.resource}:${row.id}`
|
|
1169
1299
|
if (seen.has(includeKey)) continue
|
|
1170
1300
|
seen.add(includeKey)
|
|
1171
|
-
const
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
}
|
|
1177
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1178
|
-
includeResource.relationships = translated.relationships
|
|
1179
|
-
}
|
|
1180
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1301
|
+
const includeResource = buildIncludedResource({
|
|
1302
|
+
row,
|
|
1303
|
+
descriptor: targetDescriptor,
|
|
1304
|
+
context,
|
|
1305
|
+
fieldSelectionInfo: selectionState.fieldSelectionInfo
|
|
1306
|
+
})
|
|
1181
1307
|
includes.push(includeResource)
|
|
1182
1308
|
newResources.push(includeResource)
|
|
1183
1309
|
}
|
|
@@ -1243,6 +1369,7 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1243
1369
|
|
|
1244
1370
|
let rows = []
|
|
1245
1371
|
let groupingColumn = null
|
|
1372
|
+
let fieldSelectionInfo = null
|
|
1246
1373
|
|
|
1247
1374
|
if (hasManyInfo.foreignKey) {
|
|
1248
1375
|
const foreignField = targetDescriptor.fields?.[hasManyInfo.foreignKey]
|
|
@@ -1250,21 +1377,53 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1250
1377
|
continue
|
|
1251
1378
|
}
|
|
1252
1379
|
groupingColumn = foreignField.slot
|
|
1253
|
-
|
|
1380
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1254
1381
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1255
1382
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1256
1383
|
.whereIn(groupingColumn, parentIds)
|
|
1384
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1385
|
+
query,
|
|
1386
|
+
resourceName: targetDescriptor.resource,
|
|
1387
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1388
|
+
context
|
|
1389
|
+
})
|
|
1390
|
+
query = selectionState.query
|
|
1391
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1392
|
+
query,
|
|
1393
|
+
resourceName: targetDescriptor.resource,
|
|
1394
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1395
|
+
context
|
|
1396
|
+
})
|
|
1397
|
+
query = scopedQueryState.query
|
|
1398
|
+
fieldSelectionInfo = selectionState.fieldSelectionInfo
|
|
1399
|
+
rows = await query
|
|
1257
1400
|
} else if (hasManyInfo.via) {
|
|
1258
1401
|
const polyInfo = targetDescriptor.polymorphicBelongsTo?.[hasManyInfo.via]
|
|
1259
1402
|
if (!polyInfo?.idColumn || !polyInfo?.typeColumn) {
|
|
1260
1403
|
continue
|
|
1261
1404
|
}
|
|
1262
1405
|
groupingColumn = polyInfo.idColumn
|
|
1263
|
-
|
|
1406
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1264
1407
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1265
1408
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1266
1409
|
.where(polyInfo.typeColumn, descriptor.resource)
|
|
1267
1410
|
.whereIn(groupingColumn, queryIds)
|
|
1411
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1412
|
+
query,
|
|
1413
|
+
resourceName: targetDescriptor.resource,
|
|
1414
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1415
|
+
context
|
|
1416
|
+
})
|
|
1417
|
+
query = selectionState.query
|
|
1418
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1419
|
+
query,
|
|
1420
|
+
resourceName: targetDescriptor.resource,
|
|
1421
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1422
|
+
context
|
|
1423
|
+
})
|
|
1424
|
+
query = scopedQueryState.query
|
|
1425
|
+
fieldSelectionInfo = selectionState.fieldSelectionInfo
|
|
1426
|
+
rows = await query
|
|
1268
1427
|
}
|
|
1269
1428
|
|
|
1270
1429
|
if (!groupingColumn || rows.length === 0) {
|
|
@@ -1276,16 +1435,12 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1276
1435
|
const includeKey = `${targetDescriptor.resource}:${row.id}`
|
|
1277
1436
|
if (seen.has(includeKey)) continue
|
|
1278
1437
|
seen.add(includeKey)
|
|
1279
|
-
const
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
}
|
|
1285
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1286
|
-
includeResource.relationships = translated.relationships
|
|
1287
|
-
}
|
|
1288
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1438
|
+
const includeResource = buildIncludedResource({
|
|
1439
|
+
row,
|
|
1440
|
+
descriptor: targetDescriptor,
|
|
1441
|
+
context,
|
|
1442
|
+
fieldSelectionInfo
|
|
1443
|
+
})
|
|
1289
1444
|
includes.push(includeResource)
|
|
1290
1445
|
newResources.push(includeResource)
|
|
1291
1446
|
}
|
|
@@ -1323,26 +1478,30 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1323
1478
|
const targetDescriptor = await registry.getDescriptor(tenantId, info.relInfo.target)
|
|
1324
1479
|
if (!targetDescriptor) continue
|
|
1325
1480
|
|
|
1326
|
-
|
|
1481
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1327
1482
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1328
1483
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1329
1484
|
.whereIn('id', childIds)
|
|
1485
|
+
const selectionState = await applyIncludeFieldSelection({
|
|
1486
|
+
query,
|
|
1487
|
+
resourceName: targetDescriptor.resource,
|
|
1488
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1489
|
+
context
|
|
1490
|
+
})
|
|
1491
|
+
query = selectionState.query
|
|
1492
|
+
const rows = await query
|
|
1330
1493
|
|
|
1331
1494
|
const newResources = []
|
|
1332
1495
|
for (const row of rows) {
|
|
1333
1496
|
const includeKey = `${targetDescriptor.resource}:${row.id}`
|
|
1334
1497
|
if (seen.has(includeKey)) continue
|
|
1335
1498
|
seen.add(includeKey)
|
|
1336
|
-
const
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
}
|
|
1342
|
-
if (translated.relationships && Object.keys(translated.relationships).length > 0) {
|
|
1343
|
-
includeResource.relationships = translated.relationships
|
|
1344
|
-
}
|
|
1345
|
-
addSelfLink(includeResource, targetDescriptor.resource)
|
|
1499
|
+
const includeResource = buildIncludedResource({
|
|
1500
|
+
row,
|
|
1501
|
+
descriptor: targetDescriptor,
|
|
1502
|
+
context,
|
|
1503
|
+
fieldSelectionInfo: selectionState.fieldSelectionInfo
|
|
1504
|
+
})
|
|
1346
1505
|
includes.push(includeResource)
|
|
1347
1506
|
newResources.push(includeResource)
|
|
1348
1507
|
}
|
|
@@ -1410,10 +1569,18 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1410
1569
|
if (!foreignField?.slot) continue
|
|
1411
1570
|
const column = foreignField.slot
|
|
1412
1571
|
|
|
1413
|
-
|
|
1572
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1414
1573
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1415
1574
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1416
1575
|
.whereIn(column, normalizedParentIds)
|
|
1576
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1577
|
+
query,
|
|
1578
|
+
resourceName: targetDescriptor.resource,
|
|
1579
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1580
|
+
context
|
|
1581
|
+
})
|
|
1582
|
+
query = scopedQueryState.query
|
|
1583
|
+
const rows = await query
|
|
1417
1584
|
|
|
1418
1585
|
const grouped = rows.reduce((acc, row) => {
|
|
1419
1586
|
const parentId = row[column]
|
|
@@ -1447,11 +1614,19 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1447
1614
|
.filter((value) => value !== null),
|
|
1448
1615
|
]))
|
|
1449
1616
|
|
|
1450
|
-
|
|
1617
|
+
let query = db(targetDescriptor.canonical.tableName)
|
|
1451
1618
|
.where(targetDescriptor.canonical.tenantColumn, targetDescriptor.tenant)
|
|
1452
1619
|
.where(targetDescriptor.canonical.resourceColumn, targetDescriptor.resource)
|
|
1453
1620
|
.where(polyInfo.typeColumn, descriptor.resource)
|
|
1454
1621
|
.whereIn(polyInfo.idColumn, queryIds)
|
|
1622
|
+
const scopedQueryState = await applyTargetScopeFilters({
|
|
1623
|
+
query,
|
|
1624
|
+
resourceName: targetDescriptor.resource,
|
|
1625
|
+
tableName: targetDescriptor.canonical.tableName,
|
|
1626
|
+
context
|
|
1627
|
+
})
|
|
1628
|
+
query = scopedQueryState.query
|
|
1629
|
+
const rows = await query
|
|
1455
1630
|
|
|
1456
1631
|
const grouped = rows.reduce((acc, row) => {
|
|
1457
1632
|
const parentId = row[polyInfo.idColumn]
|
|
@@ -1522,15 +1697,35 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1522
1697
|
context.computedDependencies = []
|
|
1523
1698
|
}
|
|
1524
1699
|
|
|
1525
|
-
|
|
1700
|
+
let query = context.db(canonical.tableName)
|
|
1526
1701
|
.where('id', id)
|
|
1527
1702
|
.where(canonical.resourceColumn, descriptor.resource)
|
|
1528
1703
|
.where(canonical.tenantColumn, descriptor.tenant)
|
|
1529
|
-
|
|
1704
|
+
|
|
1705
|
+
if (scope && fieldSelectionInfo) {
|
|
1706
|
+
const selectionState = await applyFieldSelectionToQuery({
|
|
1707
|
+
query,
|
|
1708
|
+
scope,
|
|
1709
|
+
fieldSelectionInfo,
|
|
1710
|
+
tableName: canonical.tableName,
|
|
1711
|
+
useTablePrefix: false,
|
|
1712
|
+
storageAdapter,
|
|
1713
|
+
db: context.db,
|
|
1714
|
+
context,
|
|
1715
|
+
scopeName
|
|
1716
|
+
})
|
|
1717
|
+
query = selectionState.query
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
const row = await query.first()
|
|
1530
1721
|
|
|
1531
1722
|
if (!row) return null
|
|
1532
1723
|
|
|
1533
|
-
const record =
|
|
1724
|
+
const record = translateCanonicalRecordFromStorage(
|
|
1725
|
+
row,
|
|
1726
|
+
descriptor,
|
|
1727
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
1728
|
+
)
|
|
1534
1729
|
const data = {
|
|
1535
1730
|
type: descriptor.resource,
|
|
1536
1731
|
id: String(row.id),
|
|
@@ -1592,10 +1787,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1592
1787
|
const rehydrateSchemaInfo = async (scopeName) => {
|
|
1593
1788
|
const scope = api.resources?.[scopeName] || scopes?.[scopeName] || {}
|
|
1594
1789
|
const storedOptions = scopeOptionsRegistry.get(scopeName) || {}
|
|
1790
|
+
const rawSchema = scope.scopeOptions?.schema || storedOptions.schema || {}
|
|
1595
1791
|
|
|
1596
1792
|
if (!scope.scopeOptions) {
|
|
1597
1793
|
scope.scopeOptions = {
|
|
1598
|
-
schema:
|
|
1794
|
+
schema: rawSchema,
|
|
1599
1795
|
relationships: storedOptions.relationships || {},
|
|
1600
1796
|
searchSchema: storedOptions.searchSchema || null,
|
|
1601
1797
|
tableName: storedOptions.tableName || scopeName,
|
|
@@ -1611,7 +1807,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1611
1807
|
|
|
1612
1808
|
const mergedSchema = { ...(existingInfo.schemaInstance?.structure || {}) }
|
|
1613
1809
|
const computed = { ...(existingInfo.computed || {}) }
|
|
1614
|
-
|
|
1615
1810
|
for (const [fieldName, definition] of Object.entries(descriptor.schema || {})) {
|
|
1616
1811
|
if (definition?.computed) {
|
|
1617
1812
|
if (!computed[fieldName]) {
|
|
@@ -1699,26 +1894,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1699
1894
|
return { resourceToTableName, tableNameToResource }
|
|
1700
1895
|
}
|
|
1701
1896
|
|
|
1702
|
-
const translateSelectFieldsForAdapter = (fields, adapter) => {
|
|
1703
|
-
if (!adapter || !fields) return fields
|
|
1704
|
-
|
|
1705
|
-
return fields.map((field) => {
|
|
1706
|
-
if (typeof field !== 'string') return field
|
|
1707
|
-
|
|
1708
|
-
if (field === '*') return '*'
|
|
1709
|
-
|
|
1710
|
-
const aliasMatch = field.match(/\s+as\s+/i)
|
|
1711
|
-
if (aliasMatch) {
|
|
1712
|
-
const [source, alias] = field.split(/\s+as\s+/i)
|
|
1713
|
-
const translatedSource = adapter.translateColumn(source.trim()) || source.trim()
|
|
1714
|
-
return `${translatedSource} as ${alias.trim()}`
|
|
1715
|
-
}
|
|
1716
|
-
|
|
1717
|
-
const translated = adapter.translateColumn(field)
|
|
1718
|
-
return translated || field
|
|
1719
|
-
})
|
|
1720
|
-
}
|
|
1721
|
-
|
|
1722
1897
|
const scopesProxy = new Proxy({}, {
|
|
1723
1898
|
get (_target, prop) {
|
|
1724
1899
|
if (typeof prop !== 'string') {
|
|
@@ -1809,11 +1984,26 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1809
1984
|
|
|
1810
1985
|
const countQuery = queryBuilder.clone()
|
|
1811
1986
|
|
|
1987
|
+
const selectionState = scope && fieldSelectionInfo
|
|
1988
|
+
? await applyFieldSelectionToQuery({
|
|
1989
|
+
query: queryBuilder,
|
|
1990
|
+
scope,
|
|
1991
|
+
fieldSelectionInfo,
|
|
1992
|
+
tableName: adapter.tableAlias,
|
|
1993
|
+
useTablePrefix: false,
|
|
1994
|
+
storageAdapter,
|
|
1995
|
+
db,
|
|
1996
|
+
context,
|
|
1997
|
+
scopeName
|
|
1998
|
+
})
|
|
1999
|
+
: { query: queryBuilder, queryFieldRuntimeByField: new Map() }
|
|
2000
|
+
|
|
1812
2001
|
const sortDescriptors = applySortingToQuery({
|
|
1813
|
-
query:
|
|
2002
|
+
query: selectionState.query,
|
|
1814
2003
|
sort: queryParams.sort,
|
|
1815
2004
|
descriptor,
|
|
1816
2005
|
scope,
|
|
2006
|
+
queryFieldRuntimeByField: selectionState.queryFieldRuntimeByField
|
|
1817
2007
|
})
|
|
1818
2008
|
|
|
1819
2009
|
if (queryParams?.page?.after) {
|
|
@@ -1827,14 +2017,6 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1827
2017
|
adapter,
|
|
1828
2018
|
})
|
|
1829
2019
|
|
|
1830
|
-
if (queryParams?.page?.after) {
|
|
1831
|
-
}
|
|
1832
|
-
|
|
1833
|
-
if (fieldSelectionInfo && fieldSelectionInfo.fieldsToSelect && fieldSelectionInfo.fieldsToSelect.length > 0) {
|
|
1834
|
-
const translatedSelect = translateSelectFieldsForAdapter(fieldSelectionInfo.fieldsToSelect, adapter)
|
|
1835
|
-
queryBuilder.select(translatedSelect)
|
|
1836
|
-
}
|
|
1837
|
-
|
|
1838
2020
|
let rows = await queryBuilder
|
|
1839
2021
|
let cursorRecords = null
|
|
1840
2022
|
let hasMore = false
|
|
@@ -1846,6 +2028,8 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1846
2028
|
for (const descriptorEntry of cursorDescriptors) {
|
|
1847
2029
|
if (descriptorEntry.field === 'id') {
|
|
1848
2030
|
record.id = row.id
|
|
2031
|
+
} else if (descriptorEntry.queryFieldRuntime) {
|
|
2032
|
+
record[descriptorEntry.field] = row[descriptorEntry.field]
|
|
1849
2033
|
} else if (descriptorEntry.column) {
|
|
1850
2034
|
record[descriptorEntry.field] = row[descriptorEntry.column]
|
|
1851
2035
|
}
|
|
@@ -1861,7 +2045,11 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
1861
2045
|
}
|
|
1862
2046
|
|
|
1863
2047
|
const data = rows.map((row) => {
|
|
1864
|
-
const translated =
|
|
2048
|
+
const translated = translateCanonicalRecordFromStorage(
|
|
2049
|
+
row,
|
|
2050
|
+
descriptor,
|
|
2051
|
+
{ allowedExtraFields: getAllowedQueryFieldNames(scopeName) }
|
|
2052
|
+
)
|
|
1865
2053
|
const resource = {
|
|
1866
2054
|
type: descriptor.resource,
|
|
1867
2055
|
id: String(row.id),
|
|
@@ -2194,64 +2382,3 @@ export const RestApiAnyapiKnexPlugin = {
|
|
|
2194
2382
|
})
|
|
2195
2383
|
},
|
|
2196
2384
|
}
|
|
2197
|
-
|
|
2198
|
-
const translateAttributesForStorage = (attributes, descriptor) => {
|
|
2199
|
-
const row = {}
|
|
2200
|
-
for (const [fieldName, value] of Object.entries(attributes)) {
|
|
2201
|
-
const slot = descriptor.fields[fieldName]
|
|
2202
|
-
if (!slot) continue
|
|
2203
|
-
|
|
2204
|
-
if (slot.slotType === 'belongsTo') {
|
|
2205
|
-
row[slot.slot] = value == null ? null : String(value)
|
|
2206
|
-
} else {
|
|
2207
|
-
row[slot.slot] = value
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
if (slot.slotType === 'belongsTo') {
|
|
2211
|
-
const alias = slot.alias || descriptor.fields[fieldName]?.alias
|
|
2212
|
-
const belongsToInfo = alias ? descriptor.belongsTo?.[alias] : null
|
|
2213
|
-
if (belongsToInfo) {
|
|
2214
|
-
row[belongsToInfo.typeColumn] = value == null ? null : belongsToInfo.target
|
|
2215
|
-
}
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
return row
|
|
2219
|
-
}
|
|
2220
|
-
|
|
2221
|
-
const translateRecordFromStorage = (row, descriptor) => {
|
|
2222
|
-
const attributes = {}
|
|
2223
|
-
for (const [slot, logical] of Object.entries(descriptor.reverseAttributes)) {
|
|
2224
|
-
if (slot in row) {
|
|
2225
|
-
attributes[logical] = row[slot]
|
|
2226
|
-
}
|
|
2227
|
-
}
|
|
2228
|
-
|
|
2229
|
-
const relationships = {}
|
|
2230
|
-
for (const [alias, info] of Object.entries(descriptor.belongsTo || {})) {
|
|
2231
|
-
const idValue = row[info.idColumn]
|
|
2232
|
-
const relationshipData = idValue == null
|
|
2233
|
-
? null
|
|
2234
|
-
: { type: info.target, id: String(idValue) }
|
|
2235
|
-
|
|
2236
|
-
relationships[alias] = { data: relationshipData }
|
|
2237
|
-
}
|
|
2238
|
-
|
|
2239
|
-
for (const [alias, info] of Object.entries(descriptor.polymorphicBelongsTo || {})) {
|
|
2240
|
-
const typeValue = info.typeColumn ? row[info.typeColumn] : null
|
|
2241
|
-
const idValue = info.idColumn ? row[info.idColumn] : null
|
|
2242
|
-
|
|
2243
|
-
let relationshipData = null
|
|
2244
|
-
if (typeValue != null && idValue != null) {
|
|
2245
|
-
relationshipData = {
|
|
2246
|
-
type: String(typeValue),
|
|
2247
|
-
id: String(idValue),
|
|
2248
|
-
}
|
|
2249
|
-
} else if (typeValue == null && idValue == null) {
|
|
2250
|
-
relationshipData = null
|
|
2251
|
-
}
|
|
2252
|
-
|
|
2253
|
-
relationships[alias] = { data: relationshipData }
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
return { attributes, relationships }
|
|
2257
|
-
}
|