json-rest-api 1.0.28 → 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 (289) 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 +746 -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} +90 -31
  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 -174
  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 +67 -330
  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 +205 -30
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +20 -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 +238 -187
  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 +111 -287
  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 +109 -44
  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 +142 -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 -2509
  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 -1069
  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 -385
  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 -145
  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 +38 -69
  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 +39 -82
  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 -1238
  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 -150
  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 -118
  251. package/tests/fixtures/api-configs.js +0 -2509
  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-cursor-multifield.test.js +0 -643
  264. package/tests/pagination-enhanced.test.js +0 -563
  265. package/tests/polymorphic-relationships.test.js +0 -347
  266. package/tests/polymorphic-simple.test.js +0 -148
  267. package/tests/positioning-advanced.test.js +0 -692
  268. package/tests/positioning-extended.test.js +0 -915
  269. package/tests/positioning-multifilter.test.js +0 -699
  270. package/tests/positioning.test.js +0 -560
  271. package/tests/post-commit-hooks.test.js +0 -120
  272. package/tests/projected-fields.test.js +0 -319
  273. package/tests/public-exports.test.js +0 -15
  274. package/tests/put-create-parity.test.js +0 -216
  275. package/tests/queries.test.js +0 -521
  276. package/tests/query-limits.test.js +0 -584
  277. package/tests/relationship-endpoints.test.js +0 -620
  278. package/tests/relationship-include-storage-adapter.test.js +0 -56
  279. package/tests/relationships.test.js +0 -681
  280. package/tests/rest-api.test.js +0 -337
  281. package/tests/return-record-settings.test.js +0 -184
  282. package/tests/row-policy.test.js +0 -489
  283. package/tests/s3-storage.test.js +0 -38
  284. package/tests/searchschema-knex-behavior.test.js +0 -187
  285. package/tests/searchschema-merge.test.js +0 -151
  286. package/tests/socketio.test.js +0 -860
  287. package/tests/storage-mapping.test.js +0 -362
  288. package/tests/test-development-guide.md +0 -370
  289. 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)