json-rest-api 1.0.10 → 1.0.13

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 (117) hide show
  1. package/.claude/settings.local.json +9 -1
  2. package/AGENTS.md +34 -0
  3. package/CLAUDE.md +26 -1
  4. package/CRUSH.md +55 -0
  5. package/README.md +3 -3
  6. package/TODO.todo +8 -0
  7. package/docs/API.md +17 -18
  8. package/docs/COMPARISON.md +18 -13
  9. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +17 -21
  10. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +33 -2
  11. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +61 -7
  12. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +58 -61
  13. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +37 -37
  14. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +103 -87
  15. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +37 -34
  16. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +45 -42
  17. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +3 -3
  18. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +1 -1
  19. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -5
  20. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +498 -755
  21. package/docs/GUIDE/GUIDE_6_Positioning.md +1 -3
  22. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +4 -5
  23. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +15 -6
  24. package/docs/GUIDE/GUIDE_X_Cors.md +2 -5
  25. package/docs/GUIDE/GUIDE_X_File_Uploads.md +2 -3
  26. package/docs/GUIDE/GUIDE_X_Multihome.md +1 -4
  27. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -2
  28. package/docs/GUIDE/GUIDE_X_URL_Management.md +317 -0
  29. package/docs/GUIDE/index.md +3 -0
  30. package/docs/QUICKSTART.md +12 -12
  31. package/docs/index.md +2 -2
  32. package/docs/mini-guides/file-uploads-complete.md +0 -1
  33. package/examples/url-override-example.js +137 -0
  34. package/index.js +7 -1
  35. package/package.json +7 -11
  36. package/plugins/core/bulk-operations-plugin.js +2 -0
  37. package/plugins/core/connectors/express-plugin.js +43 -24
  38. package/plugins/core/connectors/lib/request-helpers.js +2 -2
  39. package/plugins/core/lib/anyapi/anyapi-registry.js +821 -0
  40. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +647 -0
  41. package/plugins/core/lib/anyapi/schema-utils.js +186 -0
  42. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +113 -0
  43. package/plugins/core/lib/dbTablesOperations.js +305 -0
  44. package/plugins/core/lib/querying/knex-cross-table-search.js +64 -108
  45. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +21 -27
  46. package/plugins/core/lib/querying/knex-pagination-helpers.js +36 -20
  47. package/plugins/core/lib/querying/knex-query-helpers-base.js +49 -14
  48. package/plugins/core/lib/querying/knex-query-helpers.js +378 -222
  49. package/plugins/core/lib/querying/knex-relationship-includes.js +262 -87
  50. package/plugins/core/lib/querying/url-helpers.js +119 -0
  51. package/plugins/core/lib/querying-writing/compile-schemas.js +36 -28
  52. package/plugins/core/lib/querying-writing/database-capabilities.js +10 -9
  53. package/plugins/core/lib/querying-writing/knex-field-helpers.js +9 -3
  54. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +2 -2
  55. package/plugins/core/lib/querying-writing/payload-validators.js +10 -16
  56. package/plugins/core/lib/querying-writing/schema-helpers.js +52 -60
  57. package/plugins/core/lib/querying-writing/scope-validations.js +78 -22
  58. package/plugins/core/lib/querying-writing/simplified-helpers.js +208 -61
  59. package/plugins/core/lib/storage/storage-adapter.js +160 -0
  60. package/plugins/core/lib/writing/relationship-processor.js +4 -11
  61. package/plugins/core/rest-api-access.js +456 -0
  62. package/plugins/core/rest-api-anyapi-knex-plugin.js +2285 -0
  63. package/plugins/core/rest-api-knex-plugin.js +226 -87
  64. package/plugins/core/rest-api-multihome-plugin.js +89 -27
  65. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +0 -1
  66. package/plugins/core/rest-api-plugin-methods/check-permissions.js +1 -5
  67. package/plugins/core/rest-api-plugin-methods/common.js +46 -10
  68. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +31 -27
  69. package/plugins/core/rest-api-plugin-methods/delete.js +1 -4
  70. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +13 -1
  71. package/plugins/core/rest-api-plugin-methods/get-related.js +114 -56
  72. package/plugins/core/rest-api-plugin-methods/get-relationship.js +4 -3
  73. package/plugins/core/rest-api-plugin-methods/get.js +2 -5
  74. package/plugins/core/rest-api-plugin-methods/patch.js +15 -16
  75. package/plugins/core/rest-api-plugin-methods/post-relationship.js +21 -14
  76. package/plugins/core/rest-api-plugin-methods/post.js +23 -11
  77. package/plugins/core/rest-api-plugin-methods/put.js +18 -21
  78. package/plugins/core/rest-api-plugin-methods/query.js +3 -4
  79. package/plugins/core/rest-api-plugin-methods/release.js +13 -0
  80. package/plugins/core/rest-api-plugin.js +3 -4
  81. package/plugins/core/rest-api-positioning-plugin.js +253 -76
  82. package/plugins/core/socketio-plugin.js +436 -502
  83. package/quickTest.js +2 -2
  84. package/scripts/debug-anyapi-review-filter.mjs +63 -0
  85. package/scripts/inspect-anyapi-descriptor-country.mjs +19 -0
  86. package/scripts/inspect-anyapi-descriptor.mjs +19 -0
  87. package/test-debug.js +59 -0
  88. package/tests/README.md +7 -1
  89. package/tests/anyapi-basic.test.js +102 -0
  90. package/tests/anyapi-cursor-pagination.test.js +144 -0
  91. package/tests/computed-fields.test.js +27 -12
  92. package/tests/cors.test.js +5 -6
  93. package/tests/curl.test.js +1 -1
  94. package/tests/custom-idproperty-relationships.test.js +37 -29
  95. package/tests/field-getters.test.js +25 -7
  96. package/tests/fixtures/api-configs.js +576 -150
  97. package/tests/helpers/storage-mode.js +65 -0
  98. package/tests/helpers/test-utils.js +85 -3
  99. package/tests/include-depth-validation.test.js +35 -1
  100. package/tests/nested-includes.test.js +130 -103
  101. package/tests/pagination-cursor-multifield.test.js +17 -13
  102. package/tests/pagination-enhanced.test.js +19 -24
  103. package/tests/polymorphic-simple.test.js +9 -9
  104. package/tests/positioning-advanced.test.js +19 -19
  105. package/tests/positioning-extended.test.js +25 -25
  106. package/tests/positioning-multifilter.test.js +62 -62
  107. package/tests/positioning.test.js +7 -7
  108. package/tests/queries.test.js +5 -5
  109. package/tests/query-limits.test.js +77 -92
  110. package/tests/rest-api-access.test.js +206 -0
  111. package/tests/searchschema-merge.test.js +182 -0
  112. package/tests/socketio.test.js +33 -14
  113. package/tests/virtual-fields.test.js +0 -1
  114. package/docs/DOCS_ALL.md +0 -21894
  115. package/plugins/core/jwt-auth-plugin.js +0 -897
  116. package/plugins/core/lib/jwt-auth-helpers.js +0 -130
  117. package/tests/jwt-auth.test.js +0 -692
