json-rest-api 1.0.14 → 1.0.16

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 (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +99 -99
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +11 -15
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,32 +1,31 @@
1
1
  /**
2
2
  * @module knex-constants
3
3
  * @description Shared constants for the REST API Knex plugin
4
- *
4
+ *
5
5
  * This module contains all the magic strings and shared constants used
6
6
  * throughout the plugin to avoid repetition and improve maintainability.
7
7
  */
8
8
 
9
9
  // Internal property names - using unique names to avoid collisions with user data
10
- export const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__';
11
- export const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__';
12
- export const ROW_NUMBER_KEY = '__$jsonrestapi_rn$__';
13
- export const COMPUTED_DEPENDENCIES_KEY = '__$jsonrestapi_computed_deps$__';
10
+ export const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__'
11
+ export const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__'
12
+ export const ROW_NUMBER_KEY = '__$jsonrestapi_rn$__'
13
+ export const COMPUTED_DEPENDENCIES_KEY = '__$jsonrestapi_computed_deps$__'
14
14
 
15
15
  // Default query limits
16
- export const DEFAULT_QUERY_LIMIT = 20;
17
- export const DEFAULT_MAX_QUERY_LIMIT = 100;
18
- export const DEFAULT_MAX_INCLUDE_LIMIT = 1000;
16
+ export const DEFAULT_QUERY_LIMIT = 20
17
+ export const DEFAULT_MAX_QUERY_LIMIT = 100
18
+ export const DEFAULT_MAX_INCLUDE_LIMIT = 1000
19
19
  export const DEFAULT_INCLUDE_DEPTH_LIMIT = 3
20
20
 
21
-
22
21
  // Schema handling helpers
23
- export const getSchemaStructure = (schema) => schema?.structure || schema || {};
24
- export const getIdProperty = (schemaInfo) => schemaInfo?.idProperty || 'id';
22
+ export const getSchemaStructure = (schema) => schema?.structure || schema || {}
23
+ export const getIdProperty = (schemaInfo) => schemaInfo?.idProperty || 'id'
25
24
 
26
25
  // Default values
27
- export const DEFAULT_PAGE_SIZE = 20;
28
- export const DEFAULT_MAX_PAGE_SIZE = 100;
29
- export const DEFAULT_PAGE_NUMBER = 1;
26
+ export const DEFAULT_PAGE_SIZE = 20
27
+ export const DEFAULT_MAX_PAGE_SIZE = 100
28
+ export const DEFAULT_PAGE_NUMBER = 1
30
29
 
31
30
  // Database clients
32
31
  export const DB_CLIENTS = {
@@ -35,7 +34,7 @@ export const DB_CLIENTS = {
35
34
  MYSQL2: 'mysql2',
36
35
  SQLITE: 'sqlite3',
37
36
  MSSQL: 'mssql'
38
- };
37
+ }
39
38
 
40
39
  // Error subtypes
41
40
  export const ERROR_SUBTYPES = {
@@ -43,4 +42,4 @@ export const ERROR_SUBTYPES = {
43
42
  VALIDATION: 'validation',
44
43
  PERMISSION: 'permission',
45
44
  CONFLICT: 'conflict'
46
- };
45
+ }
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Checks if a field exists only in code, not in the database
3
- *
3
+ *
4
4
  * @param {string} fieldName - The field name to check
5
5
  * @param {Object} schemaInfo - Schema info with computed fields and structure
6
6
  * @returns {boolean} True if field is computed or virtual
7
- *
7
+ *
8
8
  * @example
9
9
  * // Input: Check computed field
10
10
  * const schemaInfo = {
@@ -13,44 +13,44 @@
13
13
  * };
14
14
  * isNonDatabaseField('profit_margin', schemaInfo);
15
15
  * // Output: true (it's computed, not stored)
16
- *
16
+ *
17
17
  * @example
18
18
  * // Input: Check virtual field
19
19
  * const schemaInfo = {
20
20
  * computed: {},
21
- * schemaStructure: {
21
+ * schemaStructure: {
22
22
  * temp_password: { type: 'string', virtual: true }
23
23
  * }
24
24
  * };
25
25
  * isNonDatabaseField('temp_password', schemaInfo);
26
26
  * // Output: true (it's virtual, not stored)
27
- *
27
+ *
28
28
  * @description
29
29
  * Used by:
30
30
  * - buildFieldSelection to exclude from SELECT queries
31
31
  * - dataPost/dataPatch to skip non-database fields
32
32
  * - enrichAttributes to identify computed fields
33
- *
33
+ *
34
34
  * Purpose:
35
35
  * - Prevents SQL errors by excluding non-existent columns
36
36
  * - Identifies fields that need computation or special handling
37
37
  * - Supports virtual fields for temporary/input-only data
38
38
  */
39
39
  export const isNonDatabaseField = (fieldName, schemaInfo) => {
40
- const { computed = {}, schemaStructure = {} } = schemaInfo;
41
- if (fieldName in computed) return true;
42
- const fieldDef = schemaStructure[fieldName];
43
- return fieldDef && fieldDef.virtual === true;
44
- };
40
+ const { computed = {}, schemaStructure = {} } = schemaInfo
41
+ if (fieldName in computed) return true
42
+ const fieldDef = schemaStructure[fieldName]
43
+ return fieldDef && fieldDef.virtual === true
44
+ }
45
45
 
46
46
  /**
47
47
  * Builds the field selection list for database queries with sparse fieldset support
48
- *
48
+ *
49
49
  * @param {Object} scope - Resource scope with schema information
50
50
  * @param {Object} deps - Dependencies object
51
51
  * @param {Object} deps.context - Request context with queryParams and schemaInfo
52
52
  * @returns {Promise<Object>} Field selection information
53
- *
53
+ *
54
54
  * @example
55
55
  * // Input: Basic sparse fieldset request
56
56
  * const deps = {
@@ -61,7 +61,7 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
61
61
  * }
62
62
  * };
63
63
  * const result = await buildFieldSelection(scope, deps);
64
- *
64
+ *
65
65
  * // Output: Includes requested fields plus required fields
66
66
  * // {
67
67
  * // fieldsToSelect: ['id', 'title', 'body', 'author_id', 'category_id'],
@@ -70,12 +70,12 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
70
70
  * // idProperty: 'id'
71
71
  * // }
72
72
  * // Note: Foreign keys (author_id, category_id) always included for relationships
73
- *
73
+ *
74
74
  * @example
75
75
  * // Input: Computed field with hidden dependencies
76
76
  * const schemaInfo = {
77
77
  * computed: {
78
- * profit_margin: {
78
+ * profit_margin: {
79
79
  * compute: (record) => (record.price - record.cost) / record.price,
80
80
  * dependencies: ['price', 'cost']
81
81
  * }
@@ -91,7 +91,7 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
91
91
  * queryParams: { fields: { products: 'name,profit_margin' } }
92
92
  * }
93
93
  * };
94
- *
94
+ *
95
95
  * // Output: Fetches hidden dependency for computation
96
96
  * // {
97
97
  * // fieldsToSelect: ['id', 'name', 'price', 'cost'],
@@ -99,7 +99,7 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
99
99
  * // computedDependencies: ['cost'], // Will be removed after computation
100
100
  * // idProperty: 'id'
101
101
  * // }
102
- *
102
+ *
103
103
  * @example
104
104
  * // Input: No sparse fieldsets (returns all visible fields)
105
105
  * const deps = {
@@ -108,7 +108,7 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
108
108
  * schemaInfo: { ... }
109
109
  * }
110
110
  * };
111
- *
111
+ *
112
112
  * // Output: All non-hidden, non-virtual fields
113
113
  * // {
114
114
  * // fieldsToSelect: ['id', 'title', 'body', 'published_at', 'author_id'],
@@ -116,20 +116,20 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
116
116
  * // computedDependencies: [],
117
117
  * // idProperty: 'id'
118
118
  * // }
119
- *
119
+ *
120
120
  * @description
121
121
  * Used by:
122
122
  * - dataGet to build SELECT query
123
- * - dataQuery to build SELECT query
123
+ * - dataQuery to build SELECT query
124
124
  * - buildQuerySelection as the main field resolver
125
- *
125
+ *
126
126
  * Purpose:
127
127
  * - Implements JSON:API sparse fieldsets specification
128
128
  * - Ensures relationships work by including foreign keys
129
129
  * - Handles computed field dependencies intelligently
130
130
  * - Respects field visibility rules (hidden/normallyHidden)
131
131
  * - Supports custom ID column names
132
- *
132
+ *
133
133
  * Data flow:
134
134
  * 1. Always includes ID (with aliasing if needed)
135
135
  * 2. Parses requested fields from query string
@@ -139,55 +139,57 @@ export const isNonDatabaseField = (fieldName, schemaInfo) => {
139
139
  * 6. Returns complete field list for SQL SELECT
140
140
  */
141
141
  export const buildFieldSelection = async (scope, deps) => {
142
- let fieldsToSelect = new Set();
143
- let computedDependencies = new Set();
142
+ const fieldsToSelect = new Set()
143
+ const computedDependencies = new Set()
144
144
 
145
145
  // Extract values from scope
146
- const {
147
- vars: {
148
- schemaInfo: { schemaInstance: schemaInstance, computed: computedFields = {}, schemaStructure }
146
+ const {
147
+ vars: {
148
+ schemaInfo: { schemaInstance, computed: computedFields = {}, schemaStructure }
149
149
  }
150
- } = scope;
151
-
150
+ } = scope
151
+
152
152
  // Extract values from deps
153
- const { context } = deps;
154
- const scopeName = context.scopeName;
155
- const requestedFields = context.queryParams?.fields?.[scopeName];
156
- const idProperty = context.schemaInfo.idProperty;
157
-
153
+ const { context } = deps
154
+ const scopeName = context.scopeName
155
+ const requestedFields = context.queryParams?.fields?.[scopeName]
156
+ const idProperty = context.schemaInfo.idProperty
157
+
158
158
  // Always include the ID field - required for JSON:API
159
159
  // Handle aliasing if idProperty is not 'id'
160
160
  if (idProperty !== 'id') {
161
- fieldsToSelect.add(`${idProperty} as id`);
161
+ fieldsToSelect.add(`${idProperty} as id`)
162
162
  } else {
163
- fieldsToSelect.add('id');
163
+ fieldsToSelect.add('id')
164
164
  }
165
-
165
+
166
166
  // Handle both Schema objects and plain objects
167
167
  if (!schemaStructure) {
168
- schemaStructure = schemaInstance?.structure || schemaInstance || {};
168
+ schemaStructure = schemaInstance?.structure || schemaInstance || {}
169
169
  }
170
-
170
+
171
171
  // Get computed fields and virtual fields from schema
172
- const computedFieldNames = new Set(Object.keys(computedFields));
173
-
172
+ const computedFieldNames = new Set(Object.keys(computedFields))
173
+
174
174
  // Find fields marked as virtual in the schema
175
- const virtualFieldNames = new Set();
175
+ const virtualFieldNames = new Set()
176
176
  Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
177
177
  if (fieldDef.virtual === true) {
178
- virtualFieldNames.add(fieldName);
178
+ virtualFieldNames.add(fieldName)
179
179
  }
180
- });
181
-
182
- const nonDatabaseFields = new Set([...computedFieldNames, ...virtualFieldNames]);
183
-
180
+ })
181
+
182
+ const nonDatabaseFields = new Set([...computedFieldNames, ...virtualFieldNames])
183
+
184
184
  // Parse requested fields
