json-rest-api 1.0.26 → 1.0.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/docs/API.md +5 -0
  2. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +2 -2
  3. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +2 -2
  4. package/docs/GUIDE/GUIDE_Y_Appendices.md +42 -29
  5. package/index.js +2 -0
  6. package/lib/error-context.js +21 -0
  7. package/lib/rest-api-errors.js +70 -0
  8. package/package.json +2 -2
  9. package/plugins/core/connectors/lib/transport-http-helpers.js +21 -0
  10. package/plugins/core/lib/anyapi/schema-utils.js +2 -1
  11. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -2
  12. package/plugins/core/lib/dbTablesOperations.js +20 -10
  13. package/plugins/core/lib/querying/knex-pagination-helpers.js +11 -1
  14. package/plugins/core/lib/querying/knex-process-includes.js +11 -5
  15. package/plugins/core/lib/querying/knex-relationship-includes.js +63 -46
  16. package/plugins/core/lib/querying-writing/database-value-normalizers.js +231 -143
  17. package/plugins/core/lib/querying-writing/knex-field-helpers.js +7 -1
  18. package/plugins/core/lib/storage/canonical-storage-mapping.js +11 -1
  19. package/plugins/core/lib/storage/storage-adapter.js +4 -2
  20. package/plugins/core/lib/storage/storage-mapping.js +7 -1
  21. package/plugins/core/rest-api-plugin-methods/common.js +5 -0
  22. package/plugins/core/rest-api-plugin-methods/get.js +6 -0
  23. package/plugins/core/rest-api-plugin-methods/query.js +6 -0
  24. package/tests/computed-fields.test.js +90 -2
  25. package/tests/db-table-operations.test.js +13 -1
  26. package/tests/fixtures/api-configs.js +83 -3
  27. package/tests/includes.test.js +49 -0
  28. package/tests/pagination-cursor-multifield.test.js +4 -0
  29. package/tests/relationship-include-storage-adapter.test.js +56 -0
  30. package/tests/storage-mapping.test.js +112 -10