@@ -17,8 +17,8 @@ await api.addResource('publishers', {
17
17
  name: { type: 'string', required: true, max: 255, search: true, indexed: true},
18
18
  },
19
19
  relationships: {
20
- authors: { hasMany: 'authors', foreignKey: 'publisher_id' },
21
- reviews: { hasMany: 'reviews', via: 'reviewable' } // Polymorphic relationship
20
+ authors: { type: 'hasMany', target: 'authors', foreignKey: 'publisher_id' },
21
+ reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' } // Polymorphic relationship
22
22
  },
23
23
  });
24
24
  await api.resources.publishers.createKnexTable();
@@ -31,7 +31,7 @@ await api.addResource('authors', {
31
31
  publisher_id: { type: 'id', belongsTo: 'publishers', as: 'publisher', nullable: true }
32
32
  },
33
33
  relationships: {
34
- reviews: { hasMany: 'reviews', via: 'reviewable' } // Polymorphic relationship
34
+ reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' } // Polymorphic relationship
35
35
  },
36
36
  });
37
37
  await api.resources.authors.createKnexTable();
@@ -78,10 +78,26 @@ So when the parent defines the relationship, they just have to mention `reviewab
78
78
 
79
79
  ```javascript
80
80
  relationships: {
81
- reviews: { hasMany: 'reviews', via: 'reviewable' } // Polymorphic relationship
81
+ reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' } // Polymorphic relationship
82
82
  },
