json-rest-api 1.0.22 → 1.0.23
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/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +51 -14
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/package.json +2 -2
- package/plugins/core/connectors/express-plugin.js +97 -184
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1104 -160
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
- package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
- package/plugins/core/lib/storage/storage-adapter.js +79 -2
- package/plugins/core/lib/storage/storage-mapping.js +204 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +249 -211
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +121 -83
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get.js +23 -14
- package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
- package/plugins/core/rest-api-plugin-methods/post.js +13 -8
- package/plugins/core/rest-api-plugin-methods/put.js +45 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin.js +2 -0
- package/plugins/core/rest-api-positioning-plugin.js +1 -1
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -3
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +518 -0
- package/tests/db-table-operations.test.js +781 -0
- package/tests/fastify-plugin.test.js +141 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +188 -219
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +210 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -314,66 +314,17 @@ await api.resources.books.put({
|
|
|
314
314
|
// publisher_id and genres NOT included!
|
|
315
315
|
});
|
|
316
316
|
```
|
|
317
|
-
**
|
|
317
|
+
**Result: request rejected**
|
|
318
318
|
|
|
319
|
-
|
|
320
|
-
|-------|---------|
|
|
321
|
-
| **books** | • title: Updated<br>• author_id: Remains 1 (Tolkien)<br>• publisher_id: **SET TO NULL** |
|
|
322
|
-
| **book_genres** | **ALL 3 RECORDS DELETED** - Book no longer has any genres |
|
|
323
|
-
| **Other books** | **NO CHANGES** - LOTR and Silmarillion unchanged |
|
|
319
|
+
PUT now fails fast when you omit a persisted field that already has a stored value. Since this book already has a `publisher_id`, leaving it out would silently discard data, so the API rejects the request instead of clearing it.
|
|
324
320
|
|
|
325
|
-
|
|
326
|
-
- `publisher_id` becomes NULL (it's nullable, so this is allowed)
|
|
327
|
-
- All genre relationships are removed from the pivot table
|
|
328
|
-
- Missing fields are NOT preserved from the current state - they're cleared
|
|
321
|
+
Relationship collections still follow replacement semantics when you provide a `relationships` object. The rejection in this example happens because `publisher_id` is an existing stored belongsTo value that was omitted entirely.
|
|
329
322
|
|
|
330
323
|
**Expected output**
|
|
331
324
|
|
|
332
325
|
```text
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
title: 'The Hobbit',
|
|
336
|
-
isbn: '9780547928227',
|
|
337
|
-
published_year: 1937,
|
|
338
|
-
page_count: 310,
|
|
339
|
-
genres_ids: [ '1', '2', '3' ],
|
|
340
|
-
genres: [
|
|
341
|
-
{ id: '1', name: 'Fantasy' },
|
|
342
|
-
{ id: '2', name: 'Adventure' },
|
|
343
|
-
{ id: '3', name: 'Classic' }
|
|
344
|
-
],
|
|
345
|
-
author_id: '1',
|
|
346
|
-
author: {
|
|
347
|
-
id: '1',
|
|
348
|
-
name: 'J.R.R.',
|
|
349
|
-
surname: 'Tolkien',
|
|
350
|
-
birth_year: 1892,
|
|
351
|
-
books_ids: []
|
|
352
|
-
},
|
|
353
|
-
publisher_id: '1',
|
|
354
|
-
publisher: {
|
|
355
|
-
id: '1',
|
|
356
|
-
name: 'Penguin Random House',
|
|
357
|
-
country: 'USA',
|
|
358
|
-
books_ids: []
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
Book state AFTER the change: {
|
|
362
|
-
id: '1',
|
|
363
|
-
title: 'The Hobbit - Revised',
|
|
364
|
-
isbn: '9780547928227',
|
|
365
|
-
published_year: 1937,
|
|
366
|
-
page_count: 310,
|
|
367
|
-
genres_ids: [],
|
|
368
|
-
author_id: '1',
|
|
369
|
-
author: {
|
|
370
|
-
id: '1',
|
|
371
|
-
name: 'J.R.R.',
|
|
372
|
-
surname: 'Tolkien',
|
|
373
|
-
birth_year: 1892,
|
|
374
|
-
books_ids: []
|
|
375
|
-
}
|
|
376
|
-
}
|
|
326
|
+
RestApiValidationError: PUT request omits persisted fields that already have stored values
|
|
327
|
+
fields: [ 'data.attributes.publisher_id' ]
|
|
377
328
|
```
|
|
378
329
|
|
|
379
330
|
### Example 3: PUT with Explicit Nulls
|
|
@@ -400,9 +351,10 @@ await api.resources.books.put({
|
|
|
400
351
|
| **book_genres** | **ALL 3 RECORDS DELETED** |
|
|
401
352
|
| **Other books** | **NO CHANGES** |
|
|
402
353
|
|
|
403
|
-
This example shows PUT with **explicit nulls and empty arrays**.
|
|
354
|
+
This example shows PUT with **explicit nulls and empty arrays**. Explicit clearing is still allowed when the schema permits it, but it must be intentional. This reinforces that PUT is about **complete state replacement**:
|
|
404
355
|
- You must include ALL data you want to keep
|
|
405
|
-
-
|
|
356
|
+
- Existing stored values are not cleared by omission; omission is rejected
|
|
357
|
+
- Explicit `null` / `[]` is how you clear data intentionally
|
|
406
358
|
- Required fields (like `author_id`) must always be provided and cannot be null
|
|
407
359
|
|
|
408
360
|
|
|
@@ -782,8 +734,8 @@ This preservation is crucial for:
|
|
|
782
734
|
## Best Practices
|
|
783
735
|
|
|
784
736
|
1. **Use PATCH for targeted updates** - When you only want to change specific fields
|
|
785
|
-
2. **Use PUT when replacing everything** - When you
|
|
786
|
-
3. **Always include
|
|
737
|
+
2. **Use PUT when replacing everything** - When you are explicitly resending the stored values you want to keep
|
|
738
|
+
3. **Always include existing stored fields you want to preserve with PUT** - Omitted persisted values are rejected
|
|
787
739
|
4. **Remember required fields** - PUT must include all required fields like author_id
|
|
788
740
|
5. **Child records are independent** - Other books by the same author are never affected
|
|
789
741
|
|
|
@@ -791,6 +743,6 @@ This preservation is crucial for:
|
|
|
791
743
|
|
|
792
744
|
Now that you understand how updates affect relationships:
|
|
793
745
|
- Practice with PATCH for surgical updates
|
|
794
|
-
- Use PUT for
|
|
746
|
+
- Use PUT for explicit full-state replacements
|
|
795
747
|
- Plan your API calls to avoid unintended data loss
|
|
796
|
-
- Remember that the intelligent sync preserves pivot table metadata
|
|
748
|
+
- Remember that the intelligent sync preserves pivot table metadata
|
|
@@ -49,7 +49,7 @@ This guide has been split into the following sections for easier navigation:
|
|
|
49
49
|
|
|
50
50
|
## [2.8 Effects of PUT and PATCH](./GUIDE_2_8_Effects_of_PUT_and_PATCH.md)
|
|
51
51
|
- Understanding the difference between PUT and PATCH
|
|
52
|
-
- How PUT affects relationships
|
|
52
|
+
- How PUT affects relationships and existing stored fields
|
|
53
53
|
- How PATCH affects relationships (partial updates)
|
|
54
54
|
- Managing belongsTo relationships
|
|
55
55
|
- Handling hasMany relationships
|
|
@@ -63,4 +63,4 @@ This guide has been split into the following sections for easier navigation:
|
|
|
63
63
|
|
|
64
64
|
---
|
|
65
65
|
|
|
66
|
-
[Back to Guide](./index.md)
|
|
66
|
+
[Back to Guide](./index.md)
|
|
@@ -11,6 +11,8 @@ Field transformations allow you to:
|
|
|
11
11
|
- Calculate values from other fields (computed fields)
|
|
12
12
|
- Control which fields are visible in responses (hidden fields)
|
|
13
13
|
|
|
14
|
+
For SQL-backed derived values that must participate in sorting or cursor pagination, use [Query Projections](GUIDE_X_Query_Projections.md). Those are query-layer fields, not normal computed fields.
|
|
15
|
+
|
|
14
16
|
## The Data Transformation Pipeline
|
|
15
17
|
|
|
16
18
|
Understanding when each transformation occurs is crucial for building robust APIs:
|
|
@@ -235,6 +237,8 @@ setter: (value, context) => {
|
|
|
235
237
|
|
|
236
238
|
Computed fields are output-only fields calculated from other fields. They're never stored and always calculated fresh when requested.
|
|
237
239
|
|
|
240
|
+
If a derived field must participate in SQL list behavior such as sorting or cursor pagination, use a query projection instead. Query projections are documented separately in [Query Projections](GUIDE_X_Query_Projections.md).
|
|
241
|
+
|
|
238
242
|
### Basic Computed Fields
|
|
239
243
|
|
|
240
244
|
```javascript
|
|
@@ -1243,4 +1247,4 @@ Common uses:
|
|
|
1243
1247
|
- Title case formatting
|
|
1244
1248
|
- Password hashing
|
|
1245
1249
|
- Price calculations
|
|
1246
|
-
- Data consistency across related records
|
|
1250
|
+
- Data consistency across related records
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
This guide provides a comprehensive reference for all hooks available in the json-rest-api system, including system-wide hooks from hooked-api and method-specific hooks from the REST API plugin.
|
|
4
4
|
|
|
5
|
+
If you are building a reusable plugin rather than just attaching hooks to one API, see [Writing Plugins](GUIDE_X_Writing_Plugins.md). This guide focuses on hook behavior; the plugin guide focuses on the supported extension surface.
|
|
6
|
+
|
|
5
7
|
An important concept when working with hooks is understanding how simplified mode affects data flow.
|
|
6
8
|
|
|
7
9
|
Inside hooks, JSON:API format is king _regardless of simplified mode_.
|
|
@@ -0,0 +1,355 @@
|
|
|
1
|
+
# Autofiltering
|
|
2
|
+
|
|
3
|
+
The `AutoFilterPlugin` provides declarative dataset scoping for `json-rest-api`.
|
|
4
|
+
|
|
5
|
+
It does one job:
|
|
6
|
+
|
|
7
|
+
- resolve scope values from request/runtime context
|
|
8
|
+
- apply those values as automatic filters
|
|
9
|
+
- stamp scoped fields on create
|
|
10
|
+
- preserve scoped fields on replace
|
|
11
|
+
- reject inconsistent scoped-field updates
|
|
12
|
+
|
|
13
|
+
It does **not** define authentication, roles, or permissions.
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Autofiltering is useful whenever a resource should be constrained by one or more persisted fields:
|
|
18
|
+
|
|
19
|
+
- `workspace_id`
|
|
20
|
+
- `user_id`
|
|
21
|
+
- `workspace_id + user_id`
|
|
22
|
+
- any other application-defined scope field
|
|
23
|
+
|
|
24
|
+
The plugin is generic. It does not assume:
|
|
25
|
+
|
|
26
|
+
- `context.auth`
|
|
27
|
+
- `authenticated`
|
|
28
|
+
- `admin`
|
|
29
|
+
- JWTs
|
|
30
|
+
- sessions
|
|
31
|
+
|
|
32
|
+
Your application decides where scope values come from. The plugin only consumes configured resolver functions.
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```js
|
|
37
|
+
import { Api } from 'hooked-api'
|
|
38
|
+
import {
|
|
39
|
+
RestApiPlugin,
|
|
40
|
+
RestApiKnexPlugin,
|
|
41
|
+
AutoFilterPlugin
|
|
42
|
+
} from 'json-rest-api'
|
|
43
|
+
|
|
44
|
+
const api = new Api({ name: 'scoped-api' })
|
|
45
|
+
|
|
46
|
+
await api.use(RestApiPlugin)
|
|
47
|
+
await api.use(RestApiKnexPlugin, { knex })
|
|
48
|
+
|
|
49
|
+
await api.use(AutoFilterPlugin, {
|
|
50
|
+
resolvers: {
|
|
51
|
+
workspace: ({ context }) => context.session?.workspaceId,
|
|
52
|
+
user: ({ context }) => context.subject?.id,
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
presets: {
|
|
56
|
+
public: { filters: [] },
|
|
57
|
+
|
|
58
|
+
workspace: {
|
|
59
|
+
filters: [
|
|
60
|
+
{ field: 'workspace_id', resolver: 'workspace' }
|
|
61
|
+
]
|
|
62
|
+
},
|
|
63
|
+
|
|
64
|
+
user: {
|
|
65
|
+
filters: [
|
|
66
|
+
{ field: 'user_id', resolver: 'user' }
|
|
67
|
+
]
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
workspace_user: {
|
|
71
|
+
filters: [
|
|
72
|
+
{ field: 'workspace_id', resolver: 'workspace' },
|
|
73
|
+
{ field: 'user_id', resolver: 'user' }
|
|
74
|
+
]
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
})
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Resource Configuration
|
|
81
|
+
|
|
82
|
+
Apply a preset by name:
|
|
83
|
+
|
|
84
|
+
```js
|
|
85
|
+
await api.addResource('projects', {
|
|
86
|
+
schema: {
|
|
87
|
+
id: { type: 'id' },
|
|
88
|
+
name: { type: 'string', required: true },
|
|
89
|
+
workspace_id: { type: 'string', required: true },
|
|
90
|
+
user_id: { type: 'number', required: true }
|
|
91
|
+
},
|
|
92
|
+
autofilter: 'workspace_user'
|
|
93
|
+
})
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Use a preset with extra filters:
|
|
97
|
+
|
|
98
|
+
```js
|
|
99
|
+
await api.addResource('documents', {
|
|
100
|
+
schema: {
|
|
101
|
+
id: { type: 'id' },
|
|
102
|
+
workspace_id: { type: 'string', required: true },
|
|
103
|
+
user_id: { type: 'number', required: true },
|
|
104
|
+
locale: { type: 'string', required: true }
|
|
105
|
+
},
|
|
106
|
+
autofilter: {
|
|
107
|
+
preset: 'workspace_user',
|
|
108
|
+
filters: [
|
|
109
|
+
{
|
|
110
|
+
field: 'locale',
|
|
111
|
+
resolve: ({ context }) => context.requestState?.locale
|
|
112
|
+
}
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Use inline filters with no preset:
|
|
119
|
+
|
|
120
|
+
```js
|
|
121
|
+
await api.addResource('reports', {
|
|
122
|
+
schema: {
|
|
123
|
+
id: { type: 'id' },
|
|
124
|
+
account_id: { type: 'string', required: true }
|
|
125
|
+
},
|
|
126
|
+
autofilter: {
|
|
127
|
+
filters: [
|
|
128
|
+
{
|
|
129
|
+
field: 'account_id',
|
|
130
|
+
resolve: ({ context }) => context.scopeValues?.accountId
|
|
131
|
+
}
|
|
132
|
+
]
|
|
133
|
+
}
|
|
134
|
+
})
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Declare a public resource explicitly:
|
|
138
|
+
|
|
139
|
+
```js
|
|
140
|
+
await api.addResource('system_settings', {
|
|
141
|
+
schema: {
|
|
142
|
+
id: { type: 'id' },
|
|
143
|
+
key: { type: 'string', required: true },
|
|
144
|
+
value: { type: 'string', required: true }
|
|
145
|
+
},
|
|
146
|
+
autofilter: 'public'
|
|
147
|
+
})
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
If a resource has no `autofilter` setting, the plugin does nothing for that resource.
|
|
151
|
+
|
|
152
|
+
## Runtime Behavior
|
|
153
|
+
|
|
154
|
+
### Query Scoping
|
|
155
|
+
|
|
156
|
+
Collection queries automatically receive all configured filters.
|
|
157
|
+
|
|
158
|
+
For a `workspace_user` resource:
|
|
159
|
+
|
|
160
|
+
```js
|
|
161
|
+
await api.resources.projects.query(
|
|
162
|
+
{ simplified: false },
|
|
163
|
+
{
|
|
164
|
+
session: { workspaceId: 'acme' },
|
|
165
|
+
subject: { id: 101 }
|
|
166
|
+
}
|
|
167
|
+
)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
behaves like:
|
|
171
|
+
|
|
172
|
+
```sql
|
|
173
|
+
SELECT * FROM projects
|
|
174
|
+
WHERE workspace_id = 'acme'
|
|
175
|
+
AND user_id = 101
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Single-Record Scoping
|
|
179
|
+
|
|
180
|
+
`get`, `put`, `patch`, and `delete` operate on the already-scoped dataset.
|
|
181
|
+
|
|
182
|
+
If a record is outside the current scope, it behaves as not found.
|
|
183
|
+
|
|
184
|
+
### Create-Time Stamping
|
|
185
|
+
|
|
186
|
+
On `POST`, scoped fields are injected automatically when missing.
|
|
187
|
+
|
|
188
|
+
Example:
|
|
189
|
+
|
|
190
|
+
```js
|
|
191
|
+
await api.resources.projects.post({
|
|
192
|
+
inputRecord: {
|
|
193
|
+
data: {
|
|
194
|
+
type: 'projects',
|
|
195
|
+
attributes: {
|
|
196
|
+
name: 'Roadmap'
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
simplified: false
|
|
201
|
+
}, {
|
|
202
|
+
session: { workspaceId: 'acme' },
|
|
203
|
+
subject: { id: 101 }
|
|
204
|
+
})
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Stored attributes:
|
|
208
|
+
|
|
209
|
+
```js
|
|
210
|
+
{
|
|
211
|
+
name: 'Roadmap',
|
|
212
|
+
workspace_id: 'acme',
|
|
213
|
+
user_id: 101
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Replace/Update Consistency
|
|
218
|
+
|
|
219
|
+
On `PUT`:
|
|
220
|
+
|
|
221
|
+
- missing scoped fields are injected so replacement stays consistent
|
|
222
|
+
- mismatched scoped fields are rejected
|
|
223
|
+
|
|
224
|
+
On `PATCH`:
|
|
225
|
+
|
|
226
|
+
- omitted scoped fields stay omitted
|
|
227
|
+
- explicitly provided mismatched scoped fields are rejected
|
|
228
|
+
|
|
229
|
+
## Relationship-Aware Scoping
|
|
230
|
+
|
|
231
|
+
Autofiltering also affects relationship validation because scoped single-record lookups are used when checking related resources.
|
|
232
|
+
|
|
233
|
+
So this will fail if the referenced project is outside scope:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
await api.resources.tasks.post({
|
|
237
|
+
inputRecord: {
|
|
238
|
+
data: {
|
|
239
|
+
type: 'tasks',
|
|
240
|
+
attributes: { title: 'Cross-scope task' },
|
|
241
|
+
relationships: {
|
|
242
|
+
project: {
|
|
243
|
+
data: { type: 'projects', id: '123' }
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
simplified: false
|
|
249
|
+
}, {
|
|
250
|
+
scopeValues: {
|
|
251
|
+
workspaceId: 'workspace-a',
|
|
252
|
+
userId: 101
|
|
253
|
+
}
|
|
254
|
+
})
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Missing Scope Values
|
|
258
|
+
|
|
259
|
+
By default, each filter is required.
|
|
260
|
+
|
|
261
|
+
If a resolver returns `undefined`, the plugin throws:
|
|
262
|
+
|
|
263
|
+
```text
|
|
264
|
+
Missing autofilter value for resolver 'workspace' on resource 'projects'
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
To make a filter optional, set `required: false`:
|
|
268
|
+
|
|
269
|
+
```js
|
|
270
|
+
{
|
|
271
|
+
field: 'workspace_id',
|
|
272
|
+
resolver: 'workspace',
|
|
273
|
+
required: false
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
When `required: false` and the resolver returns `undefined`, that filter is skipped.
|
|
278
|
+
|
|
279
|
+
## BelongsTo Foreign Keys
|
|
280
|
+
|
|
281
|
+
If the scoped field is a `belongsTo` foreign key with an alias, the plugin works through the JSON:API relationship shape.
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
|
|
285
|
+
```js
|
|
286
|
+
user_id: {
|
|
287
|
+
type: 'number',
|
|
288
|
+
belongsTo: 'users',
|
|
289
|
+
as: 'user'
|
|
290
|
+
}
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
The plugin will stamp or validate:
|
|
294
|
+
|
|
295
|
+
```js
|
|
296
|
+
data.relationships.user.data.id
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
instead of treating it as a direct client-owned attribute.
|
|
300
|
+
|
|
301
|
+
## Storage Mapping
|
|
302
|
+
|
|
303
|
+
Autofiltering uses the compiled storage adapter automatically.
|
|
304
|
+
|
|
305
|
+
So this works correctly:
|
|
306
|
+
|
|
307
|
+
```js
|
|
308
|
+
workspace_id: {
|
|
309
|
+
type: 'string',
|
|
310
|
+
storage: { column: 'workspace_key' }
|
|
311
|
+
}
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The plugin scopes using the logical field name, while Knex filtering uses the mapped storage column.
|
|
315
|
+
|
|
316
|
+
## Introspection Helpers
|
|
317
|
+
|
|
318
|
+
The plugin exposes light runtime inspection helpers:
|
|
319
|
+
|
|
320
|
+
```js
|
|
321
|
+
api.autofilter.getConfig()
|
|
322
|
+
api.autofilter.getScopeConfig('projects')
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
|
|
327
|
+
```js
|
|
328
|
+
api.autofilter.getScopeConfig('projects')
|
|
329
|
+
// {
|
|
330
|
+
// preset: 'workspace_user',
|
|
331
|
+
// filters: [
|
|
332
|
+
// { field: 'workspace_id', resolver: 'workspace', required: true },
|
|
333
|
+
// { field: 'user_id', resolver: 'user', required: true }
|
|
334
|
+
// ]
|
|
335
|
+
// }
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Design Boundary
|
|
339
|
+
|
|
340
|
+
`AutoFilterPlugin` is intentionally narrower than an auth plugin.
|
|
341
|
+
|
|
342
|
+
It owns:
|
|
343
|
+
|
|
344
|
+
- dataset scoping
|
|
345
|
+
- scoped-field stamping
|
|
346
|
+
- scoped-field consistency
|
|
347
|
+
|
|
348
|
+
It does **not** own:
|
|
349
|
+
|
|
350
|
+
- authentication meaning
|
|
351
|
+
- role policy
|
|
352
|
+
- admission checks
|
|
353
|
+
- permissions like `authenticated` or `owns`
|
|
354
|
+
|
|
355
|
+
That logic belongs in higher layers or custom `checkPermissions` hooks.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Fastify Integration
|
|
2
|
+
|
|
3
|
+
`json-rest-api` now ships a Fastify-oriented connector:
|
|
4
|
+
|
|
5
|
+
```js
|
|
6
|
+
import fastify from 'fastify'
|
|
7
|
+
import {
|
|
8
|
+
RestApiPlugin,
|
|
9
|
+
RestApiKnexPlugin,
|
|
10
|
+
FastifyPlugin
|
|
11
|
+
} from 'json-rest-api'
|
|
12
|
+
|
|
13
|
+
const app = fastify()
|
|
14
|
+
|
|
15
|
+
await api.use(RestApiPlugin, {
|
|
16
|
+
simplifiedApi: false,
|
|
17
|
+
simplifiedTransport: false
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
await api.use(RestApiKnexPlugin, { knex })
|
|
21
|
+
|
|
22
|
+
await api.use(FastifyPlugin, {
|
|
23
|
+
app,
|
|
24
|
+
mountPath: '/api'
|
|
25
|
+
})
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## What the connector does
|
|
29
|
+
|
|
30
|
+
The Fastify connector listens to the same `addRoute` hook as the Express connector and registers the generated REST routes on the Fastify instance you pass in `pluginOptions.app`.
|
|
31
|
+
|
|
32
|
+
It also consumes the transport schema exported by `json-rest-schema` before the request reaches the resource method:
|
|
33
|
+
|
|
34
|
+
- `POST` routes use the schema export in `create` mode
|
|
35
|
+
- `PUT` routes use the schema export in `replace` mode
|
|
36
|
+
- `PATCH` routes use the schema export in `patch` mode
|
|
37
|
+
- relationship `POST` / `PATCH` / `DELETE` routes use JSON:API relationship document schemas
|
|
38
|
+
|
|
39
|
+
Those field schemas are wrapped in the JSON:API document envelope expected by `json-rest-api`.
|
|
40
|
+
|
|
41
|
+
## What gets rejected early
|
|
42
|
+
|
|
43
|
+
Fastify can now stop malformed write payloads before they reach the app layer:
|
|
44
|
+
|
|
45
|
+
- missing `data`
|
|
46
|
+
- invalid `data.type`
|
|
47
|
+
- invalid `data.id` on `PUT` / `PATCH`
|
|
48
|
+
- malformed attribute payloads
|
|
49
|
+
- unknown attribute fields
|
|
50
|
+
- invalid scalar types according to the resource schema
|
|
51
|
+
- malformed relationship write documents
|
|
52
|
+
|
|
53
|
+
The transport schema also reflects the existing write contract:
|
|
54
|
+
|
|
55
|
+
- output-only computed fields are removed from `data.attributes`
|
|
56
|
+
- direct `belongsTo` foreign keys are removed from `data.attributes`
|
|
57
|
+
- relationship linkage still belongs under `data.relationships`
|
|
58
|
+
|
|
59
|
+
This keeps Fastify transport validation aligned with the same transport rules already enforced by the runtime methods.
|
|
60
|
+
|
|
61
|
+
## Content types
|
|
62
|
+
|
|
63
|
+
The connector registers an `application/vnd.api+json` parser and rejects unsupported write content types before the route handler runs.
|
|
64
|
+
|
|
65
|
+
Accepted write content types are:
|
|
66
|
+
|
|
67
|
+
- `application/vnd.api+json`
|
|
68
|
+
- `application/json`
|
|
69
|
+
|
|
70
|
+
## Scope
|
|
71
|
+
|
|
72
|
+
This connector is intentionally narrow:
|
|
73
|
+
|
|
74
|
+
- it uses the exported schema already owned by `json-rest-schema`
|
|
75
|
+
- it does not add a JSKIT-specific compatibility layer
|
|
76
|
+
- it does not move business validation out of the resource methods
|
|
77
|
+
|
|
78
|
+
The goal is transport-level shape rejection, not a second validation system.
|