@@ -84,14 +84,26 @@ import {
84
84
  import { unwrapQueryBuilderState } from './query-builder-utils.js'
85
85
  import { RELATIONSHIPS_KEY, RELATIONSHIP_METADATA_KEY, ROW_NUMBER_KEY, COMPUTED_DEPENDENCIES_KEY, DEFAULT_QUERY_LIMIT } from '../querying-writing/knex-constants.js'
86
86
  import { RestApiResourceError } from '../../../../lib/rest-api-errors.js'
87
+ import { isRestApiError, wrapUnexpectedError } from '../../../../lib/error-context.js'
87
88
  import {
88
89
  getFieldValue as getStorageFieldValue,
89
90
  getIdColumn as getStorageIdColumn,
90
91
  getStorageColumn as getMappedStorageColumn,
91
92
  } from '../storage/storage-mapping.js'
93
+ import { createStorageAdapter } from '../storage/storage-adapter.js'
92
94
 
93
95
  const getScopeStorageAdapter = (scopes, scopeName) => scopes[scopeName]?.vars?.storageAdapter || null
94
96
 
97
+ const resolveScopeStorageAdapter = (scopes, scopeName, knex) => {
98
+ const configuredAdapter = getScopeStorageAdapter(scopes, scopeName)
99
+ if (configuredAdapter) return configuredAdapter
100
+
101
+ const schemaInfo = scopes[scopeName]?.vars?.schemaInfo
102
+ if (!schemaInfo) return null
103
+
104
+ return createStorageAdapter({ knex, schemaInfo })
105
+ }
106
+
95
107
  const getIdColumnForScope = (scopes, scopeName) => {
96
108
  const adapter = getScopeStorageAdapter(scopes, scopeName)
97
109
  if (adapter?.getIdColumn) {
@@ -161,7 +173,7 @@ const applyScopeFiltersToIncludeQuery = async ({
161
173
  return { query }
162
174
  }
163
175
 
164
- const storageAdapter = scopeObject.vars.storageAdapter
176
+ const storageAdapter = resolveScopeStorageAdapter(scopes, scopeName, db)
165
177
 
166
178
  const queryState = await scopeObject.applyQueryFilters({
167
179
  query,
@@ -415,14 +427,18 @@ const loadRelationshipMetadata = async (scopes, records, scopeName) => {
415
427
  }
416
428
  })
417
429
  } catch (error) {
418
- // Log error with context and re-throw
430
+ if (isRestApiError(error)) throw error
431
+
419
432
  const errorContext = {
420
433
  scopeName,
421
434
  recordCount: records?.length || 0,
422
435
  error: error.message
423
436
  }
424
437
  console.error('[loadRelationshipMetadata] Error loading relationship metadata:', errorContext)
425
- throw new Error(`Failed to load relationship metadata for scope '${scopeName}': ${error.message}`)
438
+ throw wrapUnexpectedError(error, {
439
+ message: `Failed to load relationship metadata for scope '${scopeName}'`,
440
+ context: errorContext
441
+ })
426
442
  }
427
443
  }
428
444
 
@@ -498,7 +514,7 @@ export const loadBelongsTo = async (scope, deps) => {
498
514
  const targetIdColumn = getIdColumnForScope(scopes, targetScope)
499
515
  // Build field selection for sparse fieldsets
500
516
  const targetScopeObject = scopes[targetScope]
501
- const storageAdapter = targetScopeObject.vars.storageAdapter
517
+ const storageAdapter = resolveScopeStorageAdapter(scopes, targetScope, knex)
502
518
  const fieldSelectionInfo = fields?.[targetScope]
503
519
  ? await buildFieldSelection(targetScopeObject, {
504
520
  context: {
@@ -626,6 +642,8 @@ export const loadBelongsTo = async (scope, deps) => {
626
642
  }
627
643
  }
628
644
  } catch (error) {
645
+ if (isRestApiError(error)) throw error
646
+
629
647
  // Log error with detailed context
630
648
  log.error('[INCLUDE] Error loading belongsTo relationship:', {
631
649
  fieldName,
@@ -636,13 +654,10 @@ export const loadBelongsTo = async (scope, deps) => {
636
654
  stack: error.stack
637
655
  })
638
656
 
639
- // Re-throw with enhanced error message
640
- const enhancedError = new Error(
641
- `Failed to load belongsTo relationship '${includeName}' for field '${fieldName}': ${error.message}`
642
- )
643
- enhancedError.originalError = error
644
- enhancedError.context = { fieldName, includeName, targetScope: fieldDef.belongsTo }
645
- throw enhancedError
657
+ throw wrapUnexpectedError(error, {
658
+ message: `Failed to load belongsTo relationship '${includeName}' for field '${fieldName}'`,
659
+ context: { fieldName, includeName, targetScope: fieldDef.belongsTo }
660
+ })
646
661
  }
647
662
  }
648
663
 
@@ -741,7 +756,7 @@ export const loadHasMany = async (scope, deps) => {
741
756
 
742
757
  // Step 3: Build field selection for sparse fieldsets
743
758
  const targetScopeObject = scopes[targetScope]
744
- const storageAdapter = targetScopeObject.vars.storageAdapter
759
+ const storageAdapter = resolveScopeStorageAdapter(scopes, targetScope, knex)
745
760
  const targetIdColumn = getIdColumnForScope(scopes, targetScope)
746
761
  const fieldSelectionInfo = fields?.[targetScope]
747
762
  ? await buildFieldSelection(targetScopeObject, {
@@ -970,7 +985,7 @@ export const loadHasMany = async (scope, deps) => {
970
985
 
971
986
  // Build field selection for sparse fieldsets
972
987
  const targetScopeObject = scopes[targetScope]
973
- const storageAdapter = targetScopeObject.vars.storageAdapter
988
+ const storageAdapter = resolveScopeStorageAdapter(scopes, targetScope, knex)
974
989
  const fieldSelectionInfo = fields?.[targetScope]
975
990
  ? await buildFieldSelection(targetScopeObject, {
976
991
  context: {
@@ -1159,6 +1174,8 @@ export const loadHasMany = async (scope, deps) => {
1159
1174
  }
1160
1175
  }
1161
1176
  } catch (error) {
1177
+ if (isRestApiError(error)) throw error
1178
+
1162
1179
  // Log error with detailed context
1163
1180
  log.error('[INCLUDE] Error loading hasMany relationship:', {
1164
1181
  scopeName,
@@ -1169,13 +1186,10 @@ export const loadHasMany = async (scope, deps) => {
1169
1186
  stack: error.stack
1170
1187
  })
1171
1188
 
1172
- // Re-throw with enhanced error message
1173
- const enhancedError = new Error(
1174
- `Failed to load hasMany relationship '${includeName}' for scope '${scopeName}': ${error.message}`
1175
- )
1176
- enhancedError.originalError = error
1177
- enhancedError.context = { scopeName, includeName, hasThrough: !!relDef.through }
1178
- throw enhancedError
1189
+ throw wrapUnexpectedError(error, {
1190
+ message: `Failed to load hasMany relationship '${includeName}' for scope '${scopeName}'`,
1191
+ context: { scopeName, includeName, hasThrough: !!relDef.through }
1192
+ })
1179
1193
  }
1180
1194
  }
1181
1195
 
@@ -1221,7 +1235,7 @@ export const loadHasOne = async (scope, deps) => {
1221
1235
 
1222
1236
  // Build query for hasOne - expects single result per parent
1223
1237
  const targetScopeObject = scopes[targetScope]
1224
- const storageAdapter = targetScopeObject.vars.storageAdapter
1238
+ const storageAdapter = resolveScopeStorageAdapter(scopes, targetScope, knex)
1225
1239
 
1226
1240
  // Build field selection for sparse fieldsets
1227
1241
  const fieldSelectionInfo = fields?.[targetScope]
@@ -1393,7 +1407,7 @@ export const loadPolymorphicBelongsTo = async (scope, deps) => {
1393
1407
 
1394
1408
  // Build field selection for sparse fieldsets
1395
1409
  const targetScopeObject = scopes[targetType]
1396
- const storageAdapter = targetScopeObject.vars.storageAdapter
1410
+ const storageAdapter = resolveScopeStorageAdapter(scopes, targetType, knex)
1397
1411
  const fieldSelectionInfo = fields?.[targetType]
1398
1412
  ? await buildFieldSelection(targetScopeObject, {
1399
1413
  context: {
@@ -1539,6 +1553,8 @@ export const loadPolymorphicBelongsTo = async (scope, deps) => {
1539
1553
  }
1540
1554
  })
1541
1555
  } catch (error) {
1556
+ if (isRestApiError(error)) throw error
1557
+
1542
1558
  // Log error with detailed context
1543
1559
  log.error('[INCLUDE] Error loading polymorphic belongsTo relationship:', {
1544
1560
  relName,
@@ -1548,13 +1564,10 @@ export const loadPolymorphicBelongsTo = async (scope, deps) => {
1548
1564
  stack: error.stack
1549
1565
  })
1550
1566
 
1551
- // Re-throw with enhanced error message
1552
- const enhancedError = new Error(
1553
- `Failed to load polymorphic belongsTo relationship '${relName}': ${error.message}`
1554
- )
1555
- enhancedError.originalError = error
1556
- enhancedError.context = { relName, types: relDef?.belongsToPolymorphic?.types }
1557
- throw enhancedError
1567
+ throw wrapUnexpectedError(error, {
1568
+ message: `Failed to load polymorphic belongsTo relationship '${relName}'`,
1569
+ context: { relName, types: relDef?.belongsToPolymorphic?.types }
1570
+ })
1558
1571
  }
1559
1572
  }
1560
1573
 
@@ -1622,7 +1635,7 @@ export const loadReversePolymorphic = async (scope, deps) => {
1622
1635
 
1623
1636
  // Build field selection for sparse fieldsets
1624
1637
  const targetScopeObject = scopes[targetScope]
1625
- const storageAdapter = targetScopeObject.vars.storageAdapter
1638
+ const storageAdapter = resolveScopeStorageAdapter(scopes, targetScope, knex)
1626
1639
  const targetIdColumn = getIdColumnForScope(scopes, targetScope)
1627
1640
  const fieldSelectionInfo = fields?.[targetScope]
1628
1641
  ? await buildFieldSelection(targetScopeObject, {
@@ -1759,6 +1772,8 @@ export const loadReversePolymorphic = async (scope, deps) => {
1759
1772
  }
1760
1773
  }
1761
1774
  } catch (error) {
1775
+ if (isRestApiError(error)) throw error
1776
+
1762
1777
  // Log error with detailed context
1763
1778
  log.error('[INCLUDE] Error loading reverse polymorphic relationship:', {
1764
1779
  scopeName,
@@ -1770,13 +1785,10 @@ export const loadReversePolymorphic = async (scope, deps) => {
1770
1785
  stack: error.stack
1771
1786
  })
1772
1787
 
1773
- // Re-throw with enhanced error message
1774
- const enhancedError = new Error(
1775
- `Failed to load reverse polymorphic relationship '${includeName}' via '${relDef?.via}' for scope '${scopeName}': ${error.message}`
1776
- )
1777
- enhancedError.originalError = error
1778
- enhancedError.context = { scopeName, includeName, via: relDef?.via, targetScope: relDef?.target }
1779
- throw enhancedError
1788
+ throw wrapUnexpectedError(error, {
1789
+ message: `Failed to load reverse polymorphic relationship '${includeName}' via '${relDef?.via}' for scope '${scopeName}'`,
1790
+ context: { scopeName, includeName, via: relDef?.via, targetScope: relDef?.target }
1791
+ })
1780
1792
  }
1781
1793
  }
1782
1794
 
@@ -1897,6 +1909,8 @@ export const processIncludes = async (scope, deps) => {
1897
1909
  }
1898
1910
  }
1899
1911
  } catch (includeError) {
1912
+ if (isRestApiError(includeError)) throw includeError
1913
+
1900
1914
  // Log specific include error and continue with other includes
1901
1915
  log.error('[INCLUDE] Error processing include:', {
1902
1916
  scopeName,
@@ -1918,6 +1932,8 @@ export const processIncludes = async (scope, deps) => {
1918
1932
  }
1919
1933
  }
1920
1934
  } catch (error) {
1935
+ if (isRestApiError(error)) throw error
1936
+
1921
1937
  // Log error with full context
1922
1938
  log.error('[INCLUDE] Error in processIncludes:', {
1923
1939
  scopeName,
@@ -2019,6 +2035,8 @@ export const buildIncludedResources = async (scope, deps) => {
2019
2035
  recordsWithRelationships: records
2020
2036
  }
2021
2037
  } catch (error) {
2038
+ if (isRestApiError(error)) throw error
2039
+
2022
2040
  // Log comprehensive error information
2023
2041
  log.error('[INCLUDE] Failed to build included resources:', {
2024
2042
  scopeName,
@@ -2028,15 +2046,14 @@ export const buildIncludedResources = async (scope, deps) => {
2028
2046
  stack: error.stack
2029
2047
  })
2030
2048
 
2031
- // Re-throw with additional context
2032
- const enhancedError = new Error(`Failed to build included resources for scope '${scopeName}': ${error.message}`)
2033
- enhancedError.originalError = error
2034
- enhancedError.context = {
2035
- scopeName,
2036
- includeParam,
2037
- recordCount: records?.length || 0
2038
- }
2039
- throw enhancedError
2049
+ throw wrapUnexpectedError(error, {
2050
+ message: `Failed to build included resources for scope '${scopeName}'`,
2051
+ context: {
2052
+ scopeName,
2053
+ includeParam,
2054
+ recordCount: records?.length || 0
2055
+ }
2056
+ })
2040
2057
  }
2041
2058
  }
2042
2059