83
83
  ```
84
84
 
85
+ ## Field Visibility and Polymorphic Relationships
86
+
87
+ Polymorphic relationships are a prime example of json-rest-api's database-first philosophy and field abstraction:
88
+
89
+ **Database Reality (What Backend Developers Define):**
90
+ - Schema includes `reviewable_type` and `reviewable_id` fields
91
+ - These are actual database columns that store the polymorphic relationship
92
+ - Hooks receive these field names in `context.belongsToUpdates`
93
+
94
+ **API Abstraction (What Consumers See):**
95
+ - Input: Use `reviewable` relationship object instead of type/id fields
96
+ - Output: Returns `reviewable: { id: '123', _type: 'authors' }`
97
+ - The `reviewable_type` and `reviewable_id` fields are hidden from API responses
98
+
99
+ This ensures API consumers work with clean relationship objects while backend developers maintain full control over the database implementation.
100
+
85
101
  `json-rest-api` provides flexibility in how you provide data for polymorphic relationships when creating new records, depending on whether you're using the simplified API mode (default for programmatic calls) or the strict JSON:API format.
86
102
 
87
103
  Here is how to add reviews, both in simplified and non-simplified mode:
@@ -96,15 +112,13 @@ const germanAuthor_ns = await api.resources.authors.post({ name: 'Johann (NS)',
96
112
  const review1_simplified = await api.resources.reviews.post({
97
113
  rating: 4,
98
114
  comment: 'Great German author! (Simplified)',
99
- reviewable_type: 'authors',
100
- reviewable_id: germanAuthor_ns.id
115
+ reviewable: { id: germanAuthor_ns.id, _type: 'authors' }
101
116
  });
102
117
 
103
118
  const review2_simplified = await api.resources.reviews.post({
104
119
  rating: 1,
105
120
  comment: 'I do not enjoy their books',
106
- reviewable_type: 'publishers',
107
- reviewable_id: frenchPublisher_ns.id
121
+ reviewable: { id: frenchPublisher_ns.id, _type: 'publishers' }
108
122
  });
109
123
 
110
124
 
@@ -151,8 +165,8 @@ const frenchAuthor1_with_reviews_non_simplified = await api.resources.authors.ge
151
165
  const french_authors_simplified = await api.resources.authors.query({});
152
166
  // HTTP: GET /api/authors
153
167
  // Returns (simplified): [
154
- // { id: '1', name: 'Victor Hugo', publisher_id: '1' },
155
- // { id: '2', name: 'Alexandre Dumas', publisher_id: '1' }
168
+ // { id: '1', name: 'Victor Hugo', surname: 'Hugo', publisher: { id: '1' } },
169
+ // { id: '2', name: 'Alexandre Dumas', surname: 'Dumas', publisher: { id: '1' } }
156
170
  // ]
157
171
 
158
172
  const french_authors_non_simplified = await api.resources.authors.query({simplified: false });
@@ -199,26 +213,26 @@ Added Publisher Review (non-Simplified): {
199
213
  type: 'reviews',
200
214
  id: '3',
201
215
  attributes: { rating: 3, comment: 'Decent publisher, some good titles (NS).' },
202
- links: { self: '/api/1.0/reviews/3' }
216
+ links: { self: '/api/reviews/3' }
203
217
  },
204
- links: { self: '/api/1.0/reviews/3' }
218
+ links: { self: '/api/reviews/3' }
205
219
  }
206
220
  Added Author Review (non-Simplified): {
207
221
  data: {
208
222
  type: 'reviews',
209
223
  id: '4',
210
224
  attributes: { rating: 5, comment: 'Hugo is a master storyteller! (NS)' },
211
- links: { self: '/api/1.0/reviews/4' }
225
+ links: { self: '/api/reviews/4' }
212
226
  },
213
- links: { self: '/api/1.0/reviews/4' }
227
+ links: { self: '/api/reviews/4' }
214
228
  }
215
229
  French author with the newly added reviews (simplified):
216
230
  {
217
231
  id: '1',
218
232
  name: 'Victor (NS)',
219
233
  surname: 'Hugo (NS)',
220
- reviews_ids: [ '4' ],
221
- publisher_id: '1'
234
+ reviews: [ { id: '4' } ],
235
+ publisher: { id: '1' }
222
236
  }
223
237
  French author with the newly added reviews (non-simplified):
224
238
  {
@@ -231,14 +245,14 @@ French author with the newly added reviews (non-simplified):
231
245
  publisher: {
232
246
  data: { type: 'publishers', id: '1' },
233
247
  links: {
234
- self: '/api/1.0/authors/1/relationships/publisher',
235
- related: '/api/1.0/authors/1/publisher'
248
+ self: '/api/authors/1/relationships/publisher',
249
+ related: '/api/authors/1/publisher'
236
250
  }
237
251
  }
238
252
  },
239
- links: { self: '/api/1.0/authors/1' }
253
+ links: { self: '/api/authors/1' }
240
254
  },
241
- links: { self: '/api/1.0/authors/1' }
255
+ links: { self: '/api/authors/1' }
242
256
  }
243
257
  French authors with the newly added reviews (simplified):
244
258
  [
@@ -246,15 +260,15 @@ French authors with the newly added reviews (simplified):
246
260
  id: '1',
247
261
  name: 'Victor (NS)',
248
262
  surname: 'Hugo (NS)',
249
- reviews_ids: [ '4' ],
250
- publisher_id: '1'
263
+ reviews: [ { id: '4' } ],
264
+ publisher: { id: '1' }
251
265
  },
252
266
  {
253
267
  id: '2',
254
268
  name: 'Johann (NS)',
255
269
  surname: 'Goethe (NS)',
256
- reviews_ids: [ '1' ],
257
- publisher_id: '2'
270
+ reviews: [ { id: '1' } ],
271
+ publisher: { id: '2' }
258
272
  }
259
273
  ]
260
274
  French authors with the newly added reviews (non-simplified):
@@ -269,12 +283,12 @@ French authors with the newly added reviews (non-simplified):
269
283
  publisher: {
270
284
  data: { type: 'publishers', id: '1' },
271
285
  links: {
272
- self: '/api/1.0/authors/1/relationships/publisher',
273
- related: '/api/1.0/authors/1/publisher'
286
+ self: '/api/authors/1/relationships/publisher',
287
+ related: '/api/authors/1/publisher'
274
288
  }
275
289
  }
276
290
  },
277
- links: { self: '/api/1.0/authors/1' }
291
+ links: { self: '/api/authors/1' }
278
292
  },
279
293
  {
280
294
  type: 'authors',
@@ -285,15 +299,15 @@ French authors with the newly added reviews (non-simplified):
285
299
  publisher: {
286
300
  data: { type: 'publishers', id: '2' },
287
301
  links: {
288
- self: '/api/1.0/authors/2/relationships/publisher',
289
- related: '/api/1.0/authors/2/publisher'
302
+ self: '/api/authors/2/relationships/publisher',
303
+ related: '/api/authors/2/publisher'
290
304
  }
291
305
  }
292
306
  },
293
- links: { self: '/api/1.0/authors/2' }
307
+ links: { self: '/api/authors/2' }
294
308
  }
295
309
  ],
296
- links: { self: '/api/1.0/authors' }
310
+ links: { self: '/api/authors' }
297
311
  }
298
312
  ```
