json-rest-api 1.0.29 → 2.0.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.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,366 +1,96 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import { JsonApiRelationship, JsonApiResource } from '../../../../types/representations.js' */
|
|
3
|
+
/** @import { DataDocument, DataOperationScope, DataReadContext, DataResource, DataStorageRow, ResourceConversionScope, StorageRow } from '../storage/storage-types.js' */
|
|
4
|
+
import { RELATIONSHIPS_KEY, RELATIONSHIP_METADATA_KEY, ROW_NUMBER_KEY, COMPUTED_DEPENDENCIES_KEY } from '../querying-writing/knex-constants.js'
|
|
5
|
+
import { getUrlPrefix, buildResourceUrl, buildRelationshipUrl, buildJsonApiLink } from './url-helpers.js'
|
|
4
6
|
import { translateRecordFromStorage } from '../storage/storage-mapping.js'
|
|
7
|
+
import { getPolymorphicLinkage } from '../querying-writing/relationship-contracts.js'
|
|
8
|
+
import { wrapUnexpectedError } from '../../../../lib/error-context.js'
|
|
9
|
+
|
|
10
|
+
const internalFields = new Set([
|
|
11
|
+
RELATIONSHIPS_KEY,
|
|
12
|
+
RELATIONSHIP_METADATA_KEY,
|
|
13
|
+
ROW_NUMBER_KEY,
|
|
14
|
+
COMPUTED_DEPENDENCIES_KEY
|
|
15
|
+
])
|
|
5
16
|
|
|
6
17
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* @
|
|
10
|
-
* @param {
|
|
11
|
-
* @
|
|
12
|
-
* @param {Object} deps - Dependencies including context with scopeName and polymorphicFields
|
|
13
|
-
* @returns {Object|null} JSON:API resource object or null if no record
|
|
14
|
-
*
|
|
15
|
-
* @example
|
|
16
|
-
* // Input: Database record with foreign keys and internal fields
|
|
17
|
-
* const record = {
|
|
18
|
-
* id: 1,
|
|
19
|
-
* title: 'Hello World',
|
|
20
|
-
* content: 'Article content',
|
|
21
|
-
* author_id: 2, // Foreign key
|
|
22
|
-
* category_id: 5, // Foreign key
|
|
23
|
-
* __$jsonrestapi_rn$__: 1 // Internal field
|
|
24
|
-
* };
|
|
25
|
-
*
|
|
26
|
-
* const jsonApiResource = toJsonApi(scope, record, deps);
|
|
27
|
-
*
|
|
28
|
-
* // Output: Clean JSON:API resource
|
|
29
|
-
* // {
|
|
30
|
-
* // type: 'articles',
|
|
31
|
-
* // id: '1',
|
|
32
|
-
* // attributes: {
|
|
33
|
-
* // title: 'Hello World',
|
|
34
|
-
* // content: 'Article content'
|
|
35
|
-
* // // Note: author_id, category_id, and internal fields are removed
|
|
36
|
-
* // }
|
|
37
|
-
* // }
|
|
38
|
-
*
|
|
39
|
-
* @example
|
|
40
|
-
* // Input: Polymorphic record
|
|
41
|
-
* const record = {
|
|
42
|
-
* id: 10,
|
|
43
|
-
* text: 'Great article!',
|
|
44
|
-
* commentable_type: 'articles', // Polymorphic type field
|
|
45
|
-
* commentable_id: 1 // Polymorphic id field
|
|
46
|
-
* };
|
|
47
|
-
* // deps.context.polymorphicFields = Set(['commentable_type', 'commentable_id'])
|
|
48
|
-
*
|
|
49
|
-
* const jsonApiResource = toJsonApi(scope, record, deps);
|
|
50
|
-
*
|
|
51
|
-
* // Output: Polymorphic fields filtered out
|
|
52
|
-
* // {
|
|
53
|
-
* // type: 'comments',
|
|
54
|
-
* // id: '10',
|
|
55
|
-
* // attributes: {
|
|
56
|
-
* // text: 'Great article!'
|
|
57
|
-
* // }
|
|
58
|
-
* // }
|
|
59
|
-
*
|
|
60
|
-
* @description
|
|
61
|
-
* Used by:
|
|
62
|
-
* - toJsonApiRecord calls this as the core transformation logic
|
|
63
|
-
* - buildJsonApiResponse uses this indirectly through toJsonApiRecord
|
|
64
|
-
*
|
|
65
|
-
* Purpose:
|
|
66
|
-
* - Separates database structure from API structure
|
|
67
|
-
* - Filters out foreign keys that become relationships in JSON:API
|
|
68
|
-
* - Removes internal fields used for query optimization
|
|
69
|
-
* - Ensures clean attribute objects without implementation details
|
|
70
|
-
*
|
|
71
|
-
* Data flow:
|
|
72
|
-
* - Called after database query returns flat records
|
|
73
|
-
* - Transforms each record individually
|
|
74
|
-
* - Output feeds into relationship building and response assembly
|
|
18
|
+
* @overload
|
|
19
|
+
* @param {ResourceConversionScope} scope
|
|
20
|
+
* @param {StorageRow} record
|
|
21
|
+
* @param {string} scopeName
|
|
22
|
+
* @returns {JsonApiResource}
|
|
75
23
|
*/
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
24
|
+
/**
|
|
25
|
+
* @overload
|
|
26
|
+
* @param {ResourceConversionScope} scope
|
|
27
|
+
* @param {StorageRow | null | undefined} record
|
|
28
|
+
* @param {string} scopeName
|
|
29
|
+
* @returns {JsonApiResource | null}
|
|
30
|
+
*/
|
|
31
|
+
/** @param {ResourceConversionScope} scope @param {StorageRow | null | undefined} record @param {string} scopeName @returns {JsonApiResource | null} */
|
|
32
|
+
export function toJsonApiRecord (scope, record, scopeName) {
|
|
33
|
+
let foreignKeys
|
|
34
|
+
try {
|
|
35
|
+
foreignKeys = scope.vars.schemaInfo.foreignKeyFields
|
|
36
|
+
} catch (error) {
|
|
37
|
+
throw wrapUnexpectedError(error, {
|
|
38
|
+
message: `Failed to read relationship metadata for '${scopeName}'`,
|
|
39
|
+
context: { scopeName, phase: 'relationshipMetadata' }
|
|
40
|
+
})
|
|
41
|
+
}
|
|
82
42
|
|
|
83
|
-
const {
|
|
84
|
-
|
|
85
|
-
const idProperty = context.schemaInfo?.idProperty || 'id'
|
|
86
|
-
const polymorphicFields = context.polymorphicFields || new Set()
|
|
43
|
+
const { idProperty = 'id' } = scope.vars.schemaInfo
|
|
44
|
+
if (!record) return null
|
|
87
45
|
|
|
46
|
+
const logicalRecord = translateRecordFromStorage(record, scope.vars.schemaInfo)
|
|
88
47
|
const { id, ...allAttributes } = logicalRecord
|
|
89
|
-
|
|
90
|
-
const foreignKeys = schemaInstance ? getForeignKeyFieldsFromUtils(schemaInstance) : new Set()
|
|
91
|
-
|
|
92
|
-
const internalFields = new Set([
|
|
93
|
-
RELATIONSHIPS_KEY,
|
|
94
|
-
RELATIONSHIP_METADATA_KEY,
|
|
95
|
-
ROW_NUMBER_KEY,
|
|
96
|
-
COMPUTED_DEPENDENCIES_KEY
|
|
97
|
-
])
|
|
98
|
-
|
|
48
|
+
/** @type {StorageRow} */
|
|
99
49
|
const attributes = {}
|
|
100
|
-
|
|
101
|
-
if (!foreignKeys.has(key) && !
|
|
50
|
+
for (const [key, value] of Object.entries(allAttributes)) {
|
|
51
|
+
if (!foreignKeys.has(key) && !internalFields.has(key) && key !== idProperty) {
|
|
102
52
|
attributes[key] = value
|
|
103
53
|
}
|
|
104
|
-
})
|
|
105
|
-
|
|
106
|
-
return {
|
|
107
|
-
type: scopeName,
|
|
108
|
-
id: String(id),
|
|
109
|
-
attributes
|
|
110
54
|
}
|
|
55
|
+
return { type: scopeName, id: String(id), attributes }
|
|
111
56
|
}
|
|
112
57
|
|
|
113
58
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* @param {
|
|
117
|
-
* @param {
|
|
118
|
-
* @param {
|
|
119
|
-
* @
|
|
120
|
-
*
|
|
121
|
-
* @
|
|
122
|
-
* // Input: Database record with various field types
|
|
123
|
-
* const record = {
|
|
124
|
-
* id: 1,
|
|
125
|
-
* title: 'My Article',
|
|
126
|
-
* author_id: 2, // belongsTo relationship
|
|
127
|
-
* category_id: 3, // belongsTo relationship
|
|
128
|
-
* views: 150,
|
|
129
|
-
* published: true
|
|
130
|
-
* };
|
|
131
|
-
*
|
|
132
|
-
* const jsonApi = toJsonApiRecord(scope, record, 'articles');
|
|
133
|
-
*
|
|
134
|
-
* // Output: Foreign keys filtered out
|
|
135
|
-
* // {
|
|
136
|
-
* // type: 'articles',
|
|
137
|
-
* // id: '1',
|
|
138
|
-
* // attributes: {
|
|
139
|
-
* // title: 'My Article',
|
|
140
|
-
* // views: 150,
|
|
141
|
-
* // published: true
|
|
142
|
-
* // }
|
|
143
|
-
* // }
|
|
144
|
-
*
|
|
145
|
-
* @example
|
|
146
|
-
* // Input: Record with polymorphic relationship
|
|
147
|
-
* const record = {
|
|
148
|
-
* id: 5,
|
|
149
|
-
* body: 'Nice post!',
|
|
150
|
-
* author_id: 10,
|
|
151
|
-
* commentable_type: 'posts', // Polymorphic
|
|
152
|
-
* commentable_id: 3 // Polymorphic
|
|
153
|
-
* };
|
|
154
|
-
* // Schema has polymorphic relationship defined
|
|
155
|
-
*
|
|
156
|
-
* const jsonApi = toJsonApiRecord(scope, record, 'comments');
|
|
157
|
-
*
|
|
158
|
-
* // Output: Both foreign keys and polymorphic fields filtered
|
|
159
|
-
* // {
|
|
160
|
-
* // type: 'comments',
|
|
161
|
-
* // id: '5',
|
|
162
|
-
* // attributes: {
|
|
163
|
-
* // body: 'Nice post!'
|
|
164
|
-
* // }
|
|
165
|
-
* // }
|
|
166
|
-
*
|
|
167
|
-
* @description
|
|
168
|
-
* Used by:
|
|
169
|
-
* - rest-api-knex-plugin's dataGet method for single resource responses
|
|
170
|
-
* - rest-api-knex-plugin's dataQuery method for collection responses
|
|
171
|
-
* - processIncludes when transforming included resources
|
|
172
|
-
* - buildJsonApiResponse as the primary transformation function
|
|
173
|
-
*
|
|
174
|
-
* Purpose:
|
|
175
|
-
* - Provides consistent JSON:API transformation across all query operations
|
|
176
|
-
* - Automatically identifies and filters foreign keys based on schema
|
|
177
|
-
* - Handles polymorphic relationship fields (type/id pairs)
|
|
178
|
-
* - Ensures IDs are always strings as required by JSON:API spec
|
|
179
|
-
*
|
|
180
|
-
* Data flow:
|
|
181
|
-
* 1. Database query returns flat records with all fields
|
|
182
|
-
* 2. toJsonApiRecord identifies foreign keys from schema
|
|
183
|
-
* 3. Filters out foreign keys and internal fields from attributes
|
|
184
|
-
* 4. Returns clean JSON:API resource ready for relationship processing
|
|
185
|
-
* 5. buildJsonApiResponse adds relationships and links to complete the response
|
|
59
|
+
* @overload
|
|
60
|
+
* @param {DataOperationScope} scope
|
|
61
|
+
* @param {DataStorageRow[]} records
|
|
62
|
+
* @param {DataResource[]} included
|
|
63
|
+
* @param {true} isSingle
|
|
64
|
+
* @param {string} scopeName
|
|
65
|
+
* @param {DataReadContext} context
|
|
66
|
+
* @returns {Promise<DataDocument<DataResource>>}
|
|
186
67
|
*/
|
|
187
|
-
export const toJsonApiRecord = (scope, record, scopeName) => {
|
|
188
|
-
const {
|
|
189
|
-
vars: {
|
|
190
|
-
schemaInfo: { schemaRelationships: relationships, idProperty }
|
|
191
|
-
}
|
|
192
|
-
} = scope
|
|
193
|
-
|
|
194
|
-
const polymorphicFields = new Set()
|
|
195
|
-
try {
|
|
196
|
-
Object.entries(relationships || {}).forEach(([relName, relDef]) => {
|
|
197
|
-
if (relDef.belongsToPolymorphic) {
|
|
198
|
-
const typeFieldName = relDef.belongsToPolymorphic.typeField || `${relName}_type`
|
|
199
|
-
const idFieldName = relDef.belongsToPolymorphic.idField || `${relName}_id`
|
|
200
|
-
polymorphicFields.add(typeFieldName)
|
|
201
|
-
polymorphicFields.add(idFieldName)
|
|
202
|
-
}
|
|
203
|
-
})
|
|
204
|
-
} catch (e) {
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
const deps = {
|
|
208
|
-
context: {
|
|
209
|
-
scopeName,
|
|
210
|
-
schemaInfo: { idProperty },
|
|
211
|
-
polymorphicFields
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
return toJsonApi(scope, record, deps)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
68
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* @
|
|
222
|
-
* @param {
|
|
223
|
-
* @param {
|
|
224
|
-
* @param {
|
|
225
|
-
* @param {
|
|
226
|
-
* @
|
|
227
|
-
* @param {Object} context - Request context with pagination metadata
|
|
228
|
-
* @returns {Promise<Object>} Complete JSON:API response document
|
|
229
|
-
*
|
|
230
|
-
* @example
|
|
231
|
-
* // Input: Single article with author include
|
|
232
|
-
* const records = [{
|
|
233
|
-
* id: 1,
|
|
234
|
-
* title: 'Article Title',
|
|
235
|
-
* content: 'Article content',
|
|
236
|
-
* author_id: 2,
|
|
237
|
-
* category_id: 3
|
|
238
|
-
* }];
|
|
239
|
-
*
|
|
240
|
-
* const included = [{
|
|
241
|
-
* type: 'authors',
|
|
242
|
-
* id: '2',
|
|
243
|
-
* attributes: { name: 'John Doe', email: 'john@example.com' }
|
|
244
|
-
* }];
|
|
245
|
-
*
|
|
246
|
-
* const response = await buildJsonApiResponse(scope, records, included, true, 'articles', context);
|
|
247
|
-
*
|
|
248
|
-
* // Output: Complete JSON:API document
|
|
249
|
-
* // {
|
|
250
|
-
* // data: {
|
|
251
|
-
* // type: 'articles',
|
|
252
|
-
* // id: '1',
|
|
253
|
-
* // attributes: {
|
|
254
|
-
* // title: 'Article Title',
|
|
255
|
-
* // content: 'Article content'
|
|
256
|
-
* // },
|
|
257
|
-
* // relationships: {
|
|
258
|
-
* // author: {
|
|
259
|
-
* // data: { type: 'authors', id: '2' },
|
|
260
|
-
* // links: {
|
|
261
|
-
* // self: '/articles/1/relationships/author',
|
|
262
|
-
* // related: '/articles/1/author'
|
|
263
|
-
* // }
|
|
264
|
-
* // },
|
|
265
|
-
* // category: {
|
|
266
|
-
* // data: { type: 'categories', id: '3' },
|
|
267
|
-
* // links: {
|
|
268
|
-
* // self: '/articles/1/relationships/category',
|
|
269
|
-
* // related: '/articles/1/category'
|
|
270
|
-
* // }
|
|
271
|
-
* // }
|
|
272
|
-
* // },
|
|
273
|
-
* // links: {
|
|
274
|
-
* // self: '/articles/1'
|
|
275
|
-
* // }
|
|
276
|
-
* // },
|
|
277
|
-
* // included: [{
|
|
278
|
-
* // type: 'authors',
|
|
279
|
-
* // id: '2',
|
|
280
|
-
* // attributes: { name: 'John Doe', email: 'john@example.com' },
|
|
281
|
-
* // links: { self: '/authors/2' }
|
|
282
|
-
* // }],
|
|
283
|
-
* // links: {
|
|
284
|
-
* // self: '/articles/1'
|
|
285
|
-
* // }
|
|
286
|
-
* // }
|
|
287
|
-
*
|
|
288
|
-
* @example
|
|
289
|
-
* // Input: Collection with pagination
|
|
290
|
-
* const records = [
|
|
291
|
-
* { id: 1, title: 'Article 1', author_id: 10 },
|
|
292
|
-
* { id: 2, title: 'Article 2', author_id: 10 },
|
|
293
|
-
* { id: 3, title: 'Article 3', author_id: 11 }
|
|
294
|
-
* ];
|
|
295
|
-
*
|
|
296
|
-
* context.returnMeta = {
|
|
297
|
-
* paginationMeta: { page: 2, pageSize: 3, pageCount: 10, total: 30 },
|
|
298
|
-
* paginationLinks: {
|
|
299
|
-
* self: '/articles?page[number]=2&page[size]=3',
|
|
300
|
-
* first: '/articles?page[number]=1&page[size]=3',
|
|
301
|
-
* prev: '/articles?page[number]=1&page[size]=3',
|
|
302
|
-
* next: '/articles?page[number]=3&page[size]=3',
|
|
303
|
-
* last: '/articles?page[number]=10&page[size]=3'
|
|
304
|
-
* }
|
|
305
|
-
* };
|
|
306
|
-
*
|
|
307
|
-
* const response = await buildJsonApiResponse(scope, records, [], false, 'articles', context);
|
|
308
|
-
*
|
|
309
|
-
* // Output: Collection with pagination metadata
|
|
310
|
-
* // {
|
|
311
|
-
* // data: [
|
|
312
|
-
* // { type: 'articles', id: '1', attributes: { title: 'Article 1' }, ... },
|
|
313
|
-
* // { type: 'articles', id: '2', attributes: { title: 'Article 2' }, ... },
|
|
314
|
-
* // { type: 'articles', id: '3', attributes: { title: 'Article 3' }, ... }
|
|
315
|
-
* // ],
|
|
316
|
-
* // meta: {
|
|
317
|
-
* // pagination: { page: 2, pageSize: 3, pageCount: 10, total: 30 }
|
|
318
|
-
* // },
|
|
319
|
-
* // links: {
|
|
320
|
-
* // self: '/articles?page[number]=2&page[size]=3',
|
|
321
|
-
* // first: '/articles?page[number]=1&page[size]=3',
|
|
322
|
-
* // prev: '/articles?page[number]=1&page[size]=3',
|
|
323
|
-
* // next: '/articles?page[number]=3&page[size]=3',
|
|
324
|
-
* // last: '/articles?page[number]=10&page[size]=3'
|
|
325
|
-
* // }
|
|
326
|
-
* // }
|
|
327
|
-
*
|
|
328
|
-
* @description
|
|
329
|
-
* Used by:
|
|
330
|
-
* - rest-api-knex-plugin's dataGet method for single resource responses
|
|
331
|
-
* - rest-api-knex-plugin's dataQuery method for collection responses
|
|
332
|
-
* - Called at the end of the query pipeline to assemble the final response
|
|
333
|
-
*
|
|
334
|
-
* Purpose:
|
|
335
|
-
* - Assembles all parts of a JSON:API response in the correct structure
|
|
336
|
-
* - Adds relationship objects with proper links for each foreign key
|
|
337
|
-
* - Handles both regular belongsTo and polymorphic relationships
|
|
338
|
-
* - Adds self links to all resources as required by JSON:API
|
|
339
|
-
* - Includes pagination metadata and links when applicable
|
|
340
|
-
*
|
|
341
|
-
* Data flow:
|
|
342
|
-
* 1. Query operations fetch primary records and optional includes
|
|
343
|
-
* 2. Records are transformed to JSON:API format via toJsonApiRecord
|
|
344
|
-
* 3. buildJsonApiResponse adds relationship objects for all foreign keys
|
|
345
|
-
* 4. Adds proper links (self, related) for navigating the API
|
|
346
|
-
* 5. Assembles included resources with their own links
|
|
347
|
-
* 6. Adds pagination metadata and links if provided
|
|
348
|
-
* 7. Returns complete JSON:API document ready for HTTP response
|
|
69
|
+
* @overload
|
|
70
|
+
* @param {DataOperationScope} scope
|
|
71
|
+
* @param {DataStorageRow[]} records
|
|
72
|
+
* @param {DataResource[]} included
|
|
73
|
+
* @param {false} isSingle
|
|
74
|
+
* @param {string} scopeName
|
|
75
|
+
* @param {DataReadContext} context
|
|
76
|
+
* @returns {Promise<DataDocument<DataResource[]>>}
|
|
349
77
|
*/
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
78
|
+
/**
|
|
79
|
+
* @param {DataOperationScope} scope
|
|
80
|
+
* @param {DataStorageRow[]} records
|
|
81
|
+
* @param {DataResource[]} included
|
|
82
|
+
* @param {boolean} isSingle
|
|
83
|
+
* @param {string} scopeName
|
|
84
|
+
* @param {DataReadContext} context
|
|
85
|
+
*/
|
|
86
|
+
export async function buildJsonApiResponse (scope, records, included = [], isSingle = false, scopeName, context) {
|
|
87
|
+
const { schemaInfo } = scope.vars
|
|
356
88
|
|
|
357
89
|
const {
|
|
358
|
-
|
|
90
|
+
schemaStructure,
|
|
359
91
|
schemaRelationships: relationships
|
|
360
92
|
} = schemaInfo
|
|
361
93
|
|
|
362
|
-
const schemaStructure = getSchemaStructure(schemaInstance)
|
|
363
|
-
|
|
364
94
|
const processedRecords = records.map(record => {
|
|
365
95
|
const { [RELATIONSHIPS_KEY]: _relationships, ...cleanRecord } = record
|
|
366
96
|
const logicalRecord = translateRecordFromStorage(cleanRecord, schemaInfo)
|
|
@@ -372,34 +102,18 @@ export const buildJsonApiResponse = async (scope, records, included = [], isSing
|
|
|
372
102
|
}
|
|
373
103
|
|
|
374
104
|
for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
|
|
375
|
-
if (fieldDef.belongsTo && fieldDef.as && fieldName
|
|
105
|
+
if (fieldDef.belongsTo && fieldDef.as && Object.hasOwn(logicalRecord, fieldName)) {
|
|
376
106
|
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
377
|
-
if (!jsonApiRecord.relationships
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
relationshipObject.links = {
|
|
107
|
+
if (!Object.hasOwn(jsonApiRecord.relationships, fieldDef.as)) {
|
|
108
|
+
const value = logicalRecord[fieldName]
|
|
109
|
+
jsonApiRecord.relationships[fieldDef.as] = {
|
|
110
|
+
data: value === null || value === undefined
|
|
111
|
+
? null
|
|
112
|
+
: { type: fieldDef.belongsTo, id: String(value) },
|
|
113
|
+
links: {
|
|
387
114
|
self: buildRelationshipUrl(context, scope, scopeName, resourceId, fieldDef.as, true),
|
|
388
115
|
related: buildRelationshipUrl(context, scope, scopeName, resourceId, fieldDef.as, false)
|
|
389
116
|
}
|
|
390
|
-
|
|
391
|
-
jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
392
|
-
} else {
|
|
393
|
-
const relationshipObject = {
|
|
394
|
-
data: null
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
relationshipObject.links = {
|
|
398
|
-
self: buildRelationshipUrl(context, scope, scopeName, resourceId, fieldDef.as, true),
|
|
399
|
-
related: buildRelationshipUrl(context, scope, scopeName, resourceId, fieldDef.as, false)
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
jsonApiRecord.relationships[fieldDef.as] = relationshipObject
|
|
403
117
|
}
|
|
404
118
|
}
|
|
405
119
|
}
|
|
@@ -409,27 +123,18 @@ export const buildJsonApiResponse = async (scope, records, included = [], isSing
|
|
|
409
123
|
if (relDef.belongsToPolymorphic) {
|
|
410
124
|
const typeValue = logicalRecord[relDef.belongsToPolymorphic.typeField]
|
|
411
125
|
const idValue = logicalRecord[relDef.belongsToPolymorphic.idField]
|
|
412
|
-
|
|
413
|
-
|
|
126
|
+
const data = getPolymorphicLinkage({
|
|
127
|
+
type: typeValue,
|
|
128
|
+
id: idValue,
|
|
129
|
+
types: relDef.belongsToPolymorphic.types,
|
|
130
|
+
scopeName,
|
|
131
|
+
relationshipName: relName
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
if (data || typeValue === null || idValue === null) {
|
|
414
135
|
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
type: typeValue,
|
|
418
|
-
id: String(idValue)
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
relationshipObject.links = {
|
|
423
|
-
self: buildRelationshipUrl(context, scope, scopeName, resourceId, relName, true),
|
|
424
|
-
related: buildRelationshipUrl(context, scope, scopeName, resourceId, relName, false)
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
jsonApiRecord.relationships[relName] = relationshipObject
|
|
428
|
-
} else if (typeValue === null || idValue === null) {
|
|
429
|
-
jsonApiRecord.relationships = jsonApiRecord.relationships || {}
|
|
430
|
-
const relationshipObject = {
|
|
431
|
-
data: null
|
|
432
|
-
}
|
|
136
|
+
/** @type {JsonApiRelationship} */
|
|
137
|
+
const relationshipObject = { data }
|
|
433
138
|
|
|
434
139
|
relationshipObject.links = {
|
|
435
140
|
self: buildRelationshipUrl(context, scope, scopeName, resourceId, relName, true),
|
|
@@ -441,28 +146,21 @@ export const buildJsonApiResponse = async (scope, records, included = [], isSing
|
|
|
441
146
|
}
|
|
442
147
|
})
|
|
443
148
|
|
|
149
|
+
jsonApiRecord.links ||= {}
|
|
150
|
+
jsonApiRecord.links.self = buildResourceUrl(context, scope, scopeName, resourceId)
|
|
444
151
|
return jsonApiRecord
|
|
445
152
|
})
|
|
446
153
|
|
|
447
|
-
const
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
if (Array.isArray(normalizedData)) {
|
|
451
|
-
normalizedData.forEach(item => {
|
|
452
|
-
if (!item.links) item.links = {}
|
|
453
|
-
item.links.self = buildResourceUrl(context, scope, scopeName, item.id)
|
|
454
|
-
})
|
|
455
|
-
} else {
|
|
456
|
-
if (!normalizedData.links) normalizedData.links = {}
|
|
457
|
-
normalizedData.links.self = buildResourceUrl(context, scope, scopeName, normalizedData.id)
|
|
458
|
-
}
|
|
459
|
-
}
|
|
154
|
+
const singleRecord = processedRecords[0]
|
|
155
|
+
if (isSingle && !singleRecord) throw new Error('A single-resource response requires a record')
|
|
156
|
+
const normalizedData = isSingle && singleRecord ? singleRecord : processedRecords
|
|
460
157
|
|
|
158
|
+
/** @type {DataDocument<DataResource | DataResource[]>} */
|
|
461
159
|
const response = {
|
|
462
160
|
data: normalizedData
|
|
463
161
|
}
|
|
464
162
|
|
|
465
|
-
if (included.length > 0) {
|
|
163
|
+
if (included.length > 0 || context.queryParams?.include !== undefined) {
|
|
466
164
|
included.forEach(item => {
|
|
467
165
|
if (!item.links) item.links = {}
|
|
468
166
|
item.links.self = buildResourceUrl(context, scope, item.type, item.id)
|
|
@@ -482,8 +180,8 @@ export const buildJsonApiResponse = async (scope, records, included = [], isSing
|
|
|
482
180
|
} else {
|
|
483
181
|
const urlPrefix = getUrlPrefix(context, scope)
|
|
484
182
|
response.links = {
|
|
485
|
-
self: isSingle
|
|
486
|
-
? buildResourceUrl(context, scope, scopeName,
|
|
183
|
+
self: isSingle && singleRecord
|
|
184
|
+
? buildJsonApiLink(buildResourceUrl(context, scope, scopeName, singleRecord.id), context.queryParams)
|
|
487
185
|
: `${urlPrefix}/${scopeName}${context?.returnMeta?.queryString || ''}`
|
|
488
186
|
}
|
|
489
187
|
}
|