json-rest-api 1.0.13 → 1.0.15
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/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +13 -0
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +9 -5
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,198 +1,199 @@
|
|
|
1
|
-
import { filterHiddenFields } from '../lib/querying-writing/field-utils.js'
|
|
1
|
+
import { filterHiddenFields } from '../lib/querying-writing/field-utils.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* enrichAttributes
|
|
5
5
|
* Runs the enrichAttributes hook for a specific scope to allow plugins to modify attributes
|
|
6
6
|
* before they are returned to the client. This is a scope method so each resource type
|
|
7
7
|
* can have its own attribute enrichment logic.
|
|
8
|
-
*
|
|
9
|
-
*
|
|
8
|
+
*
|
|
9
|
+
*
|
|
10
10
|
*/
|
|
11
|
-
export default async function enrichAttributesMethod({ context, params, runHooks, scopeName, scopes, api, helpers }) {
|
|
11
|
+
export default async function enrichAttributesMethod ({ context, params, runHooks, scopeName, scopes, api, helpers }) {
|
|
12
12
|
// Extract parameters passed to enrichAttributes
|
|
13
13
|
// - attributes: The raw attributes from database
|
|
14
14
|
// - parentContext: The context from the calling method (has queryParams, transaction, etc.)
|
|
15
15
|
// - requestedComputedFields: Which computed fields to calculate (from sparse fieldsets)
|
|
16
16
|
// - isMainResource: Whether this is the main resource or an included one
|
|
17
17
|
// - computedDependencies: Fields fetched only for computation (to be removed)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
18
|
+
// Extract parameters passed to enrichAttributes
|
|
19
|
+
// - attributes: The raw attributes from database
|
|
20
|
+
// - parentContext: The context from the calling method (has queryParams, transaction, etc.)
|
|
21
|
+
// - requestedComputedFields: Which computed fields to calculate (from sparse fieldsets)
|
|
22
|
+
// - isMainResource: Whether this is the main resource or an included one
|
|
23
|
+
// - computedDependencies: Fields fetched only for computation (to be removed)
|
|
24
|
+
const { attributes, parentContext, requestedComputedFields, isMainResource, computedDependencies } = params || {}
|
|
25
|
+
|
|
26
|
+
// Return empty object if no attributes provided
|
|
27
|
+
if (!attributes) {
|
|
28
|
+
return {}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Get schema, computed field definitions
|
|
32
|
+
const schemaStructure = scopes[scopeName]?.vars?.schemaInfo?.schemaStructure || {}
|
|
33
|
+
const computedFields = scopes[scopeName]?.vars?.schemaInfo?.computed || {}
|
|
34
|
+
const fieldGetters = scopes[scopeName]?.vars?.schemaInfo?.fieldGetters || {}
|
|
35
|
+
const sortedGetterFields = scopes[scopeName]?.vars?.schemaInfo?.sortedGetterFields || []
|
|
30
36
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
record: transformedAttributes, // Alias for compatibility
|
|
53
|
-
parentContext,
|
|
54
|
-
scopeName,
|
|
55
|
-
api,
|
|
56
|
-
helpers,
|
|
57
|
-
isMainResource: isMainResource !== false
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// Apply getter (can be async)
|
|
61
|
-
transformedAttributes[fieldName] = await getterInfo.getter(
|
|
62
|
-
transformedAttributes[fieldName],
|
|
63
|
-
getterContext
|
|
64
|
-
);
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error(`Error in getter for field '${fieldName}' in ${scopeName}:`, error);
|
|
67
|
-
// Keep current value on error (don't break the whole request)
|
|
68
|
-
}
|
|
37
|
+
// Make a copy of attributes for transformation
|
|
38
|
+
const transformedAttributes = { ...attributes }
|
|
39
|
+
|
|
40
|
+
// STEP 1: Apply field getters in dependency order (before filtering)
|
|
41
|
+
// This ensures getters see all fields including hidden ones
|
|
42
|
+
for (const fieldName of sortedGetterFields) {
|
|
43
|
+
// Only process if field exists in attributes
|
|
44
|
+
if (fieldName in transformedAttributes) {
|
|
45
|
+
const getterInfo = fieldGetters[fieldName]
|
|
46
|
+
try {
|
|
47
|
+
const getterContext = {
|
|
48
|
+
attributes: transformedAttributes, // Current state with previous getters applied
|
|
49
|
+
fieldName,
|
|
50
|
+
originalValue: attributes[fieldName], // Original untransformed value
|
|
51
|
+
originalAttributes: attributes, // All original attributes
|
|
52
|
+
record: transformedAttributes, // Alias for compatibility
|
|
53
|
+
parentContext,
|
|
54
|
+
scopeName,
|
|
55
|
+
api,
|
|
56
|
+
helpers,
|
|
57
|
+
isMainResource: isMainResource !== false
|
|
69
58
|
}
|
|
70
|
-
}
|
|
71
59
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
const inputAttrs = parentContext.inputRecord.data.attributes;
|
|
81
|
-
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
82
|
-
if (fieldDef.virtual === true && fieldName in inputAttrs && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
|
|
83
|
-
// Add virtual field to filtered attributes if not already there
|
|
84
|
-
if (!(fieldName in filteredAttributes)) {
|
|
85
|
-
filteredAttributes[fieldName] = inputAttrs[fieldName];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// Now handle sparse fieldsets for virtual fields
|
|
92
|
-
if (requestedFields && requestedFields.length > 0) {
|
|
93
|
-
const requestedFieldsList = typeof requestedFields === 'string' ?
|
|
94
|
-
requestedFields.split(',').map(f => f.trim()) : requestedFields;
|
|
95
|
-
|
|
96
|
-
// Remove virtual fields that weren't requested
|
|
97
|
-
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
98
|
-
if (fieldDef.virtual === true && fieldName in filteredAttributes) {
|
|
99
|
-
if (!requestedFieldsList.includes(fieldName)) {
|
|
100
|
-
delete filteredAttributes[fieldName];
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
});
|
|
60
|
+
// Apply getter (can be async)
|
|
61
|
+
transformedAttributes[fieldName] = await getterInfo.getter(
|
|
62
|
+
transformedAttributes[fieldName],
|
|
63
|
+
getterContext
|
|
64
|
+
)
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error(`Error in getter for field '${fieldName}' in ${scopeName}:`, error)
|
|
67
|
+
// Keep current value on error (don't break the whole request)
|
|
104
68
|
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
105
71
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
// Explicit list provided (from sparse fieldsets)
|
|
111
|
-
// Example: ?fields[products]=name,profit_margin -> only compute profit_margin
|
|
112
|
-
fieldsToCompute = requestedComputedFields;
|
|
113
|
-
} else if (isMainResource || !parentContext?.queryParams?.fields) {
|
|
114
|
-
// No sparse fieldsets or this is the main resource - compute all fields
|
|
115
|
-
// This ensures all computed fields are available when no filtering is applied
|
|
116
|
-
fieldsToCompute = Object.keys(computedFields);
|
|
117
|
-
}
|
|
72
|
+
// Filter hidden fields from attributes based on visibility rules
|
|
73
|
+
// This removes hidden:true fields and normallyHidden:true fields (unless requested)
|
|
74
|
+
const requestedFields = parentContext?.queryParams?.fields?.[scopeName]
|
|
75
|
+
const filteredAttributes = filterHiddenFields(transformedAttributes, { structure: schemaStructure }, requestedFields)
|
|
118
76
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
api, // Full API instance
|
|
129
|
-
};
|
|
130
|
-
|
|
131
|
-
// Auto-compute fields that have compute functions
|
|
132
|
-
for (const fieldName of fieldsToCompute) {
|
|
133
|
-
const fieldDef = computedFields[fieldName];
|
|
134
|
-
if (fieldDef && fieldDef.compute) {
|
|
135
|
-
try {
|
|
136
|
-
// Call the compute function with full context
|
|
137
|
-
// Example: profit_margin compute gets { attributes: { price: 100, cost: 60 } }
|
|
138
|
-
// and returns: ((100 - 60) / 100 * 100) = "40.00"
|
|
139
|
-
filteredAttributes[fieldName] = await fieldDef.compute(computeContext);
|
|
140
|
-
} catch (error) {
|
|
141
|
-
// Log error but don't fail the request - computed fields shouldn't break API
|
|
142
|
-
console.error(`Error computing field '${fieldName}' for ${scopeName}:`, error);
|
|
143
|
-
filteredAttributes[fieldName] = null;
|
|
144
|
-
}
|
|
77
|
+
// Handle virtual fields - they need to be added from parent context if available
|
|
78
|
+
// Virtual fields come from input and need to be preserved in responses
|
|
79
|
+
if (parentContext && parentContext.inputRecord?.data?.attributes) {
|
|
80
|
+
const inputAttrs = parentContext.inputRecord.data.attributes
|
|
81
|
+
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
82
|
+
if (fieldDef.virtual === true && fieldName in inputAttrs && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
|
|
83
|
+
// Add virtual field to filtered attributes if not already there
|
|
84
|
+
if (!(fieldName in filteredAttributes)) {
|
|
85
|
+
filteredAttributes[fieldName] = inputAttrs[fieldName]
|
|
145
86
|
}
|
|
146
87
|
}
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Now handle sparse fieldsets for virtual fields
|
|
92
|
+
if (requestedFields && requestedFields.length > 0) {
|
|
93
|
+
const requestedFieldsList = typeof requestedFields === 'string'
|
|
94
|
+
? requestedFields.split(',').map(f => f.trim())
|
|
95
|
+
: requestedFields
|
|
147
96
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
// Example: User requests profit_margin, we fetch cost, compute, then remove cost
|
|
154
|
-
const finalAttributes = { ...filteredAttributes };
|
|
155
|
-
if (requestedFields && computedDependencies && computedDependencies.length > 0) {
|
|
156
|
-
// Parse requested fields if it's a string
|
|
157
|
-
const requested = typeof requestedFields === 'string'
|
|
158
|
-
? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
159
|
-
: requestedFields;
|
|
160
|
-
|
|
161
|
-
for (const dep of computedDependencies) {
|
|
162
|
-
// Only remove if it wasn't explicitly requested
|
|
163
|
-
// Example: 'cost' is removed unless user explicitly asked for it
|
|
164
|
-
if (!requested.includes(dep)) {
|
|
165
|
-
delete finalAttributes[dep];
|
|
166
|
-
}
|
|
97
|
+
// Remove virtual fields that weren't requested
|
|
98
|
+
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
99
|
+
if (fieldDef.virtual === true && fieldName in filteredAttributes) {
|
|
100
|
+
if (!requestedFieldsList.includes(fieldName)) {
|
|
101
|
+
delete filteredAttributes[fieldName]
|
|
167
102
|
}
|
|
168
103
|
}
|
|
104
|
+
})
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Determine which computed fields to calculate
|
|
108
|
+
// We only compute fields that are requested to optimize performance
|
|
109
|
+
let fieldsToCompute = []
|
|
110
|
+
if (requestedComputedFields) {
|
|
111
|
+
// Explicit list provided (from sparse fieldsets)
|
|
112
|
+
// Example: ?fields[products]=name,profit_margin -> only compute profit_margin
|
|
113
|
+
fieldsToCompute = requestedComputedFields
|
|
114
|
+
} else if (isMainResource || !parentContext?.queryParams?.fields) {
|
|
115
|
+
// No sparse fieldsets or this is the main resource - compute all fields
|
|
116
|
+
// This ensures all computed fields are available when no filtering is applied
|
|
117
|
+
fieldsToCompute = Object.keys(computedFields)
|
|
118
|
+
}
|
|
169
119
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
120
|
+
// Create compute context with all available resources
|
|
121
|
+
// IMPORTANT: We pass the TRANSFORMED attributes (after getters) to compute functions
|
|
122
|
+
// This ensures computed fields see the getter-processed values
|
|
123
|
+
// Example: if price getter converts string to number, profit_margin sees the number
|
|
124
|
+
const computeContext = {
|
|
125
|
+
attributes: transformedAttributes, // Transformed attributes after getters
|
|
126
|
+
record: { ...transformedAttributes }, // Full record for convenience
|
|
127
|
+
context: parentContext,
|
|
128
|
+
helpers, // API helpers for complex operations
|
|
129
|
+
api, // Full API instance
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Auto-compute fields that have compute functions
|
|
133
|
+
for (const fieldName of fieldsToCompute) {
|
|
134
|
+
const fieldDef = computedFields[fieldName]
|
|
135
|
+
if (fieldDef && fieldDef.compute) {
|
|
136
|
+
try {
|
|
137
|
+
// Call the compute function with full context
|
|
138
|
+
// Example: profit_margin compute gets { attributes: { price: 100, cost: 60 } }
|
|
139
|
+
// and returns: ((100 - 60) / 100 * 100) = "40.00"
|
|
140
|
+
filteredAttributes[fieldName] = await fieldDef.compute(computeContext)
|
|
141
|
+
} catch (error) {
|
|
142
|
+
// Log error but don't fail the request - computed fields shouldn't break API
|
|
143
|
+
console.error(`Error computing field '${fieldName}' for ${scopeName}:`, error)
|
|
144
|
+
filteredAttributes[fieldName] = null
|
|
180
145
|
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Remove fields that were only fetched as dependencies
|
|
150
|
+
// This is the key to the dependency resolution feature:
|
|
151
|
+
// 1. We fetched dependencies from DB (e.g., 'cost' for profit_margin)
|
|
152
|
+
// 2. We used them in compute functions
|
|
153
|
+
// 3. Now we remove them if they weren't explicitly requested
|
|
154
|
+
// Example: User requests profit_margin, we fetch cost, compute, then remove cost
|
|
155
|
+
const finalAttributes = { ...filteredAttributes }
|
|
156
|
+
if (requestedFields && computedDependencies && computedDependencies.length > 0) {
|
|
157
|
+
// Parse requested fields if it's a string
|
|
158
|
+
const requested = typeof requestedFields === 'string'
|
|
159
|
+
? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
160
|
+
: requestedFields
|
|
161
|
+
|
|
162
|
+
for (const dep of computedDependencies) {
|
|
163
|
+
// Only remove if it wasn't explicitly requested
|
|
164
|
+
// Example: 'cost' is removed unless user explicitly asked for it
|
|
165
|
+
if (!requested.includes(dep)) {
|
|
166
|
+
delete finalAttributes[dep]
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (requestedFields && requestedFields.length > 0) {
|
|
172
|
+
const requestedList = typeof requestedFields === 'string'
|
|
173
|
+
? requestedFields.split(',').map((field) => field.trim()).filter((field) => field)
|
|
174
|
+
: requestedFields
|
|
175
|
+
const allowed = new Set(requestedList)
|
|
176
|
+
Object.keys(finalAttributes).forEach((key) => {
|
|
177
|
+
if (!allowed.has(key)) {
|
|
178
|
+
delete finalAttributes[key]
|
|
179
|
+
}
|
|
180
|
+
})
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Create context for enrichAttributes hooks
|
|
184
|
+
Object.assign(context, {
|
|
185
|
+
parentContext,
|
|
186
|
+
attributes: finalAttributes, // Use the final attributes after dependency removal
|
|
187
|
+
computedFields,
|
|
188
|
+
requestedComputedFields: fieldsToCompute,
|
|
189
|
+
scopeName,
|
|
190
|
+
helpers,
|
|
191
|
+
api
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Run enrichAttributes hooks for additional/override computations
|
|
195
|
+
await runHooks('enrichAttributes')
|
|
181
196
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
attributes: finalAttributes, // Use the final attributes after dependency removal
|
|
186
|
-
computedFields,
|
|
187
|
-
requestedComputedFields: fieldsToCompute,
|
|
188
|
-
scopeName,
|
|
189
|
-
helpers,
|
|
190
|
-
api
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
// Run enrichAttributes hooks for additional/override computations
|
|
194
|
-
await runHooks('enrichAttributes');
|
|
195
|
-
|
|
196
|
-
// Return the attributes from context, which hooks may have modified
|
|
197
|
-
return context.attributes;
|
|
198
|
-
};
|
|
197
|
+
// Return the attributes from context, which hooks may have modified
|
|
198
|
+
return context.attributes
|
|
199
|
+
};
|