299
313
 
@@ -314,10 +328,10 @@ const frenchAuthor1_with_reviews_and_includes_non_simplified = await api.resourc
314
328
  const french_authors_with_includes_simplified = await api.resources.authors.query({queryParams: { include: ['reviews'] } });
315
329
  // HTTP: GET /api/authors?include=reviews
316
330
  // Returns (simplified): [
317
- // { id: '1', name: 'Victor Hugo', publisher_id: '1',
318
- // reviews: [{ id: '2', comment: 'A master storyteller', rating: 5, reviewable_type: 'authors', reviewable_id: '1' }]
331
+ // { id: '1', name: 'Victor Hugo', surname: 'Hugo', publisher: { id: '1' },
332
+ // reviews: [{ id: '2', comment: 'A master storyteller', rating: 5, reviewable: { id: '1', _type: 'authors' } }]
319
333
  // },
320
- // { id: '2', name: 'Alexandre Dumas', publisher_id: '1', reviews: [] }
334
+ // { id: '2', name: 'Alexandre Dumas', surname: 'Dumas', publisher: { id: '1' }, reviews: [] }
321
335
  // ]
322
336
 
323
337
  const french_authors_with_includes_non_simplified = await api.resources.authors.query({queryParams: { include: ['reviews'] }, simplified: false });
