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