json-rest-api 1.0.28 → 2.0.0
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/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +746 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -174
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +20 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +142 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1069
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -385
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +38 -69
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +39 -82
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -150
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -118
- package/tests/fixtures/api-configs.js +0 -2509
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-cursor-multifield.test.js +0 -643
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,7 +1,15 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import {
|
|
3
|
+
* Identifier, LifecycleResource, RelationshipWriteArguments, WriteRelationshipContext
|
|
4
|
+
* } from './lifecycle-types.js' */
|
|
5
|
+
import { lockRelationshipParent } from '../lib/writing/relationship-processor.js'
|
|
6
|
+
import { advanceResourceVersion } from '../lib/writing/resource-version.js'
|
|
7
|
+
import { beginWriteTransaction } from '../../../lib/error-context.js'
|
|
8
|
+
import { rejectRemovedOptions, resolveFormat } from '../lib/querying-writing/response-options.js'
|
|
9
|
+
import { findRelationshipDefinition } from '../lib/querying-writing/relationship-contracts.js'
|
|
1
10
|
import { RestApiResourceError, RestApiValidationError } from '../../../lib/rest-api-errors.js'
|
|
2
11
|
import {
|
|
3
12
|
commitOwnedTransaction,
|
|
4
|
-
findRelationshipDefinition,
|
|
5
13
|
getVisibleRelationshipParent,
|
|
6
14
|
handleWriteMethodError,
|
|
7
15
|
validateRelationshipRoutePayload
|
|
@@ -10,32 +18,33 @@ import {
|
|
|
10
18
|
normalizeRelationshipIdentifiers,
|
|
11
19
|
requireExistingResourceId
|
|
12
20
|
} from '../lib/querying-writing/resource-id-normalization.js'
|
|
21
|
+
import { updateReverseRelationship } from '../lib/writing/reverse-relationship-manipulations.js'
|
|
22
|
+
import { invalidateRemovedPivotVersions } from '../lib/writing/many-to-many-manipulations.js'
|
|
23
|
+
import { getStorageColumn } from '../lib/storage/storage-mapping.js'
|
|
24
|
+
import { RELATIONSHIP_WRITE_BATCH_SIZE } from '../lib/querying-writing/knex-constants.js'
|
|
13
25
|
|
|
14
26
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* @param {string} id - The ID of the resource
|
|
20
|
-
* @param {string} relationshipName - The name of the relationship
|
|
21
|
-
* @param {array} relationshipData - Array of resource identifiers to remove
|
|
22
|
-
* @returns {Promise<void>} 204 No Content
|
|
27
|
+
* Remove the identifiers in params.relationshipData from a to-many relationship.
|
|
28
|
+
* Authorization and parent/version locking precede mutation. The method
|
|
29
|
+
* returns no resource and completes only an owned transaction.
|
|
30
|
+
* @param {RelationshipWriteArguments<Identifier[]>} args
|
|
23
31
|
*/
|
|
24
|
-
export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
|
|
25
|
-
|
|
26
|
-
|
|
32
|
+
export default async function deleteRelationshipMethod ({ params, context: callerContext, vars, helpers, scope, scopes, runHooks, scopeOptions, scopeName, api, log }) {
|
|
33
|
+
rejectRemovedOptions(params)
|
|
34
|
+
if (params.format !== undefined) resolveFormat(params.format)
|
|
35
|
+
callerContext.method = 'deleteRelationship'
|
|
36
|
+
callerContext.scopeName = scopeName
|
|
37
|
+
callerContext.id = requireExistingResourceId(params.id, {
|
|
27
38
|
scopeOptions,
|
|
28
39
|
vars,
|
|
29
40
|
scopeName
|
|
30
41
|
})
|
|
31
|
-
|
|
32
|
-
|
|
42
|
+
callerContext.relationshipName = params.relationshipName
|
|
43
|
+
callerContext.schemaInfo = scope.vars.schemaInfo
|
|
33
44
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
context.shouldCommit = !params.transaction && !!context.transaction
|
|
38
|
-
context.db = context.transaction || api.knex.instance
|
|
45
|
+
await beginWriteTransaction(callerContext, params.transaction, helpers.newTransaction, runHooks)
|
|
46
|
+
callerContext.db = callerContext.transaction || api.knex.instance
|
|
47
|
+
const context = /** @type {WriteRelationshipContext} */ (callerContext)
|
|
39
48
|
|
|
40
49
|
try {
|
|
41
50
|
// Validate
|
|
@@ -61,7 +70,7 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
|
|
|
61
70
|
operation: 'deleteRelationship',
|
|
62
71
|
relationshipData: params.relationshipData
|
|
63
72
|
})
|
|
64
|
-
params.relationshipData = normalizeRelationshipIdentifiers(params.relationshipData, { api })
|
|
73
|
+
params.relationshipData = /** @type {Identifier[]} */ (normalizeRelationshipIdentifiers(params.relationshipData, { api }))
|
|
65
74
|
|
|
66
75
|
// Check permissions
|
|
67
76
|
await runHooks('checkPermissions')
|
|
@@ -78,6 +87,14 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
|
|
|
78
87
|
if (!parentRecord) {
|
|
79
88
|
throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
80
89
|
}
|
|
90
|
+
context.minimalRecord = parentRecord
|
|
91
|
+
context.originalMinimalRecord = parentRecord
|
|
92
|
+
|
|
93
|
+
await runHooks('beforeDataCall')
|
|
94
|
+
await runHooks('beforeDataCallDeleteRelationship')
|
|
95
|
+
|
|
96
|
+
const revision = await advanceResourceVersion({ scopeName, context, helpers, expectedVersion: params.expectedVersion })
|
|
97
|
+
if (revision === undefined) await lockRelationshipParent({ context, helpers, scopeName })
|
|
81
98
|
|
|
82
99
|
// Remove relationships
|
|
83
100
|
if (relDef?.through) {
|
|
@@ -92,45 +109,30 @@ export default async function deleteRelationshipMethod ({ params, context, vars,
|
|
|
92
109
|
} else {
|
|
93
110
|
const knex = api.knex?.instance || helpers.db
|
|
94
111
|
const pivotResource = relDef.through
|
|
95
|
-
const pivotScope = api.resources[pivotResource]
|
|
112
|
+
const pivotScope = /** @type {LifecycleResource} */ (api.resources[pivotResource])
|
|
96
113
|
const pivotTable = pivotScope?.vars?.schemaInfo?.tableName || pivotResource
|
|
97
|
-
const localKey = relDef.foreignKey
|
|
98
|
-
const foreignKey = relDef.otherKey
|
|
114
|
+
const localKey = getStorageColumn(pivotScope.vars.schemaInfo, /** @type {string} */ (relDef.foreignKey))
|
|
115
|
+
const foreignKey = getStorageColumn(pivotScope.vars.schemaInfo, /** @type {string} */ (relDef.otherKey))
|
|
99
116
|
|
|
100
|
-
|
|
101
|
-
|
|
117
|
+
const ids = [...new Set(params.relationshipData.map(identifier => identifier.id))]
|
|
118
|
+
for (let offset = 0; offset < ids.length; offset += RELATIONSHIP_WRITE_BATCH_SIZE) {
|
|
119
|
+
const removals = knex(pivotTable)
|
|
102
120
|
.where(localKey, context.id)
|
|
103
|
-
.
|
|
104
|
-
.delete()
|
|
121
|
+
.whereIn(foreignKey, ids.slice(offset, offset + RELATIONSHIP_WRITE_BATCH_SIZE))
|
|
105
122
|
.transacting(context.transaction)
|
|
123
|
+
await invalidateRemovedPivotVersions(api, relDef, removals, context.transaction)
|
|
124
|
+
await removals.delete()
|
|
106
125
|
}
|
|
107
126
|
}
|
|
108
127
|
} else {
|
|
109
|
-
|
|
110
|
-
const targetType = relDef.target
|
|
111
|
-
for (const identifier of params.relationshipData) {
|
|
112
|
-
await api.resources[targetType].patch({
|
|
113
|
-
id: identifier.id,
|
|
114
|
-
inputRecord: {
|
|
115
|
-
data: {
|
|
116
|
-
type: targetType,
|
|
117
|
-
id: identifier.id,
|
|
118
|
-
attributes: { [relDef.foreignKey]: null }
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
transaction: context.transaction,
|
|
122
|
-
simplified: false
|
|
123
|
-
}, { ...context })
|
|
124
|
-
}
|
|
128
|
+
await updateReverseRelationship({ api, helpers, context, scopeName, relDef, relData: params.relationshipData, operation: 'remove' })
|
|
125
129
|
}
|
|
126
130
|
|
|
127
131
|
await runHooks('finish')
|
|
128
132
|
await runHooks('finishDeleteRelationship')
|
|
129
133
|
|
|
130
|
-
await commitOwnedTransaction(context
|
|
131
|
-
|
|
132
|
-
// 204 No Content
|
|
134
|
+
await commitOwnedTransaction(context)
|
|
133
135
|
} catch (error) {
|
|
134
|
-
await handleWriteMethodError(error, context, 'DELETE_RELATIONSHIP', scopeName, log
|
|
136
|
+
await handleWriteMethodError(error, context, 'DELETE_RELATIONSHIP', scopeName, log)
|
|
135
137
|
}
|
|
136
138
|
}
|
|
@@ -1,52 +1,56 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import {
|
|
3
|
+
* IdentityContext, LifecycleArguments
|
|
4
|
+
* } from './lifecycle-types.js' */
|
|
5
|
+
import { initializeResourceVersion, applyResourceVersion, captureInverseVersions, invalidateInverseVersions } from '../lib/writing/resource-version.js'
|
|
6
|
+
import { beginWriteTransaction } from '../../../lib/error-context.js'
|
|
7
|
+
import { rejectRemovedOptions, resolveFormat } from '../lib/querying-writing/response-options.js'
|
|
1
8
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
import { commitOwnedTransaction, handleWriteMethodError } from './common.js'
|
|
9
|
+
import { commitOwnedTransaction, handleWriteMethodError, writePrecondition } from './common.js'
|
|
3
10
|
import { requireExistingResourceId } from '../lib/querying-writing/resource-id-normalization.js'
|
|
4
11
|
|
|
5
12
|
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* This method does not return the deleted resource.
|
|
13
|
+
* Delete an existing resource and complete only an owned transaction.
|
|
14
|
+
* The programmatic method returns no resource; connectors choose HTTP status.
|
|
15
|
+
* @param {LifecycleArguments} args
|
|
10
16
|
*/
|
|
11
17
|
export default async function deleteMethod ({
|
|
12
18
|
params,
|
|
13
|
-
context,
|
|
19
|
+
context: callerContext,
|
|
14
20
|
vars,
|
|
15
21
|
helpers,
|
|
16
22
|
scope,
|
|
17
23
|
scopes,
|
|
18
24
|
runHooks,
|
|
19
|
-
apiOptions,
|
|
20
|
-
pluginOptions,
|
|
21
25
|
scopeOptions,
|
|
22
26
|
scopeName,
|
|
23
27
|
api,
|
|
24
28
|
log
|
|
25
29
|
}) {
|
|
26
30
|
// Make the method available to all hooks
|
|
27
|
-
|
|
31
|
+
rejectRemovedOptions(params)
|
|
32
|
+
if (params.format !== undefined) resolveFormat(params.format)
|
|
33
|
+
callerContext.method = 'delete'
|
|
28
34
|
|
|
29
35
|
// Set the ID in context
|
|
30
|
-
|
|
36
|
+
callerContext.id = requireExistingResourceId(params.id, {
|
|
31
37
|
scopeOptions,
|
|
32
38
|
vars,
|
|
33
39
|
scopeName
|
|
34
40
|
})
|
|
35
41
|
|
|
36
42
|
// Set scopeName in context (needed for broadcasting)
|
|
37
|
-
|
|
43
|
+
callerContext.scopeName = scopeName
|
|
38
44
|
|
|
39
45
|
// Set schema info even for DELETE (needed by storage layer)
|
|
40
|
-
|
|
46
|
+
callerContext.schemaInfo = scope.vars.schemaInfo
|
|
41
47
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
context.shouldCommit = !params.transaction && !!context.transaction
|
|
46
|
-
context.db = context.transaction || api.knex.instance
|
|
48
|
+
await beginWriteTransaction(callerContext, params.transaction, helpers.newTransaction, runHooks)
|
|
49
|
+
callerContext.db = callerContext.transaction || api.knex.instance
|
|
50
|
+
const context = /** @type {IdentityContext} */ (callerContext)
|
|
47
51
|
|
|
48
52
|
try {
|
|
49
|
-
|
|
53
|
+
const versionState = initializeResourceVersion({ context, expectedVersion: params.expectedVersion })
|
|
50
54
|
|
|
51
55
|
// Fetch minimal record for authorization and logging
|
|
52
56
|
const minimalRecord = await helpers.dataGetMinimal({
|
|
@@ -69,12 +73,13 @@ export default async function deleteMethod ({
|
|
|
69
73
|
context.originalMinimalRecord = minimalRecord
|
|
70
74
|
context.minimalRecord = minimalRecord
|
|
71
75
|
|
|
72
|
-
// Centralised checkPermissions function
|
|
73
76
|
await scope.checkPermissions({
|
|
74
77
|
method: 'delete',
|
|
75
78
|
originalContext: context,
|
|
76
79
|
})
|
|
77
80
|
|
|
81
|
+
await params[writePrecondition]?.()
|
|
82
|
+
|
|
78
83
|
// Before data operations
|
|
79
84
|
await runHooks('beforeDataCall')
|
|
80
85
|
await runHooks('beforeDataCallDelete')
|
|
@@ -82,24 +87,25 @@ export default async function deleteMethod ({
|
|
|
82
87
|
// Initialize record context for hooks
|
|
83
88
|
context.record = {}
|
|
84
89
|
|
|
90
|
+
const inverseVersions = await captureInverseVersions({ api, helpers, context, scopeName })
|
|
91
|
+
await applyResourceVersion({ state: versionState, context, helpers, scopeName })
|
|
92
|
+
|
|
85
93
|
// Call the storage helper
|
|
86
94
|
await helpers.dataDelete({
|
|
87
95
|
scopeName,
|
|
88
96
|
context
|
|
89
97
|
})
|
|
90
98
|
|
|
99
|
+
await invalidateInverseVersions({ state: inverseVersions, context, helpers, api, isDelete: true })
|
|
100
|
+
|
|
91
101
|
await runHooks('afterDataCallDelete')
|
|
92
102
|
await runHooks('afterDataCall')
|
|
93
103
|
|
|
94
|
-
// No return record for DELETE (204 No Content)
|
|
95
|
-
|
|
96
104
|
await runHooks('finish')
|
|
97
105
|
await runHooks('finishDelete')
|
|
98
106
|
|
|
99
|
-
await commitOwnedTransaction(context
|
|
100
|
-
|
|
101
|
-
// DELETE typically returns void/undefined (204 No Content)
|
|
107
|
+
await commitOwnedTransaction(context)
|
|
102
108
|
} catch (error) {
|
|
103
|
-
await handleWriteMethodError(error, context, 'DELETE', scopeName, log
|
|
109
|
+
await handleWriteMethodError(error, context, 'DELETE', scopeName, log)
|
|
104
110
|
}
|
|
105
111
|
}
|
|
@@ -1,56 +1,69 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import {
|
|
3
|
+
* EnrichmentArguments, Field, LifecycleContext, LifecycleResource, LifecycleSchema
|
|
4
|
+
* } from './lifecycle-types.js' */
|
|
5
|
+
import { filterHiddenFields, getResourceFieldset, parseFieldset } from '../lib/querying-writing/field-utils.js'
|
|
6
|
+
import { wrapUnexpectedError } from '../../../lib/error-context.js'
|
|
7
|
+
import { getFieldDependencyClosure } from '../lib/querying-writing/schema-helpers.js'
|
|
8
|
+
import { getRequestedComputedFields } from '../lib/querying-writing/knex-field-helpers.js'
|
|
9
|
+
|
|
10
|
+
// Resolve field dependencies before filtering and running resource enrichment hooks.
|
|
11
|
+
/** @param {EnrichmentArguments} args */
|
|
11
12
|
export default async function enrichAttributesMethod ({ context, params, runHooks, scopeName, scopes, api, helpers }) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
// - parentContext: The context from the calling method (has queryParams, transaction, etc.)
|
|
15
|
-
// - requestedComputedFields: Which computed fields to calculate (from sparse fieldsets)
|
|
16
|
-
// - isMainResource: Whether this is the main resource or an included one
|
|
17
|
-
// - computedDependencies: Fields fetched only for computation (to be removed)
|
|
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
|
|
13
|
+
const { id, attributes, parentContext, requestedComputedFields, isMainResource } = params || {}
|
|
14
|
+
|
|
27
15
|
if (!attributes) {
|
|
28
16
|
return {}
|
|
29
17
|
}
|
|
30
18
|
|
|
31
|
-
|
|
32
|
-
const schemaStructure =
|
|
33
|
-
const computedFields =
|
|
34
|
-
const fieldGetters =
|
|
35
|
-
const sortedGetterFields =
|
|
36
|
-
const queryFields = scopes[scopeName]?.vars?.queryFields || {}
|
|
19
|
+
const schemaInfo = scopes[scopeName]?.vars?.schemaInfo || /** @type {Partial<LifecycleSchema>} */ ({})
|
|
20
|
+
const schemaStructure = schemaInfo.schemaStructure || {}
|
|
21
|
+
const computedFields = schemaInfo.computed || {}
|
|
22
|
+
const fieldGetters = schemaInfo.fieldGetters || {}
|
|
23
|
+
const sortedGetterFields = schemaInfo.sortedGetterFields || []
|
|
37
24
|
|
|
38
|
-
// Make a copy of attributes for transformation
|
|
39
25
|
const transformedAttributes = { ...attributes }
|
|
26
|
+
const resourceFieldset = getResourceFieldset(parentContext?.queryParams?.fields, scopeName)
|
|
27
|
+
const requestedFields = parseFieldset(resourceFieldset)
|
|
28
|
+
const selectedComputedFields = requestedComputedFields ?? getRequestedComputedFields(
|
|
29
|
+
scopeName, resourceFieldset, computedFields
|
|
30
|
+
)
|
|
31
|
+
const selectedVirtualFields = Object.keys(schemaStructure).filter(name => {
|
|
32
|
+
const definition = /** @type {Field} */ (schemaStructure[name])
|
|
33
|
+
return definition.virtual === true && definition.hidden !== true &&
|
|
34
|
+
(requestedFields === null ? definition.normallyHidden !== true : requestedFields.includes(name))
|
|
35
|
+
})
|
|
36
|
+
const neededFields = getFieldDependencyClosure(schemaInfo, [
|
|
37
|
+
...Object.keys(attributes), ...selectedComputedFields, ...selectedVirtualFields
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
// Virtual input belongs only to the resource that was written.
|
|
41
|
+
const inputData = parentContext?.inputRecord?.data
|
|
42
|
+
if (isMainResource !== false && inputData?.type === scopeName &&
|
|
43
|
+
inputData.id !== undefined && String(inputData.id) === String(id) && inputData.attributes) {
|
|
44
|
+
const inputAttrs = inputData.attributes
|
|
45
|
+
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
46
|
+
if (fieldDef.virtual === true && neededFields.has(fieldName) && Object.hasOwn(inputAttrs, fieldName) && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
|
|
47
|
+
if (!Object.hasOwn(transformedAttributes, fieldName)) {
|
|
48
|
+
transformedAttributes[fieldName] = inputAttrs[fieldName]
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
})
|
|
52
|
+
}
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
//
|
|
54
|
+
const originalAttributes = { ...transformedAttributes }
|
|
55
|
+
// Getters run in dependency order while hidden dependencies are still available.
|
|
43
56
|
for (const fieldName of sortedGetterFields) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const getterInfo = fieldGetters[fieldName]
|
|
57
|
+
if (Object.hasOwn(transformedAttributes, fieldName)) {
|
|
58
|
+
const getterInfo = /** @type {NonNullable<LifecycleSchema['fieldGetters'][string]>} */ (fieldGetters[fieldName])
|
|
47
59
|
try {
|
|
48
60
|
const getterContext = {
|
|
49
|
-
|
|
61
|
+
id,
|
|
62
|
+
attributes: transformedAttributes,
|
|
50
63
|
fieldName,
|
|
51
|
-
originalValue:
|
|
52
|
-
originalAttributes
|
|
53
|
-
record: transformedAttributes,
|
|
64
|
+
originalValue: originalAttributes[fieldName],
|
|
65
|
+
originalAttributes,
|
|
66
|
+
record: transformedAttributes,
|
|
54
67
|
parentContext,
|
|
55
68
|
scopeName,
|
|
56
69
|
api,
|
|
@@ -58,126 +71,55 @@ export default async function enrichAttributesMethod ({ context, params, runHook
|
|
|
58
71
|
isMainResource: isMainResource !== false
|
|
59
72
|
}
|
|
60
73
|
|
|
61
|
-
// Apply getter (can be async)
|
|
62
74
|
transformedAttributes[fieldName] = await getterInfo.getter(
|
|
63
75
|
transformedAttributes[fieldName],
|
|
64
76
|
getterContext
|
|
65
77
|
)
|
|
66
78
|
} catch (error) {
|
|
67
|
-
|
|
68
|
-
|
|
79
|
+
throw wrapUnexpectedError(error, {
|
|
80
|
+
message: `Getter for field '${fieldName}' failed`,
|
|
81
|
+
context: { scopeName, fieldName, phase: 'getter' }
|
|
82
|
+
})
|
|
69
83
|
}
|
|
70
84
|
}
|
|
71
85
|
}
|
|
72
86
|
|
|
73
|
-
|
|
74
|
-
// This removes hidden:true fields and normallyHidden:true fields (unless requested)
|
|
75
|
-
const requestedFields = parentContext?.queryParams?.fields?.[scopeName]
|
|
76
|
-
const filteredAttributes = filterHiddenFields(
|
|
77
|
-
transformedAttributes,
|
|
78
|
-
{ structure: { ...schemaStructure, ...queryFields } },
|
|
79
|
-
requestedFields
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
// Handle virtual fields - they need to be added from parent context if available
|
|
83
|
-
// Virtual fields come from input and need to be preserved in responses
|
|
84
|
-
if (parentContext && parentContext.inputRecord?.data?.attributes) {
|
|
85
|
-
const inputAttrs = parentContext.inputRecord.data.attributes
|
|
86
|
-
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
87
|
-
if (fieldDef.virtual === true && fieldName in inputAttrs && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
|
|
88
|
-
// Add virtual field to filtered attributes if not already there
|
|
89
|
-
if (!(fieldName in filteredAttributes)) {
|
|
90
|
-
filteredAttributes[fieldName] = inputAttrs[fieldName]
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
})
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Now handle sparse fieldsets for virtual fields
|
|
97
|
-
if (requestedFields && requestedFields.length > 0) {
|
|
98
|
-
const requestedFieldsList = typeof requestedFields === 'string'
|
|
99
|
-
? requestedFields.split(',').map(f => f.trim())
|
|
100
|
-
: requestedFields
|
|
87
|
+
const fieldsToCompute = (schemaInfo.sortedComputedFields || []).filter(field => neededFields.has(field))
|
|
101
88
|
|
|
102
|
-
|
|
103
|
-
Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
104
|
-
if (fieldDef.virtual === true && fieldName in filteredAttributes) {
|
|
105
|
-
if (!requestedFieldsList.includes(fieldName)) {
|
|
106
|
-
delete filteredAttributes[fieldName]
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
})
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
// Determine which computed fields to calculate
|
|
113
|
-
// We only compute fields that are requested to optimize performance
|
|
114
|
-
let fieldsToCompute = []
|
|
115
|
-
if (requestedComputedFields) {
|
|
116
|
-
// Explicit list provided (from sparse fieldsets)
|
|
117
|
-
// Example: ?fields[products]=name,profit_margin -> only compute profit_margin
|
|
118
|
-
fieldsToCompute = requestedComputedFields
|
|
119
|
-
} else if (isMainResource || !parentContext?.queryParams?.fields) {
|
|
120
|
-
// No sparse fieldsets or this is the main resource - compute all fields
|
|
121
|
-
// This ensures all computed fields are available when no filtering is applied
|
|
122
|
-
fieldsToCompute = Object.keys(computedFields)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// Create compute context with all available resources
|
|
126
|
-
// IMPORTANT: We pass the TRANSFORMED attributes (after getters) to compute functions
|
|
127
|
-
// This ensures computed fields see the getter-processed values
|
|
128
|
-
// Example: if price getter converts string to number, profit_margin sees the number
|
|
89
|
+
// Computations receive getter-transformed values and earlier computed results.
|
|
129
90
|
const computeContext = {
|
|
130
|
-
|
|
131
|
-
|
|
91
|
+
id,
|
|
92
|
+
attributes: transformedAttributes,
|
|
93
|
+
record: { ...transformedAttributes },
|
|
132
94
|
context: parentContext,
|
|
133
|
-
helpers,
|
|
134
|
-
api,
|
|
95
|
+
helpers,
|
|
96
|
+
api,
|
|
135
97
|
}
|
|
136
98
|
|
|
137
|
-
// Auto-compute fields that have compute functions
|
|
138
99
|
for (const fieldName of fieldsToCompute) {
|
|
139
100
|
const fieldDef = computedFields[fieldName]
|
|
140
101
|
if (fieldDef && fieldDef.compute) {
|
|
141
102
|
try {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
filteredAttributes[fieldName] = await fieldDef.compute(computeContext)
|
|
103
|
+
const value = await fieldDef.compute(computeContext)
|
|
104
|
+
transformedAttributes[fieldName] = value
|
|
105
|
+
computeContext.record[fieldName] = value
|
|
146
106
|
} catch (error) {
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
107
|
+
throw wrapUnexpectedError(error, {
|
|
108
|
+
message: `Computation for field '${fieldName}' failed`,
|
|
109
|
+
context: { scopeName, fieldName, phase: 'computed' }
|
|
110
|
+
})
|
|
150
111
|
}
|
|
151
112
|
}
|
|
152
113
|
}
|
|
153
114
|
|
|
154
|
-
// Remove fields
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
// Parse requested fields if it's a string
|
|
163
|
-
const requested = typeof requestedFields === 'string'
|
|
164
|
-
? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
165
|
-
: requestedFields
|
|
166
|
-
|
|
167
|
-
for (const dep of computedDependencies) {
|
|
168
|
-
// Only remove if it wasn't explicitly requested
|
|
169
|
-
// Example: 'cost' is removed unless user explicitly asked for it
|
|
170
|
-
if (!requested.includes(dep)) {
|
|
171
|
-
delete finalAttributes[dep]
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
if (requestedFields && requestedFields.length > 0) {
|
|
177
|
-
const requestedList = typeof requestedFields === 'string'
|
|
178
|
-
? requestedFields.split(',').map((field) => field.trim()).filter((field) => field)
|
|
179
|
-
: requestedFields
|
|
180
|
-
const allowed = new Set(requestedList)
|
|
115
|
+
// Remove private fields and values fetched only for computation.
|
|
116
|
+
const finalAttributes = filterHiddenFields(
|
|
117
|
+
transformedAttributes,
|
|
118
|
+
{ structure: schemaInfo.outputFields },
|
|
119
|
+
requestedFields
|
|
120
|
+
)
|
|
121
|
+
if (requestedFields !== null) {
|
|
122
|
+
const allowed = new Set(requestedFields)
|
|
181
123
|
Object.keys(finalAttributes).forEach((key) => {
|
|
182
124
|
if (!allowed.has(key)) {
|
|
183
125
|
delete finalAttributes[key]
|
|
@@ -185,20 +127,41 @@ export default async function enrichAttributesMethod ({ context, params, runHook
|
|
|
185
127
|
})
|
|
186
128
|
}
|
|
187
129
|
|
|
188
|
-
// Create context for enrichAttributes hooks
|
|
189
130
|
Object.assign(context, {
|
|
190
131
|
parentContext,
|
|
191
|
-
attributes: finalAttributes,
|
|
132
|
+
attributes: finalAttributes,
|
|
192
133
|
computedFields,
|
|
193
|
-
requestedComputedFields:
|
|
134
|
+
requestedComputedFields: selectedComputedFields,
|
|
194
135
|
scopeName,
|
|
195
136
|
helpers,
|
|
196
137
|
api
|
|
197
138
|
})
|
|
198
139
|
|
|
199
|
-
// Run enrichAttributes hooks for additional/override computations
|
|
200
140
|
await runHooks('enrichAttributes')
|
|
201
141
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
142
|
+
return /** @type {import('../lib/storage/storage-types.js').StorageRow} */ (context.attributes)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Preserve include order and each resource's enrichment hooks and fieldset.
|
|
146
|
+
/** @param {LifecycleContext} context @param {Record<string, LifecycleResource>} scopes */
|
|
147
|
+
export async function enrichIncludedAttributes (context, scopes) {
|
|
148
|
+
for (const entry of (context.record?.included || [])) {
|
|
149
|
+
const entryScope = /** @type {LifecycleResource} */ (scopes[entry.type])
|
|
150
|
+
const entryComputed = entryScope.vars.schemaInfo?.computed || {}
|
|
151
|
+
const entryRequestedFields = getResourceFieldset(context.queryParams?.fields, entry.type)
|
|
152
|
+
const entryRequestedComputed = getRequestedComputedFields(
|
|
153
|
+
entry.type,
|
|
154
|
+
entryRequestedFields,
|
|
155
|
+
entryComputed
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
entry.attributes = await entryScope.enrichAttributes({
|
|
159
|
+
id: entry.id,
|
|
160
|
+
attributes: entry.attributes,
|
|
161
|
+
parentContext: context,
|
|
162
|
+
requestedComputedFields: entryRequestedComputed,
|
|
163
|
+
isMainResource: false,
|
|
164
|
+
computedDependencies: entry.__$jsonrestapi_computed_deps$__
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
}
|