@@ -363,11 +377,10 @@ French author with the newly added reviews (simplified):
363
377
  id: '4',
364
378
  rating: 5,
365
379
  comment: 'Hugo is a master storyteller! (NS)',
366
- reviewable_type: 'authors',
367
- reviewable_id: '1'
380
+ reviewable: { id: '1', _type: 'authors' }
368
381
  }
369
382
  ],
370
- publisher_id: '1'
383
+ publisher: { id: '1' }
371
384
  }
372
385
  French author with the newly added reviews (non-simplified):
373
386
  {
@@ -379,19 +392,19 @@ French author with the newly added reviews (non-simplified):
379
392
  reviews: {
380
393
  data: [ { type: 'reviews', id: '4' } ],
381
394
  links: {
382
- self: '/api/1.0/authors/1/relationships/reviews',
383
- related: '/api/1.0/authors/1/reviews'
395
+ self: '/api/authors/1/relationships/reviews',
396
+ related: '/api/authors/1/reviews'
384
397
  }
385
398
  },
386
399
  publisher: {
387
400
  data: { type: 'publishers', id: '1' },
388
401
  links: {
389
- self: '/api/1.0/authors/1/relationships/publisher',
390
- related: '/api/1.0/authors/1/publisher'
402
+ self: '/api/authors/1/relationships/publisher',
403
+ related: '/api/authors/1/publisher'
391
404
  }
392
405
  }
393
406
  },
394
- links: { self: '/api/1.0/authors/1' }
407
+ links: { self: '/api/authors/1' }
395
408
  },
396
409
  included: [
397
410
  {
@@ -399,10 +412,10 @@ French author with the newly added reviews (non-simplified):
399
412
  id: '4',
400
413
  attributes: { rating: 5, comment: 'Hugo is a master storyteller! (NS)' },
401
414
  relationships: { reviewable: { data: { type: 'authors', id: '1' } } },
402
- links: { self: '/api/1.0/reviews/4' }
415
+ links: { self: '/api/reviews/4' }
403
416
  }
404
417
  ],
405
- links: { self: '/api/1.0/authors/1' }
418
+ links: { self: '/api/authors/1' }
406
419
  }
407
420
  French authors with the newly added reviews (simplified):
408
421
  [
@@ -416,27 +429,24 @@ French authors with the newly added reviews (simplified):
416
429
  id: '4',
417
430
  rating: 5,
418
431
  comment: 'Hugo is a master storyteller! (NS)',
419
- reviewable_type: 'authors',
420
- reviewable_id: '1'
432
+ reviewable: { id: '1', _type: 'authors' }
421
433
  }
422
434
  ],
423
- publisher_id: '1'
435
+ publisher: { id: '1' }
424
436
  },
425
437
  {
426
438
  id: '2',
427
439
  name: 'Johann (NS)',
428
440
  surname: 'Goethe (NS)',
429
- reviews_ids: [ '1' ],
430
441
  reviews: [
431
442
  {
432
443
  id: '1',
433
444
  rating: 4,
434
445
  comment: 'Great German author! (Simplified)',
435
- reviewable_type: 'authors',
436
- reviewable_id: '2'
446
+ reviewable: { id: '2', _type: 'authors' }
437
447
  }
438
448
  ],
439
- publisher_id: '2'
449
+ publisher: { id: '2' }
440
450
  }
