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.
- package/.claude/settings.local.json +9 -1
- package/AGENTS.md +34 -0
- package/CLAUDE.md +26 -1
- package/CRUSH.md +55 -0
- package/README.md +3 -3
- package/TODO.todo +8 -0
- package/docs/API.md +17 -18
- package/docs/COMPARISON.md +18 -13
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +17 -21
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +33 -2
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +61 -7
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +58 -61
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +37 -37
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +103 -87
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +37 -34
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +45 -42
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +3 -3
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +1 -1
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -5
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +498 -755
- package/docs/GUIDE/GUIDE_6_Positioning.md +1 -3
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +4 -5
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +15 -6
- package/docs/GUIDE/GUIDE_X_Cors.md +2 -5
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +2 -3
- package/docs/GUIDE/GUIDE_X_Multihome.md +1 -4
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -2
- package/docs/GUIDE/GUIDE_X_URL_Management.md +317 -0
- package/docs/GUIDE/index.md +3 -0
- package/docs/QUICKSTART.md +12 -12
- package/docs/index.md +2 -2
- package/docs/mini-guides/file-uploads-complete.md +0 -1
- package/examples/url-override-example.js +137 -0
- package/index.js +7 -1
- package/package.json +7 -11
- package/plugins/core/bulk-operations-plugin.js +2 -0
- package/plugins/core/connectors/express-plugin.js +43 -24
- package/plugins/core/connectors/lib/request-helpers.js +2 -2
- package/plugins/core/lib/anyapi/anyapi-registry.js +821 -0
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +647 -0
- package/plugins/core/lib/anyapi/schema-utils.js +186 -0
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +113 -0
- package/plugins/core/lib/dbTablesOperations.js +305 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +64 -108
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +21 -27
- package/plugins/core/lib/querying/knex-pagination-helpers.js +36 -20
- package/plugins/core/lib/querying/knex-query-helpers-base.js +49 -14
- package/plugins/core/lib/querying/knex-query-helpers.js +378 -222
- package/plugins/core/lib/querying/knex-relationship-includes.js +262 -87
- package/plugins/core/lib/querying/url-helpers.js +119 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +36 -28
- package/plugins/core/lib/querying-writing/database-capabilities.js +10 -9
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +9 -3
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +2 -2
- package/plugins/core/lib/querying-writing/payload-validators.js +10 -16
- package/plugins/core/lib/querying-writing/schema-helpers.js +52 -60
- package/plugins/core/lib/querying-writing/scope-validations.js +78 -22
- package/plugins/core/lib/querying-writing/simplified-helpers.js +208 -61
- package/plugins/core/lib/storage/storage-adapter.js +160 -0
- package/plugins/core/lib/writing/relationship-processor.js +4 -11
- package/plugins/core/rest-api-access.js +456 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +2285 -0
- package/plugins/core/rest-api-knex-plugin.js +226 -87
- package/plugins/core/rest-api-multihome-plugin.js +89 -27
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +0 -1
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +1 -5
- package/plugins/core/rest-api-plugin-methods/common.js +46 -10
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +31 -27
- package/plugins/core/rest-api-plugin-methods/delete.js +1 -4
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +13 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +114 -56
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +4 -3
- package/plugins/core/rest-api-plugin-methods/get.js +2 -5
- package/plugins/core/rest-api-plugin-methods/patch.js +15 -16
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +21 -14
- package/plugins/core/rest-api-plugin-methods/post.js +23 -11
- package/plugins/core/rest-api-plugin-methods/put.js +18 -21
- package/plugins/core/rest-api-plugin-methods/query.js +3 -4
- package/plugins/core/rest-api-plugin-methods/release.js +13 -0
- package/plugins/core/rest-api-plugin.js +3 -4
- package/plugins/core/rest-api-positioning-plugin.js +253 -76
- package/plugins/core/socketio-plugin.js +436 -502
- package/quickTest.js +2 -2
- package/scripts/debug-anyapi-review-filter.mjs +63 -0
- package/scripts/inspect-anyapi-descriptor-country.mjs +19 -0
- package/scripts/inspect-anyapi-descriptor.mjs +19 -0
- package/test-debug.js +59 -0
- package/tests/README.md +7 -1
- package/tests/anyapi-basic.test.js +102 -0
- package/tests/anyapi-cursor-pagination.test.js +144 -0
- package/tests/computed-fields.test.js +27 -12
- package/tests/cors.test.js +5 -6
- package/tests/curl.test.js +1 -1
- package/tests/custom-idproperty-relationships.test.js +37 -29
- package/tests/field-getters.test.js +25 -7
- package/tests/fixtures/api-configs.js +576 -150
- package/tests/helpers/storage-mode.js +65 -0
- package/tests/helpers/test-utils.js +85 -3
- package/tests/include-depth-validation.test.js +35 -1
- package/tests/nested-includes.test.js +130 -103
- package/tests/pagination-cursor-multifield.test.js +17 -13
- package/tests/pagination-enhanced.test.js +19 -24
- package/tests/polymorphic-simple.test.js +9 -9
- package/tests/positioning-advanced.test.js +19 -19
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +62 -62
- package/tests/positioning.test.js +7 -7
- package/tests/queries.test.js +5 -5
- package/tests/query-limits.test.js +77 -92
- package/tests/rest-api-access.test.js +206 -0
- package/tests/searchschema-merge.test.js +182 -0
- package/tests/socketio.test.js +33 -14
- package/tests/virtual-fields.test.js +0 -1
- package/docs/DOCS_ALL.md +0 -21894
- package/plugins/core/jwt-auth-plugin.js +0 -897
- package/plugins/core/lib/jwt-auth-helpers.js +0 -130
- 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
|
-
|
|
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
|
-
|
|
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',
|
|
155
|
-
// { id: '2', name: 'Alexandre Dumas',
|
|
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/
|
|
216
|
+
links: { self: '/api/reviews/3' }
|
|
203
217
|
},
|
|
204
|
-
links: { self: '/api/
|
|
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/
|
|
225
|
+
links: { self: '/api/reviews/4' }
|
|
212
226
|
},
|
|
213
|
-
links: { self: '/api/
|
|
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
|
-
|
|
221
|
-
|
|
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/
|
|
235
|
-
related: '/api/
|
|
248
|
+
self: '/api/authors/1/relationships/publisher',
|
|
249
|
+
related: '/api/authors/1/publisher'
|
|
236
250
|
}
|
|
237
251
|
}
|
|
238
252
|
},
|
|
239
|
-
links: { self: '/api/
|
|
253
|
+
links: { self: '/api/authors/1' }
|
|
240
254
|
},
|
|
241
|
-
links: { self: '/api/
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
257
|
-
|
|
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/
|
|
273
|
-
related: '/api/
|
|
286
|
+
self: '/api/authors/1/relationships/publisher',
|
|
287
|
+
related: '/api/authors/1/publisher'
|
|
274
288
|
}
|
|
275
289
|
}
|
|
276
290
|
},
|
|
277
|
-
links: { self: '/api/
|
|
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/
|
|
289
|
-
related: '/api/
|
|
302
|
+
self: '/api/authors/2/relationships/publisher',
|
|
303
|
+
related: '/api/authors/2/publisher'
|
|
290
304
|
}
|
|
291
305
|
}
|
|
292
306
|
},
|
|
293
|
-
links: { self: '/api/
|
|
307
|
+
links: { self: '/api/authors/2' }
|
|
294
308
|
}
|
|
295
309
|
],
|
|
296
|
-
links: { self: '/api/
|
|
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',
|
|
318
|
-
// reviews: [{ id: '2', comment: 'A master storyteller', rating: 5,
|
|
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',
|
|
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
|
-
|
|
367
|
-
reviewable_id: '1'
|
|
380
|
+
reviewable: { id: '1', _type: 'authors' }
|
|
368
381
|
}
|
|
369
382
|
],
|
|
370
|
-
|
|
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/
|
|
383
|
-
related: '/api/
|
|
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/
|
|
390
|
-
related: '/api/
|
|
402
|
+
self: '/api/authors/1/relationships/publisher',
|
|
403
|
+
related: '/api/authors/1/publisher'
|
|
391
404
|
}
|
|
392
405
|
}
|
|
393
406
|
},
|
|
394
|
-
links: { self: '/api/
|
|
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/
|
|
415
|
+
links: { self: '/api/reviews/4' }
|
|
403
416
|
}
|
|
404
417
|
],
|
|
405
|
-
links: { self: '/api/
|
|
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
|
-
|
|
420
|
-
reviewable_id: '1'
|
|
432
|
+
reviewable: { id: '1', _type: 'authors' }
|
|
421
433
|
}
|
|
422
434
|
],
|
|
423
|
-
|
|
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
|
-
|
|
436
|
-
reviewable_id: '2'
|
|
446
|
+
reviewable: { id: '2', _type: 'authors' }
|
|
437
447
|
}
|
|
438
448
|
],
|
|
439
|
-
|
|
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/
|
|
454
|
-
related: '/api/
|
|
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/
|
|
461
|
-
related: '/api/
|
|
470
|
+
self: '/api/authors/1/relationships/publisher',
|
|
471
|
+
related: '/api/authors/1/publisher'
|
|
462
472
|
}
|
|
463
473
|
}
|
|
464
474
|
},
|
|
465
|
-
links: { self: '/api/
|
|
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/
|
|
476
|
-
related: '/api/
|
|
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/
|
|
483
|
-
related: '/api/
|
|
492
|
+
self: '/api/authors/2/relationships/publisher',
|
|
493
|
+
related: '/api/authors/2/publisher'
|
|
484
494
|
}
|
|
485
495
|
}
|
|
486
496
|
},
|
|
487
|
-
links: { self: '/api/
|
|
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/
|
|
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/
|
|
513
|
+
links: { self: '/api/reviews/1' }
|
|
504
514
|
}
|
|
505
515
|
],
|
|
506
|
-
links: { self: '/api/
|
|
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,
|
|
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: [{
|
|
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',
|
|
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.'
|
|
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
|
-
|
|
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/
|
|
763
|
-
related: '/api/
|
|
778
|
+
self: '/api/reviews/4/relationships/reviewable',
|
|
779
|
+
related: '/api/reviews/4/reviewable'
|
|
764
780
|
}
|
|
765
781
|
}
|
|
766
782
|
},
|
|
767
|
-
links: { self: '/api/
|
|
783
|
+
links: { self: '/api/reviews/4' }
|
|
768
784
|
}
|
|
769
785
|
],
|
|
770
|
-
links: { self: '/api/
|
|
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
|
-
|
|
780
|
-
|
|
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/
|
|
799
|
-
related: '/api/
|
|
814
|
+
self: '/api/authors/1/relationships/publisher',
|
|
815
|
+
related: '/api/authors/1/publisher'
|
|
800
816
|
}
|
|
801
817
|
}
|
|
802
818
|
},
|
|
803
|
-
links: { self: '/api/
|
|
819
|
+
links: { self: '/api/authors/1' }
|
|
804
820
|
}
|
|
805
821
|
],
|
|
806
|
-
links: { self: '/api/
|
|
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/
|
|
853
|
+
links: { self: '/api/publishers/1' }
|
|
838
854
|
}
|
|
839
855
|
],
|
|
840
|
-
links: { self: '/api/
|
|
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
|
|
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)
|