185
- const requested = requestedFields ? (
186
- typeof requestedFields === 'string'
187
- ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
188
- : requestedFields
189
- ) : null;
190
-
185
+ const requested = requestedFields
186
+ ? (
187
+ typeof requestedFields === 'string'
188
+ ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
189
+ : requestedFields
190
+ )
191
+ : null
192
+
191
193
  if (requested && requested.length > 0) {
192
194
  // Sparse fieldsets requested - only select specified fields
193
195
  // Example: ?fields[products]=name,price,profit_margin
@@ -195,79 +197,79 @@ export const buildFieldSelection = async (scope, deps) => {
195
197
  // Skip computed and virtual fields - they don't exist in database
196
198
  // Computed fields will be calculated later in enrichAttributes
197
199
  // Virtual fields are handled separately (from request input)
198
- if (nonDatabaseFields.has(field)) return;
199
-
200
- const fieldDef = schemaStructure[field];
201
- if (!fieldDef) throw new Error(`Unknown sparse field '${field}' requested for '${scopeName}'`);
200
+ if (nonDatabaseFields.has(field)) return
201
+
202
+ const fieldDef = schemaStructure[field]
203
+ if (!fieldDef) throw new Error(`Unknown sparse field '${field}' requested for '${scopeName}'`)
202
204
 
203
205
  if (fieldDef.belongsToPolymorphic) {
204
- return;
206
+ return
205
207
  }
206
-
208
+
207
209
  // NEVER include hidden fields, even if explicitly requested
208
210
  // Example: password_hash with hidden:true is never returned
209
- if (fieldDef.hidden === true) return;
210
-
211
- fieldsToSelect.add(field);
212
- });
213
-
211
+ if (fieldDef.hidden === true) return
212
+
213
+ fieldsToSelect.add(field)
214
+ })
215
+
214
216
  // Handle computed field dependencies - fetch fields needed for calculations
