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,639 @@
|
|
|
1
|
+
1 import {
|
|
2
|
+
2 RestApiValidationError,
|
|
3
|
+
3 RestApiPayloadError
|
|
4
|
+
4 } from '../../../lib/rest-api-errors.js'
|
|
5
|
+
5 import { transformSimplifiedToJsonApi } from '../lib/querying-writing/simplified-helpers.js'
|
|
6
|
+
6 import { createEnhancedLogger } from '../../../lib/enhanced-logger.js'
|
|
7
|
+
7
|
|
8
|
+
8 /**
|
|
9
|
+
9 * Gets an enhanced logger instance with full error details and stack traces
|
|
10
|
+
10 * @param {Object} log - The base logger instance
|
|
11
|
+
11 * @returns {Object} Enhanced logger instance
|
|
12
|
+
12 */
|
|
13
|
+
13 export const getEnhancedLogger = (log) => {
|
|
14
|
+
14 return createEnhancedLogger(log, {
|
|
15
|
+
15 logFullErrors: true,
|
|
16
|
+
16 includeStack: true
|
|
17
|
+
17 })
|
|
18
|
+
18 }
|
|
19
|
+
19
|
|
20
|
+
20 /**
|
|
21
|
+
21 * Cascades configuration values through multiple sources with fallback to default
|
|
22
|
+
22 * @param {string} settingName - The setting name to look for
|
|
23
|
+
23 * @param {Array} sources - Array of objects to search through
|
|
24
|
+
24 * @param {*} defaultValue - Default value if not found
|
|
25
|
+
25 * @returns {*} The first defined value found or the default
|
|
26
|
+
26 */
|
|
27
|
+
27 export const cascadeConfig = (settingName, sources, defaultValue) =>
|
|
28
|
+
28 sources.find(source => source?.[settingName] !== undefined)?.[settingName] ?? defaultValue
|
|
29
|
+
29
|
|
30
|
+
30 /**
|
|
31
|
+
31 * Normalizes return record settings to valid values
|
|
32
|
+
32 * @param {string|boolean} value - The value to normalize
|
|
33
|
+
33 * @returns {string} Normalized value: 'no', 'minimal', or 'full'
|
|
34
|
+
34 */
|
|
35
|
+
35 export function normalizeReturnValue (value) {
|
|
36
|
+
36 if (['no', 'minimal', 'full'].includes(value)) return value
|
|
37
|
+
37 return 'no' // default
|
|
38
|
+
38 }
|
|
39
|
+
39
|
|
40
|
+
40 /**
|
|
41
|
+
41 * Sets up common request context for REST API methods
|
|
42
|
+
42 * Handles simplified mode, transaction setup, and initial validation
|
|
43
|
+
43 *
|
|
44
|
+
44 * @param {Object} params - The method parameters
|
|
45
|
+
45 * @param {Object} context - The request context
|
|
46
|
+
46 * @param {Object} vars - Plugin variables
|
|
47
|
+
47 * @param {Object} scopes - All available scopes
|
|
48
|
+
48 * @param {Object} scopeOptions - Scope-specific options
|
|
49
|
+
49 * @param {string} scopeName - The name of the current scope
|
|
50
|
+
50 * @param {Object} api - The API instance
|
|
51
|
+
51 * @param {Object} helpers - Helper functions
|
|
52
|
+
52 * @returns {Object} An object containing schema-related shortcuts
|
|
53
|
+
53 */
|
|
54
|
+
54 export async function setupCommonRequest ({ params, context, vars, scopes, scopeOptions, scopeName, api, helpers }) {
|
|
55
|
+
55 // Determine which simplified setting to use based on transport
|
|
56
|
+
56 const isTransport = params.isTransport === true
|
|
57
|
+
57
|
|
58
|
+
58 // Use vars which automatically cascade from scope to global
|
|
59
|
+
59 const defaultSimplified = isTransport ? vars.simplifiedTransport : vars.simplifiedApi
|
|
60
|
+
60
|
|
61
|
+
61 // Get simplified setting - from params only (per-call override) or use default
|
|
62
|
+
62 context.simplified = params.simplified !== undefined ? params.simplified : defaultSimplified
|
|
63
|
+
63
|
|
64
|
+
64 // Special case: if no inputRecord provided, force simplified mode
|
|
65
|
+
65 if (!params.inputRecord && context.simplified === false) {
|
|
66
|
+
66 context.simplified = true
|
|
67
|
+
67 }
|
|
68
|
+
68
|
|
69
|
+
69 // Params is totally bypassed in simplified mode
|
|
70
|
+
70 if (context.simplified) {
|
|
71
|
+
71 if (params.inputRecord) {
|
|
72
|
+
72 context.inputRecord = params.inputRecord
|
|
73
|
+
73 context.params = params
|
|
74
|
+
74 } else {
|
|
75
|
+
75 context.inputRecord = params
|
|
76
|
+
76 // Preserve returnFullRecord if specified in params
|
|
77
|
+
77 context.params = params.returnFullRecord ? { returnFullRecord: params.returnFullRecord } : {}
|
|
78
|
+
78 }
|
|
79
|
+
79 } else {
|
|
80
|
+
80 context.inputRecord = params.inputRecord
|
|
81
|
+
81 context.params = params
|
|
82
|
+
82 }
|
|
83
|
+
83
|
|
84
|
+
84 // Assign common context properties
|
|
85
|
+
85 context.schemaInfo = scopes[scopeName].vars.schemaInfo
|
|
86
|
+
86
|
|
87
|
+
87 // Use vars which automatically cascade from scope to global
|
|
88
|
+
88 const defaultReturnFullRecord = isTransport ? vars.returnRecordTransport : vars.returnRecordApi
|
|
89
|
+
89
|
|
90
|
+
90 // Get return record setting - from params only (per-call override) or use default
|
|
91
|
+
91 const returnFullRecordRaw = context.params.returnFullRecord !== undefined
|
|
92
|
+
92 ? context.params.returnFullRecord
|
|
93
|
+
93 : defaultReturnFullRecord
|
|
94
|
+
94
|
|
95
|
+
95 // Normalize return record setting to always be an object with method keys
|
|
96
|
+
96 if (typeof returnFullRecordRaw === 'object' && returnFullRecordRaw !== null) {
|
|
97
|
+
97 // It's already an object, normalize the values
|
|
98
|
+
98 context.returnRecordSetting = {
|
|
99
|
+
99 post: normalizeReturnValue(returnFullRecordRaw.post),
|
|
100
|
+
100 put: normalizeReturnValue(returnFullRecordRaw.put),
|
|
101
|
+
101 patch: normalizeReturnValue(returnFullRecordRaw.patch)
|
|
102
|
+
102 }
|
|
103
|
+
103 } else {
|
|
104
|
+
104 // It's a single value (string or boolean), apply to all methods
|
|
105
|
+
105 const normalized = normalizeReturnValue(returnFullRecordRaw)
|
|
106
|
+
106 context.returnRecordSetting = {
|
|
107
|
+
107 post: normalized,
|
|
108
|
+
108 put: normalized,
|
|
109
|
+
109 patch: normalized
|
|
110
|
+
110 }
|
|
111
|
+
111 }
|
|
112
|
+
112
|
|
113
|
+
113 // Helper function to normalize return values (same as above)
|
|
114
|
+
114 function normalizeReturnValue (value) {
|
|
115
|
+
115 if (['no', 'minimal', 'full'].includes(value)) return value
|
|
116
|
+
116 return 'no' // default
|
|
117
|
+
117 }
|
|
118
|
+
118
|
|
119
|
+
119 // These only make sense as parameter per query, not in vars etc.
|
|
120
|
+
120 context.queryParams = params.queryParams || {}
|
|
121
|
+
121 context.queryParams.fields = cascadeConfig('fields', [context.queryParams], {})
|
|
122
|
+
122 context.queryParams.include = cascadeConfig('include', [context.queryParams], [])
|
|
123
|
+
123
|
|
124
|
+
124 context.scopeName = scopeName
|
|
125
|
+
125
|
|
126
|
+
126 // Transaction handling
|
|
127
|
+
127 context.transaction = params.transaction ||
|
|
128
|
+
128 (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
129
|
+
129 context.shouldCommit = !params.transaction && !!context.transaction
|
|
130
|
+
130 context.db = context.transaction || api.knex.instance
|
|
131
|
+
131
|
|
132
|
+
132 // These are just shortcuts used in this function and will be returned
|
|
133
|
+
133 const schema = context.schemaInfo.schemaInstance
|
|
134
|
+
134 const schemaStructure = context.schemaInfo.schemaInstance.structure
|
|
135
|
+
135 const schemaRelationships = context.schemaInfo.schemaRelationships
|
|
136
|
+
136
|
|
137
|
+
137 // Transform input if in simplified mode
|
|
138
|
+
138 if (context.simplified) {
|
|
139
|
+
139 context.inputRecord = transformSimplifiedToJsonApi(
|
|
140
|
+
140 { inputRecord: context.inputRecord },
|
|
141
|
+
141 { context: { scopeName, schemaStructure, schemaRelationships } }
|
|
142
|
+
142 )
|
|
143
|
+
143 } else {
|
|
144
|
+
144 // Strict mode: validate no belongsTo fields in attributes
|
|
145
|
+
145 if (context.inputRecord?.data?.attributes) {
|
|
146
|
+
146 for (const [key, fieldDef] of Object.entries(schemaStructure)) {
|
|
147
|
+
147 if (fieldDef.belongsTo && key in context.inputRecord.data.attributes) {
|
|
148
|
+
148 throw new RestApiValidationError(
|
|
149
|
+
149 `Field '${key}' is a foreign key and must be set via relationships, not attributes`,
|
|
150
|
+
150 { fields: [`data.attributes.${key}`] }
|
|
151
|
+
151 )
|
|
152
|
+
152 }
|
|
153
|
+
153 }
|
|
154
|
+
154 }
|
|
155
|
+
155 }
|
|
156
|
+
156
|
|
157
|
+
157 if (context.inputRecord.data.type !== scopeName) {
|
|
158
|
+
158 throw new RestApiValidationError(
|
|
159
|
+
159 `Resource type mismatch. Expected '${scopeName}' but got '${context.inputRecord.data.type}'`,
|
|
160
|
+
160 {
|
|
161
|
+
161 fields: ['data.type'],
|
|
162
|
+
162 violations: [{
|
|
163
|
+
163 field: 'data.type',
|
|
164
|
+
164 rule: 'resource_type_match',
|
|
165
|
+
165 message: `Resource type must be '${scopeName}'`
|
|
166
|
+
166 }]
|
|
167
|
+
167 }
|
|
168
|
+
168 )
|
|
169
|
+
169 }
|
|
170
|
+
170
|
|
171
|
+
171 // Remove included validation since JSON:API doesn't support it
|
|
172
|
+
172 if (context.inputRecord.included) {
|
|
173
|
+
173 throw new RestApiPayloadError(
|
|
174
|
+
174 context.method + ' requests cannot include an "included" array. JSON:API does not support creating multiple resources in a single request.',
|
|
175
|
+
175 { path: 'included', expected: 'undefined', received: 'array' }
|
|
176
|
+
176 )
|
|
177
|
+
177 }
|
|
178
|
+
178
|
|
179
|
+
179 // If both URL path ID and request body ID are provided, they must match
|
|
180
|
+
180 // Convert both to strings for comparison since databases may return numeric IDs
|
|
181
|
+
181 if (context.id && context.inputRecord.data.id && String(context.id) !== String(context.inputRecord.data.id)) {
|
|
182
|
+
182 throw new RestApiValidationError(
|
|
183
|
+
183 `ID mismatch. URL path ID '${context.id}' does not match request body ID '${context.inputRecord.data.id}'`,
|
|
184
|
+
184 {
|
|
185
|
+
185 fields: ['data.id'],
|
|
186
|
+
186 violations: [{
|
|
187
|
+
187 field: 'data.id',
|
|
188
|
+
188 rule: 'id_consistency',
|
|
189
|
+
189 message: 'Request body ID must match URL path ID when both are provided'
|
|
190
|
+
190 }]
|
|
191
|
+
191 }
|
|
192
|
+
192 )
|
|
193
|
+
193 }
|
|
194
|
+
194
|
|
195
|
+
195 // Return key schema-related objects for direct use in the main methods
|
|
196
|
+
196 return { schema, schemaStructure, schemaRelationships }
|
|
197
|
+
197 }
|
|
198
|
+
198
|
|
199
|
+
199 /**
|
|
200
|
+
200 * Handles error cleanup and logging for write methods (POST, PUT, PATCH)
|
|
201
|
+
201 *
|
|
202
|
+
202 * @param {Error} error - The error that was caught
|
|
203
|
+
203 * @param {Object} context - The request context
|
|
204
|
+
204 * @param {string} method - The HTTP method name (POST, PUT, PATCH)
|
|
205
|
+
205 * @param {string} scopeName - The name of the resource scope
|
|
206
|
+
206 * @param {Object} log - The logger instance
|
|
207
|
+
207 * @param {Function} runHooks - Function to run hooks
|
|
208
|
+
208 * @throws {Error} Re-throws the original error after cleanup
|
|
209
|
+
209 */
|
|
210
|
+
210 export const handleWriteMethodError = async (error, context, method, scopeName, log, runHooks) => {
|
|
211
|
+
211 // Rollback transaction if we created it
|
|
212
|
+
212 if (context.shouldCommit) {
|
|
213
|
+
213 await context.transaction.rollback()
|
|
214
|
+
214 await runHooks('afterRollback')
|
|
215
|
+
215 }
|
|
216
|
+
216
|
|
217
|
+
217 // Create enhanced logger for error logging
|
|
218
|
+
218 const enhancedLog = getEnhancedLogger(log)
|
|
219
|
+
219
|
|
220
|
+
220 // Log the full error details
|
|
221
|
+
221 enhancedLog.logError(`Error in ${method} method`, error, {
|
|
222
|
+
222 scopeName,
|
|
223
|
+
223 method: method.toLowerCase(),
|
|
224
|
+
224 inputRecord: context.inputRecord
|
|
225
|
+
225 })
|
|
226
|
+
226
|
|
227
|
+
227 throw error
|
|
228
|
+
228 }
|
|
229
|
+
229
|
|
230
|
+
230 /**
|
|
231
|
+
231 * Validates that a pivot resource exists for many-to-many relationships
|
|
232
|
+
232 *
|
|
233
|
+
233 * @param {Object} scopes - All available scopes/resources
|
|
234
|
+
234 * @param {Object} relDef - The relationship definition
|
|
235
|
+
235 * @param {string} relName - The relationship name
|
|
236
|
+
236 * @throws {RestApiValidationError} If the pivot resource doesn't exist
|
|
237
|
+
237 */
|
|
238
|
+
238 export const validatePivotResource = (scopes, relDef, relName) => {
|
|
239
|
+
239 if (!scopes[relDef.through]) {
|
|
240
|
+
240 throw new RestApiValidationError(
|
|
241
|
+
241 `Pivot resource '${relDef.through}' not found for relationship '${relName}'`,
|
|
242
|
+
242 {
|
|
243
|
+
243 fields: [`relationships.${relName}`],
|
|
244
|
+
244 violations: [{
|
|
245
|
+
245 field: `relationships.${relName}`,
|
|
246
|
+
246 rule: 'missing_pivot_resource',
|
|
247
|
+
247 message: `Pivot resource '${relDef.through}' must be defined`
|
|
248
|
+
248 }]
|
|
249
|
+
249 }
|
|
250
|
+
250 )
|
|
251
|
+
251 }
|
|
252
|
+
252 }
|
|
253
|
+
253
|
|
254
|
+
254 /**
|
|
255
|
+
255 * Gets the appropriate hook suffix based on HTTP method
|
|
256
|
+
256 *
|
|
257
|
+
257 * @param {string} method - The HTTP method (e.g., 'post', 'get')
|
|
258
|
+
258 * @returns {string} The capitalized method name for hook naming
|
|
259
|
+
259 */
|
|
260
|
+
260 export const getMethodHookSuffix = (method) => {
|
|
261
|
+
261 return method.charAt(0).toUpperCase() + method.slice(1)
|
|
262
|
+
262 }
|
|
263
|
+
263
|
|
264
|
+
264 /**
|
|
265
|
+
265 * Validates resource attributes before write operations
|
|
266
|
+
266 *
|
|
267
|
+
267 * @param {Object} params - Validation parameters
|
|
268
|
+
268 * @param {Object} params.context - The request context
|
|
269
|
+
269 * @param {Object} params.schema - The resource schema
|
|
270
|
+
270 * @param {Object} params.belongsToUpdates - BelongsTo relationship updates
|
|
271
|
+
271 * @param {Function} params.runHooks - Function to run hooks
|
|
272
|
+
272 * @param {boolean} params.isPartialValidation - Whether this is partial validation (for PATCH)
|
|
273
|
+
273 * @throws {RestApiValidationError} If validation fails
|
|
274
|
+
274 */
|
|
275
|
+
275 export const validateResourceAttributesBeforeWrite = async ({
|
|
276
|
+
276 context,
|
|
277
|
+
277 schema,
|
|
278
|
+
278 belongsToUpdates,
|
|
279
|
+
279 runHooks,
|
|
280
|
+
280 isPartialValidation = false
|
|
281
|
+
281 }) => {
|
|
282
|
+
282 // Dynamically get the method suffix
|
|
283
|
+
283 const methodSpecificHookSuffix = getMethodHookSuffix(context.method)
|
|
284
|
+
284
|
|
285
|
+
285 await runHooks('beforeSchemaValidate')
|
|
286
|
+
286 await runHooks(`beforeSchemaValidate${methodSpecificHookSuffix}`)
|
|
287
|
+
287
|
|
288
|
+
288 // Store original input attributes before validation adds defaults (primarily for POST)
|
|
289
|
+
289 // Only if it's not already set and the current method is POST
|
|
290
|
+
290 if (!context.originalInputAttributes && context.method === 'post') {
|
|
291
|
+
291 context.originalInputAttributes = { ...(context.inputRecord.data.attributes || {}) }
|
|
292
|
+
292 }
|
|
293
|
+
293
|
|
294
|
+
294 // Merge belongsTo updates with attributes for validation
|
|
295
|
+
295 const attributesToValidate = {
|
|
296
|
+
296 ...context.inputRecord.data.attributes,
|
|
297
|
+
297 ...belongsToUpdates
|
|
298
|
+
298 }
|
|
299
|
+
299
|
|
300
|
+
300 // Extract computed fields and foreign key fields before validation
|
|
301
|
+
301 const computedFields = {}
|
|
302
|
+
302 const foreignKeyFields = {}
|
|
303
|
+
303 const schemaStructure = context.schemaInfo.schemaStructure || {}
|
|
304
|
+
304 Object.entries(attributesToValidate).forEach(([key, value]) => {
|
|
305
|
+
305 const fieldDef = schemaStructure[key]
|
|
306
|
+
306 // Computed fields should be stripped from input
|
|
307
|
+
307 if (fieldDef && fieldDef.computed === true && value !== undefined) {
|
|
308
|
+
308 computedFields[key] = value
|
|
309
|
+
309 }
|
|
310
|
+
310 // Foreign key fields (belongsTo with 'as' property) should NOT be in attributes
|
|
311
|
+
311 // They should only come through relationships or belongsToUpdates
|
|
312
|
+
312 // Check if key exists in belongsToUpdates (not just truthy value)
|
|
313
|
+
313 if (fieldDef && fieldDef.belongsTo && fieldDef.as && value !== undefined && !(key in belongsToUpdates)) {
|
|
314
|
+
314 foreignKeyFields[key] = value
|
|
315
|
+
315 }
|
|
316
|
+
316 })
|
|
317
|
+
317
|
|
318
|
+
318 // Warn if computed fields were sent in the input
|
|
319
|
+
319 if (Object.keys(computedFields).length > 0) {
|
|
320
|
+
320 const fieldNames = Object.keys(computedFields).join(', ')
|
|
321
|
+
321 console.warn(`Computed fields [${fieldNames}] were sent in input for resource '${context.scopeName}' but will be ignored as they are output-only`)
|
|
322
|
+
322 }
|
|
323
|
+
323
|
|
324
|
+
324 // Reject if foreign key fields were sent directly in attributes
|
|
325
|
+
325 if (Object.keys(foreignKeyFields).length > 0) {
|
|
326
|
+
326 const violations = Object.keys(foreignKeyFields).map(field => {
|
|
327
|
+
327 const fieldDef = schemaStructure[field]
|
|
328
|
+
328 return {
|
|
329
|
+
329 field: `data.attributes.${field}`,
|
|
330
|
+
330 rule: 'foreign_key_in_attributes',
|
|
331
|
+
331 message: `Foreign key field '${field}' should not be in attributes. Use 'data.relationships.${fieldDef.as}' instead.`
|
|
332
|
+
332 }
|
|
333
|
+
333 })
|
|
334
|
+
334
|
|
335
|
+
335 throw new RestApiValidationError(
|
|
336
|
+
336 'Foreign key fields cannot be set directly in attributes',
|
|
337
|
+
337 {
|
|
338
|
+
338 fields: violations.map(v => v.field),
|
|
339
|
+
339 violations
|
|
340
|
+
340 }
|
|
341
|
+
341 )
|
|
342
|
+
342 }
|
|
343
|
+
343
|
|
344
|
+
344 // Filter out computed fields and foreign key fields from validation
|
|
345
|
+
345 // Virtual fields MUST go through validation
|
|
346
|
+
346 const attributesForValidation = Object.entries(attributesToValidate)
|
|
347
|
+
347 .filter(([key, _]) => {
|
|
348
|
+
348 const fieldDef = schemaStructure[key]
|
|
349
|
+
349 // Exclude computed fields
|
|
350
|
+
350 if (fieldDef && fieldDef.computed === true) {
|
|
351
|
+
351 return false
|
|
352
|
+
352 }
|
|
353
|
+
353 // Exclude foreign key fields that weren't provided via belongsToUpdates
|
|
354
|
+
354 if (fieldDef && fieldDef.belongsTo && fieldDef.as && !belongsToUpdates[key]) {
|
|
355
|
+
355 return false
|
|
356
|
+
356 }
|
|
357
|
+
357 return true
|
|
358
|
+
358 })
|
|
359
|
+
359 .reduce((acc, [key, value]) => {
|
|
360
|
+
360 acc[key] = value
|
|
361
|
+
361 return acc
|
|
362
|
+
362 }, {})
|
|
363
|
+
363
|
|
364
|
+
364 const validationOptions = isPartialValidation ? { onlyObjectValues: true } : {}
|
|
365
|
+
365
|
|
366
|
+
366 const { validatedObject, errors } = await schema.validate(attributesForValidation, validationOptions)
|
|
367
|
+
367
|
|
368
|
+
368 if (Object.keys(errors).length > 0) {
|
|
369
|
+
369 // --- START OF MODIFICATION ---
|
|
370
|
+
370 const schemaStructure = context.schemaInfo.schemaInstance.structure // Get the schema structure for lookup
|
|
371
|
+
371
|
|
372
|
+
372 const violations = Object.entries(errors).map(([field, error]) => {
|
|
373
|
+
373 let fieldPath = `data.attributes.${field}` // Default path for attributes
|
|
374
|
+
374
|
|
375
|
+
375 // Check if this field is a foreign key that has an 'as' alias
|
|
376
|
+
376 const fieldDef = schemaStructure[field]
|
|
377
|
+
377 if (fieldDef && fieldDef.belongsTo && fieldDef.as) {
|
|
378
|
+
378 // If it's a belongsTo field with an alias, rewrite the path to the relationship alias
|
|
379
|
+
379 fieldPath = `data.relationships.${fieldDef.as}.data.id`
|
|
380
|
+
380 }
|
|
381
|
+
381 // For many-to-many relationships, the original `transformSimplifiedToJsonApi`
|
|
382
|
+
382 // already puts them under `relationships.relName.data`, so `field` here
|
|
383
|
+
383 // would already be the relationship name, not a foreign key.
|
|
384
|
+
384 // However, if a validation error somehow slips through for a pivot table field
|
|
385
|
+
385 // that doesn't have an 'as' alias but is a foreign key, you might need
|
|
386
|
+
386 // more sophisticated mapping. For now, this covers belongsTo.
|
|
387
|
+
387
|
|
388
|
+
388 return {
|
|
389
|
+
389 field: fieldPath,
|
|
390
|
+
390 rule: error.code || 'invalid_value',
|
|
391
|
+
391 message: error.message
|
|
392
|
+
392 }
|
|
393
|
+
393 })
|
|
394
|
+
394 // --- END OF MODIFICATION ---
|
|
395
|
+
395
|
|
396
|
+
396 throw new RestApiValidationError(
|
|
397
|
+
397 'Schema validation failed for resource attributes',
|
|
398
|
+
398 {
|
|
399
|
+
399 fields: violations.map(v => v.field), // Use the potentially rewritten fields
|
|
400
|
+
400 violations
|
|
401
|
+
401 }
|
|
402
|
+
402 )
|
|
403
|
+
403 }
|
|
404
|
+
404
|
|
405
|
+
405 // Update attributes with validated values
|
|
406
|
+
406 // Virtual fields have now been validated and cast properly
|
|
407
|
+
407 context.inputRecord.data.attributes = validatedObject
|
|
408
|
+
408
|
|
409
|
+
409 await runHooks(`afterSchemaValidate${methodSpecificHookSuffix}`)
|
|
410
|
+
410 await runHooks('afterSchemaValidate')
|
|
411
|
+
411 }
|
|
412
|
+
412
|
|
413
|
+
413 /**
|
|
414
|
+
414 * Validates that the user has access to all resources referenced in relationships
|
|
415
|
+
415 *
|
|
416
|
+
416 * @param {object} context - The context object containing authentication info
|
|
417
|
+
417 * @param {object} inputRecord - The input record containing relationships to validate
|
|
418
|
+
418 * @param {object} helpers - Data helpers including dataGetMinimal
|
|
419
|
+
419 * @param {function} runHooks - Function to run hooks
|
|
420
|
+
420 * @param {object} api - API instance to access resources
|
|
421
|
+
421 * @throws {Error} If user doesn't have access to any related resource
|
|
422
|
+
422 */
|
|
423
|
+
423 export const validateRelationshipAccess = async (context, inputRecord, helpers, runHooks, api) => {
|
|
424
|
+
424 if (!inputRecord?.data?.relationships) return
|
|
425
|
+
425
|
|
426
|
+
426 for (const [relName, relData] of Object.entries(inputRecord.data.relationships)) {
|
|
427
|
+
427 if (!relData?.data) continue
|
|
428
|
+
428
|
|
429
|
+
429 // Handle both single and array relationships
|
|
430
|
+
430 const relatedItems = Array.isArray(relData.data) ? relData.data : [relData.data]
|
|
431
|
+
431
|
|
432
|
+
432 for (const item of relatedItems) {
|
|
433
|
+
433 // Get the scope for the related resource
|
|
434
|
+
434 const relatedScope = api.resources[item.type]
|
|
435
|
+
435 if (!relatedScope) {
|
|
436
|
+
436 throw new Error(`Unknown resource type: ${item.type}`)
|
|
437
|
+
437 }
|
|
438
|
+
438
|
|
439
|
+
439 // Create context for dataGetMinimal
|
|
440
|
+
440 const getContext = {
|
|
441
|
+
441 ...context,
|
|
442
|
+
442 id: item.id,
|
|
443
|
+
443 schemaInfo: relatedScope.vars.schemaInfo,
|
|
444
|
+
444 scopeName: item.type,
|
|
445
|
+
445 method: 'get', // We're checking read permission
|
|
446
|
+
446 isUpdate: false
|
|
447
|
+
447 }
|
|
448
|
+
448
|
|
449
|
+
449 // Get the minimal record
|
|
450
|
+
450 const record = await helpers.dataGetMinimal({
|
|
451
|
+
451 scopeName: item.type,
|
|
452
|
+
452 context: getContext,
|
|
453
|
+
453 runHooks
|
|
454
|
+
454 })
|
|
455
|
+
455
|
|
456
|
+
456 if (!record) {
|
|
457
|
+
457 throw new Error(`Cannot create relationship to non-existent ${item.type} with id ${item.id}`)
|
|
458
|
+
458 }
|
|
459
|
+
459
|
|
460
|
+
460 // Check permissions using the related scope's checkPermissions
|
|
461
|
+
461 await relatedScope.checkPermissions({
|
|
462
|
+
462 method: 'get',
|
|
463
|
+
463 originalContext: {
|
|
464
|
+
464 ...context,
|
|
465
|
+
465 id: item.id,
|
|
466
|
+
466 minimalRecord: record
|
|
467
|
+
467 }
|
|
468
|
+
468 })
|
|
469
|
+
469 }
|
|
470
|
+
470 }
|
|
471
|
+
471 }
|
|
472
|
+
472
|
|
473
|
+
473 /**
|
|
474
|
+
474 * Applies field setters to transform attribute values before storage
|
|
475
|
+
475 * Setters are applied in dependency order to handle interdependent fields
|
|
476
|
+
476 *
|
|
477
|
+
477 * @param {object} attributes - The validated attributes to transform
|
|
478
|
+
478 * @param {object} schemaInfo - Schema information including fieldSetters
|
|
479
|
+
479 * @param {object} context - The request context
|
|
480
|
+
480 * @param {object} api - The API instance
|
|
481
|
+
481 * @param {object} helpers - Helper functions
|
|
482
|
+
482 * @returns {object} Transformed attributes ready for storage
|
|
483
|
+
483 */
|
|
484
|
+
484 export const applyFieldSetters = async (attributes, schemaInfo, context, api, helpers) => {
|
|
485
|
+
485 const fieldSetters = schemaInfo.fieldSetters || {}
|
|
486
|
+
486 const sortedSetterFields = schemaInfo.sortedSetterFields || []
|
|
487
|
+
487
|
|
488
|
+
488 // No setters to apply
|
|
489
|
+
489 if (sortedSetterFields.length === 0) {
|
|
490
|
+
490 return attributes
|
|
491
|
+
491 }
|
|
492
|
+
492
|
|
493
|
+
493 const transformedAttributes = { ...attributes }
|
|
494
|
+
494
|
|
495
|
+
495 for (const fieldName of sortedSetterFields) {
|
|
496
|
+
496 // Only process fields that exist in the attributes
|
|
497
|
+
497 if (fieldName in transformedAttributes) {
|
|
498
|
+
498 const setterInfo = fieldSetters[fieldName]
|
|
499
|
+
499 try {
|
|
500
|
+
500 const setterContext = {
|
|
501
|
+
501 attributes: transformedAttributes, // Current state with previous setters applied
|
|
502
|
+
502 fieldName,
|
|
503
|
+
503 originalValue: attributes[fieldName],
|
|
504
|
+
504 originalAttributes: attributes,
|
|
505
|
+
505 scopeName: context.scopeName,
|
|
506
|
+
506 method: context.method,
|
|
507
|
+
507 api,
|
|
508
|
+
508 helpers,
|
|
509
|
+
509 auth: context.auth
|
|
510
|
+
510 }
|
|
511
|
+
511 transformedAttributes[fieldName] = await setterInfo.setter(
|
|
512
|
+
512 transformedAttributes[fieldName],
|
|
513
|
+
513 setterContext
|
|
514
|
+
514 )
|
|
515
|
+
515 } catch (error) {
|
|
516
|
+
516 console.warn(`Setter for field '${fieldName}' failed:`, error)
|
|
517
|
+
517 // Keep original value if setter fails
|
|
518
|
+
518 }
|
|
519
|
+
519 }
|
|
520
|
+
520 }
|
|
521
|
+
521
|
|
522
|
+
522 return transformedAttributes
|
|
523
|
+
523 }
|
|
524
|
+
524
|
|
525
|
+
525 export async function handleRecordReturnAfterWrite ({
|
|
526
|
+
526 context,
|
|
527
|
+
527 scopeName,
|
|
528
|
+
528 api,
|
|
529
|
+
529 scopes,
|
|
530
|
+
530 schemaStructure,
|
|
531
|
+
531 schemaRelationships,
|
|
532
|
+
532 scopeOptions,
|
|
533
|
+
533 vars,
|
|
534
|
+
534 runHooks,
|
|
535
|
+
535 helpers,
|
|
536
|
+
536 log
|
|
537
|
+
537 }) {
|
|
538
|
+
538 // Create enhanced logger for warnings
|
|
539
|
+
539 const enhancedLog = getEnhancedLogger(log)
|
|
540
|
+
540 const methodSpecificHookSuffix = getMethodHookSuffix(context.method)
|
|
541
|
+
541
|
|
542
|
+
542 // Step 1: Set up record state for hooks
|
|
543
|
+
543 // Handle originalMinimalRecord and minimalRecord based on method type
|
|
544
|
+
544 if (context.method === 'DELETE') {
|
|
545
|
+
545 // For DELETE, keep the deleted record reference
|
|
546
|
+
546 if (context.minimalRecord) {
|
|
547
|
+
547 context.originalMinimalRecord = context.minimalRecord
|
|
548
|
+
548 }
|
|
549
|
+
549 } else {
|
|
550
|
+
550 // For POST, PUT, PATCH - save the original state if it exists
|
|
551
|
+
551 if (context.minimalRecord) {
|
|
552
|
+
552 context.originalMinimalRecord = context.minimalRecord
|
|
553
|
+
553 }
|
|
554
|
+
554
|
|
555
|
+
555 // Fetch the current state of the record after the write operation
|
|
556
|
+
556 try {
|
|
557
|
+
557 const currentRecord = await helpers.dataGetMinimal({
|
|
558
|
+
558 scopeName,
|
|
559
|
+
559 context,
|
|
560
|
+
560 runHooks
|
|
561
|
+
561 })
|
|
562
|
+
562 context.minimalRecord = currentRecord
|
|
563
|
+
563 } catch (error) {
|
|
564
|
+
564 enhancedLog.warn(`Could not fetch minimal record after ${context.method} operation`, { error, id: context.id })
|
|
565
|
+
565 }
|
|
566
|
+
566 }
|
|
567
|
+
567
|
|
568
|
+
568 // Step 2: Determine what to return based on configuration
|
|
569
|
+
569 const returnMode = context.returnRecordSetting[context.method]
|
|
570
|
+
570
|
|
571
|
+
571 // Case 1: Return nothing (204 No Content)
|
|
572
|
+
572 if (returnMode === 'no') {
|
|
573
|
+
573 context.responseRecord = undefined
|
|
574
|
+
574 await runHooks('finish')
|
|
575
|
+
575 await runHooks(`finish${methodSpecificHookSuffix}`)
|
|
576
|
+
576 return undefined
|
|
577
|
+
577 }
|
|
578
|
+
578
|
|
579
|
+
579 // Case 2: Return minimal record (just type and id)
|
|
580
|
+
580 if (returnMode === 'minimal') {
|
|
581
|
+
581 if (context.simplified) {
|
|
582
|
+
582 context.responseRecord = {
|
|
583
|
+
583 id: String(context.id),
|
|
584
|
+
584 type: scopeName
|
|
585
|
+
585 }
|
|
586
|
+
586 } else {
|
|
587
|
+
587 context.responseRecord = {
|
|
588
|
+
588 data: {
|
|
589
|
+
589 type: scopeName,
|
|
590
|
+
590 id: String(context.id)
|
|
591
|
+
591 }
|
|
592
|
+
592 }
|
|
593
|
+
593 }
|
|
594
|
+
594 await runHooks('finish')
|
|
595
|
+
595 await runHooks(`finish${methodSpecificHookSuffix}`)
|
|
596
|
+
596 return context.responseRecord
|
|
597
|
+
597 }
|
|
598
|
+
598
|
|
599
|
+
599 // Case 3: Return full record
|
|
600
|
+
600 if (returnMode === 'full') {
|
|
601
|
+
601 // Fetch the complete record using the GET method
|
|
602
|
+
602 const fullRecord = await api.resources[scopeName].get({
|
|
603
|
+
603 id: context.id,
|
|
604
|
+
604 queryParams: context.queryParams,
|
|
605
|
+
605 transaction: context.transaction,
|
|
606
|
+
606 simplified: context.simplified
|
|
607
|
+
607 }, { ...context })
|
|
608
|
+
608
|
|
609
|
+
609 context.responseRecord = fullRecord || undefined
|
|
610
|
+
610
|
|
611
|
+
611 // Run finish hooks
|
|
612
|
+
612 await runHooks('finish')
|
|
613
|
+
613 await runHooks(`finish${methodSpecificHookSuffix}`)
|
|
614
|
+
614
|
|
615
|
+
615 // No transformation needed - GET already returns the correct format
|
|
616
|
+
616 // based on context.simplified
|
|
617
|
+
617 return context.responseRecord
|
|
618
|
+
618 }
|
|
619
|
+
619
|
|
620
|
+
620 // This should never be reached, but just in case
|
|
621
|
+
621 throw new Error(`Invalid returnMode: ${returnMode}`)
|
|
622
|
+
622 }
|
|
623
|
+
623
|
|
624
|
+
624 export const findRelationshipDefinition = (schemaInfo, relationshipName) => {
|
|
625
|
+
625 // First check schemaRelationships (for relationships defined in relationships object)
|
|
626
|
+
626 const relDef = schemaInfo.schemaRelationships?.[relationshipName]
|
|
627
|
+
627 if (relDef) {
|
|
628
|
+
628 return relDef
|
|
629
|
+
629 }
|
|
630
|
+
630
|
|
631
|
+
631 // Then check schema fields for belongsTo relationships with matching 'as' property
|
|
632
|
+
632 for (const [fieldName, fieldDef] of Object.entries(schemaInfo.schemaStructure || {})) {
|
|
633
|
+
633 if (fieldDef.as === relationshipName && fieldDef.belongsTo) {
|
|
634
|
+
634 return fieldDef
|
|
635
|
+
635 }
|
|
636
|
+
636 }
|
|
637
|
+
637
|
|
638
|
+
638 return null
|
|
639
|
+
639 }
|