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
@@ -1,517 +0,0 @@
1
- # REST API Relationships Plugin Guide
2
-
3
- The REST API Relationships Plugin adds JSON:API compliant relationship endpoints to your API, allowing clients to view and manage relationships between resources as first-class citizens. This guide shows you how to use these powerful features with our book catalog system.
4
-
5
- ## Table of Contents
6
- - [Why Use Relationship Endpoints?](#why-use-relationship-endpoints)
7
- - [Installation](#installation)
8
- - [Understanding Relationship Endpoints](#understanding-relationship-endpoints)
9
- - [Working with Relationships](#working-with-relationships)
10
- - [Viewing Relationship Links](#viewing-relationship-links)
11
- - [Fetching Related Resources](#fetching-related-resources)
12
- - [Adding Relationships](#adding-relationships)
13
- - [Replacing Relationships](#replacing-relationships)
14
- - [Removing Relationships](#removing-relationships)
15
- - [Relationship Types](#relationship-types)
16
- - [Security and Permissions](#security-and-permissions)
17
- - [Error Handling](#error-handling)
18
- - [Best Practices](#best-practices)
19
-
20
- ## Why Use Relationship Endpoints?
21
-
22
- Relationship endpoints provide several advantages:
23
-
24
- 1. **Efficient Relationship Management**: Add or remove related items without fetching and updating entire resources
25
- 2. **Clear API Navigation**: Self-documenting links show how resources connect
26
- 3. **Reduced Payload Size**: Fetch just relationship data without full resource details
27
- 4. **Atomic Operations**: Manage relationships in isolation with proper transaction support
28
-
29
- ## Installation
30
-
31
- To use relationship endpoints, install the plugin after your core REST API setup:
32
-
33
- ```javascript
34
- import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';
35
- import { Api } from 'hooked-api';
36
-
37
- const api = new Api();
38
-
39
- // Core plugins first
40
- await api.use(RestApiPlugin, {
41
- resourceUrlPrefix: '/api' // Important: enables relationship links
42
- });
43
- await api.use(RestApiKnexPlugin, { knex });
44
-
45
- // Define your resources as usual
46
- await api.addResource('books', { /* schema */ });
47
- await api.addResource('authors', { /* schema */ });
48
- ```
49
-
50
- ## Understanding Relationship Endpoints
51
-
52
- The plugin creates two types of endpoints for each relationship:
53
-
54
- ### 1. Relationship Endpoints (Linkage)
55
- - **URL Pattern**: `/api/{resource}/{id}/relationships/{relationshipName}`
56
- - **Purpose**: View and manage just the linkage data (IDs and types)
57
- - **Example**: `/api/books/1/relationships/authors`
58
- - **Returns**: Minimal data showing which resources are connected
59
-
60
- ### 2. Related Resource Endpoints
61
- - **URL Pattern**: `/api/{resource}/{id}/{relationshipName}`
62
- - **Purpose**: Fetch the full related resources
63
- - **Example**: `/api/books/1/authors`
64
- - **Returns**: Complete resource data for all related items
65
-
66
- ## Working with Relationships
67
-
68
- Let's explore each operation using our book catalog system.
69
-
70
- ### Setup Test Data
71
-
72
- First, let's create some test data to work with:
73
-
74
- ```javascript
75
- // Create a country
76
- const usa = await api.resources.countries.post({
77
- name: 'United States',
78
- code: 'US'
79
- });
80
-
81
- // Create authors
82
- const stephenKing = await api.resources.authors.post({
83
- name: 'Stephen King'
84
- });
85
-
86
- const peterStraub = await api.resources.authors.post({
87
- name: 'Peter Straub'
88
- });
89
-
90
- // Create a publisher
91
- const scribner = await api.resources.publishers.post({
92
- name: 'Scribner',
93
- country_id: usa.id
94
- });
95
-
96
- // Create a book with initial relationships
97
- const talisman = await api.resources.books.post({
98
- title: 'The Talisman',
99
- country_id: usa.id,
100
- publisher_id: scribner.id
101
- }, {
102
- simplified: false // Use full JSON:API format
103
- });
104
- ```
105
-
106
- ### Viewing Relationship Links
107
-
108
- Get just the relationship linkage data without fetching full resources:
109
-
110
- **Programmatic:**
111
- ```javascript
112
- const bookAuthorsRelationship = await api.resources.books.getRelationship({
113
- id: talisman.data.id,
114
- relationshipName: 'authors'
115
- });
116
-
117
- console.log(bookAuthorsRelationship);
118
- // Output:
119
- // {
120
- // data: [], // Empty because we haven't added authors yet
121
- // links: {
122
- // self: "/api/books/1/relationships/authors",
123
- // related: "/api/books/1/authors"
124
- // }
125
- // }
126
- ```
127
-
128
- **HTTP:**
129
- ```bash
130
- curl -X GET http://localhost:3000/api/books/1/relationships/authors
131
-
132
- # Response:
133
- # {
134
- # "data": [],
135
- # "links": {
136
- # "self": "/api/books/1/relationships/authors",
137
- # "related": "/api/books/1/authors"
138
- # }
139
- # }
140
- ```
141
-
142
- ### Fetching Related Resources
143
-
144
- Get the full related resources with all their attributes:
145
-
146
- **Programmatic:**
147
- ```javascript
148
- const bookAuthors = await api.resources.books.getRelated({
149
- id: talisman.data.id,
150
- relationshipName: 'authors',
151
- queryParams: {
152
- fields: { authors: 'name' } // Optional: sparse fieldsets
153
- }
154
- });
155
-
156
- console.log(bookAuthors);
157
- // Output:
158
- // {
159
- // data: [
160
- // {
161
- // type: "authors",
162
- // id: "1",
163
- // attributes: { name: "Stephen King" }
164
- // }
165
- // ]
166
- // }
167
- ```
168
-
169
- **HTTP:**
170
- ```bash
171
- curl -X GET 'http://localhost:3000/api/books/1/authors?fields[authors]=name'
172
-
173
- # Response:
174
- # {
175
- # "data": [
176
- # {
177
- # "type": "authors",
178
- # "id": "1",
179
- # "attributes": { "name": "Stephen King" }
180
- # }
181
- # ]
182
- # }
183
- ```
184
-
185
- ### Adding Relationships
186
-
187
- Add new relationships without replacing existing ones (only for to-many relationships):
188
-
189
- **Programmatic:**
190
- ```javascript
191
- // Add Stephen King and Peter Straub as authors
192
- const addAuthorsResult = await api.resources.books.postRelationship({
193
- id: talisman.data.id,
194
- relationshipName: 'authors',
195
- inputRecord: {
196
- data: [
197
- { type: 'authors', id: stephenKing.id },
198
- { type: 'authors', id: peterStraub.id }
199
- ]
200
- }
201
- });
202
-
203
- console.log('Authors added successfully');
204
- ```
205
-
206
- **HTTP:**
207
- ```bash
208
- curl -X POST http://localhost:3000/api/books/1/relationships/authors \
209
- -H "Content-Type: application/vnd.api+json" \
210
- -d '{
211
- "data": [
212
- { "type": "authors", "id": "1" },
213
- { "type": "authors", "id": "2" }
214
- ]
215
- }'
216
-
217
- # Response: 204 No Content (success)
218
- ```
219
-
220
- ### Replacing Relationships
221
-
222
- Replace all existing relationships with a new set:
223
-
224
- **Programmatic:**
225
- ```javascript
226
- // Replace all authors with just Stephen King
227
- const replaceAuthorsResult = await api.resources.books.patchRelationship({
228
- id: talisman.data.id,
229
- relationshipName: 'authors',
230
- inputRecord: {
231
- data: [
232
- { type: 'authors', id: stephenKing.id }
233
- ]
234
- }
235
- });
236
-
237
- // For to-one relationships, you can also set to null
238
- const removePublisher = await api.resources.books.patchRelationship({
239
- id: talisman.data.id,
240
- relationshipName: 'publisher',
241
- inputRecord: {
242
- data: null
243
- }
244
- });
245
- ```
246
-
247
- **HTTP:**
248
- ```bash
249
- # Replace all authors
250
- curl -X PATCH http://localhost:3000/api/books/1/relationships/authors \
251
- -H "Content-Type: application/vnd.api+json" \
252
- -d '{
253
- "data": [
254
- { "type": "authors", "id": "1" }
255
- ]
256
- }'
257
-
258
- # Remove publisher (set to null)
259
- curl -X PATCH http://localhost:3000/api/books/1/relationships/publisher \
260
- -H "Content-Type: application/vnd.api+json" \
261
- -d '{
262
- "data": null
263
- }'
264
- ```
265
-
266
- ### Removing Relationships
267
-
268
- Remove specific relationships without affecting others (only for to-many relationships):
269
-
270
- **Programmatic:**
271
- ```javascript
272
- // Remove Peter Straub from the book's authors
273
- const removeAuthorResult = await api.resources.books.deleteRelationship({
274
- id: talisman.data.id,
275
- relationshipName: 'authors',
276
- inputRecord: {
277
- data: [
278
- { type: 'authors', id: peterStraub.id }
279
- ]
280
- }
281
- });
282
- ```
283
-
284
- **HTTP:**
285
- ```bash
286
- curl -X DELETE http://localhost:3000/api/books/1/relationships/authors \
287
- -H "Content-Type: application/vnd.api+json" \
288
- -d '{
289
- "data": [
290
- { "type": "authors", "id": "2" }
291
- ]
292
- }'
293
-
294
- # Response: 204 No Content (success)
295
- ```
296
-
297
- ## Relationship Types
298
-
299
- The plugin handles all relationship types defined in your schema:
300
-
301
- ### belongsTo Relationships
302
-
303
- Books belong to publishers:
304
-
305
- ```javascript
306
- // View the publisher relationship
307
- const bookPublisher = await api.resources.books.getRelationship({
308
- id: talisman.data.id,
309
- relationshipName: 'publisher'
310
- });
311
- // Returns: { data: { type: "publishers", id: "1" }, links: {...} }
312
-
313
- // Change the publisher
314
- await api.resources.books.patchRelationship({
315
- id: talisman.data.id,
316
- relationshipName: 'publisher',
317
- inputRecord: {
318
- data: { type: 'publishers', id: '2' }
319
- }
320
- });
321
- ```
322
-
323
- ### hasOne Relationships
324
-
325
- The inverse of belongsTo (automatically created):
326
-
327
- ```javascript
328
- // If you define country → publishers (hasMany)
329
- // Each publisher has one country (implicit hasOne)
330
- const publisherCountry = await api.resources.publishers.getRelationship({
331
- id: scribner.id,
332
- relationshipName: 'country'
333
- });
334
- ```
335
-
336
- ### hasMany Relationships
337
-
338
- Publishers have many books:
339
-
340
- ```javascript
341
- // View all books for a publisher
342
- const publisherBooks = await api.resources.publishers.getRelated({
343
- id: scribner.id,
344
- relationshipName: 'books',
345
- queryParams: {
346
- sort: '-year', // Sort by year descending
347
- filter: { inStock: true } // Only in-stock books
348
- }
349
- });
350
- ```
351
-
352
- ### Many-to-Many Relationships
353
-
354
- Books have many authors through the book_authors pivot table:
355
-
356
- ```javascript
357
- // This is the most flexible relationship type
358
- // Supports POST (add), PATCH (replace), and DELETE (remove)
359
- const bookAuthors = await api.resources.books.getRelationship({
360
- id: talisman.data.id,
361
- relationshipName: 'authors'
362
- });
363
- ```
364
-
365
- ## Security and Permissions
366
-
367
- The plugin respects your existing security setup and adds specific hooks:
368
-
369
- ```javascript
370
- // Add permission checks for relationship operations
371
- api.addHook('checkPermissionsGetRelationship', async ({ context }) => {
372
- // Check if user can view this relationship
373
- if (!context.auth?.userId) {
374
- throw new Error('Authentication required');
375
- }
376
- });
377
-
378
- api.addHook('checkPermissionsPostRelationship', async ({ context }) => {
379
- // Check if user can add relationships
380
- const { scopeName, relationshipName } = context;
381
-
382
- if (scopeName === 'books' && relationshipName === 'authors') {
383
- // Only editors can modify book authors
384
- if (context.auth?.role !== 'editor') {
385
- throw new Error('Only editors can modify book authors');
386
- }
387
- }
388
- });
389
- ```
390
-
391
- ## Error Handling
392
-
393
- Common errors you might encounter:
394
-
395
- ### Relationship Not Found
396
- ```javascript
397
- try {
398
- await api.resources.books.getRelationship({
399
- id: '1',
400
- relationshipName: 'invalid'
401
- });
402
- } catch (error) {
403
- // RestApiResourceError: Relationship 'invalid' not found on resource 'books'
404
- }
405
- ```
406
-
407
- ### Invalid Operation
408
- ```javascript
409
- try {
410
- // Can't POST to a to-one relationship
411
- await api.resources.books.postRelationship({
412
- id: '1',
413
- relationshipName: 'publisher', // belongsTo is to-one
414
- inputRecord: { data: { type: 'publishers', id: '1' } }
415
- });
416
- } catch (error) {
417
- // RestApiValidationError: POST operation not allowed on to-one relationship
418
- }
419
- ```
420
-
421
- ### Resource Not Found
422
- ```javascript
423
- try {
424
- await api.resources.books.getRelationship({
425
- id: '999', // Non-existent book
426
- relationshipName: 'authors'
427
- });
428
- } catch (error) {
429
- // RestApiResourceError: Resource not found
430
- }
431
- ```
432
-
433
- ## Best Practices
434
-
435
- ### 1. Use Relationship Endpoints for Bulk Operations
436
-
437
- Instead of updating each book individually to add an author:
438
- ```javascript
439
- // ❌ Inefficient
440
- for (const bookId of bookIds) {
441
- const book = await api.resources.books.get({ id: bookId });
442
- await api.resources.books.patch({
443
- id: bookId,
444
- inputRecord: {
445
- data: {
446
- type: 'books',
447
- id: bookId,
448
- relationships: {
449
- authors: {
450
- data: [...book.data.relationships.authors.data, newAuthor]
451
- }
452
- }
453
- }
454
- }
455
- });
456
- }
457
-
458
- // ✅ Efficient
459
- for (const bookId of bookIds) {
460
- await api.resources.books.postRelationship({
461
- id: bookId,
462
- relationshipName: 'authors',
463
- inputRecord: {
464
- data: [newAuthor]
465
- }
466
- });
467
- }
468
- ```
469
-
470
- ### 2. Use Links for API Discovery
471
-
472
- The `links` object in responses helps clients navigate your API:
473
- ```javascript
474
- const relationship = await api.resources.books.getRelationship({
475
- id: '1',
476
- relationshipName: 'authors'
477
- });
478
-
479
- console.log(relationship.links);
480
- // {
481
- // self: "/api/books/1/relationships/authors",
482
- // related: "/api/books/1/authors"
483
- // }
484
-
485
- // Client can use these links directly
486
- const fullAuthors = await fetch(relationship.links.related);
487
- ```
488
-
489
- ### 3. Choose the Right Endpoint
490
-
491
- - **Use relationship endpoints** when you only need to manage connections
492
- - **Use related endpoints** when you need full resource data
493
- - **Use regular PATCH** when updating multiple aspects of a resource
494
-
495
- ### 4. Handle Transactions Properly
496
-
497
- The plugin automatically handles transactions for data integrity:
498
- ```javascript
499
- // This is atomic - either all authors are added or none
500
- await api.resources.books.postRelationship({
501
- id: bookId,
502
- relationshipName: 'authors',
503
- inputRecord: {
504
- data: [
505
- { type: 'authors', id: '1' },
506
- { type: 'authors', id: '2' },
507
- { type: 'authors', id: '3' }
508
- ]
509
- }
510
- });
511
- ```
512
-
513
- ## Summary
514
-
515
- The REST API Relationships Plugin transforms relationships from second-class citizens to fully manageable resources. It provides efficient, standards-compliant endpoints that make working with related data intuitive and performant. By following JSON:API specifications, it ensures your API remains consistent and predictable for clients.
516
-
517
- Whether you're building a simple blog or a complex e-commerce system, relationship endpoints help you create cleaner, more maintainable APIs that scale with your application's needs.
@@ -1,66 +0,0 @@
1
- # Data and relations
2
-
3
- The true power of json-rest-api lies in its sophisticated handling of relationships between resources. Unlike traditional ORMs that focus on object mapping, this library provides a fully JSON:API compliant REST interface that elegantly manages complex data relationships. This guide will walk you through defining datasets and explore all the possible relationship types available in the system.
4
-
5
- This guide has been split into the following sections for easier navigation:
6
-
7
- ## [2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md)
8
- - Basic setup and configuration
9
- - Creating the API instance
10
- - Setting up the database connection
11
-
12
- ## [2.2 Manipulating and searching tables with no relationships](./GUIDE_2_2_Manipulating_And_Searching_Tables.md)
13
- - Setting up schemas
14
- - Basic CRUD operations (POST, GET, PATCH, PUT)
15
- - Search and filtering capabilities
16
- - Multi-word search with AND logic
17
- - Custom search functions
18
- - Sparse fieldsets
19
-
20
- ## [2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)
21
- - Understanding belongsTo relationships
22
- - Including related records
23
- - Sparse fieldsets with belongsTo relations
24
- - Filtering by belongsTo relationships
25
-
26
- ## [2.4 hasMany records](./GUIDE_2_4_HasMany_Records.md)
27
- - Understanding hasMany relationships
28
- - Including hasMany records
29
- - Filtering by hasMany relationships
30
- - Cross-table searches
31
-
32
- ## [2.5 hasMany records (polymorphic)](./GUIDE_2_5_HasMany_Polymorphic.md)
33
- - Understanding polymorphic relationships
34
- - Including polymorphic records
35
- - Forward and reverse polymorphic search
36
- - Complex polymorphic filtering
37
-
38
- ## [2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md)
39
- - Understanding many-to-many relationships
40
- - Working with pivot tables directly
41
- - Including many-to-many records
42
- - Search across many-to-many relationships
43
-
44
- ## [2.7 Pagination and ordering](./GUIDE_2_7_Pagination_And_Ordering.md)
45
- - Offset-based vs cursor-based pagination
46
- - Sorting and multi-field sorting
47
- - Limits and ordering for included relationships
48
- - Combining filters, sorting, pagination, and includes
49
-
50
- ## [2.8 Effects of PUT and PATCH](./GUIDE_2_8_Effects_of_PUT_and_PATCH.md)
51
- - Understanding the difference between PUT and PATCH
52
- - How PUT affects relationships and existing stored fields
53
- - How PATCH affects relationships (partial updates)
54
- - Managing belongsTo relationships
55
- - Handling hasMany relationships
56
- - Working with many-to-many relationships
57
-
58
- ## [2.9 Relationships URLs](./GUIDE_2_9_Relationships_Urls.md)
59
- - Working with relationship endpoints
60
- - Direct relationship manipulation
61
- - Relationship links and meta information
62
- - Managing relationship collections
63
-
64
- ---
65
-
66
- [Back to Guide](./index.md)