215
217
  // Example: User requests 'profit_margin' which depends on 'price' and 'cost'
216
218
  // We need to fetch price and cost from DB even if not explicitly requested
217
- const requestedComputedFields = requested.filter(f => computedFieldNames.has(f));
219
+ const requestedComputedFields = requested.filter(f => computedFieldNames.has(f))
218
220
  for (const computedField of requestedComputedFields) {
219
- const fieldDef = computedFields[computedField];
221
+ const fieldDef = computedFields[computedField]
220
222
  if (fieldDef.dependencies) {
221
223
  for (const dep of fieldDef.dependencies) {
222
- const depFieldDef = schemaStructure[dep];
224
+ const depFieldDef = schemaStructure[dep]
223
225
  // Only add dependency if it exists and isn't hidden
224
226
  if (depFieldDef && depFieldDef.hidden !== true) {
225
- fieldsToSelect.add(dep);
227
+ fieldsToSelect.add(dep)
226
228
  // Track dependencies that weren't explicitly requested
227
229
  // These will be removed from the final response
228
230
  if (!requested.includes(dep)) {
229
- computedDependencies.add(dep);
231
+ computedDependencies.add(dep)
230
232
  }
231
233
  }
232
234
  }
233
235
  }
234
236
  }
235
-
237
+
236
238
  // Still handle normallyHidden fields for backward compatibility
