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,485 @@
|
|
|
1
|
+
1 import { getForeignKeyFields as getForeignKeyFieldsFromUtils } from '../querying-writing/field-utils.js'
|
|
2
|
+
2 import { RELATIONSHIPS_KEY, RELATIONSHIP_METADATA_KEY, ROW_NUMBER_KEY, COMPUTED_DEPENDENCIES_KEY, getSchemaStructure } from '../querying-writing/knex-constants.js'
|
|
3
|
+
3 import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl } from './url-helpers.js'
|
|
4
|
+
4
|
|
5
|
+
5 /**
|
|
6
|
+
6 * Transforms a flat database record into JSON:API resource format
|
|
7
|
+
7 *
|
|
8
|
+
8 * @private
|
|
9
|
+
9 * @param {Object} scope - Scope containing schema and configuration
|
|
10
|
+
10 * @param {Object} record - Database record to transform
|
|
11
|
+
11 * @param {Object} deps - Dependencies including context with scopeName and polymorphicFields
|
|
12
|
+
12 * @returns {Object|null} JSON:API resource object or null if no record
|
|
13
|
+
13 *
|
|
14
|
+
14 * @example
|
|
15
|
+
15 * // Input: Database record with foreign keys and internal fields
|
|
16
|
+
16 * const record = {
|
|
17
|
+
17 * id: 1,
|
|
18
|
+
18 * title: 'Hello World',
|
|
19
|
+
19 * content: 'Article content',
|
|
20
|
+
20 * author_id: 2, // Foreign key
|
|
21
|
+
21 * category_id: 5, // Foreign key
|
|
22
|
+
22 * __$jsonrestapi_rn$__: 1 // Internal field
|
|
23
|
+
23 * };
|
|
24
|
+
24 *
|
|
25
|
+
25 * const jsonApiResource = toJsonApi(scope, record, deps);
|
|
26
|
+
26 *
|
|
27
|
+
27 * // Output: Clean JSON:API resource
|
|
28
|
+
28 * // {
|
|
29
|
+
29 * // type: 'articles',
|
|
30
|
+
30 * // id: '1',
|
|
31
|
+
31 * // attributes: {
|
|
32
|
+
32 * // title: 'Hello World',
|
|
33
|
+
33 * // content: 'Article content'
|
|
34
|
+
34 * // // Note: author_id, category_id, and internal fields are removed
|
|
35
|
+
35 * // }
|
|
36
|
+
36 * // }
|
|
37
|
+
37 *
|
|
38
|
+
38 * @example
|
|
39
|
+
39 * // Input: Polymorphic record
|
|
40
|
+
40 * const record = {
|
|
41
|
+
41 * id: 10,
|
|
42
|
+
42 * text: 'Great article!',
|
|
43
|
+
43 * commentable_type: 'articles', // Polymorphic type field
|
|
44
|
+
44 * commentable_id: 1 // Polymorphic id field
|
|
45
|
+
45 * };
|
|
46
|
+
46 * // deps.context.polymorphicFields = Set(['commentable_type', 'commentable_id'])
|
|
47
|
+
47 *
|
|
48
|
+
48 * const jsonApiResource = toJsonApi(scope, record, deps);
|
|
49
|
+
49 *
|
|
50
|
+
50 * // Output: Polymorphic fields filtered out
|
|
51
|
+
51 * // {
|
|
52
|
+
52 * // type: 'comments',
|
|
53
|
+
53 * // id: '10',
|
|
54
|
+
54 * // attributes: {
|
|
55
|
+
55 * // text: 'Great article!'
|
|
56
|
+
56 * // }
|
|
57
|
+
57 * // }
|
|
58
|
+
58 *
|
|
59
|
+
59 * @description
|
|
60
|
+
60 * Used by:
|
|
61
|
+
61 * - toJsonApiRecord calls this as the core transformation logic
|
|
62
|
+
62 * - buildJsonApiResponse uses this indirectly through toJsonApiRecord
|
|
63
|
+
63 *
|
|
64
|
+
64 * Purpose:
|
|
65
|
+
65 * - Separates database structure from API structure
|
|
66
|
+
66 * - Filters out foreign keys that become relationships in JSON:API
|
|
67
|
+
67 * - Removes internal fields used for query optimization
|
|
68
|
+
68 * - Ensures clean attribute objects without implementation details
|
|
69
|
+
69 *
|
|
70
|
+
70 * Data flow:
|
|
71
|
+
71 * - Called after database query returns flat records
|
|
72
|
+
72 * - Transforms each record individually
|
|
73
|
+
73 * - Output feeds into relationship building and response assembly
|
|
74
|
+
74 */
|
|
75
|
+
75 const toJsonApi = (scope, record, deps) => {
|
|
76
|
+
76 if (!record) return null
|
|
77
|
+
77
|
|
78
|
+
78 const schemaInstance = scope.vars.schemaInfo.schemaInstance
|
|
79
|
+
79
|
|
80
|
+
80 const { context } = deps
|
|
81
|
+
81 const scopeName = context.scopeName
|
|
82
|
+
82 const idProperty = context.schemaInfo?.idProperty || 'id'
|
|
83
|
+
83 const polymorphicFields = context.polymorphicFields || new Set()
|
|
84
|
+
84
|
|
85
|
+
85 const { id, ...allAttributes } = record
|
|
86
|
+
86
|
|
87
|
+
87 const foreignKeys = schemaInstance ? getForeignKeyFieldsFromUtils(schemaInstance) : new Set()
|
|
88
|
+
88
|
|
89
|
+
89 const internalFields = new Set([
|
|
90
|
+
90 RELATIONSHIPS_KEY,
|
|
91
|
+
91 RELATIONSHIP_METADATA_KEY,
|
|
92
|
+
92 ROW_NUMBER_KEY,
|
|
93
|
+
93 COMPUTED_DEPENDENCIES_KEY
|
|
94
|
+
94 ])
|
|
95
|
+
95
|
|
96
|
+
96 const attributes = {}
|
|
97
|
+
97 Object.entries(allAttributes).forEach(([key, value]) => {
|
|
98
|
+
98 if (!foreignKeys.has(key) && !polymorphicFields.has(key) && !internalFields.has(key) && key !== idProperty) {
|
|
99
|
+
99 attributes[key] = value
|
|
100
|
+
100 }
|
|
101
|
+
101 })
|
|
102
|
+
102
|
|
103
|
+
103 return {
|
|
104
|
+
104 type: scopeName,
|
|
105
|
+
105 id: String(id),
|
|
106
|
+
106 attributes
|
|
107
|
+
107 }
|
|
108
|
+
108 }
|
|
109
|
+
109
|
|
110
|
+
110 /**
|
|
111
|
+
111 * Converts a database record to JSON:API format with proper field filtering
|
|
112
|
+
112 *
|
|
113
|
+
113 * @param {Object} scope - Scope containing schema and relationship definitions
|
|
114
|
+
114 * @param {Object} record - Raw database record
|
|
115
|
+
115 * @param {string} scopeName - Resource type name
|
|
116
|
+
116 * @returns {Object} JSON:API formatted resource
|
|
117
|
+
117 *
|
|
118
|
+
118 * @example
|
|
119
|
+
119 * // Input: Database record with various field types
|
|
120
|
+
120 * const record = {
|
|
121
|
+
121 * id: 1,
|
|
122
|
+
122 * title: 'My Article',
|
|
123
|
+
123 * author_id: 2, // belongsTo relationship
|
|
124
|
+
124 * category_id: 3, // belongsTo relationship
|
|
125
|
+
125 * views: 150,
|
|
126
|
+
126 * published: true
|
|
127
|
+
127 * };
|
|
128
|
+
128 *
|
|
129
|
+
129 * const jsonApi = toJsonApiRecord(scope, record, 'articles');
|
|
130
|
+
130 *
|
|
131
|
+
131 * // Output: Foreign keys filtered out
|
|
132
|
+
132 * // {
|
|
133
|
+
133 * // type: 'articles',
|
|
134
|
+
134 * // id: '1',
|
|
135
|
+
135 * // attributes: {
|
|
136
|
+
136 * // title: 'My Article',
|
|
137
|
+
137 * // views: 150,
|
|
138
|
+
138 * // published: true
|
|
139
|
+
139 * // }
|
|
140
|
+
140 * // }
|
|
141
|
+
141 *
|
|
142
|
+
142 * @example
|
|
143
|
+
143 * // Input: Record with polymorphic relationship
|
|
144
|
+
144 * const record = {
|
|
145
|
+
145 * id: 5,
|
|
146
|
+
146 * body: 'Nice post!',
|
|
147
|
+
147 * author_id: 10,
|
|
148
|
+
148 * commentable_type: 'posts', // Polymorphic
|
|
149
|
+
149 * commentable_id: 3 // Polymorphic
|
|
150
|
+
150 * };
|
|
151
|
+
151 * // Schema has polymorphic relationship defined
|
|
152
|
+
152 *
|
|
153
|
+
153 * const jsonApi = toJsonApiRecord(scope, record, 'comments');
|
|
154
|
+
154 *
|
|
155
|
+
155 * // Output: Both foreign keys and polymorphic fields filtered
|
|
156
|
+
156 * // {
|
|
157
|
+
157 * // type: 'comments',
|
|
158
|
+
158 * // id: '5',
|
|
159
|
+
159 * // attributes: {
|
|
160
|
+
160 * // body: 'Nice post!'
|
|
161
|
+
161 * // }
|
|
162
|
+
162 * // }
|
|
163
|
+
163 *
|
|
164
|
+
164 * @description
|
|
165
|
+
165 * Used by:
|
|
166
|
+
166 * - rest-api-knex-plugin's dataGet method for single resource responses
|
|
167
|
+
167 * - rest-api-knex-plugin's dataQuery method for collection responses
|
|
168
|
+
168 * - processIncludes when transforming included resources
|
|
169
|
+
169 * - buildJsonApiResponse as the primary transformation function
|
|
170
|
+
170 *
|
|
171
|
+
171 * Purpose:
|
|
172
|
+
172 * - Provides consistent JSON:API transformation across all query operations
|
|
173
|
+
173 * - Automatically identifies and filters foreign keys based on schema
|
|
174
|
+
174 * - Handles polymorphic relationship fields (type/id pairs)
|
|
175
|
+
175 * - Ensures IDs are always strings as required by JSON:API spec
|
|
176
|
+
176 *
|
|
177
|
+
177 * Data flow:
|
|
178
|
+
178 * 1. Database query returns flat records with all fields
|
|
179
|
+
179 * 2. toJsonApiRecord identifies foreign keys from schema
|
|
180
|
+
180 * 3. Filters out foreign keys and internal fields from attributes
|
|
181
|
+
181 * 4. Returns clean JSON:API resource ready for relationship processing
|
|
182
|
+
182 * 5. buildJsonApiResponse adds relationships and links to complete the response
|
|
183
|
+
183 */
|
|
184
|
+
184 export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
185
|
+
185 const {
|
|
186
|
+
186 vars: {
|
|
187
|
+
187 schemaInfo: { schemaRelationships: relationships, idProperty }
|
|
188
|
+
188 }
|
|
189
|
+
189 } = scope
|
|
190
|
+
190
|
|
191
|
+
191 const polymorphicFields = new Set()
|
|
192
|
+
192 try {
|
|
193
|
+
193 Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
194
|
+
194 if (relDef.belongsToPolymorphic) {
|
|
195
|
+
195 const typeFieldName = relDef.belongsToPolymorphic.typeField || `${relName}_type`
|
|
196
|
+
196 const idFieldName = relDef.belongsToPolymorphic.idField || `${relName}_id`
|
|
197
|
+
197 polymorphicFields.add(typeFieldName)
|
|
198
|
+
198 polymorphicFields.add(idFieldName)
|
|
199
|
+
199 }
|
|
200
|
+
200 })
|
|
201
|
+
201 } catch (e) {
|
|
202
|
+
202 }
|
|
203
|
+
203
|
|
204
|
+
204 const deps = {
|
|
205
|
+
205 context: {
|
|
206
|
+
206 scopeName,
|
|
207
|
+
207 schemaInfo: { idProperty },
|
|
208
|
+
208 polymorphicFields
|
|
209
|
+
209 }
|
|
210
|
+
210 }
|
|
211
|
+
211
|
|
212
|
+
212 return toJsonApi(scope, record, deps)
|
|
213
|
+
213 }
|
|
214
|
+
214
|
|
215
|
+
215 /**
|
|
216
|
+
216 * Builds complete JSON:API response with data, relationships, links, and optional includes
|
|
217
|
+
217 *
|
|
218
|
+
218 * @async
|
|
219
|
+
219 * @param {Object} scope - Scope containing schema and configuration
|
|
220
|
+
220 * @param {Array<Object>} records - Primary records to include in response
|
|
221
|
+
221 * @param {Array<Object>} included - Resources to include in 'included' array
|
|
222
|
+
222 * @param {boolean} isSingle - Whether this is a single resource response
|
|
223
|
+
223 * @param {string} scopeName - Resource type name
|
|
224
|
+
224 * @param {Object} context - Request context with pagination metadata
|
|
225
|
+
225 * @returns {Promise<Object>} Complete JSON:API response document
|
|
226
|
+
226 *
|
|
227
|
+
227 * @example
|
|
228
|
+
228 * // Input: Single article with author include
|
|
229
|
+
229 * const records = [{
|
|
230
|
+
230 * id: 1,
|
|
231
|
+
231 * title: 'Article Title',
|
|
232
|
+
232 * content: 'Article content',
|
|
233
|
+
233 * author_id: 2,
|
|
234
|
+
234 * category_id: 3
|
|
235
|
+
235 * }];
|
|
236
|
+
236 *
|
|
237
|
+
237 * const included = [{
|
|
238
|
+
238 * type: 'authors',
|
|
239
|
+
239 * id: '2',
|
|
240
|
+
240 * attributes: { name: 'John Doe', email: 'john@example.com' }
|
|
241
|
+
241 * }];
|
|
242
|
+
242 *
|
|
243
|
+
243 * const response = await buildJsonApiResponse(scope, records, included, true, 'articles', context);
|
|
244
|
+
244 *
|
|
245
|
+
245 * // Output: Complete JSON:API document
|
|
246
|
+
246 * // {
|
|
247
|
+
247 * // data: {
|
|
248
|
+
248 * // type: 'articles',
|
|
249
|
+
249 * // id: '1',
|
|
250
|
+
250 * // attributes: {
|
|
251
|
+
251 * // title: 'Article Title',
|
|
252
|
+
252 * // content: 'Article content'
|
|
253
|
+
253 * // },
|
|
254
|
+
254 * // relationships: {
|
|
255
|
+
255 * // author: {
|
|
256
|
+
256 * // data: { type: 'authors', id: '2' },
|
|
257
|
+
257 * // links: {
|
|
258
|
+
258 * // self: '/articles/1/relationships/author',
|
|
259
|
+
259 * // related: '/articles/1/author'
|
|
260
|
+
260 * // }
|
|
261
|
+
261 * // },
|
|
262
|
+
262 * // category: {
|
|
263
|
+
263 * // data: { type: 'categories', id: '3' },
|
|
264
|
+
264 * // links: {
|
|
265
|
+
265 * // self: '/articles/1/relationships/category',
|
|
266
|
+
266 * // related: '/articles/1/category'
|
|
267
|
+
267 * // }
|
|
268
|
+
268 * // }
|
|
269
|
+
269 * // },
|
|
270
|
+
270 * // links: {
|
|
271
|
+
271 * // self: '/articles/1'
|
|
272
|
+
272 * // }
|
|
273
|
+
273 * // },
|
|
274
|
+
274 * // included: [{
|
|
275
|
+
275 * // type: 'authors',
|
|
276
|
+
276 * // id: '2',
|
|
277
|
+
277 * // attributes: { name: 'John Doe', email: 'john@example.com' },
|
|
278
|
+
278 * // links: { self: '/authors/2' }
|
|
279
|
+
279 * // }],
|
|
280
|
+
280 * // links: {
|
|
281
|
+
281 * // self: '/articles/1'
|
|
282
|
+
282 * // }
|
|
283
|
+
283 * // }
|
|
284
|
+
284 *
|
|
285
|
+
285 * @example
|
|
286
|
+
286 * // Input: Collection with pagination
|
|
287
|
+
287 * const records = [
|
|
288
|
+
288 * { id: 1, title: 'Article 1', author_id: 10 },
|
|
289
|
+
289 * { id: 2, title: 'Article 2', author_id: 10 },
|
|
290
|
+
290 * { id: 3, title: 'Article 3', author_id: 11 }
|
|
291
|
+
291 * ];
|
|
292
|
+
292 *
|
|
293
|
+
293 * context.returnMeta = {
|
|
294
|
+
294 * paginationMeta: { page: 2, pageSize: 3, pageCount: 10, total: 30 },
|
|
295
|
+
295 * paginationLinks: {
|
|
296
|
+
296 * self: '/articles?page[number]=2&page[size]=3',
|
|
297
|
+
297 * first: '/articles?page[number]=1&page[size]=3',
|
|
298
|
+
298 * prev: '/articles?page[number]=1&page[size]=3',
|
|
299
|
+
299 * next: '/articles?page[number]=3&page[size]=3',
|
|
300
|
+
300 * last: '/articles?page[number]=10&page[size]=3'
|
|
301
|
+
301 * }
|
|
302
|
+
302 * };
|
|
303
|
+
303 *
|
|
304
|
+
304 * const response = await buildJsonApiResponse(scope, records, [], false, 'articles', context);
|
|
305
|
+
305 *
|
|
306
|
+
306 * // Output: Collection with pagination metadata
|
|
307
|
+
307 * // {
|
|
308
|
+
308 * // data: [
|
|
309
|
+
309 * // { type: 'articles', id: '1', attributes: { title: 'Article 1' }, ... },
|
|
310
|
+
310 * // { type: 'articles', id: '2', attributes: { title: 'Article 2' }, ... },
|
|
311
|
+
311 * // { type: 'articles', id: '3', attributes: { title: 'Article 3' }, ... }
|
|
312
|
+
312 * // ],
|
|
313
|
+
313 * // meta: {
|
|
314
|
+
314 * // pagination: { page: 2, pageSize: 3, pageCount: 10, total: 30 }
|
|
315
|
+
315 * // },
|
|
316
|
+
316 * // links: {
|
|
317
|
+
317 * // self: '/articles?page[number]=2&page[size]=3',
|
|
318
|
+
318 * // first: '/articles?page[number]=1&page[size]=3',
|
|
319
|
+
319 * // prev: '/articles?page[number]=1&page[size]=3',
|
|
320
|
+
320 * // next: '/articles?page[number]=3&page[size]=3',
|
|
321
|
+
321 * // last: '/articles?page[number]=10&page[size]=3'
|
|
322
|
+
322 * // }
|
|
323
|
+
323 * // }
|
|
324
|
+
324 *
|
|
325
|
+
325 * @description
|
|
326
|
+
326 * Used by:
|
|
327
|
+
327 * - rest-api-knex-plugin's dataGet method for single resource responses
|
|
328
|
+
328 * - rest-api-knex-plugin's dataQuery method for collection responses
|
|
329
|
+
329 * - Called at the end of the query pipeline to assemble the final response
|
|
330
|
+
330 *
|
|
331
|
+
331 * Purpose:
|
|
332
|
+
332 * - Assembles all parts of a JSON:API response in the correct structure
|
|
333
|
+
333 * - Adds relationship objects with proper links for each foreign key
|
|
334
|
+
334 * - Handles both regular belongsTo and polymorphic relationships
|
|
335
|
+
335 * - Adds self links to all resources as required by JSON:API
|
|
336
|
+
336 * - Includes pagination metadata and links when applicable
|
|
337
|
+
337 *
|
|
338
|
+
338 * Data flow:
|
|
339
|
+
339 * 1. Query operations fetch primary records and optional includes
|
|
340
|
+
340 * 2. Records are transformed to JSON:API format via toJsonApiRecord
|
|
341
|
+
341 * 3. buildJsonApiResponse adds relationship objects for all foreign keys
|
|
342
|
+
342 * 4. Adds proper links (self, related) for navigating the API
|
|
343
|
+
343 * 5. Assembles included resources with their own links
|
|
344
|
+
344 * 6. Adds pagination metadata and links if provided
|
|
345
|
+
345 * 7. Returns complete JSON:API document ready for HTTP response
|
|
346
|
+
346 */
|
|
347
|
+
347 export const buildJsonApiResponse = async (scope, records, included = [], isSingle = false, scopeName, context) => {
|
|
348
|
+
348 const {
|
|
349
|
+
349 vars: {
|
|
350
|
+
350 schemaInfo: { schemaInstance, schemaRelationships: relationships, idProperty }
|
|
351
|
+
351 }
|
|
352
|
+
352 } = scope
|
|
353
|
+
353
|
|
354
|
+
354 const idField = idProperty || 'id'
|
|
355
|
+
355
|
|
356
|
+
356 const schemaStructure = getSchemaStructure(schemaInstance)
|
|
357
|
+
357
|
|
358
|
+
358 const processedRecords = records.map(record => {
|
|
359
|
+
359 const { [RELATIONSHIPS_KEY]: _relationships, ...cleanRecord } = record
|
|
360
|
+
360 const jsonApiRecord = toJsonApiRecord(scope, cleanRecord, scopeName)
|
|
361
|
+
361
|
|
362
|
+
362 if (_relationships) {
|
|
363
|
+
363 jsonApiRecord.relationships = _relationships
|
|
364
|
+
364 }
|
|
365
|
+
365
|
|
366
|
+
366 for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
|
|
367
|
+
367 if (fieldDef.belongsTo && fieldDef.as && fieldName in cleanRecord) {
|
|
368
|
+
368 jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
369
|
+
369 if (!jsonApiRecord.relationships[fieldDef.as]) {
|
|
370
|
+
370 if (cleanRecord[fieldName] !== null && cleanRecord[fieldName] !== undefined) {
|
|
371
|
+
371 const relationshipObject = {
|
|
372
|
+
372 data: {
|
|
373
|
+
373 type: fieldDef.belongsTo,
|
|
374
|
+
374 id: String(cleanRecord[fieldName])
|
|
375
|
+
375 }
|
|
376
|
+
376 }
|
|
377
|
+
377
|
|
378
|
+
378 relationshipObject.links = {
|
|
379
|
+
379 self: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, true),
|
|
380
|
+
380 related: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, false)
|
|
381
|
+
381 }
|
|
382
|
+
382
|
|
383
|
+
383 jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
384
|
+
384 } else {
|
|
385
|
+
385 const relationshipObject = {
|
|
386
|
+
386 data: null
|
|
387
|
+
387 }
|
|
388
|
+
388
|
|
389
|
+
389 relationshipObject.links = {
|
|
390
|
+
390 self: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, true),
|
|
391
|
+
391 related: buildRelationshipUrl(context, scope, scopeName, record[idField], fieldDef.as, false)
|
|
392
|
+
392 }
|
|
393
|
+
393
|
|
394
|
+
394 jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
395
|
+
395 }
|
|
396
|
+
396 }
|
|
397
|
+
397 }
|
|
398
|
+
398 }
|
|
399
|
+
399
|
|
400
|
+
400 Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
401
|
+
401 if (relDef.belongsToPolymorphic) {
|
|
402
|
+
402 const typeValue = cleanRecord[relDef.belongsToPolymorphic.typeField]
|
|
403
|
+
403 const idValue = cleanRecord[relDef.belongsToPolymorphic.idField]
|
|
404
|
+
404
|
|
405
|
+
405 if (typeValue && idValue) {
|
|
406
|
+
406 jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
407
|
+
407 const relationshipObject = {
|
|
408
|
+
408 data: {
|
|
409
|
+
409 type: typeValue,
|
|
410
|
+
410 id: String(idValue)
|
|
411
|
+
411 }
|
|
412
|
+
412 }
|
|
413
|
+
413
|
|
414
|
+
414 relationshipObject.links = {
|
|
415
|
+
415 self: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, true),
|
|
416
|
+
416 related: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, false)
|
|
417
|
+
417 }
|
|
418
|
+
418
|
|
419
|
+
419 jsonApiRecord.relationships[relName] = relationshipObject
|
|
420
|
+
420 } else if (typeValue === null || idValue === null) {
|
|
421
|
+
421 jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
422
|
+
422 const relationshipObject = {
|
|
423
|
+
423 data: null
|
|
424
|
+
424 }
|
|
425
|
+
425
|
|
426
|
+
426 relationshipObject.links = {
|
|
427
|
+
427 self: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, true),
|
|
428
|
+
428 related: buildRelationshipUrl(context, scope, scopeName, record[idField], relName, false)
|
|
429
|
+
429 }
|
|
430
|
+
430
|
|
431
|
+
431 jsonApiRecord.relationships[relName] = relationshipObject
|
|
432
|
+
432 }
|
|
433
|
+
433 }
|
|
434
|
+
434 })
|
|
435
|
+
435
|
|
436
|
+
436 return jsonApiRecord
|
|
437
|
+
437 })
|
|
438
|
+
438
|
|
439
|
+
439 const urlPrefix = context.urlPrefix || scope.vars.transport?.mountPath || ''
|
|
440
|
+
440 const normalizedData = isSingle ? processedRecords[0] : processedRecords
|
|
441
|
+
441
|
|
442
|
+
442 if (normalizedData) {
|
|
443
|
+
443 if (Array.isArray(normalizedData)) {
|
|
444
|
+
444 normalizedData.forEach(item => {
|
|
445
|
+
445 if (!item.links) item.links = {}
|
|
446
|
+
446 item.links.self = buildResourceUrl(context, scope, scopeName, item.id)
|
|
447
|
+
447 })
|
|
448
|
+
448 } else {
|
|
449
|
+
449 if (!normalizedData.links) normalizedData.links = {}
|
|
450
|
+
450 normalizedData.links.self = buildResourceUrl(context, scope, scopeName, normalizedData.id)
|
|
451
|
+
451 }
|
|
452
|
+
452 }
|
|
453
|
+
453
|
|
454
|
+
454 const response = {
|
|
455
|
+
455 data: normalizedData
|
|
456
|
+
456 }
|
|
457
|
+
457
|
|
458
|
+
458 if (included.length > 0) {
|
|
459
|
+
459 included.forEach(item => {
|
|
460
|
+
460 if (!item.links) item.links = {}
|
|
461
|
+
461 item.links.self = buildResourceUrl(context, scope, item.type, item.id)
|
|
462
|
+
462 })
|
|
463
|
+
463
|
|
464
|
+
464 response.included = included
|
|
465
|
+
465 }
|
|
466
|
+
466
|
|
467
|
+
467 if (context?.returnMeta?.paginationMeta) {
|
|
468
|
+
468 response.meta = {
|
|
469
|
+
469 pagination: context.returnMeta.paginationMeta
|
|
470
|
+
470 }
|
|
471
|
+
471 }
|
|
472
|
+
472
|
|
473
|
+
473 if (context?.returnMeta?.paginationLinks) {
|
|
474
|
+
474 response.links = context.returnMeta.paginationLinks
|
|
475
|
+
475 } else {
|
|
476
|
+
476 const urlPrefix = getUrlPrefix(context, scope)
|
|
477
|
+
477 response.links = {
|
|
478
|
+
478 self: isSingle
|
|
479
|
+
479 ? buildResourceUrl(context, scope, scopeName, normalizedData.id)
|
|
480
|
+
480 : `${urlPrefix}/${scopeName}${context?.returnMeta?.queryString || ''}`
|
|
481
|
+
481 }
|
|
482
|
+
482 }
|
|
483
|
+
483
|
|
484
|
+
484 return response
|
|
485
|
+
485 }
|