json-rest-api 1.0.29 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (294) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +733 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +103 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3838 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +37 -0
  44. package/index.js +3 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +70 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. package/tests/virtual-fields.test.js +0 -404
@@ -0,0 +1,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.