237
239
  if (requestedComputedFields.length > 0) {
238
240
  Object.entries(schemaStructure).forEach(([field, fieldDef]) => {
239
241
  if (fieldDef.normallyHidden === true && fieldDef.hidden !== true) {
240
242
  // Only add if not already handled by dependencies
241
243
  if (!fieldsToSelect.has(field)) {
242
- fieldsToSelect.add(field);
244
+ fieldsToSelect.add(field)
243
245
  if (!requested.includes(field)) {
244
- computedDependencies.add(field);
246
+ computedDependencies.add(field)
245
247
  }
246
248
  }
247
249
  }
248
- });
250
+ })
249
251
  }
250
252
  } else {
251
253
  // No sparse fieldsets - return all visible fields
252
254
  // This is the default behavior when no ?fields parameter is provided
253
255
  Object.entries(schemaStructure).forEach(([field, fieldDef]) => {
254
256
  // Skip virtual fields - they don't exist in database
255
- if (fieldDef.virtual === true) return;
256
-
257
+ if (fieldDef.virtual === true) return
258
+
257
259
  // Skip hidden fields - these are NEVER returned
258
260
  // Example: password_hash with hidden:true
259
- if (fieldDef.hidden === true) return;
260
-
261
+ if (fieldDef.hidden === true) return
262
+
261
263
  // Skip polymorphic placeholder fields - handled via type/id columns
262
- if (fieldDef.belongsToPolymorphic) return;
264
+ if (fieldDef.belongsToPolymorphic) return
263
265
 
264
266
  // Skip normallyHidden fields - these are hidden by default
265
267
  // Example: cost with normallyHidden:true (only returned when explicitly requested)
266
- if (fieldDef.normallyHidden === true) return;
267
-
268
- fieldsToSelect.add(field);
269
- });
270
-
268
+ if (fieldDef.normallyHidden === true) return
269
+
270
+ fieldsToSelect.add(field)
271
+ })
272
+
271
273
  // When no sparse fieldsets, we compute all computed fields
