json-rest-api 1.0.28 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +746 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -174
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +20 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +142 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1069
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -385
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +38 -69
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +39 -82
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -150
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -118
- package/tests/fixtures/api-configs.js +0 -2509
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-cursor-multifield.test.js +0 -643
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,347 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { createExtendedApi } from './fixtures/api-configs.js'
|
|
5
|
-
import {
|
|
6
|
-
validateJsonApiStructure,
|
|
7
|
-
cleanTables,
|
|
8
|
-
createJsonApiDocument,
|
|
9
|
-
createRelationship
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
|
|
12
|
-
// Create Knex instance for tests
|
|
13
|
-
const knex = knexLib({
|
|
14
|
-
client: 'better-sqlite3',
|
|
15
|
-
connection: {
|
|
16
|
-
filename: ':memory:'
|
|
17
|
-
},
|
|
18
|
-
useNullAsDefault: true
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// API instance
|
|
22
|
-
let api
|
|
23
|
-
|
|
24
|
-
describe('Polymorphic Relationship Operations', () => {
|
|
25
|
-
before(async () => {
|
|
26
|
-
// Initialize API
|
|
27
|
-
api = await createExtendedApi(knex)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
after(async () => {
|
|
31
|
-
// Close database connection
|
|
32
|
-
await knex.destroy()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
describe('Creating Reviews with Polymorphic Relationships', () => {
|
|
36
|
-
beforeEach(async () => {
|
|
37
|
-
// Clean all tables before each test
|
|
38
|
-
await cleanTables(knex, [
|
|
39
|
-
'ext_countries', 'ext_publishers', 'ext_authors', 'ext_books',
|
|
40
|
-
'ext_book_authors', 'ext_reviews'
|
|
41
|
-
])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it('should create a review for a book', async () => {
|
|
45
|
-
// Create a country first
|
|
46
|
-
const country = await api.resources.countries.post({
|
|
47
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
48
|
-
name: 'USA',
|
|
49
|
-
code: 'US'
|
|
50
|
-
})
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
// Create a book
|
|
54
|
-
const book = await api.resources.books.post({
|
|
55
|
-
inputRecord: createJsonApiDocument('books', {
|
|
56
|
-
title: 'Test Book'
|
|
57
|
-
}, {
|
|
58
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
|
|
62
|
-
// Create a review for the book
|
|
63
|
-
const review = await api.resources.reviews.post({
|
|
64
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
65
|
-
rating: 5,
|
|
66
|
-
title: 'Great book!',
|
|
67
|
-
content: 'This is an excellent book about testing.',
|
|
68
|
-
reviewer_name: 'John Doe',
|
|
69
|
-
reviewable_type: 'books',
|
|
70
|
-
reviewable_id: book.data.id
|
|
71
|
-
})
|
|
72
|
-
})
|
|
73
|
-
|
|
74
|
-
validateJsonApiStructure(review)
|
|
75
|
-
assert.equal(review.data.type, 'reviews')
|
|
76
|
-
assert.equal(review.data.attributes.rating, 5)
|
|
77
|
-
// Since polymorphic fields are filtered out from POST responses,
|
|
78
|
-
// we need to GET the record to verify the relationship was saved correctly
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('should create a review for an author', async () => {
|
|
82
|
-
// Create an author
|
|
83
|
-
const author = await api.resources.authors.post({
|
|
84
|
-
inputRecord: createJsonApiDocument('authors', {
|
|
85
|
-
name: 'Jane Doe'
|
|
86
|
-
})
|
|
87
|
-
})
|
|
88
|
-
|
|
89
|
-
// Create a review for the author
|
|
90
|
-
const review = await api.resources.reviews.post({
|
|
91
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
92
|
-
rating: 4,
|
|
93
|
-
title: 'Talented author',
|
|
94
|
-
content: 'Love this author\'s writing style!',
|
|
95
|
-
reviewer_name: 'Book Lover',
|
|
96
|
-
reviewable_type: 'authors',
|
|
97
|
-
reviewable_id: author.data.id
|
|
98
|
-
})
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
validateJsonApiStructure(review)
|
|
102
|
-
// Polymorphic fields are no longer in attributes, they're in relationships
|
|
103
|
-
assert.equal(review.data.relationships.reviewable.data.type, 'authors')
|
|
104
|
-
assert.equal(review.data.relationships.reviewable.data.id, String(author.data.id))
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('should create a review for a publisher', async () => {
|
|
108
|
-
// Create a country
|
|
109
|
-
const country = await api.resources.countries.post({
|
|
110
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
111
|
-
name: 'UK',
|
|
112
|
-
code: 'GB'
|
|
113
|
-
})
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
// Create a publisher
|
|
117
|
-
const publisher = await api.resources.publishers.post({
|
|
118
|
-
inputRecord: createJsonApiDocument('publishers', {
|
|
119
|
-
name: 'Test Publisher'
|
|
120
|
-
}, {
|
|
121
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
122
|
-
})
|
|
123
|
-
})
|
|
124
|
-
|
|
125
|
-
// Create a review for the publisher
|
|
126
|
-
const review = await api.resources.reviews.post({
|
|
127
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
128
|
-
rating: 3,
|
|
129
|
-
title: 'Good publisher',
|
|
130
|
-
content: 'They publish quality books.',
|
|
131
|
-
reviewer_name: 'Industry Expert',
|
|
132
|
-
reviewable_type: 'publishers',
|
|
133
|
-
reviewable_id: publisher.data.id
|
|
134
|
-
})
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
validateJsonApiStructure(review)
|
|
138
|
-
// Polymorphic fields are no longer in attributes, they're in relationships
|
|
139
|
-
assert.equal(review.data.relationships.reviewable.data.type, 'publishers')
|
|
140
|
-
assert.equal(review.data.relationships.reviewable.data.id, String(publisher.data.id))
|
|
141
|
-
})
|
|
142
|
-
|
|
143
|
-
it('should fail to create review with invalid reviewable_type', async () => {
|
|
144
|
-
// Skip this test for now - polymorphic validation happens at relationship level
|
|
145
|
-
// not at the attribute level, so we need to create a proper relationship
|
|
146
|
-
// This would require more complex validation implementation
|
|
147
|
-
})
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
describe('Querying Reviews with Polymorphic Relationships', () => {
|
|
151
|
-
beforeEach(async () => {
|
|
152
|
-
await cleanTables(knex, [
|
|
153
|
-
'ext_countries', 'ext_publishers', 'ext_authors', 'ext_books',
|
|
154
|
-
'ext_book_authors', 'ext_reviews'
|
|
155
|
-
])
|
|
156
|
-
})
|
|
157
|
-
|
|
158
|
-
it('should query reviews and include polymorphic reviewable resource', async () => {
|
|
159
|
-
// Create test data
|
|
160
|
-
const country = await api.resources.countries.post({
|
|
161
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
162
|
-
name: 'France',
|
|
163
|
-
code: 'FR'
|
|
164
|
-
})
|
|
165
|
-
})
|
|
166
|
-
|
|
167
|
-
const book = await api.resources.books.post({
|
|
168
|
-
inputRecord: createJsonApiDocument('books', {
|
|
169
|
-
title: 'Test Book for Review'
|
|
170
|
-
}, {
|
|
171
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
172
|
-
})
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
await api.resources.reviews.post({
|
|
176
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
177
|
-
rating: 5,
|
|
178
|
-
title: 'Excellent!',
|
|
179
|
-
content: 'Must read!',
|
|
180
|
-
reviewer_name: 'Reader',
|
|
181
|
-
reviewable_type: 'books',
|
|
182
|
-
reviewable_id: book.data.id
|
|
183
|
-
})
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
// Query reviews with include
|
|
187
|
-
const result = await api.resources.reviews.query({
|
|
188
|
-
queryParams: {
|
|
189
|
-
include: ['reviewable']
|
|
190
|
-
}
|
|
191
|
-
})
|
|
192
|
-
|
|
193
|
-
validateJsonApiStructure(result, true) // It's a collection
|
|
194
|
-
assert.equal(result.data.length, 1)
|
|
195
|
-
assert.equal(result.data[0].type, 'reviews')
|
|
196
|
-
|
|
197
|
-
// Check included resources
|
|
198
|
-
assert(result.included)
|
|
199
|
-
assert.equal(result.included.length, 1)
|
|
200
|
-
assert.equal(result.included[0].type, 'books')
|
|
201
|
-
assert.equal(result.included[0].id, book.data.id)
|
|
202
|
-
assert.equal(result.included[0].attributes.title, 'Test Book for Review')
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
it('should handle mixed polymorphic types in a single query', async () => {
|
|
206
|
-
// Create test data
|
|
207
|
-
const country = await api.resources.countries.post({
|
|
208
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
209
|
-
name: 'Germany',
|
|
210
|
-
code: 'DE'
|
|
211
|
-
})
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
const book = await api.resources.books.post({
|
|
215
|
-
inputRecord: createJsonApiDocument('books', {
|
|
216
|
-
title: 'German Book'
|
|
217
|
-
}, {
|
|
218
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
219
|
-
})
|
|
220
|
-
})
|
|
221
|
-
|
|
222
|
-
const author = await api.resources.authors.post({
|
|
223
|
-
inputRecord: createJsonApiDocument('authors', {
|
|
224
|
-
name: 'German Author'
|
|
225
|
-
})
|
|
226
|
-
})
|
|
227
|
-
|
|
228
|
-
// Create reviews for different types
|
|
229
|
-
await api.resources.reviews.post({
|
|
230
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
231
|
-
rating: 5,
|
|
232
|
-
title: 'Great book',
|
|
233
|
-
content: 'Love it',
|
|
234
|
-
reviewer_name: 'Reader 1',
|
|
235
|
-
reviewable_type: 'books',
|
|
236
|
-
reviewable_id: book.data.id
|
|
237
|
-
})
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
await api.resources.reviews.post({
|
|
241
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
242
|
-
rating: 4,
|
|
243
|
-
title: 'Great author',
|
|
244
|
-
content: 'Talented writer',
|
|
245
|
-
reviewer_name: 'Reader 2',
|
|
246
|
-
reviewable_type: 'authors',
|
|
247
|
-
reviewable_id: author.data.id
|
|
248
|
-
})
|
|
249
|
-
})
|
|
250
|
-
|
|
251
|
-
// Query all reviews with includes
|
|
252
|
-
const result = await api.resources.reviews.query({
|
|
253
|
-
queryParams: {
|
|
254
|
-
include: ['reviewable']
|
|
255
|
-
}
|
|
256
|
-
})
|
|
257
|
-
|
|
258
|
-
validateJsonApiStructure(result, true) // It's a collection
|
|
259
|
-
assert.equal(result.data.length, 2)
|
|
260
|
-
|
|
261
|
-
// Check included resources
|
|
262
|
-
assert(result.included)
|
|
263
|
-
assert.equal(result.included.length, 2)
|
|
264
|
-
|
|
265
|
-
// Find each type in included
|
|
266
|
-
const includedBook = result.included.find(r => r.type === 'books')
|
|
267
|
-
const includedAuthor = result.included.find(r => r.type === 'authors')
|
|
268
|
-
|
|
269
|
-
assert(includedBook)
|
|
270
|
-
assert.equal(includedBook.attributes.title, 'German Book')
|
|
271
|
-
|
|
272
|
-
assert(includedAuthor)
|
|
273
|
-
assert.equal(includedAuthor.attributes.name, 'German Author')
|
|
274
|
-
})
|
|
275
|
-
})
|
|
276
|
-
|
|
277
|
-
describe('Reverse Polymorphic Queries', () => {
|
|
278
|
-
beforeEach(async () => {
|
|
279
|
-
await cleanTables(knex, [
|
|
280
|
-
'ext_countries', 'ext_publishers', 'ext_authors', 'ext_books',
|
|
281
|
-
'ext_book_authors', 'ext_reviews'
|
|
282
|
-
])
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
it('should query books and include their reviews', async () => {
|
|
286
|
-
// Create test data
|
|
287
|
-
const country = await api.resources.countries.post({
|
|
288
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
289
|
-
name: 'Spain',
|
|
290
|
-
code: 'ES'
|
|
291
|
-
})
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
const book = await api.resources.books.post({
|
|
295
|
-
inputRecord: createJsonApiDocument('books', {
|
|
296
|
-
title: 'Spanish Book'
|
|
297
|
-
}, {
|
|
298
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
299
|
-
})
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
// Create multiple reviews for the book
|
|
303
|
-
await api.resources.reviews.post({
|
|
304
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
305
|
-
rating: 5,
|
|
306
|
-
title: 'Amazing!',
|
|
307
|
-
content: 'Best book ever',
|
|
308
|
-
reviewer_name: 'Fan 1',
|
|
309
|
-
reviewable_type: 'books',
|
|
310
|
-
reviewable_id: book.data.id
|
|
311
|
-
})
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
await api.resources.reviews.post({
|
|
315
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
316
|
-
rating: 4,
|
|
317
|
-
title: 'Very good',
|
|
318
|
-
content: 'Enjoyed reading it',
|
|
319
|
-
reviewer_name: 'Fan 2',
|
|
320
|
-
reviewable_type: 'books',
|
|
321
|
-
reviewable_id: book.data.id
|
|
322
|
-
})
|
|
323
|
-
})
|
|
324
|
-
|
|
325
|
-
// Query book with reviews
|
|
326
|
-
const result = await api.resources.books.get({
|
|
327
|
-
id: book.data.id,
|
|
328
|
-
queryParams: {
|
|
329
|
-
include: ['reviews']
|
|
330
|
-
}
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
validateJsonApiStructure(result)
|
|
334
|
-
assert.equal(result.data.type, 'books')
|
|
335
|
-
|
|
336
|
-
// Check included reviews
|
|
337
|
-
console.log('Result included:', JSON.stringify(result.included, null, 2))
|
|
338
|
-
assert(result.included)
|
|
339
|
-
assert.equal(result.included.length, 2)
|
|
340
|
-
assert(result.included.every(r => r.type === 'reviews'))
|
|
341
|
-
assert(result.included.every(r =>
|
|
342
|
-
r.relationships.reviewable.data.type === 'books' &&
|
|
343
|
-
String(r.relationships.reviewable.data.id) === String(book.data.id)
|
|
344
|
-
))
|
|
345
|
-
})
|
|
346
|
-
})
|
|
347
|
-
})
|
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { createExtendedApi } from './fixtures/api-configs.js'
|
|
5
|
-
import {
|
|
6
|
-
validateJsonApiStructure,
|
|
7
|
-
cleanTables,
|
|
8
|
-
createJsonApiDocument,
|
|
9
|
-
createRelationship
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
|
|
12
|
-
// Create Knex instance for tests
|
|
13
|
-
const knex = knexLib({
|
|
14
|
-
client: 'better-sqlite3',
|
|
15
|
-
connection: {
|
|
16
|
-
filename: ':memory:'
|
|
17
|
-
},
|
|
18
|
-
useNullAsDefault: true
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
// API instance
|
|
22
|
-
let api
|
|
23
|
-
|
|
24
|
-
describe('Polymorphic Relationships - Basic Tests', () => {
|
|
25
|
-
before(async () => {
|
|
26
|
-
// Initialize API
|
|
27
|
-
api = await createExtendedApi(knex)
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
after(async () => {
|
|
31
|
-
// Close database connection
|
|
32
|
-
await knex.destroy()
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
beforeEach(async () => {
|
|
36
|
-
// Clean all tables before each test
|
|
37
|
-
await cleanTables(knex, [
|
|
38
|
-
'ext_countries', 'ext_publishers', 'ext_authors', 'ext_books',
|
|
39
|
-
'ext_book_authors', 'ext_reviews'
|
|
40
|
-
])
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('should create and retrieve a review for a book', async () => {
|
|
44
|
-
// Create a country first
|
|
45
|
-
const country = await api.resources.countries.post({
|
|
46
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
47
|
-
name: 'USA',
|
|
48
|
-
code: 'US'
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
// Create a book
|
|
53
|
-
const book = await api.resources.books.post({
|
|
54
|
-
inputRecord: createJsonApiDocument('books', {
|
|
55
|
-
title: 'Test Book'
|
|
56
|
-
}, {
|
|
57
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
58
|
-
})
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
// Create a review for the book
|
|
62
|
-
const review = await api.resources.reviews.post({
|
|
63
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
64
|
-
rating: 5,
|
|
65
|
-
title: 'Great book!',
|
|
66
|
-
content: 'This is an excellent book about testing.',
|
|
67
|
-
reviewer_name: 'John Doe'
|
|
68
|
-
}, {
|
|
69
|
-
reviewable: { data: { type: 'books', id: book.data.id } }
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
validateJsonApiStructure(review)
|
|
74
|
-
assert.equal(review.data.type, 'reviews')
|
|
75
|
-
assert.equal(review.data.attributes.rating, 5)
|
|
76
|
-
|
|
77
|
-
// Get the review back
|
|
78
|
-
const retrievedReview = await api.resources.reviews.get({
|
|
79
|
-
id: review.data.id
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
validateJsonApiStructure(retrievedReview)
|
|
83
|
-
// Polymorphic fields are no longer in attributes, they're in relationships
|
|
84
|
-
assert.equal(retrievedReview.data.relationships.reviewable.data.type, 'books')
|
|
85
|
-
assert.equal(retrievedReview.data.relationships.reviewable.data.id, String(book.data.id))
|
|
86
|
-
})
|
|
87
|
-
|
|
88
|
-
it('should query all reviews', async () => {
|
|
89
|
-
// Create test data
|
|
90
|
-
const country = await api.resources.countries.post({
|
|
91
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
92
|
-
name: 'UK',
|
|
93
|
-
code: 'GB'
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
const book = await api.resources.books.post({
|
|
98
|
-
inputRecord: createJsonApiDocument('books', {
|
|
99
|
-
title: 'Book 1'
|
|
100
|
-
}, {
|
|
101
|
-
country: createRelationship({ type: 'countries', id: country.data.id })
|
|
102
|
-
})
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
const author = await api.resources.authors.post({
|
|
106
|
-
inputRecord: createJsonApiDocument('authors', {
|
|
107
|
-
name: 'Author 1'
|
|
108
|
-
})
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
// Create reviews
|
|
112
|
-
await api.resources.reviews.post({
|
|
113
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
114
|
-
rating: 5,
|
|
115
|
-
title: 'Great book',
|
|
116
|
-
content: 'Love it',
|
|
117
|
-
reviewer_name: 'Reader 1'
|
|
118
|
-
}, {
|
|
119
|
-
reviewable: { data: { type: 'books', id: book.data.id } }
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
await api.resources.reviews.post({
|
|
124
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
125
|
-
rating: 4,
|
|
126
|
-
title: 'Good author',
|
|
127
|
-
content: 'Nice writing',
|
|
128
|
-
reviewer_name: 'Reader 2'
|
|
129
|
-
}, {
|
|
130
|
-
reviewable: { data: { type: 'authors', id: author.data.id } }
|
|
131
|
-
})
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
// Query all reviews
|
|
135
|
-
const result = await api.resources.reviews.query({})
|
|
136
|
-
|
|
137
|
-
validateJsonApiStructure(result, true)
|
|
138
|
-
assert.equal(result.data.length, 2)
|
|
139
|
-
|
|
140
|
-
// Check we have one of each type
|
|
141
|
-
// Polymorphic fields are no longer in attributes, they're in relationships
|
|
142
|
-
const bookReview = result.data.find(r => r.relationships.reviewable.data.type === 'books')
|
|
143
|
-
const authorReview = result.data.find(r => r.relationships.reviewable.data.type === 'authors')
|
|
144
|
-
|
|
145
|
-
assert(bookReview, 'Should have a book review')
|
|
146
|
-
assert(authorReview, 'Should have an author review')
|
|
147
|
-
})
|
|
148
|
-
})
|