441
451
  ]
442
452
  French authors with the newly added reviews (non-simplified):
@@ -450,19 +460,19 @@ French authors with the newly added reviews (non-simplified):
450
460
  reviews: {
451
461
  data: [ { type: 'reviews', id: '4' } ],
452
462
  links: {
453
- self: '/api/1.0/authors/1/relationships/reviews',
454
- related: '/api/1.0/authors/1/reviews'
463
+ self: '/api/authors/1/relationships/reviews',
464
+ related: '/api/authors/1/reviews'
455
465
  }
456
466
  },
457
467
  publisher: {
458
468
  data: { type: 'publishers', id: '1' },
459
469
  links: {
460
- self: '/api/1.0/authors/1/relationships/publisher',
461
- related: '/api/1.0/authors/1/publisher'
470
+ self: '/api/authors/1/relationships/publisher',
471
+ related: '/api/authors/1/publisher'
462
472
  }
463
473
  }
464
474
  },
465
- links: { self: '/api/1.0/authors/1' }
475
+ links: { self: '/api/authors/1' }
466
476
  },
467
477
  {
468
478
  type: 'authors',
@@ -472,19 +482,19 @@ French authors with the newly added reviews (non-simplified):
472
482
  reviews: {
473
483
  data: [ { type: 'reviews', id: '1' } ],
474
484
  links: {
475
- self: '/api/1.0/authors/2/relationships/reviews',
476
- related: '/api/1.0/authors/2/reviews'
485
+ self: '/api/authors/2/relationships/reviews',
486
+ related: '/api/authors/2/reviews'
477
487
  }
478
488
  },
479
489
  publisher: {
480
490
  data: { type: 'publishers', id: '2' },
481
491
  links: {
482
- self: '/api/1.0/authors/2/relationships/publisher',
483
- related: '/api/1.0/authors/2/publisher'
492
+ self: '/api/authors/2/relationships/publisher',
493
+ related: '/api/authors/2/publisher'
484
494
  }
485
495
  }
486
496
  },
487
- links: { self: '/api/1.0/authors/2' }
497
+ links: { self: '/api/authors/2' }
488
498
  }
489
499
  ],
490
500
  included: [
@@ -493,17 +503,17 @@ French authors with the newly added reviews (non-simplified):
493
503
  id: '4',
494
504
  attributes: { rating: 5, comment: 'Hugo is a master storyteller! (NS)' },
495
505
  relationships: { reviewable: { data: { type: 'authors', id: '1' } } },
496
- links: { self: '/api/1.0/reviews/4' }
506
+ links: { self: '/api/reviews/4' }
497
507
  },
498
508
  {
499
509
  type: 'reviews',
500
510
  id: '1',
501
511
  attributes: { rating: 4, comment: 'Great German author! (Simplified)' },
502
512
  relationships: { reviewable: { data: { type: 'authors', id: '2' } } },
503
- links: { self: '/api/1.0/reviews/1' }
513
+ links: { self: '/api/reviews/1' }
504
514
  }
505
515
  ],
506
- links: { self: '/api/1.0/authors?include=reviews' }
516
+ links: { self: '/api/authors?include=reviews' }
507
517
  }
