json-rest-api 1.0.29 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +733 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +103 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3838 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +37 -0
  44. package/index.js +3 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +70 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. package/tests/virtual-fields.test.js +0 -404
@@ -0,0 +1,683 @@
1
+ // @ts-check
2
+ /** @import { CanonicalDataDependencies, CanonicalDataReadHelpers, CanonicalDataSortRequest, CanonicalDataWriteHelpers, CanonicalDescriptor, DataDocument, DataMinimalRequest, DataReadContext, DataResource, DataSortDescriptor, StorageRow } from './storage-types.js' */
3
+ import { prepareReferenceSortColumns } from '../querying/knex-query-helpers.js'
4
+ import { applySortDescriptorOrder, buildEffectiveSortList, parseSortEntry, resolveSortField } from '../querying/query-field-sort-helpers.js'
5
+ import { normalizeId, resolveFieldInfo } from '../anyapi/utils/descriptor-helpers.js'
6
+ import { decorateResourceLinks, getAllowedQueryFieldNames } from '../anyapi/canonical-relationship-reader.js'
7
+ import { applyQueryConstraint } from '../querying/query-constraint.js'
8
+ import { calculatePaginationMeta, generatePaginationLinks, generateCursorPaginationLinks, buildCursorMeta, applyPaginationToQuery } from '../querying/knex-pagination-helpers.js'
9
+ import { getUrlPrefix, buildJsonApiLink } from '../querying/url-helpers.js'
10
+ import { applyFieldSelectionToQuery, buildFieldSelection } from '../querying-writing/knex-field-helpers.js'
11
+ import { applyDatabaseReadOptions } from '../querying-writing/database-value-normalizers.js'
12
+ import { translateCanonicalAttributesForStorage, getCanonicalResourceId, getCanonicalResourceIdColumn, translateCanonicalRecordFromStorage } from '../storage/canonical-storage-mapping.js'
13
+ import { unwrapQueryBuilderState, withQueryFilteringContext } from '../querying/query-builder-utils.js'
14
+ import { applyInsertReturning } from '../querying-writing/database-capabilities.js'
15
+ import { serializeJsonApiQuery } from '../querying-writing/connectors-query-parser.js'
16
+
17
+ /** @param {StorageRow} row @param {CanonicalDescriptor} descriptor */
18
+ function requireCanonicalResourceId (row, descriptor) {
19
+ const id = getCanonicalResourceId(row, descriptor)
20
+ if (id === null) throw new Error(`Storage row for '${descriptor.resource}' has no resource ID`)
21
+ return id
22
+ }
23
+
24
+ /** @param {CanonicalDataDependencies} dependencies */
25
+ export function createCanonicalDataHelpers ({
26
+ api, knex, getScopeStorageAdapter, getDescriptor, linkStore,
27
+ buildIncludes, attachReverseRelationships, attachManyToManyRelationships,
28
+ applyBuiltInAnyApiQueryFilters
29
+ }) {
30
+ /** @param {CanonicalDataSortRequest} request @returns {Promise<DataSortDescriptor[]>} */
31
+ const applySortingToQuery = async ({ query, sort, descriptor, scope, tableAlias, context, before = false, queryFieldRuntimeByField = new Map() }) => {
32
+ const effectiveSort = buildEffectiveSortList(sort, {
33
+ defaultSort: scope.vars.defaultSort,
34
+ schemaInfo: scope.vars.schemaInfo,
35
+ idField: 'id'
36
+ })
37
+ /** @type {DataSortDescriptor[]} */
38
+ const descriptors = []
39
+ const referenceColumns = await prepareReferenceSortColumns({
40
+ query,
41
+ fields: effectiveSort.map(entry => parseSortEntry(entry).field).filter(field => !queryFieldRuntimeByField.has(field)),
42
+ scopeName: descriptor.resource,
43
+ tableAlias,
44
+ context
45
+ }, { scopes: api.resources, knex, getStorageAdapter: getScopeStorageAdapter })
46
+
47
+ for (const entry of effectiveSort) {
48
+ const { field, direction } = parseSortEntry(entry)
49
+ const queryFieldRuntime = queryFieldRuntimeByField.get(field)
50
+ if (queryFieldRuntime) {
51
+ applySortDescriptorOrder(query, { queryFieldRuntime, direction }, { before })
52
+ descriptors.push({
53
+ field,
54
+ direction,
55
+ definition: queryFieldRuntime.definition,
56
+ isRelationship: false,
57
+ queryFieldRuntime,
58
+ })
59
+ continue
60
+ }
61
+
62
+ const reference = referenceColumns.get(field)
63
+ const actualField = resolveSortField(field, scope.vars.schemaInfo)
64
+ const fieldInfo = resolveFieldInfo(descriptor, actualField)
65
+ if (!fieldInfo?.column) continue
66
+ const column = reference?.column || `${tableAlias}.${fieldInfo.column}`
67
+ applySortDescriptorOrder(query, { column, direction }, { before })
68
+ descriptors.push({
69
+ field,
70
+ column,
71
+ resultColumn: fieldInfo.column,
72
+ actualField,
73
+ ...(reference || {}),
74
+ direction,
75
+ definition: scope.vars.schemaInfo.schemaStructure[actualField] || fieldInfo.definition || null,
76
+ isRelationship: fieldInfo.isRelationship || false,
77
+ })
78
+ }
79
+
80
+ if (descriptors.length === 0) {
81
+ descriptors.push({
82
+ field: 'id',
83
+ column: 'id',
84
+ direction: 'asc',
85
+ definition: { type: 'id' },
86
+ isRelationship: false,
87
+ })
88
+ }
89
+
90
+ return descriptors
91
+ }
92
+
93
+ /** @type {CanonicalDataWriteHelpers['dataExists']} */
94
+ const dataExists = async ({ scopeName, context }) => {
95
+ const storageAdapter = getScopeStorageAdapter(scopeName)
96
+ if (storageAdapter) {
97
+ context.storageAdapter = storageAdapter
98
+ }
99
+ const descriptor = getDescriptor(scopeName)
100
+ const { canonical } = descriptor
101
+ const id = context.id
102
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
103
+
104
+ const row = await context.db(canonical.tableName)
105
+ .select('id')
106
+ .where(canonical.tenantColumn, descriptor.tenant)
107
+ .where(canonical.resourceColumn, descriptor.resource)
108
+ .where(logicalIdColumn, id)
109
+ .first()
110
+
111
+ return !!row
112
+ }
113
+
114
+ /** @type {CanonicalDataWriteHelpers['dataPost']} */
115
+ const dataPost = async ({ scopeName, context }) => {
116
+ const storageAdapter = getScopeStorageAdapter(scopeName)
117
+ if (storageAdapter) {
118
+ context.storageAdapter = storageAdapter
119
+ }
120
+ const descriptor = getDescriptor(scopeName)
121
+ const { canonical } = descriptor
122
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
123
+ const explicitId = normalizeId(context.inputRecord?.data?.id)
124
+ const attributes = context.inputRecord?.data?.attributes || {}
125
+ const row = storageAdapter
126
+ ? storageAdapter.toStorageRow(attributes, { context, operation: 'post' })
127
+ : translateCanonicalAttributesForStorage(attributes, descriptor)
128
+
129
+ if (explicitId !== undefined && explicitId !== null) {
130
+ row[logicalIdColumn] = explicitId
131
+ }
132
+
133
+ row[canonical.tenantColumn] = descriptor.tenant
134
+ row[canonical.resourceColumn] = descriptor.resource
135
+
136
+ const result = await applyInsertReturning(context.db(canonical.tableName).insert(row), 'id')
137
+
138
+ const inserted = Array.isArray(result) ? result[0] : result
139
+ const insertedRowId = inserted && typeof inserted === 'object' && 'id' in inserted
140
+ ? inserted.id
141
+ : inserted
142
+
143
+ if (explicitId !== undefined && explicitId !== null) {
144
+ return explicitId
145
+ }
146
+
147
+ if (insertedRowId === undefined || insertedRowId === null) {
148
+ return insertedRowId
149
+ }
150
+
151
+ const generatedLogicalId = String(insertedRowId)
152
+
153
+ await context.db(canonical.tableName)
154
+ .where({ id: insertedRowId })
155
+ .update({ [logicalIdColumn]: generatedLogicalId })
156
+
157
+ return generatedLogicalId
158
+ }
159
+
160
+ /** @type {CanonicalDataWriteHelpers['dataPut']} */
161
+ const dataPut = async ({ scopeName, context }) => {
162
+ const storageAdapter = getScopeStorageAdapter(scopeName)
163
+ if (storageAdapter) {
164
+ context.storageAdapter = storageAdapter
165
+ }
166
+ const descriptor = getDescriptor(scopeName)
167
+ const { canonical } = descriptor
168
+ const id = context.id
169
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
170
+ const attributes = context.inputRecord?.data?.attributes || {}
171
+ const row = storageAdapter
172
+ ? storageAdapter.toStorageRow(attributes, { context, operation: 'put' })
173
+ : translateCanonicalAttributesForStorage(attributes, descriptor)
174
+
175
+ row[canonical.resourceColumn] = descriptor.resource
176
+ row[canonical.tenantColumn] = descriptor.tenant
177
+
178
+ const writeRow = Object.fromEntries(
179
+ Object.entries(row).filter(([, value]) => value !== undefined)
180
+ )
181
+
182
+ if (context.isCreate) {
183
+ await context.db(canonical.tableName).insert({
184
+ ...writeRow,
185
+ [logicalIdColumn]: id,
186
+ })
187
+ return 1
188
+ }
189
+
190
+ const result = await context.db(canonical.tableName)
191
+ .where(logicalIdColumn, id)
192
+ .where(canonical.resourceColumn, descriptor.resource)
193
+ .where(canonical.tenantColumn, descriptor.tenant)
194
+ .update(writeRow)
195
+
196
+ return result
197
+ }
198
+
199
+ /** @type {CanonicalDataWriteHelpers['dataPatch']} */
200
+ const dataPatch = async ({ scopeName, context }) => {
201
+ const storageAdapter = getScopeStorageAdapter(scopeName)
202
+ if (storageAdapter) {
203
+ context.storageAdapter = storageAdapter
204
+ }
205
+ const descriptor = getDescriptor(scopeName)
206
+ const { canonical } = descriptor
207
+ const id = context.id
208
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
209
+ const attributes = context.inputRecord?.data?.attributes || {}
210
+ const row = storageAdapter
211
+ ? storageAdapter.toStorageRow(attributes, { context, operation: 'patch' })
212
+ : translateCanonicalAttributesForStorage(attributes, descriptor)
213
+
214
+ const updateRow = Object.fromEntries(
215
+ Object.entries(row).filter(([, value]) => value !== undefined)
216
+ )
217
+
218
+ if (Object.keys(updateRow).length === 0) {
219
+ return 0
220
+ }
221
+
222
+ const result = await context.db(canonical.tableName)
223
+ .where(logicalIdColumn, id)
224
+ .where(canonical.resourceColumn, descriptor.resource)
225
+ .where(canonical.tenantColumn, descriptor.tenant)
226
+ .update(updateRow)
227
+
228
+ return result
229
+ }
230
+
231
+ /** @type {CanonicalDataWriteHelpers['dataDelete']} */
232
+ const dataDelete = async ({ scopeName, context }) => {
233
+ const storageAdapter = getScopeStorageAdapter(scopeName)
234
+ if (storageAdapter) {
235
+ context.storageAdapter = storageAdapter
236
+ }
237
+ const descriptor = getDescriptor(scopeName)
238
+ const { canonical } = descriptor
239
+ const id = context.id
240
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
241
+
242
+ await linkStore.invalidateDeletedLinkTargets(scopeName, context)
243
+ const result = await context.db(canonical.tableName)
244
+ .where(logicalIdColumn, id)
245
+ .where(canonical.resourceColumn, descriptor.resource)
246
+ .where(canonical.tenantColumn, descriptor.tenant)
247
+ .delete()
248
+
249
+ await linkStore.deleteResourceLinks({ descriptor, scopeName, id, db: context.db })
250
+
251
+ return result
252
+ }
253
+
254
+ // Supplying ids returns minimal records for the caller's bounded validation batch.
255
+ /** @overload @param {DataMinimalRequest & { ids: Array<string | number> }} request @returns {Promise<DataResource[]>} */
256
+ /** @overload @param {DataMinimalRequest & { ids?: undefined, context: DataReadContext & {id: string | number} }} request @returns {Promise<DataResource | null>} */
257
+ /** @param {DataMinimalRequest & { ids?: Array<string | number> }} request */
258
+ async function dataGetMinimal ({
259
+ scopeName,
260
+ context,
261
+ ids,
262
+ runHooks,
263
+ applyQueryFilters,
264
+ filters = context.queryParams?.filters,
265
+ queryPurpose = 'single'
266
+ }) {
267
+ const storageAdapter = getScopeStorageAdapter(scopeName)
268
+ if (!storageAdapter) throw new Error(`Storage adapter for resource '${scopeName}' not found`)
269
+ context.storageAdapter = storageAdapter
270
+ const descriptor = getDescriptor(scopeName)
271
+ const { canonical } = descriptor
272
+ const id = context.id
273
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
274
+ const scope = api.resources?.[scopeName]
275
+
276
+ let query = context.db(canonical.tableName)
277
+ .where(canonical.resourceColumn, descriptor.resource)
278
+ .where(canonical.tenantColumn, descriptor.tenant)
279
+ if (ids === undefined) query.where(logicalIdColumn, id)
280
+ else {
281
+ // Knex types exclude unknown bindings; the configured driver still validates serializer results.
282
+ query.whereIn(logicalIdColumn, /** @type {import('knex').Knex.Value[]} */ (ids.map(value => storageAdapter.translateFilterValue('id', value))))
283
+ .distinct(`${canonical.tableName}.${logicalIdColumn}`)
284
+ }
285
+
286
+ if (typeof applyQueryFilters === 'function') {
287
+ const scopedQueryState = await applyQueryFilters({
288
+ query,
289
+ filters,
290
+ tableName: canonical.tableName,
291
+ db: context.db,
292
+ scopeName,
293
+ queryPurpose,
294
+ storageAdapter,
295
+ isAnyApi: true
296
+ })
297
+ query = unwrapQueryBuilderState(scopedQueryState, query)
298
+ } else if (runHooks) {
299
+ const filteredState = await withQueryFilteringContext(context, {
300
+ query,
301
+ filters,
302
+ schemaInfo: context.schemaInfo,
303
+ scopeName,
304
+ tableName: canonical.tableName,
305
+ db: context.db,
306
+ queryPurpose,
307
+ isAnyApi: true,
308
+ adapter: storageAdapter,
309
+ storageAdapter,
310
+ }, async () => {
311
+ await applyBuiltInAnyApiQueryFilters(context)
312
+ await runHooks('knexQueryFiltering')
313
+ })
314
+ query = filteredState.query
315
+ }
316
+
317
+ /** @param {StorageRow} row @returns {DataResource} */
318
+ const toMinimal = row => {
319
+ const translated = translateCanonicalRecordFromStorage(
320
+ row,
321
+ descriptor,
322
+ { allowedExtraFields: getAllowedQueryFieldNames(api, scopeName) }
323
+ )
324
+ const minimal = {
325
+ type: descriptor.resource,
326
+ id: requireCanonicalResourceId(row, descriptor),
327
+ attributes: translated.attributes,
328
+ relationships: translated.relationships,
329
+ }
330
+ if (scope) decorateResourceLinks({ resource: minimal, scope, scopeName, context })
331
+ return minimal
332
+ }
333
+ if (ids !== undefined) {
334
+ // Filtering owns predicates; the validation batch owns its identity projection.
335
+ query.clearSelect().distinct(`${canonical.tableName}.${logicalIdColumn}`)
336
+ /** @type {StorageRow[]} */
337
+ const rows = await applyDatabaseReadOptions(storageAdapter.buildBaseQuery({ transaction: context.db }).whereIn(logicalIdColumn, query))
338
+ return rows.map(toMinimal)
339
+ }
340
+ const row = await applyDatabaseReadOptions(query.select(`${canonical.tableName}.*`)).first()
341
+ return row ? toMinimal(row) : null
342
+ }
343
+
344
+ /** @type {CanonicalDataReadHelpers['dataGet']} */
345
+ const dataGet = async ({ scopeName, context }) => {
346
+ const storageAdapter = getScopeStorageAdapter(scopeName)
347
+ if (!storageAdapter) throw new Error(`Storage adapter for resource '${scopeName}' not found`)
348
+ context.storageAdapter = storageAdapter
349
+ const descriptor = getDescriptor(scopeName)
350
+ const { canonical } = descriptor
351
+ const id = context.id
352
+ const logicalIdColumn = getCanonicalResourceIdColumn(descriptor)
353
+ const scope = api.resources[scopeName]
354
+ if (!scope) throw new Error(`Scope '${scopeName}' not found in api.resources`)
355
+
356
+ const fieldSelectionInfo = await buildFieldSelection(scope, { context })
357
+ context.computedDependencies = fieldSelectionInfo.computedDependencies
358
+
359
+ let query = context.db(canonical.tableName)
360
+ .where(logicalIdColumn, id)
361
+ .where(canonical.resourceColumn, descriptor.resource)
362
+ .where(canonical.tenantColumn, descriptor.tenant)
363
+
364
+ const selectionState = await applyFieldSelectionToQuery({
365
+ query,
366
+ scope,
367
+ fieldSelectionInfo,
368
+ tableName: canonical.tableName,
369
+ useTablePrefix: false,
370
+ storageAdapter,
371
+ db: context.db,
372
+ context,
373
+ scopeName
374
+ })
375
+ query = selectionState.query
376
+
377
+ const row = await applyDatabaseReadOptions(query).first()
378
+
379
+ if (!row) return null
380
+
381
+ const record = translateCanonicalRecordFromStorage(
382
+ row,
383
+ descriptor,
384
+ { allowedExtraFields: getAllowedQueryFieldNames(api, scopeName) }
385
+ )
386
+ const resourceId = requireCanonicalResourceId(row, descriptor)
387
+ /** @type {DataResource} */
388
+ const data = {
389
+ type: descriptor.resource,
390
+ id: resourceId,
391
+ attributes: record.attributes,
392
+ }
393
+
394
+ if (record.relationships && Object.keys(record.relationships).length > 0) {
395
+ data.relationships = record.relationships
396
+ }
397
+
398
+ await attachReverseRelationships({
399
+ resources: [data],
400
+ descriptor,
401
+ context,
402
+ })
403
+
404
+ await attachManyToManyRelationships({
405
+ resources: [data],
406
+ descriptor,
407
+ context,
408
+ })
409
+
410
+ const included = await buildIncludes({
411
+ parentResources: [data],
412
+ descriptor,
413
+ context,
414
+ })
415
+
416
+ /** @type {DataDocument<typeof data>} */
417
+ const response = { data }
418
+ if (included.length > 0 || context.queryParams?.include !== undefined) {
419
+ response.included = included
420
+ }
421
+
422
+ decorateResourceLinks({ resource: data, scope, scopeName, context })
423
+ response.links = { self: buildJsonApiLink(data.links?.self, context.queryParams) }
424
+
425
+ for (const includeResource of included) {
426
+ const targetScope = api.resources?.[includeResource.type]
427
+ if (!targetScope) continue
428
+ decorateResourceLinks({
429
+ resource: includeResource,
430
+ scope: targetScope,
431
+ scopeName: includeResource.type,
432
+ context,
433
+ })
434
+ }
435
+
436
+ return response
437
+ }
438
+
439
+ /** @type {CanonicalDataReadHelpers['dataQuery']} */
440
+ const dataQuery = async ({ scopeName, context, runHooks }) => {
441
+ const storageAdapter = getScopeStorageAdapter(scopeName)
442
+ if (!storageAdapter) throw new Error(`Storage adapter for resource '${scopeName}' not found`)
443
+ context.storageAdapter = storageAdapter
444
+ const descriptor = getDescriptor(scopeName)
445
+ const db = context.db || context.transaction || api.knex.instance
446
+ const scope = api.resources?.[scopeName]
447
+ const queryParams = context.queryParams || {}
448
+ if (!scope) throw new Error(`Scope '${scopeName}' not found in api.resources`)
449
+
450
+ const fieldSelectionInfo = await buildFieldSelection(scope, { context })
451
+ context.computedDependencies = fieldSelectionInfo.computedDependencies
452
+ const tableAlias = scope.vars.schemaInfo.tableName || descriptor.resource
453
+ let queryBuilder = storageAdapter.buildBaseQuery({ transaction: db, tableAlias })
454
+ applyQueryConstraint({ query: queryBuilder, context, scopeName, storageAdapter, tableName: tableAlias })
455
+
456
+ const schemaInfo = scope.vars.schemaInfo
457
+
458
+ const filteredState = await withQueryFilteringContext(context, {
459
+ query: queryBuilder,
460
+ filters: queryParams.filters,
461
+ schemaInfo,
462
+ scopeName,
463
+ tableName: tableAlias,
464
+ db,
465
+ queryPurpose: 'collection',
466
+ isAnyApi: true,
467
+ adapter: storageAdapter,
468
+ storageAdapter,
469
+ }, async () => {
470
+ await applyBuiltInAnyApiQueryFilters(context)
471
+ if (runHooks) await runHooks('knexQueryFiltering')
472
+ })
473
+ queryBuilder = filteredState.query
474
+
475
+ const countQuery = queryBuilder.clone()
476
+
477
+ const selectionState = await applyFieldSelectionToQuery({
478
+ query: queryBuilder,
479
+ scope,
480
+ fieldSelectionInfo,
481
+ tableName: tableAlias,
482
+ useTablePrefix: true,
483
+ storageAdapter,
484
+ db,
485
+ context,
486
+ scopeName
487
+ })
488
+ queryBuilder = selectionState.query
489
+
490
+ const sortDescriptors = await applySortingToQuery({
491
+ query: queryBuilder,
492
+ sort: queryParams.sort,
493
+ descriptor,
494
+ scope,
495
+ tableAlias,
496
+ context,
497
+ before: Boolean(queryParams.page?.before),
498
+ queryFieldRuntimeByField: selectionState.queryFieldRuntimeByField
499
+ })
500
+
501
+ const paginationInfo = applyPaginationToQuery({
502
+ query: queryBuilder,
503
+ page: queryParams.page,
504
+ vars: scope.vars,
505
+ sortDescriptors,
506
+ storageAdapter,
507
+ })
508
+
509
+ /** @type {StorageRow[]} */
510
+ let rows = await applyDatabaseReadOptions(queryBuilder)
511
+ let cursorRecords = null
512
+ let hasMore = false
513
+
514
+ if (paginationInfo.mode === 'cursor') {
515
+ const { pageSize } = paginationInfo
516
+ hasMore = rows.length > pageSize
517
+ if (hasMore) rows = rows.slice(0, pageSize)
518
+ if (paginationInfo.before) rows.reverse()
519
+
520
+ cursorRecords = rows.map((row) => {
521
+ /** @type {StorageRow} */
522
+ const record = {}
523
+ for (const descriptorEntry of sortDescriptors) {
524
+ if (descriptorEntry.field === 'id') {
525
+ record.id = getCanonicalResourceId(row, descriptor)
526
+ } else if (descriptorEntry.queryFieldRuntime) {
527
+ record[descriptorEntry.field] = row[descriptorEntry.field]
528
+ } else if (descriptorEntry.column) {
529
+ record[descriptorEntry.field] = row[descriptorEntry.resultColumn || descriptorEntry.column]
530
+ }
531
+ }
532
+ return record
533
+ })
534
+ }
535
+
536
+ for (const row of rows) {
537
+ for (const { referenceField, resultColumn } of sortDescriptors) {
538
+ if (referenceField && resultColumn) delete row[resultColumn]
539
+ }
540
+ }
541
+ const data = rows.map((row) => {
542
+ const translated = translateCanonicalRecordFromStorage(
543
+ row,
544
+ descriptor,
545
+ { allowedExtraFields: getAllowedQueryFieldNames(api, scopeName) }
546
+ )
547
+ const resourceId = requireCanonicalResourceId(row, descriptor)
548
+ /** @type {DataResource} */
549
+ const resource = {
550
+ type: descriptor.resource,
551
+ id: resourceId,
552
+ attributes: translated.attributes,
553
+ }
554
+ if (translated.relationships && Object.keys(translated.relationships).length > 0) {
555
+ resource.relationships = translated.relationships
556
+ }
557
+ decorateResourceLinks({ resource, scope, scopeName, context })
558
+ if (fieldSelectionInfo.computedDependencies.length) {
559
+ resource.__$jsonrestapi_computed_deps$__ = fieldSelectionInfo.computedDependencies
560
+ }
561
+ return resource
562
+ })
563
+
564
+ await attachReverseRelationships({
565
+ resources: data,
566
+ descriptor,
567
+ context,
568
+ })
569
+
570
+ await attachManyToManyRelationships({
571
+ resources: data,
572
+ descriptor,
573
+ context,
574
+ })
575
+
576
+ const included = await buildIncludes({
577
+ parentResources: data,
578
+ descriptor,
579
+ context,
580
+ })
581
+
582
+ /** @type {DataDocument<typeof data>} */
583
+ const response = { data }
584
+ if (included.length > 0 || context.queryParams?.include !== undefined) {
585
+ response.included = included
586
+ }
587
+
588
+ for (const includeResource of included) {
589
+ const targetScope = api.resources?.[includeResource.type]
590
+ if (!targetScope) continue
591
+ decorateResourceLinks({
592
+ resource: includeResource,
593
+ scope: targetScope,
594
+ scopeName: includeResource.type,
595
+ context,
596
+ })
597
+ }
598
+
599
+ context.returnMeta = context.returnMeta || {}
600
+ const queryString = serializeJsonApiQuery(queryParams)
601
+ context.returnMeta.queryString = queryString ? `?${queryString}` : ''
602
+ delete context.returnMeta.paginationMeta
603
+ delete context.returnMeta.paginationLinks
604
+
605
+ if (paginationInfo.mode === 'offset') {
606
+ const { page, pageSize } = paginationInfo
607
+ let paginationMeta
608
+
609
+ if (scope.vars.enablePaginationCounts) {
610
+ /** @type {StorageRow | undefined} */
611
+ const countResult = await countQuery.clearSelect().clearOrder()
612
+ .countDistinct({ count: `${tableAlias}.${getCanonicalResourceIdColumn(descriptor)}` }).first()
613
+ const total = Number(countResult?.count ?? countResult?.total ?? 0)
614
+ paginationMeta = calculatePaginationMeta(total, page, pageSize)
615
+ } else {
616
+ paginationMeta = { page, pageSize }
617
+ }
618
+
619
+ context.returnMeta.paginationMeta = paginationMeta
620
+ const urlPrefix = getUrlPrefix(context, scope)
621
+ context.returnMeta.paginationLinks = generatePaginationLinks(
622
+ urlPrefix,
623
+ scopeName,
624
+ queryParams,
625
+ paginationMeta
626
+ )
627
+ } else if (paginationInfo.mode === 'cursor' && cursorRecords) {
628
+ const cursorFields = sortDescriptors.map((descriptorEntry) => descriptorEntry.field)
629
+ const cursorOptions = {
630
+ schemaInfo,
631
+ definitions: Object.fromEntries(sortDescriptors.map(({ field, definition }) => [field, definition])),
632
+ before: paginationInfo.before
633
+ }
634
+ const paginationMeta = buildCursorMeta(
635
+ cursorRecords,
636
+ paginationInfo.pageSize,
637
+ hasMore,
638
+ cursorFields,
639
+ cursorOptions
640
+ )
641
+ context.returnMeta.paginationMeta = paginationMeta
642
+ const urlPrefix = getUrlPrefix(context, scope)
643
+ context.returnMeta.paginationLinks = generateCursorPaginationLinks(
644
+ urlPrefix,
645
+ scopeName,
646
+ queryParams,
647
+ cursorRecords,
648
+ paginationInfo.pageSize,
649
+ hasMore,
650
+ cursorFields,
651
+ cursorOptions
652
+ )
653
+ }
654
+
655
+ if (context.returnMeta.paginationMeta) {
656
+ response.meta = {
657
+ pagination: context.returnMeta.paginationMeta,
658
+ }
659
+ }
660
+
661
+ if (context.returnMeta.paginationLinks) {
662
+ response.links = context.returnMeta.paginationLinks
663
+ } else {
664
+ const urlPrefix = getUrlPrefix(context, scope)
665
+ response.links = {
666
+ self: `${urlPrefix}/${scopeName}${context.returnMeta.queryString || ''}`,
667
+ }
668
+ }
669
+
670
+ return response
671
+ }
672
+
673
+ return {
674
+ dataExists,
675
+ dataPost,
676
+ dataPut,
677
+ dataPatch,
678
+ dataDelete,
679
+ dataGet,
680
+ dataQuery,
681
+ dataGetMinimal
682
+ }
683
+ }