272
274
  // So we need to include their dependencies even if normallyHidden
273
275
  // Example: profit_margin depends on 'cost' which is normallyHidden
@@ -275,40 +277,40 @@ export const buildFieldSelection = async (scope, deps) => {
275
277
  for (const [fieldName, fieldDef] of Object.entries(computedFields)) {
276
278
  if (fieldDef.dependencies) {
277
279
  for (const dep of fieldDef.dependencies) {
278
- const depFieldDef = schemaStructure[dep];
280
+ const depFieldDef = schemaStructure[dep]
279
281
  if (depFieldDef && depFieldDef.hidden !== true) {
280
- fieldsToSelect.add(dep);
282
+ fieldsToSelect.add(dep)
281
283
  // Track normallyHidden dependencies for later removal
282
284
  // These are fetched for computation but not returned
283
285
  if (depFieldDef.normallyHidden === true) {
284
- computedDependencies.add(dep);
286
+ computedDependencies.add(dep)
285
287
  }
286
288
  }
287
289
  }
288
290
  }
289
291
  }
290
292
  }
291
-
293
+
292
294
  // Always include foreign keys for relationships (unless hidden)
293
295
  Object.entries(schemaStructure).forEach(([field, fieldDef]) => {
294
296
  if (fieldDef.belongsTo && fieldDef.hidden !== true) {
295
- fieldsToSelect.add(field);
297
+ fieldsToSelect.add(field)
296
298
  }
297
- });
298
-
299
+ })
300
+
299
301
  // Always include polymorphic type and id fields from relationships
300
302
  try {
301
- const relationships = scope.vars.schemaInfo.schemaRelationships;
303
+ const relationships = scope.vars.schemaInfo.schemaRelationships
302
304
  Object.entries(relationships || {}).forEach(([relName, relDef]) => {
303
305
  if (relDef.belongsToPolymorphic) {
304
- if (relDef.typeField) fieldsToSelect.add(relDef.typeField);
305
- if (relDef.idField) fieldsToSelect.add(relDef.idField);
306
+ if (relDef.typeField) fieldsToSelect.add(relDef.typeField)
307
+ if (relDef.idField) fieldsToSelect.add(relDef.idField)
306
308
  }
307
- });
309
+ })
308
310
  } catch (e) {
309
311
  // Scope might not have relationships
310
312
  }
311
-
313
+
312
314
  // Return detailed information about field selection
