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
@@ -1,491 +0,0 @@
1
- # 2.3 `belongsTo` Relationships
2
-
3
- `belongsTo` relationships represent a one-to-one or many-to-one association where the current resource "belongs to" another resource. For example, a `book` belongs to an `author`, or an `author` belongs to a `country`. These relationships are typically managed by a **foreign key** on the "belonging" resource's table.
4
-
5
- Let's expand our schema definitions to include `publishers` and link them to `countries`. These schemas will be defined **once** here and reused throughout this section.
6
-
7
- ```javascript
8
- // Define countries resource
9
- await api.addResource('countries', {
10
- schema: {
11
- name: { type: 'string', required: true, max: 100, search: true },
12
- code: { type: 'string', max: 2, unique: true, search: true, indexed: true },
13
- }
14
- });
15
- await api.resources.countries.createKnexTable();
16
-
17
- // Define publishers resource
18
- await api.addResource('publishers', {
19
- schema: {
20
- name: { type: 'string', required: true, max: 255 },
21
- country_id: { type: 'id', belongsTo: 'countries', as: 'country', nullable: true }
22
- },
23
- // searchSchema completely defines all filterable fields for this resource
24
- searchSchema: {
25
- name: { type: 'string' },
26
- country: { type: 'id', actualField: 'country_id', nullable: true },
27
- countryCode: { type: 'string', actualField: 'countries.code' }
28
- }
29
- });
30
- await api.resources.publishers.createKnexTable();
31
- ```
32
-
33
- Now, let's add some data. Use the relationship name `country` when linking a publisher to a country in simplified mode.
34
-
35
- ```javascript
36
- const france = await api.resources.countries.post({ name: 'France', code: 'FR' });
37
- const germany = await api.resources.countries.post({ name: 'Germany', code: 'DE' });
38
- const uk = await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
39
-
40
- // Create publishers linking via the relationship name (simplified syntax)
41
- const frenchPublisher = await api.resources.publishers.post({
42
- name: 'French Books Inc.',
43
- country: france.id
44
- });
45
-
46
- const germanPublisher = await api.resources.publishers.post({
47
- name: 'German Press GmbH',
48
- country: germany.id
49
- });
50
-
51
- const ukPublisher = await api.resources.publishers.post({
52
- name: 'UK Books Ltd.',
53
- country: uk.id
54
- });
55
-
56
- const internationalPublisher = await api.resources.publishers.post({
57
- name: 'Global Publishing',
58
- country: null
59
- });
60
-
61
-
62
- console.log('Added French Publisher:', inspect(frenchPublisher));
63
- console.log('Added German Publisher:', inspect(germanPublisher));
64
- console.log('Added UK Publisher:', inspect(ukPublisher));
65
- console.log('Added International Publisher:', inspect(internationalPublisher));
66
- ```
67
-
68
- **Using Relationship Names:**
69
-
70
- When defining a `belongsTo` relationship with an `as` alias (e.g., `country_id: { ..., as: 'country' }`), use the relationship name `country` when providing the related resource's ID during `post`, `put`, or `patch` operations in **simplified mode**:
71
-
72
- * Use the **relationship name** (the `as` value) directly with the ID of the related resource (e.g., `country: france.id`). This provides clarity and aligns with the relationship concept.
73
- * Foreign key field names (e.g., `country_id`) are no longer supported for relationship input to maintain consistency across all relationship types.
74
-
75
- **Field Visibility and Database-First Design:**
76
-
77
- Note that while the schema defines `country_id` (the actual database column), the API layer abstracts this away:
78
- - **API Input**: Use `country` (the relationship name)
79
- - **API Output**: Returns `country: { id: '1' }` (relationship object)
80
- - **Internal/Hooks**: Work with `country_id` (actual database field via `context.belongsToUpdates`)
81
-
82
- This separation ensures API consumers never see database implementation details like foreign key fields, while backend developers retain full control over the database structure.
83
-
84
- **Expected Output (Illustrative, IDs may vary):**
85
-
86
- ```text
87
- Added French Publisher: { id: '1', name: 'French Books Inc.', country: { id: '1' } }
88
- Added German Publisher: { id: '2', name: 'German Press GmbH', country: { id: '2' } }
89
- Added UK Publisher: { id: '3', name: 'UK Books Ltd.', country: { id: '3' } }
90
- Added International Publisher: { id: '4', name: 'Global Publishing', country: null }
91
- ```
92
-
93
- ## Including `belongsTo` Records (`include`)
94
-
95
- To retrieve related `belongsTo` resources, use the `include` query parameter.
96
-
97
- When fetching data programmatically, `simplified` mode is `true` by default. This means that instead of a separate `included` array (as in full JSON:API), related `belongsTo` resources are **denormalized and embedded directly** within the main resource's object structure, providing a very convenient and flat data structure for immediate use.
98
-
99
- ### Programmatic Usage:
100
-
101
- ```javascript
102
- // Re-add data for a fresh start (schemas are reused from above)
103
- const france = await api.resources.countries.post({ name: 'France', code: 'FR' });
104
- const germany = await api.resources.countries.post({ name: 'Germany', code: 'DE' });
105
- const uk = await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
106
-
107
- await api.resources.publishers.post({ name: 'French Books Inc.', country: france.id });
108
- await api.resources.publishers.post({ name: 'Another French Books Inc.', country: france.id });
109
- await api.resources.publishers.post({ name: 'UK Books Ltd.', country: uk.id });
110
- await api.resources.publishers.post({ name: 'German Press GmbH', country: germany.id });
111
- await api.resources.publishers.post({ name: 'Global Publishing', country: null });
112
-
113
- await api.resources.publishers.post({
114
- name: 'UK Books Ltd.',
115
- country: uk.id
116
- });
117
-
118
-
119
- // Get a publisher and include its country (simplified mode output)
120
- const publisherWithCountry = await api.resources.publishers.get({
121
- id: '1', // ID of French Books Inc.
122
- queryParams: {
123
- include: ['country'] // Use the 'as' alias defined in the schema
124
- }
125
- });
126
- console.log('Publisher with Country:', inspect(publisherWithCountry));
127
-
128
- // Query all publishers and include their countries (simplified mode output)
129
- const allPublishersWithCountries = await api.resources.publishers.query({
130
- queryParams: {
131
- include: ['country']
132
- }
133
- });
134
- // HTTP: GET /api/publishers?include=country
135
- // Returns (simplified): [
136
- // { id: '1', name: 'French Books Inc.', country: { id: '1', name: 'France', code: 'FR' } },
137
- // { id: '2', name: 'German Press GmbH', country: { id: '2', name: 'Germany', code: 'DE' } },
138
- // { id: '3', name: 'UK Books Ltd.', country: { id: '3', name: 'United Kingdom', code: 'UK' } },
139
- // { id: '4', name: 'Global Publishing', country: null }
140
- // ]
141
-
142
- console.log('All Publishers with Countries:', inspect(allPublishersWithCountries));
143
- // Note: allPublishersWithCountries contains { data, meta, links }
144
-
145
- // Query all publishers and include their countries (JSON:API format)
146
- const allPublishersWithCountriesNotSimplified = await api.resources.publishers.query({
147
- queryParams: {
148
- include: ['country']
149
- },
150
- simplified: false
151
- });
152
- // HTTP: GET /api/publishers?include=country
153
- // Returns (JSON:API): {
154
- // data: [
155
- // { type: 'publishers', id: '1', attributes: { name: 'French Books Inc.' },
156
- // relationships: { country: { data: { type: 'countries', id: '1' } } } },
157
- // { type: 'publishers', id: '2', attributes: { name: 'German Press GmbH' },
158
- // relationships: { country: { data: { type: 'countries', id: '2' } } } },
159
- // { type: 'publishers', id: '3', attributes: { name: 'UK Books Ltd.' },
160
- // relationships: { country: { data: { type: 'countries', id: '3' } } } },
161
- // { type: 'publishers', id: '4', attributes: { name: 'Global Publishing' },
162
- // relationships: { country: { data: null } } }
163
- // ],
164
- // included: [
165
- // { type: 'countries', id: '1', attributes: { name: 'France', code: 'FR' } },
166
- // { type: 'countries', id: '2', attributes: { name: 'Germany', code: 'DE' } },
167
- // { type: 'countries', id: '3', attributes: { name: 'United Kingdom', code: 'UK' } }
168
- // ]
169
- // }
170
-
171
- console.log('All Publishers with Countries (not simplified):', inspect(allPublishersWithCountriesNotSimplified));
172
- ```
173
-
174
- Here is the expected output. Notice how the last call shows the non-simplified version of the response, which is muc more verbose. However, it has one _major_ advantage: it only includes the information about France _once_. It might seem like a small gain here, but when you have complex queries where the `belongsTo` table has a lot of data, the saving is much more evident.
175
-
176
- **Expected Output**
177
-
178
- ```text
179
- Publisher with Country: {
180
- id: '1',
181
- name: 'French Books Inc.',
182
- country: { id: '1', name: 'France', code: 'FR' }
183
- }
184
- All Publishers with Countries: {
185
- data: [
186
- {
187
- id: '1',
188
- name: 'French Books Inc.',
189
- country: { id: '1', name: 'France', code: 'FR' }
190
- },
191
- {
192
- id: '2',
193
- name: 'Another French Books Inc.',
194
- country: { id: '1', name: 'France', code: 'FR' }
195
- },
196
- {
197
- id: '3',
198
- name: 'UK Books Ltd.',
199
- country: { id: '3', name: 'United Kingdom', code: 'UK' }
200
- },
201
- {
202
- id: '4',
203
- name: 'German Press GmbH',
204
- country: { id: '2', name: 'Germany', code: 'DE' }
205
- },
206
- { id: '5', name: 'Global Publishing' }
207
- ],
208
- meta: {...},
209
- links: {...}
210
- }
211
- All Publishers with Countries (not simplified): {
212
- data: [
213
- {
214
- type: 'publishers',
215
- id: '1',
216
- attributes: { name: 'French Books Inc.' },
217
- relationships: {
218
- country: {
219
- data: { type: 'countries', id: '1' },
220
- links: {
221
- self: '/api/publishers/1/relationships/country',
222
- related: '/api/publishers/1/country'
223
- }
224
- }
225
- },
226
- links: { self: '/api/publishers/1' }
227
- },
228
- {
229
- type: 'publishers',
230
- id: '2',
231
- attributes: { name: 'Another French Books Inc.' },
232
- relationships: {
233
- country: {
234
- data: { type: 'countries', id: '1' },
235
- links: {
236
- self: '/api/publishers/2/relationships/country',
237
- related: '/api/publishers/2/country'
238
- }
239
- }
240
- },
241
- links: { self: '/api/publishers/2' }
242
- },
243
- {
244
- type: 'publishers',
245
- id: '3',
246
- attributes: { name: 'UK Books Ltd.' },
247
- relationships: {
248
- country: {
249
- data: { type: 'countries', id: '3' },
250
- links: {
251
- self: '/api/publishers/3/relationships/country',
252
- related: '/api/publishers/3/country'
253
- }
254
- }
255
- },
256
- links: { self: '/api/publishers/3' }
257
- },
258
- {
259
- type: 'publishers',
260
- id: '4',
261
- attributes: { name: 'German Press GmbH' },
262
- relationships: {
263
- country: {
264
- data: { type: 'countries', id: '2' },
265
- links: {
266
- self: '/api/publishers/4/relationships/country',
267
- related: '/api/publishers/4/country'
268
- }
269
- },
270
- links: { self: '/api/publishers/4' }
271
- },
272
- {
273
- type: 'publishers',
274
- id: '5',
275
- attributes: { name: 'Global Publishing' },
276
- relationships: {
277
- country: {
278
- data: null,
279
- links: {
280
- self: '/api/publishers/5/relationships/country',
281
- related: '/api/publishers/5/country'
282
- }
283
- }
284
- },
285
- links: { self: '/api/publishers/5' }
286
- }
287
- ],
288
- included: [
289
- {
290
- type: 'countries',
291
- id: '1',
292
- attributes: { name: 'France', code: 'FR' },
293
- relationships: {},
294
- links: { self: '/api/countries/1' }
295
- },
296
- {
297
- type: 'countries',
298
- id: '3',
299
- attributes: { name: 'United Kingdom', code: 'UK' },
300
- relationships: {},
301
- links: { self: '/api/countries/3' }
302
- },
303
- {
304
- type: 'countries',
305
- id: '2',
306
- attributes: { name: 'Germany', code: 'DE' },
307
- relationships: {},
308
- links: { self: '/api/countries/2' }
309
- }
310
- ],
311
- links: { self: '/api/publishers?include=country' }
312
- }
313
- ```
314
-
315
- ---
316
-
317
- ## Sparse Fieldsets with `belongsTo` Relations
318
-
319
- You can apply **sparse fieldsets** not only to the primary resource but also to the included `belongsTo` resources. This is powerful for fine-tuning your API responses and reducing payload sizes.
320
-
321
- ### Programmatic Usage:
322
-
323
- ```javascript
324
- // Re-add data for a fresh start (schemas are reused from above)
325
-
326
- const france = await api.resources.countries.post({ name: 'France', code: 'FR' });
327
- const germany = await api.resources.countries.post({ name: 'Germany', code: 'DE' });
328
- const uk = await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
329
-
330
- await api.resources.publishers.post({ name: 'French Books Inc.', country: france.id });
331
- await api.resources.publishers.post({ name: 'UK Books Ltd.', country: uk.id });
332
- await api.resources.publishers.post({ name: 'German Press GmbH', country: germany.id });
333
- await api.resources.publishers.post({ name: 'Global Publishing', country: null });
334
-
335
-
336
- // Get a publisher, include its country, but only retrieve publisher name and country code
337
- const sparsePublisher = await api.resources.publishers.get({
338
- id: '1',
339
- queryParams: {
340
- include: ['country'],
341
- fields: {
342
- publishers: 'name', // Only name for publishers
343
- countries: 'code' // Only code for countries
344
- }
345
- }
346
- // simplified: true is default for programmatic fetches
347
- });
348
- console.log('Sparse Publisher and Country:', inspect(sparsePublisher));
349
-
350
-
351
- // Query all publishers, include their countries, but only retrieve publisher name and country code
352
- const sparsePublishersQuery = await api.resources.publishers.query({
353
- queryParams: {
354
- include: ['country'],
355
- fields: {
356
- publishers: 'name', // Only name for publishers
357
- countries: 'code,name' // BOTH code and name for countries
358
- }
359
- }
360
- // simplified: true is default for programmatic fetches
361
- });
362
- // HTTP: GET /api/publishers?include=country&fields[publishers]=name&fields[countries]=code,name
363
- // Returns (simplified): [
364
- // { id: '1', name: 'French Books Inc.', country: { id: '1', code: 'FR', name: 'France' } },
365
- // { id: '2', name: 'German Press GmbH', country: { id: '2', code: 'DE', name: 'Germany' } },
366
- // { id: '3', name: 'UK Books Ltd.', country: { id: '3', code: 'UK', name: 'United Kingdom' } },
367
- // { id: '4', name: 'Global Publishing', country: null }
368
- // ]
369
- console.log('Sparse Publishers Query (all results):', inspect(sparsePublishersQuery));
370
- ```
371
-
372
- Note that you can specify multiple fields for countries, and that they need to be comma separated.
373
-
374
- **Important Note on Sparse Fieldsets for Related Resources:**
375
- When you specify `fields: { countries: ['code'] }`, this instruction applies to *all* `country` resources present in the API response, whether `country` is the primary resource you are querying directly, or if it's included as a related resource. This ensures consistent data representation across the entire response.
376
-
377
- **Expected Output (Sparse Publisher and Country - Illustrative, IDs may vary):**
378
-
379
- ```text
380
- {
381
- id: '1',
382
- name: 'French Books Inc.',
383
- country: { id: '1', code: 'FR' }
384
- }
385
- Sparse Publishers Query (all results): {
386
- data: [
387
- {
388
- id: '1',
389
- name: 'French Books Inc.',
390
- country: { id: '1', code: 'FR', name: 'France' }
391
- },
392
- {
393
- id: '2',
394
- name: 'UK Books Ltd.',
395
- country: { id: '3', code: 'UK', name: 'United Kingdom' }
396
- },
397
- {
398
- id: '3',
399
- name: 'German Press GmbH',
400
- country: { id: '2', code: 'DE', name: 'Germany' }
401
- },
402
- { id: '4', name: 'Global Publishing' }
403
- ],
404
- meta: {...},
405
- links: {...}
406
- }
407
- ```
408
-
409
- ## Filtering by `belongsTo` Relationships
410
-
411
- You can filter resources based on conditions applied to their `belongsTo` relationships. This is achieved by defining filterable fields in the `searchSchema` that map to either the foreign key or fields on the related resource.
412
-
413
- The `searchSchema` offers a clean way to define filters, abstracting away the underlying database structure and relationship navigation from the client. Clients simply use the filter field names defined in `searchSchema` (e.g., `countryCode` instead of `country.code`).
414
-
415
- ### Programmatic Usage:
416
-
417
- ```javascript
418
- // Re-add data for a fresh start (schemas are reused from above)
419
- const france = await api.resources.countries.post({ name: 'France', code: 'FR' });
420
- const germany = await api.resources.countries.post({ name: 'Germany', code: 'DE' });
421
- const uk = await api.resources.countries.post({ name: 'United Kingdom', code: 'UK' });
422
-
423
- await api.resources.publishers.post({ name: 'French Books Inc.', country: france.id });
424
- await api.resources.publishers.post({ name: 'UK Books Ltd.', country: uk.id });
425
- await api.resources.publishers.post({ name: 'German Press GmbH', country: germany.id });
426
- await api.resources.publishers.post({ name: 'Global Publishing', country: null });
427
-
428
-
429
- // Programmatic search: Find publishers from France using the country ID alias in searchSchema
430
- const publishersFromFrance = await api.resources.publishers.query({
431
- queryParams: {
432
- filters: {
433
- country: france.id // Using 'country' filter field defined in searchSchema
434
- }
435
- }
436
- // simplified: true is default for programmatic fetches
437
- });
438
- // HTTP: GET /api/publishers?filter[country]=1
439
- // Returns: {
440
- // data: [{ id: '1', name: 'French Books Inc.', country_id: '1' }]
441
- // }
442
-
443
- console.log('Publishers from France (by country ID):', inspect(publishersFromFrance));
444
- // Note: publishersFromFrance contains { data, meta, links }
445
- // Note: publishersFromFrance contains { data, meta, links } - access publishersFromFrance.data for the array
446
-
447
- // Programmatic search: Find publishers with no associated country
448
- const publishersNoCountry = await api.resources.publishers.query({
449
- queryParams: {
450
- filters: {
451
- country: null // Filtering by null for the 'country' ID filter
452
- }
453
- }
454
- // simplified: true is default for programmatic fetches
455
- });
456
- // HTTP: GET /api/publishers?filter[country]=null
457
- // Returns: {
458
- // data: [{ id: '4', name: 'Global Publishing', country_id: null }]
459
- // }
460
-
461
- console.log('Publishers with No Country (by country ID: null):', inspect(publishersNoCountry));
462
- // Note: publishersNoCountry contains { data, meta, links }
463
-
464
- // Programmatic search: Find publishers where the associated country's code is 'UK'
465
- const publishersFromUK = await api.resources.publishers.query({
466
- queryParams: {
467
- filters: {
468
- countryCode: 'UK' // Using 'countryCode' filter field defined in searchSchema
469
- }
470
- }
471
- // simplified: true is default for programmatic fetches
472
- });
473
- // HTTP: GET /api/publishers?filter[countryCode]=UK
474
- // Returns: {
475
- // data: [{ id: '3', name: 'UK Books Ltd.', country_id: '3' }]
476
- // }
477
-
478
- console.log('Publishers from UK (by countryCode):', inspect(publishersFromUK));
479
- ```
480
-
481
- **Expected Output**
482
-
483
- ```text
484
- Publishers from France (by country ID): [ { id: '1', name: 'French Books Inc.', country: { id: '1' } } ]
485
- Publishers with No Country (by country ID: null): [ { id: '4', name: 'Global Publishing' } ]
486
- Publishers from UK (by countryCode): [ { id: '2', name: 'UK Books Ltd.', country: { id: '3' } } ]
487
- ```
488
-
489
- ---
490
-
491
- [Previous: 2.2 Manipulating and searching tables with no relationships](./GUIDE_2_2_Manipulating_And_Searching_Tables.md) | [Back to Guide](./README.md) | [Next: 2.4 hasMany records](./GUIDE_2_4_HasMany_Records.md)