508
518
  ```
509
519
 
@@ -535,8 +545,8 @@ await api.addResource('publishers', {
535
545
  name: { type: 'string', required: true, max: 255, search: true, indexed: true},
536
546
  },
537
547
  relationships: {
538
- authors: { hasMany: 'authors', foreignKey: 'publisher_id' }, // Regular one-to-many
539
- reviews: { hasMany: 'reviews', via: 'reviewable' } // Polymorphic one-to-many
548
+ authors: { type: 'hasMany', target: 'authors', foreignKey: 'publisher_id' }, // Regular one-to-many
549
+ reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' } // Polymorphic one-to-many
540
550
  },
541
551
  searchSchema: {
542
552
  // Search publishers by their review fields (reverse polymorphic search)
@@ -563,7 +573,7 @@ await api.addResource('authors', {
563
573
  publisher_id: { type: 'id', belongsTo: 'publishers', as: 'publisher', nullable: true }
564
574
  },
565
575
  relationships: {
566
- reviews: { hasMany: 'reviews', via: 'reviewable' } // Polymorphic one-to-many
576
+ reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' } // Polymorphic one-to-many
567
577
  },
568
578
  searchSchema: {
569
579
  // Regular search fields
@@ -666,7 +676,7 @@ reviewComment: {
666
676
  }
667
677
 
668
678
  // In relationships:
669
- reviews: { hasMany: 'reviews', via: 'reviewable' } // Polymorphic relationship
679
+ reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' } // Polymorphic relationship
670
680
  ```
671
681
 
672
682
  This uses the `via` property to indicate a polymorphic hasMany relationship. The system automatically adds the polymorphic constraints:
@@ -682,19 +692,26 @@ With the same data, run these queries:
682
692
  // This searches across BOTH publishers and authors tables based on reviewable_type
683
693
  const reviews_filtered_simplified = await api.resources.reviews.query({ queryParams: { filters: {reviewableName: 'Victor'} }})
684
694
  // HTTP: GET /api/reviews?filter[reviewableName]=Victor
685
- // Returns: [{ id: '2', comment: 'A master storyteller', rating: 5, reviewable_type: 'authors', reviewable_id: '1' }]
695
+ // Returns: [{ id: '2', comment: 'A master storyteller', rating: 5, reviewable: { id: '1', _type: 'authors' } }]
686
696
 
687
697
  const reviews_filtered_non_simplified = await api.resources.reviews.query({queryParams: { filters: {reviewableName: 'Victor'} }, simplified: false })
688
698
  // HTTP: GET /api/reviews?filter[reviewableName]=Victor
689
699
  // Returns (JSON:API): {
690
- // data: [{ type: 'reviews', id: '2', attributes: { comment: 'A master storyteller', rating: 5, reviewable_type: 'authors', reviewable_id: '1' } }]
700
+ // data: [{
701
+ // type: 'reviews',
702
+ // id: '2',
703
+ // attributes: { comment: 'A master storyteller', rating: 5 },
704
+ // relationships: {
705
+ // reviewable: { data: { type: 'authors', id: '1' } }
706
+ // }
707
+ // }]
691
708
  // }
692
709
 
693
710
  // 2. Reverse polymorphic search: Find parents (authors) by their children's (reviews) fields
694
711
  // This uses a polymorphic JOIN: reviews.reviewable_type = 'authors' AND reviews.reviewable_id = authors.id
695
712
  const authors_filtered_simplified = await api.resources.authors.query({queryParams: { filters: {reviewComment: 'storyteller'} }})
696
713
  // HTTP: GET /api/authors?filter[reviewComment]=storyteller
697
- // Returns: [{ id: '1', name: 'Victor Hugo', publisher_id: '1' }]
714
+ // Returns: [{ id: '1', name: 'Victor Hugo', surname: 'Hugo', publisher: { id: '1' } }]
698
715
 
699
716
  const authors_filtered_non_simplified = await api.resources.authors.query({queryParams: { filters: {reviewComment: 'storyteller'} }, simplified: false })
700
717
  // HTTP: GET /api/authors?filter[reviewComment]=storyteller
