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.
Files changed (173) hide show
  1. package/audit/00-charter.md +103 -0
  2. package/audit/01-scope-map.md +84 -0
  3. package/audit/02-coverage-matrix.csv +174 -0
  4. package/audit/03-invariants.md +123 -0
  5. package/audit/04-standards-rubric.md +56 -0
  6. package/audit/05-competitor-rubric.md +77 -0
  7. package/audit/06-findings.md +160 -0
  8. package/audit/07-risk-register.csv +10 -0
  9. package/audit/08-remediation-roadmap.md +177 -0
  10. package/audit/09-signoff-check.md +23 -0
  11. package/audit/10-final-synthesis.md +29 -0
  12. package/audit/evidence/code/all-files-json-rest-api.txt +168 -0
  13. package/audit/evidence/code/all-files-json-rest-schema.txt +12 -0
  14. package/audit/evidence/code/boundary-leak-checks.txt +72 -0
  15. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +480 -0
  16. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +532 -0
  17. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +175 -0
  18. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +138 -0
  19. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +222 -0
  20. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +342 -0
  21. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +304 -0
  22. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +188 -0
  23. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +336 -0
  24. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +165 -0
  25. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +265 -0
  26. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +369 -0
  27. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +72 -0
  28. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +139 -0
  29. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +45 -0
  30. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +396 -0
  31. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +162 -0
  32. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +1314 -0
  33. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +488 -0
  34. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +484 -0
  35. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +857 -0
  36. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +565 -0
  37. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +485 -0
  38. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +560 -0
  39. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +167 -0
  40. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +109 -0
  41. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +1064 -0
  42. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +1983 -0
  43. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +328 -0
  44. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +119 -0
  45. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +160 -0
  46. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +300 -0
  47. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +245 -0
  48. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +459 -0
  49. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +237 -0
  50. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +1135 -0
  51. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +314 -0
  52. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +7 -0
  53. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +71 -0
  54. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +102 -0
  55. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +49 -0
  56. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +39 -0
  57. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +23 -0
  58. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +13 -0
  59. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +639 -0
  60. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +117 -0
  61. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +104 -0
  62. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +199 -0
  63. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +353 -0
  64. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +61 -0
  65. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +184 -0
  66. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +55 -0
  67. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +191 -0
  68. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +110 -0
  69. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +168 -0
  70. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +253 -0
  71. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +207 -0
  72. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +12 -0
  73. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +165 -0
  74. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +668 -0
  75. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +621 -0
  76. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +218 -0
  77. package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +124 -0
  78. package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +269 -0
  79. package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +242 -0
  80. package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +77 -0
  81. package/audit/evidence/code/critical-pass1-risk-keywords.txt +238 -0
  82. package/audit/evidence/code/critical-pass1-summary.csv +67 -0
  83. package/audit/evidence/code/critical-pass2-summary.csv +67 -0
  84. package/audit/evidence/code/early-silent-zones-no-test-token.txt +57 -0
  85. package/audit/evidence/code/error-model-signals.txt +98 -0
  86. package/audit/evidence/code/git-files-json-rest-api.txt +171 -0
  87. package/audit/evidence/code/git-files-json-rest-schema.txt +27 -0
  88. package/audit/evidence/code/hook-flow-signals.txt +282 -0
  89. package/audit/evidence/code/idproperty-signals.txt +157 -0
  90. package/audit/evidence/code/import-cycles-non-anyapi.txt +3 -0
  91. package/audit/evidence/code/import-hotspots-non-anyapi.txt +96 -0
  92. package/audit/evidence/code/imports-json-rest-api.txt +354 -0
  93. package/audit/evidence/code/imports-json-rest-schema.txt +15 -0
  94. package/audit/evidence/code/in-scope-files-json-rest-api.txt +160 -0
  95. package/audit/evidence/code/in-scope-files-json-rest-schema.txt +13 -0
  96. package/audit/evidence/code/non-anyapi-core-files.txt +101 -0
  97. package/audit/evidence/code/permission-signals.txt +193 -0
  98. package/audit/evidence/code/query-include-signals.txt +687 -0
  99. package/audit/evidence/code/transaction-signals.txt +28 -0
  100. package/audit/evidence/code/validation-signals.txt +650 -0
  101. package/audit/evidence/code/write-path-signals.txt +61 -0
  102. package/audit/evidence/docs/docs-claims-signals.txt +1075 -0
  103. package/audit/evidence/docs/schema-docs-claims-signals.txt +179 -0
  104. package/audit/evidence/external/corpus-freeze.md +21 -0
  105. package/audit/evidence/external/npm-competitor-metadata.txt +70 -0
  106. package/audit/evidence/external/npm-download-signals.txt +23 -0
  107. package/audit/evidence/external/npm-jsonapi-server.txt +8 -0
  108. package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +38 -0
  109. package/audit/evidence/external/readme-fortune-signals.txt +26 -0
  110. package/audit/evidence/external/readme-fortune.md +165 -0
  111. package/audit/evidence/external/readme-json-api-serializer-signals.txt +80 -0
  112. package/audit/evidence/external/readme-json-api-serializer.md +690 -0
  113. package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
  114. package/audit/evidence/external/readme-jsonapi-serde-server.md +22 -0
  115. package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +63 -0
  116. package/audit/evidence/external/readme-jsonapi-serializer.md +315 -0
  117. package/audit/evidence/external/readme-jsonapi-server-signals.txt +19 -0
  118. package/audit/evidence/external/readme-jsonapi-server.md +104 -0
  119. package/audit/evidence/external/readme-nestjs-json-api-signals.txt +4 -0
  120. package/audit/evidence/external/readme-nestjs-json-api.md +94 -0
  121. package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +47 -0
  122. package/audit/evidence/external/readme-yaser-json-api-nestjs.md +447 -0
  123. package/audit/evidence/external/standard-jsonapi-snippets.txt +84 -0
  124. package/audit/evidence/external/standard-rfc-snippets.txt +168 -0
  125. package/audit/evidence/external/standards-sources.txt +6 -0
  126. package/audit/evidence/runtime/baseline-commits.txt +2 -0
  127. package/audit/evidence/runtime/db-adapter-assumptions.txt +3 -0
  128. package/audit/evidence/runtime/environment.txt +4 -0
  129. package/audit/evidence/runtime/invariant-runtime-mapping.md +38 -0
  130. package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +1 -0
  131. package/audit/evidence/runtime/probe-patch-relationship-error.log +76 -0
  132. package/audit/evidence/runtime/probe-patch-relationship-error.mjs +67 -0
  133. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +1 -0
  134. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +62 -0
  135. package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +47 -0
  136. package/audit/evidence/runtime/test-exitcodes.txt +4 -0
  137. package/audit/evidence/runtime/test-summary.md +107 -0
  138. package/audit/evidence/runtime/trace-anchors.txt +61 -0
  139. package/audit/evidence/tests/access-multitenancy-test-signals.txt +125 -0
  140. package/audit/evidence/tests/failing-tests-summary.txt +25 -0
  141. package/audit/evidence/tests/npm-test-json-rest-api.exitcode +1 -0
  142. package/audit/evidence/tests/npm-test-json-rest-api.log +5885 -0
  143. package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +1 -0
  144. package/audit/evidence/tests/npm-test-json-rest-schema.log +356 -0
  145. package/audit/evidence/tests/query-include-test-signals.txt +948 -0
  146. package/audit/evidence/tests/relationship-test-signals.txt +223 -0
  147. package/audit/evidence/tests/skipped-tests.txt +10 -0
  148. package/audit/evidence/tests/test-suite-list-non-anyapi.txt +30 -0
  149. package/audit/evidence/tests/test-suite-list.txt +32 -0
  150. package/audit/subsystems/docs-drift.md +49 -0
  151. package/audit/subsystems/pass2-cross-review.md +131 -0
  152. package/audit/subsystems/plugins-access.md +51 -0
  153. package/audit/subsystems/plugins-bulk.md +50 -0
  154. package/audit/subsystems/plugins-cors.md +48 -0
  155. package/audit/subsystems/plugins-file.md +53 -0
  156. package/audit/subsystems/plugins-multihome.md +50 -0
  157. package/audit/subsystems/plugins-positioning.md +54 -0
  158. package/audit/subsystems/plugins-socketio.md +50 -0
  159. package/audit/subsystems/query-engine.md +61 -0
  160. package/audit/subsystems/relationships.md +55 -0
  161. package/audit/subsystems/rest-orchestration.md +66 -0
  162. package/audit/subsystems/schema-core.md +64 -0
  163. package/audit/subsystems/storage-knex.md +57 -0
  164. package/audit/subsystems/tests.md +48 -0
  165. package/audit/subsystems/transport-express.md +55 -0
  166. package/audit/traces/trace-read-get.md +22 -0
  167. package/audit/traces/trace-read-query.md +25 -0
  168. package/audit/traces/trace-relationship-endpoints.md +31 -0
  169. package/audit/traces/trace-write-delete.md +17 -0
  170. package/audit/traces/trace-write-patch.md +26 -0
  171. package/audit/traces/trace-write-post.md +22 -0
  172. package/audit/traces/trace-write-put.md +23 -0
  173. 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 }