313
315
  // This info is used by:
314
316
  // 1. SQL query builder to SELECT the right columns
@@ -319,17 +321,17 @@ export const buildFieldSelection = async (scope, deps) => {
319
321
  requestedFields: requested, // Fields explicitly requested by user
320
322
  computedDependencies: Array.from(computedDependencies), // Dependencies to remove from response
321
323
  idProperty // Pass idProperty for reference
322
- };
323
- };
324
+ }
325
+ }
324
326
 
325
327
  /**
326
328
  * Determines which computed fields to calculate based on sparse fieldsets
327
- *
329
+ *
328
330
  * @param {string} scopeName - Resource name (e.g., 'products')
329
331
  * @param {Array<string>|string} requestedFields - Requested fields from query
330
332
  * @param {Object} computedFields - Computed field definitions from schema
331
333
  * @returns {Array<string>} Names of computed fields to calculate
332
- *
334
+ *
333
335
  * @example
334
336
  * // Input: No sparse fieldsets (calculate all computed fields)
335
337
  * const computed = {
@@ -338,7 +340,7 @@ export const buildFieldSelection = async (scope, deps) => {
338
340
  * };
339
341
  * getRequestedComputedFields('users', null, computed);
340
342
  * // Output: ['full_name', 'age'] (all computed fields)
341
- *
343
+ *
342
344
  * @example
343
345
  * // Input: Sparse fieldsets with some computed fields
344
346
  * const requestedFields = 'first_name,full_name,email';
@@ -348,7 +350,7 @@ export const buildFieldSelection = async (scope, deps) => {
348
350
  * };
349
351
  * getRequestedComputedFields('users', requestedFields, computed);
350
352
  * // Output: ['full_name'] (only requested computed field)
351
- *
353
+ *
352
354
  * @example
353
355
  * // Input: Sparse fieldsets with no computed fields
354
356
  * const requestedFields = ['first_name', 'email'];
@@ -357,17 +359,17 @@ export const buildFieldSelection = async (scope, deps) => {
357
359
  * };
358
360
  * getRequestedComputedFields('users', requestedFields, computed);
359
361
  * // Output: [] (no computed fields requested)
360
- *
362
+ *
361
363
  * @description
362
364
  * Used by:
363
365
  * - enrichAttributes to determine which computations to run
364
366
  * - Applied after fetching data from database
365
- *
367
+ *
366
368
  * Purpose:
367
369
  * - Optimizes performance by only computing requested fields
368
370
  * - Supports JSON:API sparse fieldsets for computed fields
369
371
  * - Handles both string and array input formats
370
- *
372
+ *
371
373
  * Data flow:
372
374
  * 1. Checks if sparse fieldsets are specified
373
375
  * 2. If not, returns all computed field names
@@ -375,20 +377,20 @@ export const buildFieldSelection = async (scope, deps) => {
375
377
  * 4. Normalizes string input to array format
376
378
  */
377
379
  export const getRequestedComputedFields = (scopeName, requestedFields, computedFields) => {
378
- if (!computedFields) return [];
379
-
380
- const allComputedFields = Object.keys(computedFields);
381
-
380
+ if (!computedFields) return []
381
+
382
+ const allComputedFields = Object.keys(computedFields)
383
+
382
384
  if (!requestedFields || requestedFields.length === 0) {
383
385
  // No sparse fieldsets - return all computed fields
384
- return allComputedFields;
386
+ return allComputedFields
385
387
  }
386
-
388
+
387
389
  // Parse requested fields if it's a string
388
390
  const requested = typeof requestedFields === 'string'
389
391
  ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
390
- : requestedFields;
391
-
392
+ : requestedFields
393
+
392
394
  // Return only requested computed fields that exist
393
- return requested.filter(field => allComputedFields.includes(field));
394
- };
395
+ return requested.filter(field => allComputedFields.includes(field))
396
+ }