json-rest-api 1.0.21 → 1.0.22
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/audit/00-charter.md +103 -0
- package/audit/01-scope-map.md +84 -0
- package/audit/02-coverage-matrix.csv +174 -0
- package/audit/03-invariants.md +123 -0
- package/audit/04-standards-rubric.md +56 -0
- package/audit/05-competitor-rubric.md +77 -0
- package/audit/06-findings.md +160 -0
- package/audit/07-risk-register.csv +10 -0
- package/audit/08-remediation-roadmap.md +177 -0
- package/audit/09-signoff-check.md +23 -0
- package/audit/10-final-synthesis.md +29 -0
- package/audit/evidence/code/all-files-json-rest-api.txt +168 -0
- package/audit/evidence/code/all-files-json-rest-schema.txt +12 -0
- package/audit/evidence/code/boundary-leak-checks.txt +72 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +480 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +532 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +175 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +138 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +222 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +342 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +304 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +188 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +336 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +165 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +265 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +369 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +72 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +139 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +45 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +396 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +162 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +1314 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +488 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +484 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +857 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +565 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +485 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +560 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +167 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +109 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +1064 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +1983 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +328 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +119 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +160 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +300 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +245 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +459 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +237 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +1135 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +314 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +7 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +71 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +102 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +49 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +39 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +23 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +13 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +639 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +117 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +104 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +199 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +353 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +61 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +184 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +55 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +191 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +110 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +168 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +253 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +207 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +12 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +165 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +668 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +621 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +218 -0
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +124 -0
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +269 -0
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +242 -0
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +77 -0
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +238 -0
- package/audit/evidence/code/critical-pass1-summary.csv +67 -0
- package/audit/evidence/code/critical-pass2-summary.csv +67 -0
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +57 -0
- package/audit/evidence/code/error-model-signals.txt +98 -0
- package/audit/evidence/code/git-files-json-rest-api.txt +171 -0
- package/audit/evidence/code/git-files-json-rest-schema.txt +27 -0
- package/audit/evidence/code/hook-flow-signals.txt +282 -0
- package/audit/evidence/code/idproperty-signals.txt +157 -0
- package/audit/evidence/code/import-cycles-non-anyapi.txt +3 -0
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +96 -0
- package/audit/evidence/code/imports-json-rest-api.txt +354 -0
- package/audit/evidence/code/imports-json-rest-schema.txt +15 -0
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +160 -0
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +13 -0
- package/audit/evidence/code/non-anyapi-core-files.txt +101 -0
- package/audit/evidence/code/permission-signals.txt +193 -0
- package/audit/evidence/code/query-include-signals.txt +687 -0
- package/audit/evidence/code/transaction-signals.txt +28 -0
- package/audit/evidence/code/validation-signals.txt +650 -0
- package/audit/evidence/code/write-path-signals.txt +61 -0
- package/audit/evidence/docs/docs-claims-signals.txt +1075 -0
- package/audit/evidence/docs/schema-docs-claims-signals.txt +179 -0
- package/audit/evidence/external/corpus-freeze.md +21 -0
- package/audit/evidence/external/npm-competitor-metadata.txt +70 -0
- package/audit/evidence/external/npm-download-signals.txt +23 -0
- package/audit/evidence/external/npm-jsonapi-server.txt +8 -0
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +38 -0
- package/audit/evidence/external/readme-fortune-signals.txt +26 -0
- package/audit/evidence/external/readme-fortune.md +165 -0
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +80 -0
- package/audit/evidence/external/readme-json-api-serializer.md +690 -0
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +22 -0
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +63 -0
- package/audit/evidence/external/readme-jsonapi-serializer.md +315 -0
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +19 -0
- package/audit/evidence/external/readme-jsonapi-server.md +104 -0
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +4 -0
- package/audit/evidence/external/readme-nestjs-json-api.md +94 -0
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +47 -0
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +447 -0
- package/audit/evidence/external/standard-jsonapi-snippets.txt +84 -0
- package/audit/evidence/external/standard-rfc-snippets.txt +168 -0
- package/audit/evidence/external/standards-sources.txt +6 -0
- package/audit/evidence/runtime/baseline-commits.txt +2 -0
- package/audit/evidence/runtime/db-adapter-assumptions.txt +3 -0
- package/audit/evidence/runtime/environment.txt +4 -0
- package/audit/evidence/runtime/invariant-runtime-mapping.md +38 -0
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +1 -0
- package/audit/evidence/runtime/probe-patch-relationship-error.log +76 -0
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +67 -0
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +1 -0
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +62 -0
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +47 -0
- package/audit/evidence/runtime/test-exitcodes.txt +4 -0
- package/audit/evidence/runtime/test-summary.md +107 -0
- package/audit/evidence/runtime/trace-anchors.txt +61 -0
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +125 -0
- package/audit/evidence/tests/failing-tests-summary.txt +25 -0
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +1 -0
- package/audit/evidence/tests/npm-test-json-rest-api.log +5885 -0
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +1 -0
- package/audit/evidence/tests/npm-test-json-rest-schema.log +356 -0
- package/audit/evidence/tests/query-include-test-signals.txt +948 -0
- package/audit/evidence/tests/relationship-test-signals.txt +223 -0
- package/audit/evidence/tests/skipped-tests.txt +10 -0
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +30 -0
- package/audit/evidence/tests/test-suite-list.txt +32 -0
- package/audit/subsystems/docs-drift.md +49 -0
- package/audit/subsystems/pass2-cross-review.md +131 -0
- package/audit/subsystems/plugins-access.md +51 -0
- package/audit/subsystems/plugins-bulk.md +50 -0
- package/audit/subsystems/plugins-cors.md +48 -0
- package/audit/subsystems/plugins-file.md +53 -0
- package/audit/subsystems/plugins-multihome.md +50 -0
- package/audit/subsystems/plugins-positioning.md +54 -0
- package/audit/subsystems/plugins-socketio.md +50 -0
- package/audit/subsystems/query-engine.md +61 -0
- package/audit/subsystems/relationships.md +55 -0
- package/audit/subsystems/rest-orchestration.md +66 -0
- package/audit/subsystems/schema-core.md +64 -0
- package/audit/subsystems/storage-knex.md +57 -0
- package/audit/subsystems/tests.md +48 -0
- package/audit/subsystems/transport-express.md +55 -0
- package/audit/traces/trace-read-get.md +22 -0
- package/audit/traces/trace-read-query.md +25 -0
- package/audit/traces/trace-relationship-endpoints.md +31 -0
- package/audit/traces/trace-write-delete.md +17 -0
- package/audit/traces/trace-write-patch.md +26 -0
- package/audit/traces/trace-write-post.md +22 -0
- package/audit/traces/trace-write-put.md +23 -0
- package/package.json +3 -3
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
1 import { RestApiResourceError, RestApiValidationError, RestApiPayloadError } from '../../../lib/rest-api-errors.js'
|
|
2
|
+
2 import { findRelationshipDefinition, handleWriteMethodError } from './common.js'
|
|
3
|
+
3
|
|
4
|
+
4 /**
|
|
5
|
+
5 * DELETE RELATIONSHIP
|
|
6
|
+
6 * Removes specific members from a to-many relationship
|
|
7
|
+
7 * DELETE /api/articles/1/relationships/tags
|
|
8
|
+
8 *
|
|
9
|
+
9 * @param {string} id - The ID of the resource
|
|
10
|
+
10 * @param {string} relationshipName - The name of the relationship
|
|
11
|
+
11 * @param {array} relationshipData - Array of resource identifiers to remove
|
|
12
|
+
12 * @returns {Promise<void>} 204 No Content
|
|
13
|
+
13 */
|
|
14
|
+
14 export default async function deleteRelationshipMethod ({ params, context, vars, helpers, scope, scopes, runHooks, scopeName, api, log }) {
|
|
15
|
+
15 context.method = 'deleteRelationship'
|
|
16
|
+
16 context.id = params.id
|
|
17
|
+
17 context.relationshipName = params.relationshipName
|
|
18
|
+
18 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
19
|
+
19
|
|
20
|
+
20 // Transaction handling
|
|
21
|
+
21 context.transaction = params.transaction ||
|
|
22
|
+
22 (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
23
|
+
23 context.shouldCommit = !params.transaction && !!context.transaction
|
|
24
|
+
24 context.db = context.transaction || api.knex.instance
|
|
25
|
+
25
|
|
26
|
+
26 try {
|
|
27
|
+
27 // Validate
|
|
28
|
+
28 const relDef = findRelationshipDefinition(context.schemaInfo, context.relationshipName)
|
|
29
|
+
29 if (!relDef) {
|
|
30
|
+
30 throw new RestApiResourceError(
|
|
31
|
+
31 `Relationship '${context.relationshipName}' not found on resource '${scopeName}'`,
|
|
32
|
+
32 { subtype: 'relationship_not_found' }
|
|
33
|
+
33 )
|
|
34
|
+
34 }
|
|
35
|
+
35
|
|
36
|
+
36 if (relDef.type !== 'hasMany' && relDef.type !== 'manyToMany') {
|
|
37
|
+
37 throw new RestApiValidationError(
|
|
38
|
+
38 `Cannot DELETE from to-one relationship '${context.relationshipName}'`,
|
|
39
|
+
39 { fields: ['data'] }
|
|
40
|
+
40 )
|
|
41
|
+
41 }
|
|
42
|
+
42
|
|
43
|
+
43 if (!Array.isArray(params.relationshipData)) {
|
|
44
|
+
44 throw new RestApiPayloadError('DELETE from relationship requires array of resource identifiers')
|
|
45
|
+
45 }
|
|
46
|
+
46
|
|
47
|
+
47 // Check permissions
|
|
48
|
+
48 await runHooks('checkPermissions')
|
|
49
|
+
49 await runHooks('checkPermissionsDeleteRelationship')
|
|
50
|
+
50
|
|
51
|
+
51 // Verify parent exists
|
|
52
|
+
52 const exists = await helpers.dataExists({
|
|
53
|
+
53 scopeName,
|
|
54
|
+
54 context: { db: context.db, id: context.id, schemaInfo: context.schemaInfo }
|
|
55
|
+
55 })
|
|
56
|
+
56
|
|
57
|
+
57 if (!exists) {
|
|
58
|
+
58 throw new RestApiResourceError('Resource not found', { subtype: 'not_found' })
|
|
59
|
+
59 }
|
|
60
|
+
60
|
|
61
|
+
61 // Remove relationships
|
|
62
|
+
62 if (relDef?.through) {
|
|
63
|
+
63 if (api.anyapi?.links?.removeMany) {
|
|
64
|
+
64 await api.anyapi.links.removeMany({
|
|
65
|
+
65 context,
|
|
66
|
+
66 scopeName,
|
|
67
|
+
67 relName: context.relationshipName,
|
|
68
|
+
68 relDef,
|
|
69
|
+
69 relData: params.relationshipData,
|
|
70
|
+
70 })
|
|
71
|
+
71 } else {
|
|
72
|
+
72 const knex = api.knex?.instance || helpers.db
|
|
73
|
+
73 const pivotResource = relDef.through
|
|
74
|
+
74 const pivotScope = api.resources[pivotResource]
|
|
75
|
+
75 const pivotTable = pivotScope?.vars?.schemaInfo?.tableName || pivotResource
|
|
76
|
+
76 const localKey = relDef.foreignKey
|
|
77
|
+
77 const foreignKey = relDef.otherKey
|
|
78
|
+
78
|
|
79
|
+
79 for (const identifier of params.relationshipData) {
|
|
80
|
+
80 await knex(pivotTable)
|
|
81
|
+
81 .where(localKey, context.id)
|
|
82
|
+
82 .where(foreignKey, identifier.id)
|
|
83
|
+
83 .delete()
|
|
84
|
+
84 .transacting(context.transaction)
|
|
85
|
+
85 }
|
|
86
|
+
86 }
|
|
87
|
+
87 } else {
|
|
88
|
+
88 // Null out foreign keys for hasMany
|
|
89
|
+
89 const targetType = relDef.target
|
|
90
|
+
90 for (const identifier of params.relationshipData) {
|
|
91
|
+
91 await api.resources[targetType].patch({
|
|
92
|
+
92 id: identifier.id,
|
|
93
|
+
93 inputRecord: {
|
|
94
|
+
94 data: {
|
|
95
|
+
95 type: targetType,
|
|
96
|
+
96 id: identifier.id,
|
|
97
|
+
97 attributes: { [relDef.foreignKey]: null }
|
|
98
|
+
98 }
|
|
99
|
+
99 },
|
|
100
|
+
100 transaction: context.transaction,
|
|
101
|
+
101 simplified: false
|
|
102
|
+
102 })
|
|
103
|
+
103 }
|
|
104
|
+
104 }
|
|
105
|
+
105
|
|
106
|
+
106 await runHooks('finish')
|
|
107
|
+
107 await runHooks('finishDeleteRelationship')
|
|
108
|
+
108
|
|
109
|
+
109 if (context.shouldCommit) {
|
|
110
|
+
110 await context.transaction.commit()
|
|
111
|
+
111 }
|
|
112
|
+
112
|
|
113
|
+
113 // 204 No Content
|
|
114
|
+
114 } catch (error) {
|
|
115
|
+
115 await handleWriteMethodError(error, context, 'DELETE_RELATIONSHIP', scopeName, log, runHooks)
|
|
116
|
+
116 }
|
|
117
|
+
117 }
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
1 import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
+
2 import { handleWriteMethodError } from './common.js'
|
|
3
|
+
3
|
|
4
|
+
4 /**
|
|
5
|
+
5 * DELETE
|
|
6
|
+
6 * Permanently deletes a resource.
|
|
7
|
+
7 * Returns 204 No Content on success, indicating the resource has been removed.
|
|
8
|
+
8 * This method does not return the deleted resource.
|
|
9
|
+
9 */
|
|
10
|
+
10 export default async function deleteMethod ({
|
|
11
|
+
11 params,
|
|
12
|
+
12 context,
|
|
13
|
+
13 vars,
|
|
14
|
+
14 helpers,
|
|
15
|
+
15 scope,
|
|
16
|
+
16 scopes,
|
|
17
|
+
17 runHooks,
|
|
18
|
+
18 apiOptions,
|
|
19
|
+
19 pluginOptions,
|
|
20
|
+
20 scopeOptions,
|
|
21
|
+
21 scopeName,
|
|
22
|
+
22 api,
|
|
23
|
+
23 log
|
|
24
|
+
24 }) {
|
|
25
|
+
25 // Make the method available to all hooks
|
|
26
|
+
26 context.method = 'delete'
|
|
27
|
+
27
|
|
28
|
+
28 // Set the ID in context
|
|
29
|
+
29 context.id = params.id
|
|
30
|
+
30
|
|
31
|
+
31 // Set scopeName in context (needed for broadcasting)
|
|
32
|
+
32 context.scopeName = scopeName
|
|
33
|
+
33
|
|
34
|
+
34 // Set schema info even for DELETE (needed by storage layer)
|
|
35
|
+
35 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
36
|
+
36
|
|
37
|
+
37 // Transaction handling
|
|
38
|
+
38 context.transaction = params.transaction ||
|
|
39
|
+
39 (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
40
|
+
40 context.shouldCommit = !params.transaction && !!context.transaction
|
|
41
|
+
41 context.db = context.transaction || api.knex.instance
|
|
42
|
+
42
|
|
43
|
+
43 try {
|
|
44
|
+
44 // No payload validation needed for DELETE
|
|
45
|
+
45
|
|
46
|
+
46 // Fetch minimal record for authorization and logging
|
|
47
|
+
47 const minimalRecord = await helpers.dataGetMinimal({
|
|
48
|
+
48 scopeName,
|
|
49
|
+
49 context,
|
|
50
|
+
50 runHooks
|
|
51
|
+
51 })
|
|
52
|
+
52
|
|
53
|
+
53 if (!minimalRecord) {
|
|
54
|
+
54 throw new RestApiResourceError(
|
|
55
|
+
55 'Resource not found',
|
|
56
|
+
56 {
|
|
57
|
+
57 subtype: 'not_found',
|
|
58
|
+
58 resourceType: scopeName,
|
|
59
|
+
59 resourceId: context.id
|
|
60
|
+
60 }
|
|
61
|
+
61 )
|
|
62
|
+
62 }
|
|
63
|
+
63
|
|
64
|
+
64 context.originalMinimalRecord = minimalRecord
|
|
65
|
+
65 context.minimalRecord = minimalRecord
|
|
66
|
+
66
|
|
67
|
+
67 // Centralised checkPermissions function
|
|
68
|
+
68 await scope.checkPermissions({
|
|
69
|
+
69 method: 'delete',
|
|
70
|
+
70 originalContext: context,
|
|
71
|
+
71 })
|
|
72
|
+
72
|
|
73
|
+
73 // Before data operations
|
|
74
|
+
74 await runHooks('beforeDataCall')
|
|
75
|
+
75 await runHooks('beforeDataCallDelete')
|
|
76
|
+
76
|
|
77
|
+
77 // Initialize record context for hooks
|
|
78
|
+
78 context.record = {}
|
|
79
|
+
79
|
|
80
|
+
80 // Call the storage helper
|
|
81
|
+
81 await helpers.dataDelete({
|
|
82
|
+
82 scopeName,
|
|
83
|
+
83 context
|
|
84
|
+
84 })
|
|
85
|
+
85
|
|
86
|
+
86 await runHooks('afterDataCallDelete')
|
|
87
|
+
87 await runHooks('afterDataCall')
|
|
88
|
+
88
|
|
89
|
+
89 // No return record for DELETE (204 No Content)
|
|
90
|
+
90
|
|
91
|
+
91 await runHooks('finish')
|
|
92
|
+
92 await runHooks('finishDelete')
|
|
93
|
+
93
|
|
94
|
+
94 // Commit transaction if we created it
|
|
95
|
+
95 if (context.shouldCommit) {
|
|
96
|
+
96 await context.transaction.commit()
|
|
97
|
+
97 await runHooks('afterCommit')
|
|
98
|
+
98 }
|
|
99
|
+
99
|
|
100
|
+
100 // DELETE typically returns void/undefined (204 No Content)
|
|
101
|
+
101 } catch (error) {
|
|
102
|
+
102 await handleWriteMethodError(error, context, 'DELETE', scopeName, log, runHooks)
|
|
103
|
+
103 }
|
|
104
|
+
104 }
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
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
|
+
8 *
|
|
9
|
+
9 *
|
|
10
|
+
10 */
|
|
11
|
+
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
|
+
18 // Extract parameters passed to enrichAttributes
|
|
19
|
+
19 // - attributes: The raw attributes from database
|
|
20
|
+
20 // - parentContext: The context from the calling method (has queryParams, transaction, etc.)
|
|
21
|
+
21 // - requestedComputedFields: Which computed fields to calculate (from sparse fieldsets)
|
|
22
|
+
22 // - isMainResource: Whether this is the main resource or an included one
|
|
23
|
+
23 // - computedDependencies: Fields fetched only for computation (to be removed)
|
|
24
|
+
24 const { attributes, parentContext, requestedComputedFields, isMainResource, computedDependencies } = params || {}
|
|
25
|
+
25
|
|
26
|
+
26 // Return empty object if no attributes provided
|
|
27
|
+
27 if (!attributes) {
|
|
28
|
+
28 return {}
|
|
29
|
+
29 }
|
|
30
|
+
30
|
|
31
|
+
31 // Get schema, computed field definitions
|
|
32
|
+
32 const schemaStructure = scopes[scopeName]?.vars?.schemaInfo?.schemaStructure || {}
|
|
33
|
+
33 const computedFields = scopes[scopeName]?.vars?.schemaInfo?.computed || {}
|
|
34
|
+
34 const fieldGetters = scopes[scopeName]?.vars?.schemaInfo?.fieldGetters || {}
|
|
35
|
+
35 const sortedGetterFields = scopes[scopeName]?.vars?.schemaInfo?.sortedGetterFields || []
|
|
36
|
+
36
|
|
37
|
+
37 // Make a copy of attributes for transformation
|
|
38
|
+
38 const transformedAttributes = { ...attributes }
|
|
39
|
+
39
|
|
40
|
+
40 // STEP 1: Apply field getters in dependency order (before filtering)
|
|
41
|
+
41 // This ensures getters see all fields including hidden ones
|
|
42
|
+
42 for (const fieldName of sortedGetterFields) {
|
|
43
|
+
43 // Only process if field exists in attributes
|
|
44
|
+
44 if (fieldName in transformedAttributes) {
|
|
45
|
+
45 const getterInfo = fieldGetters[fieldName]
|
|
46
|
+
46 try {
|
|
47
|
+
47 const getterContext = {
|
|
48
|
+
48 attributes: transformedAttributes, // Current state with previous getters applied
|
|
49
|
+
49 fieldName,
|
|
50
|
+
50 originalValue: attributes[fieldName], // Original untransformed value
|
|
51
|
+
51 originalAttributes: attributes, // All original attributes
|
|
52
|
+
52 record: transformedAttributes, // Alias for compatibility
|
|
53
|
+
53 parentContext,
|
|
54
|
+
54 scopeName,
|
|
55
|
+
55 api,
|
|
56
|
+
56 helpers,
|
|
57
|
+
57 isMainResource: isMainResource !== false
|
|
58
|
+
58 }
|
|
59
|
+
59
|
|
60
|
+
60 // Apply getter (can be async)
|
|
61
|
+
61 transformedAttributes[fieldName] = await getterInfo.getter(
|
|
62
|
+
62 transformedAttributes[fieldName],
|
|
63
|
+
63 getterContext
|
|
64
|
+
64 )
|
|
65
|
+
65 } catch (error) {
|
|
66
|
+
66 console.error(`Error in getter for field '${fieldName}' in ${scopeName}:`, error)
|
|
67
|
+
67 // Keep current value on error (don't break the whole request)
|
|
68
|
+
68 }
|
|
69
|
+
69 }
|
|
70
|
+
70 }
|
|
71
|
+
71
|
|
72
|
+
72 // Filter hidden fields from attributes based on visibility rules
|
|
73
|
+
73 // This removes hidden:true fields and normallyHidden:true fields (unless requested)
|
|
74
|
+
74 const requestedFields = parentContext?.queryParams?.fields?.[scopeName]
|
|
75
|
+
75 const filteredAttributes = filterHiddenFields(transformedAttributes, { structure: schemaStructure }, requestedFields)
|
|
76
|
+
76
|
|
77
|
+
77 // Handle virtual fields - they need to be added from parent context if available
|
|
78
|
+
78 // Virtual fields come from input and need to be preserved in responses
|
|
79
|
+
79 if (parentContext && parentContext.inputRecord?.data?.attributes) {
|
|
80
|
+
80 const inputAttrs = parentContext.inputRecord.data.attributes
|
|
81
|
+
81 Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
82
|
+
82 if (fieldDef.virtual === true && fieldName in inputAttrs && inputAttrs[fieldName] !== undefined && inputAttrs[fieldName] !== null) {
|
|
83
|
+
83 // Add virtual field to filtered attributes if not already there
|
|
84
|
+
84 if (!(fieldName in filteredAttributes)) {
|
|
85
|
+
85 filteredAttributes[fieldName] = inputAttrs[fieldName]
|
|
86
|
+
86 }
|
|
87
|
+
87 }
|
|
88
|
+
88 })
|
|
89
|
+
89 }
|
|
90
|
+
90
|
|
91
|
+
91 // Now handle sparse fieldsets for virtual fields
|
|
92
|
+
92 if (requestedFields && requestedFields.length > 0) {
|
|
93
|
+
93 const requestedFieldsList = typeof requestedFields === 'string'
|
|
94
|
+
94 ? requestedFields.split(',').map(f => f.trim())
|
|
95
|
+
95 : requestedFields
|
|
96
|
+
96
|
|
97
|
+
97 // Remove virtual fields that weren't requested
|
|
98
|
+
98 Object.entries(schemaStructure).forEach(([fieldName, fieldDef]) => {
|
|
99
|
+
99 if (fieldDef.virtual === true && fieldName in filteredAttributes) {
|
|
100
|
+
100 if (!requestedFieldsList.includes(fieldName)) {
|
|
101
|
+
101 delete filteredAttributes[fieldName]
|
|
102
|
+
102 }
|
|
103
|
+
103 }
|
|
104
|
+
104 })
|
|
105
|
+
105 }
|
|
106
|
+
106
|
|
107
|
+
107 // Determine which computed fields to calculate
|
|
108
|
+
108 // We only compute fields that are requested to optimize performance
|
|
109
|
+
109 let fieldsToCompute = []
|
|
110
|
+
110 if (requestedComputedFields) {
|
|
111
|
+
111 // Explicit list provided (from sparse fieldsets)
|
|
112
|
+
112 // Example: ?fields[products]=name,profit_margin -> only compute profit_margin
|
|
113
|
+
113 fieldsToCompute = requestedComputedFields
|
|
114
|
+
114 } else if (isMainResource || !parentContext?.queryParams?.fields) {
|
|
115
|
+
115 // No sparse fieldsets or this is the main resource - compute all fields
|
|
116
|
+
116 // This ensures all computed fields are available when no filtering is applied
|
|
117
|
+
117 fieldsToCompute = Object.keys(computedFields)
|
|
118
|
+
118 }
|
|
119
|
+
119
|
|
120
|
+
120 // Create compute context with all available resources
|
|
121
|
+
121 // IMPORTANT: We pass the TRANSFORMED attributes (after getters) to compute functions
|
|
122
|
+
122 // This ensures computed fields see the getter-processed values
|
|
123
|
+
123 // Example: if price getter converts string to number, profit_margin sees the number
|
|
124
|
+
124 const computeContext = {
|
|
125
|
+
125 attributes: transformedAttributes, // Transformed attributes after getters
|
|
126
|
+
126 record: { ...transformedAttributes }, // Full record for convenience
|
|
127
|
+
127 context: parentContext,
|
|
128
|
+
128 helpers, // API helpers for complex operations
|
|
129
|
+
129 api, // Full API instance
|
|
130
|
+
130 }
|
|
131
|
+
131
|
|
132
|
+
132 // Auto-compute fields that have compute functions
|
|
133
|
+
133 for (const fieldName of fieldsToCompute) {
|
|
134
|
+
134 const fieldDef = computedFields[fieldName]
|
|
135
|
+
135 if (fieldDef && fieldDef.compute) {
|
|
136
|
+
136 try {
|
|
137
|
+
137 // Call the compute function with full context
|
|
138
|
+
138 // Example: profit_margin compute gets { attributes: { price: 100, cost: 60 } }
|
|
139
|
+
139 // and returns: ((100 - 60) / 100 * 100) = "40.00"
|
|
140
|
+
140 filteredAttributes[fieldName] = await fieldDef.compute(computeContext)
|
|
141
|
+
141 } catch (error) {
|
|
142
|
+
142 // Log error but don't fail the request - computed fields shouldn't break API
|
|
143
|
+
143 console.error(`Error computing field '${fieldName}' for ${scopeName}:`, error)
|
|
144
|
+
144 filteredAttributes[fieldName] = null
|
|
145
|
+
145 }
|
|
146
|
+
146 }
|
|
147
|
+
147 }
|
|
148
|
+
148
|
|
149
|
+
149 // Remove fields that were only fetched as dependencies
|
|
150
|
+
150 // This is the key to the dependency resolution feature:
|
|
151
|
+
151 // 1. We fetched dependencies from DB (e.g., 'cost' for profit_margin)
|
|
152
|
+
152 // 2. We used them in compute functions
|
|
153
|
+
153 // 3. Now we remove them if they weren't explicitly requested
|
|
154
|
+
154 // Example: User requests profit_margin, we fetch cost, compute, then remove cost
|
|
155
|
+
155 const finalAttributes = { ...filteredAttributes }
|
|
156
|
+
156 if (requestedFields && computedDependencies && computedDependencies.length > 0) {
|
|
157
|
+
157 // Parse requested fields if it's a string
|
|
158
|
+
158 const requested = typeof requestedFields === 'string'
|
|
159
|
+
159 ? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
160
|
+
160 : requestedFields
|
|
161
|
+
161
|
|
162
|
+
162 for (const dep of computedDependencies) {
|
|
163
|
+
163 // Only remove if it wasn't explicitly requested
|
|
164
|
+
164 // Example: 'cost' is removed unless user explicitly asked for it
|
|
165
|
+
165 if (!requested.includes(dep)) {
|
|
166
|
+
166 delete finalAttributes[dep]
|
|
167
|
+
167 }
|
|
168
|
+
168 }
|
|
169
|
+
169 }
|
|
170
|
+
170
|
|
171
|
+
171 if (requestedFields && requestedFields.length > 0) {
|
|
172
|
+
172 const requestedList = typeof requestedFields === 'string'
|
|
173
|
+
173 ? requestedFields.split(',').map((field) => field.trim()).filter((field) => field)
|
|
174
|
+
174 : requestedFields
|
|
175
|
+
175 const allowed = new Set(requestedList)
|
|
176
|
+
176 Object.keys(finalAttributes).forEach((key) => {
|
|
177
|
+
177 if (!allowed.has(key)) {
|
|
178
|
+
178 delete finalAttributes[key]
|
|
179
|
+
179 }
|
|
180
|
+
180 })
|
|
181
|
+
181 }
|
|
182
|
+
182
|
|
183
|
+
183 // Create context for enrichAttributes hooks
|
|
184
|
+
184 Object.assign(context, {
|
|
185
|
+
185 parentContext,
|
|
186
|
+
186 attributes: finalAttributes, // Use the final attributes after dependency removal
|
|
187
|
+
187 computedFields,
|
|
188
|
+
188 requestedComputedFields: fieldsToCompute,
|
|
189
|
+
189 scopeName,
|
|
190
|
+
190 helpers,
|
|
191
|
+
191 api
|
|
192
|
+
192 })
|
|
193
|
+
193
|
|
194
|
+
194 // Run enrichAttributes hooks for additional/override computations
|
|
195
|
+
195 await runHooks('enrichAttributes')
|
|
196
|
+
196
|
|
197
|
+
197 // Return the attributes from context, which hooks may have modified
|
|
198
|
+
198 return context.attributes
|
|
199
|
+
199 };
|