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
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Field transformations"
|
|
3
|
+
chapter: 12
|
|
4
|
+
chapter_label: "12"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 12. Field transformations
|
|
8
|
+
|
|
9
|
+
Use setters to transform validated input before storage, getters to transform
|
|
10
|
+
selected read values, and computed fields to derive output from other fields.
|
|
11
|
+
Virtual fields accept temporary input without storing it. Hidden and normally
|
|
12
|
+
hidden declarations control which fields appear in output.
|
|
13
|
+
|
|
14
|
+
Computed fields are output-only. Supplying a computed field in POST, PUT or
|
|
15
|
+
PATCH attributes fails validation with `FIELD_NOT_ALLOWED`; it is not silently
|
|
16
|
+
discarded. The failed write rolls back. Virtual fields are the mechanism for
|
|
17
|
+
accepting temporary input that should not be stored.
|
|
18
|
+
|
|
19
|
+
For a derived SQL value that must support filtering, sorting or cursors, use
|
|
20
|
+
[query projections](15-query-projections.md). JavaScript computations happen
|
|
21
|
+
after fetching and cannot supply SQL ordering.
|
|
22
|
+
|
|
23
|
+
The examples below form one runnable configuration. Insert the JavaScript blocks
|
|
24
|
+
in order into the [starting script](03-running-example.md) before starting
|
|
25
|
+
the server, on a fresh database.
|
|
26
|
+
|
|
27
|
+
## Declare transformations
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
await api.addResource('categories', {
|
|
31
|
+
schema: { name: { type: 'string', required: true } },
|
|
32
|
+
relationships: { products: { type: 'hasMany', target: 'products', foreignKey: 'category_id' } }
|
|
33
|
+
})
|
|
34
|
+
await api.addResource('products', {
|
|
35
|
+
schema: {
|
|
36
|
+
name: {
|
|
37
|
+
type: 'string', required: true,
|
|
38
|
+
setter: value => value.trim(),
|
|
39
|
+
getter: async value => value.toUpperCase()
|
|
40
|
+
},
|
|
41
|
+
code: {
|
|
42
|
+
type: 'string', required: true,
|
|
43
|
+
setter: value => value.trim(),
|
|
44
|
+
runGetterAfter: ['name'],
|
|
45
|
+
getter: (value, { attributes }) => `${value}:${attributes.name}`
|
|
46
|
+
},
|
|
47
|
+
price: { type: 'number', required: true, setter: value => Math.round(value * 100) / 100 },
|
|
48
|
+
cost: { type: 'number', required: true, normallyHidden: true },
|
|
49
|
+
privateNote: { type: 'string', hidden: true },
|
|
50
|
+
previewLabel: { type: 'string', virtual: true },
|
|
51
|
+
category_id: { type: 'id', belongsTo: 'categories', as: 'category', nullable: true },
|
|
52
|
+
marginPercent: {
|
|
53
|
+
type: 'number', computed: true, dependencies: ['profit', 'price'],
|
|
54
|
+
compute: ({ attributes }) => attributes.price === 0 ? 0 : attributes.profit / attributes.price * 100
|
|
55
|
+
},
|
|
56
|
+
profit: {
|
|
57
|
+
type: 'number', computed: true, normallyHidden: true, dependencies: ['price', 'cost'],
|
|
58
|
+
compute: ({ attributes }) => attributes.price - attributes.cost
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
})
|
|
62
|
+
await api.resources.categories.createKnexTable()
|
|
63
|
+
await api.resources.products.createKnexTable()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Setters store the trimmed name/code and rounded price. The asynchronous name
|
|
67
|
+
getter is awaited; the code getter runs after it, using the uppercase name.
|
|
68
|
+
The declaration order deliberately places `marginPercent` before its dependency
|
|
69
|
+
`profit`; dependency ordering ensures profit is computed first.
|
|
70
|
+
|
|
71
|
+
`hidden: true` means a field is never emitted, even when requested.
|
|
72
|
+
`normallyHidden: true` omits it from default output but permits explicit selection.
|
|
73
|
+
Neither declaration prevents its use as an explicit computation dependency.
|
|
74
|
+
Review what a derived value reveals about its inputs.
|
|
75
|
+
|
|
76
|
+
## Create and refetch
|
|
77
|
+
|
|
78
|
+
```javascript
|
|
79
|
+
const category = await api.resources.categories.post({ data: { name: 'Equipment' } })
|
|
80
|
+
const created = await api.resources.products.post({
|
|
81
|
+
data: {
|
|
82
|
+
name: ' Widget ', code: ' W01 ', price: 20, cost: 5,
|
|
83
|
+
privateNote: 'Internal only', previewLabel: 'Preview', category: category.id
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
const fetched = await api.resources.products.get({ id: created.id })
|
|
87
|
+
console.log('Created:', created)
|
|
88
|
+
console.log('Fetched:', fetched)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Both responses have name `WIDGET`, code `W01:WIDGET`, and marginPercent `75`.
|
|
92
|
+
They omit cost, profit and privateNote. The full write response can include the
|
|
93
|
+
provided virtual previewLabel; a later read does not recover it from storage.
|
|
94
|
+
Getters transform read output without overwriting the stored name or code.
|
|
95
|
+
|
|
96
|
+
## Sparse fields and hidden dependencies
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
const marginOnly = await api.resources.products.get({
|
|
100
|
+
id: created.id,
|
|
101
|
+
queryParams: { fields: { products: 'marginPercent' } }
|
|
102
|
+
})
|
|
103
|
+
const selectedCost = await api.resources.products.get({
|
|
104
|
+
id: created.id,
|
|
105
|
+
queryParams: { fields: { products: 'cost,privateNote' } }
|
|
106
|
+
})
|
|
107
|
+
console.log('Margin only:', marginOnly)
|
|
108
|
+
console.log('Selected cost:', selectedCost)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
The first result contains ID and marginPercent only. Price, cost and profit are
|
|
112
|
+
available internally for computation without leaking into the selected output.
|
|
113
|
+
The second exposes cost `5` but still omits privateNote. Computed fields are
|
|
114
|
+
read-time output fields; do not submit them as stored attributes.
|
|
115
|
+
|
|
116
|
+
## Included resources and response modes
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const included = await api.resources.categories.get({
|
|
120
|
+
id: category.id,
|
|
121
|
+
queryParams: {
|
|
122
|
+
include: ['products'],
|
|
123
|
+
fields: { categories: 'name,products', products: 'code,marginPercent' }
|
|
124
|
+
}
|
|
125
|
+
})
|
|
126
|
+
const minimal = await api.resources.products.patch({
|
|
127
|
+
id: created.id, data: { price: 25 }, returning: 'minimal'
|
|
128
|
+
})
|
|
129
|
+
const afterPriceChange = await api.resources.products.get({
|
|
130
|
+
id: created.id, queryParams: { fields: { products: 'marginPercent' } }
|
|
131
|
+
})
|
|
132
|
+
console.log('Included product:', included.products[0])
|
|
133
|
+
console.log('Minimal write:', minimal)
|
|
134
|
+
console.log('Updated margin:', afterPriceChange.marginPercent)
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The included product receives the same getters and computations as a primary
|
|
138
|
+
record, with ID, code and marginPercent. The minimal PATCH returns only type/ID;
|
|
139
|
+
it does not run full response enrichment. The later read computes marginPercent
|
|
140
|
+
`80` from the changed price and existing cost. Sparse selection determines which
|
|
141
|
+
getters/computed fields are needed; they do not all run on every request.
|
|
142
|
+
|
|
143
|
+
## Validate virtual input with a supported hook
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
import { RestApiValidationError } from 'json-rest-api'
|
|
147
|
+
|
|
148
|
+
await api.customize({ hooks: {
|
|
149
|
+
afterSchemaValidatePost: {
|
|
150
|
+
functionName: 'validate-product-preview-label',
|
|
151
|
+
handler: ({ scopeName, context }) => {
|
|
152
|
+
if (scopeName !== 'products') return
|
|
153
|
+
const label = context.inputRecord.data.attributes.previewLabel
|
|
154
|
+
if (label !== undefined && label.length > 20) {
|
|
155
|
+
throw new RestApiValidationError('Preview label is too long', { fields: ['previewLabel'] })
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
} })
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
This illustrates hook registration and access to the internal JSON:API input.
|
|
163
|
+
For field validation, prefer declaring schema constraints such as `max: 20`;
|
|
164
|
+
use a typed validation error when a custom validation hook is necessary. A real
|
|
165
|
+
hook may depend on several inputs or request context. See the
|
|
166
|
+
[hook guide](13-hooks-and-lifecycle.md) for lifecycle stages.
|
|
167
|
+
Virtual input is not universally available to every hook or unrelated resource.
|
|
168
|
+
|
|
169
|
+
## Dependency contract
|
|
170
|
+
|
|
171
|
+
Declare local field names in `runSetterAfter`, `runGetterAfter` or computed
|
|
172
|
+
`dependencies`. Each declaration must be an array of nonempty names. The
|
|
173
|
+
compiler rejects missing fields, cycles and dependencies unavailable at that
|
|
174
|
+
stage; repeated names are evaluated once. Registration and canonical
|
|
175
|
+
`addKnexFields` compile the orders. Reads select the required part of that
|
|
176
|
+
graph without compiling or sorting it again.
|
|
177
|
+
|
|
178
|
+
| Stage | Available dependencies |
|
|
179
|
+
| --- | --- |
|
|
180
|
+
| Setters | Validated input attributes, including virtual input and fields without setters. Earlier setters have already run. |
|
|
181
|
+
| Getters | Selected stored attributes, SQL projections and available virtual input. Earlier getters have already run. |
|
|
182
|
+
| Computed fields | Getter results, SQL projections, available virtual input and earlier computed results. |
|
|
183
|
+
|
|
184
|
+
Setters do not fetch missing PATCH attributes from storage. A dependency orders
|
|
185
|
+
processing; it does not make an optional input present. Getters cannot depend
|
|
186
|
+
on computed fields because computations run later. Logical `id` (or the
|
|
187
|
+
configured `idProperty`) can be declared as a read dependency; read it from the
|
|
188
|
+
callback's `id`, not `attributes`. Relationship aliases, belongs-to foreign
|
|
189
|
+
keys and polymorphic backing fields are linkage rather than callback attributes
|
|
190
|
+
and cannot be declared as read dependencies. Use the appropriate relationship
|
|
191
|
+
API when a computation needs related records.
|
|
192
|
+
|
|
193
|
+
```js
|
|
194
|
+
schema: {
|
|
195
|
+
total: {
|
|
196
|
+
type: 'number', computed: true, dependencies: ['subtotal'],
|
|
197
|
+
compute: ({ attributes }) => attributes.subtotal * 1.1
|
|
198
|
+
},
|
|
199
|
+
subtotal: {
|
|
200
|
+
type: 'number', computed: true, normallyHidden: true,
|
|
201
|
+
dependencies: ['price', 'quantity'],
|
|
202
|
+
compute: ({ attributes }) => attributes.price * attributes.quantity
|
|
203
|
+
},
|
|
204
|
+
price: { type: 'number', normallyHidden: true },
|
|
205
|
+
quantity: { type: 'number', normallyHidden: true }
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Selecting only `total` fetches `price` and `quantity`, computes `subtotal`, then
|
|
210
|
+
computes `total`. Only `total` appears in the attributes. This order is independent
|
|
211
|
+
of declaration order and fieldset order, and applies to included records too.
|
|
212
|
+
The `attributes` and `record` callback views both receive earlier computed results.
|
|
213
|
+
Declare every field a callback reads so sparse reads fetch the same inputs as
|
|
214
|
+
full reads.
|
|
215
|
+
|
|
216
|
+
The `enrichAttributes` hook runs after automatic computations. A value supplied
|
|
217
|
+
only by that later hook cannot serve as an automatic computation's prerequisite;
|
|
218
|
+
give the prerequisite its own `compute` callback or supply it before computation.
|
|
219
|
+
|
|
220
|
+
Explicit dependencies may use `hidden` or `normallyHidden` fields. Authors are
|
|
221
|
+
responsible for what the derived value reveals. Hidden fields never appear in
|
|
222
|
+
output; normally hidden fields appear only when selected. Unrelated computed
|
|
223
|
+
callbacks do not run merely because their definitions exist. Async callbacks
|
|
224
|
+
are awaited in order; a failed dependency stops subsequent callbacks and rejects
|
|
225
|
+
the operation under the documented error/transaction contract.
|
|
226
|
+
|
|
227
|
+
Virtual input is available only on the written resource's full response, before
|
|
228
|
+
its getters and computations. It is not copied onto included or unrelated
|
|
229
|
+
records. It remains absent on a later read unless a hook supplies it. Null and
|
|
230
|
+
undefined virtual input are not echoed. Selected virtual getters can declare
|
|
231
|
+
stored/projected dependencies; unused virtual input does not trigger getters.
|
|
232
|
+
Getter `originalValue` and `originalAttributes` reflect the available values
|
|
233
|
+
before getters, including that virtual input.
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
### Setter and getter context
|
|
237
|
+
|
|
238
|
+
Both callbacks receive the field value first and a context object second.
|
|
239
|
+
Their context objects differ:
|
|
240
|
+
|
|
241
|
+
| Callback | Context properties |
|
|
242
|
+
| --- | --- |
|
|
243
|
+
| Setter | `attributes`, `fieldName`, `originalValue`, `originalAttributes`, `scopeName`, `method`, `api`, `helpers`, `auth` |
|
|
244
|
+
| Getter | `id`, `attributes`, `fieldName`, `originalValue`, `originalAttributes`, `record`, `parentContext`, `scopeName`, `api`, `helpers`, `isMainResource` |
|
|
245
|
+
|
|
246
|
+
`attributes` contains the current transformation state. Earlier ordered setters
|
|
247
|
+
or getters have already modified it. The getter's `record` aliases these
|
|
248
|
+
attributes; it is not a full persisted record with ID and relationship state.
|
|
249
|
+
Read its ID from `id` and request context from `parentContext`.
|
|
250
|
+
|
|
251
|
+
A setter receives validated input, including available virtual fields. On PATCH,
|
|
252
|
+
omitted attributes are not fetched from storage to populate setter context.
|
|
253
|
+
Use `runSetterAfter` or `runGetterAfter` to declare dependencies at the relevant
|
|
254
|
+
stage, as described in the dependency contract below.
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
### Compute callback context
|
|
258
|
+
|
|
259
|
+
The callback receives `{ id, attributes, record, context, helpers, api }`.
|
|
260
|
+
`attributes` contains getter results and earlier computed results. `record` is
|
|
261
|
+
a copy of transformed attributes that also receives earlier computed results;
|
|
262
|
+
it does not contain an added `id`. Read the identifier from `id`.
|
|
263
|
+
`context` is the parent request context, not another copy of the attributes.
|
|
264
|
+
There is no separate top-level `scopeName` argument in this callback object.
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
## Storage and failure boundaries
|
|
268
|
+
|
|
269
|
+
Use serializers for storage representation and getters for response presentation.
|
|
270
|
+
Built-in object/array storage already serializes structured values; an extra
|
|
271
|
+
JSON.stringify/JSON.parse getter/setter pair can serialize twice. Custom serializers
|
|
272
|
+
must follow the [storage contract](33-migrating-to-v2.md).
|
|
273
|
+
|
|
274
|
+
Async setters/getters/computations are awaited. Declare dependencies instead of
|
|
275
|
+
relying on field declaration order, and keep remote work and database calls in
|
|
276
|
+
computed fields bounded: a collection can invoke a callback for many records.
|
|
277
|
+
Use the supplied transaction/context for related operations where appropriate.
|
|
278
|
+
Do not assume a setter has fetched the existing PATCH record.
|
|
279
|
+
|
|
280
|
+
Callback failures reject the operation. On a full write response, an enrichment
|
|
281
|
+
failure can cause the owned write transaction to roll back. Use the
|
|
282
|
+
[transaction outcome](20-transaction-outcomes.md) and original error cause when
|
|
283
|
+
handling failures; do not convert unexpected failures into successful null values.
|
|
284
|
+
|
|
285
|
+
For sensitive stored values, declare hidden output rather than relying on a
|
|
286
|
+
getter to mask them. Application encryption or password hashing requires an
|
|
287
|
+
actual implementation; a string prefix is not a hashing algorithm.
|