@@ -706,7 +723,7 @@ const authors_filtered_non_simplified = await api.resources.authors.query({query
706
723
  // This uses a polymorphic JOIN: reviews.reviewable_type = 'publishers' AND reviews.reviewable_id = publishers.id
707
724
  const publishers_filtered_simplified = await api.resources.publishers.query({queryParams: { filters: {reviewComment: 'enjoy'} }})
708
725
  // HTTP: GET /api/publishers?filter[reviewComment]=enjoy
709
- // Returns: [{ id: '1', name: 'French Books Inc.', country_id: '1' }]
726
+ // Returns: [{ id: '1', name: 'French Books Inc.' }]
710
727
 
711
728
  const publishers_filtered_non_simplified = await api.resources.publishers.query({queryParams: { filters: {reviewComment: 'enjoy'} }, simplified: false })
712
729
  // HTTP: GET /api/publishers?filter[reviewComment]=enjoy
@@ -741,8 +758,7 @@ Reviews FILTERED (simplified):
741
758
  id: '4',
742
759
  rating: 5,
743
760
  comment: 'Hugo is a master storyteller! (NS)',
744
- reviewable_type: 'authors',
745
- reviewable_id: '1'
761
+ reviewable: { id: '1', _type: 'authors' }
746
762
  }
747
763
  ],
748
764
  meta: {...},
@@ -759,15 +775,15 @@ Reviews FILTERED (non-simplified):
759
775
  reviewable: {
760
776
  data: { type: 'authors', id: '1' },
761
777
  links: {
762
- self: '/api/1.0/reviews/4/relationships/reviewable',
763
- related: '/api/1.0/reviews/4/reviewable'
778
+ self: '/api/reviews/4/relationships/reviewable',
779
+ related: '/api/reviews/4/reviewable'
764
780
  }
765
781
  }
766
782
  },
767
- links: { self: '/api/1.0/reviews/4' }
783
+ links: { self: '/api/reviews/4' }
768
784
  }
769
785
  ],
770
- links: { self: '/api/1.0/reviews?filters[reviewableName]=Victor' }
786
+ links: { self: '/api/reviews?filters[reviewableName]=Victor' }
771
787
  }
772
788
  Authors FILTERED (simplified):
773
789
  {
@@ -776,8 +792,8 @@ Authors FILTERED (simplified):
776
792
  id: '1',
777
793
  name: 'Victor (NS)',
778
794
  surname: 'Hugo (NS)',
779
- reviews_ids: [ '4' ],
780
- publisher_id: '1'
795
+ reviews: [ { id: '4' } ],
796
+ publisher: { id: '1' }
781
797
  }
782
798
  ],
783
799
  meta: {...},
@@ -795,15 +811,15 @@ Authors FILTERED (non-simplified):
795
811
  publisher: {
796
812
  data: { type: 'publishers', id: '1' },
797
813
  links: {
798
- self: '/api/1.0/authors/1/relationships/publisher',
799
- related: '/api/1.0/authors/1/publisher'
814
+ self: '/api/authors/1/relationships/publisher',
815
+ related: '/api/authors/1/publisher'
800
816
  }
801
817
  }
802
818
  },
803
- links: { self: '/api/1.0/authors/1' }
819
+ links: { self: '/api/authors/1' }
804
820
  }
805
821
  ],
806
- links: { self: '/api/1.0/authors?filters[reviewComment]=storyteller' }
822
+ links: { self: '/api/authors?filters[reviewComment]=storyteller' }
807
823
  }
808
824
  Publishers FILTERED (simplified):
809
825
  {
@@ -834,10 +850,10 @@ Publishers FILTERED (non-simplified):
834
850
  ]
835
851
  }
836
852
  },
837
- links: { self: '/api/1.0/publishers/1' }
853
+ links: { self: '/api/publishers/1' }
838
854
  }
839
855
  ],
840
- links: { self: '/api/1.0/publishers?filters[reviewComment]=enjoy' }
856
+ links: { self: '/api/publishers?filters[reviewComment]=enjoy' }
841
857
  }
842
858
  ```
843
859
 
@@ -879,4 +895,4 @@ The polymorphic search seamlessly handles the fact that reviews can belong to di
879
895
 
880
896
  ---
881
897
 
882
- [Previous: 2.4 hasMany records](./GUIDE_2_4_HasMany_Records.md) | [Back to Guide](./README.md) | [Next: 2.6 Many to many (hasMany with through records)](./GUIDE_2_6_Many_To_Many.md)
898
+ [Previous: 2.4 hasMany records](./GUIDE_2_4_HasMany_Records.md) | [Back to Guide](./README.md) | [Next: 2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md)