json-rest-api 1.0.29 → 2.1.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 +744 -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 +733 -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 +103 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3838 -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 +37 -0
- package/index.js +3 -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 +70 -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 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
- 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 +120 -56
- package/plugins/core/lib/anyapi/stored-definition.js +109 -0
- 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 +48 -317
- 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 +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -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 +179 -194
- 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 +100 -290
- 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 +93 -38
- 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 +128 -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 +290 -2475
- 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 -1077
- 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 -395
- 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 -148
- 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 +36 -68
- 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 +37 -81
- 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/anyapi.d.ts +30 -0
- 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 +65 -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 -1247
- 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 -148
- 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 -155
- package/tests/fixtures/api-configs.js +0 -2566
- 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-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- 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/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
import { getSchemaStructure } from '../querying-writing/knex-constants.js'
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Processes belongsTo relationships from JSON:API input to foreign key updates
|
|
5
|
-
*
|
|
6
|
-
* @param {Object} scope - The scope object containing schema info
|
|
7
|
-
* @param {Object} deps - Dependencies object containing context with inputRecord
|
|
8
|
-
* @returns {Object} Object mapping foreign key fields to their values
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* // Input: JSON:API document with relationships
|
|
12
|
-
* const inputRecord = {
|
|
13
|
-
* data: {
|
|
14
|
-
* type: 'articles',
|
|
15
|
-
* attributes: { title: 'My Article' },
|
|
16
|
-
* relationships: {
|
|
17
|
-
* author: {
|
|
18
|
-
* data: { type: 'users', id: '123' }
|
|
19
|
-
* },
|
|
20
|
-
* category: {
|
|
21
|
-
* data: { type: 'categories', id: '456' }
|
|
22
|
-
* }
|
|
23
|
-
* }
|
|
24
|
-
* }
|
|
25
|
-
* };
|
|
26
|
-
*
|
|
27
|
-
* // Schema has: author_id field with belongsTo: 'users', as: 'author'
|
|
28
|
-
* // Schema has: category_id field with belongsTo: 'categories', as: 'category'
|
|
29
|
-
*
|
|
30
|
-
* const updates = processBelongsToRelationships(scope, { context: { inputRecord } });
|
|
31
|
-
*
|
|
32
|
-
* // Output: Foreign key values extracted
|
|
33
|
-
* // {
|
|
34
|
-
* // author_id: '123',
|
|
35
|
-
* // category_id: '456'
|
|
36
|
-
* // }
|
|
37
|
-
*
|
|
38
|
-
* @example
|
|
39
|
-
* // Input: Removing a relationship (setting to null)
|
|
40
|
-
* const inputRecord = {
|
|
41
|
-
* data: {
|
|
42
|
-
* type: 'articles',
|
|
43
|
-
* id: '1',
|
|
44
|
-
* relationships: {
|
|
45
|
-
* author: {
|
|
46
|
-
* data: null // Remove author
|
|
47
|
-
* }
|
|
48
|
-
* }
|
|
49
|
-
* }
|
|
50
|
-
* };
|
|
51
|
-
*
|
|
52
|
-
* const updates = processBelongsToRelationships(scope, { context: { inputRecord } });
|
|
53
|
-
*
|
|
54
|
-
* // Output: Foreign key set to null
|
|
55
|
-
* // {
|
|
56
|
-
* // author_id: null
|
|
57
|
-
* // }
|
|
58
|
-
*
|
|
59
|
-
* @description
|
|
60
|
-
* Used by:
|
|
61
|
-
* - rest-api-knex-plugin's dataPut and dataPatch methods
|
|
62
|
-
* - Called before saving to extract foreign keys from JSON:API relationships
|
|
63
|
-
* - Works in conjunction with relationship-processor for complete handling
|
|
64
|
-
*
|
|
65
|
-
* Purpose:
|
|
66
|
-
* - Converts JSON:API relationship format to database foreign keys
|
|
67
|
-
* - Maps from relationship names (e.g., 'author') to field names (e.g., 'author_id')
|
|
68
|
-
* - Handles both setting relationships (with id) and removing them (null)
|
|
69
|
-
* - Only processes belongsTo relationships, not hasMany
|
|
70
|
-
*
|
|
71
|
-
* Data flow:
|
|
72
|
-
* 1. Receives JSON:API document with relationships section
|
|
73
|
-
* 2. Looks up schema to find foreign key fields
|
|
74
|
-
* 3. For each belongsTo field, checks if relationship exists in input
|
|
75
|
-
* 4. Extracts id from relationship data or sets null
|
|
76
|
-
* 5. Returns object ready to merge with attributes for database update
|
|
77
|
-
*/
|
|
78
|
-
export const processBelongsToRelationships = (scope, deps) => {
|
|
79
|
-
const foreignKeyUpdates = {}
|
|
80
|
-
|
|
81
|
-
// Extract values from deps
|
|
82
|
-
const { context } = deps
|
|
83
|
-
const inputRecord = context.inputRecord
|
|
84
|
-
|
|
85
|
-
if (!inputRecord.data.relationships) {
|
|
86
|
-
return foreignKeyUpdates
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Extract schema from scope
|
|
90
|
-
const {
|
|
91
|
-
vars: {
|
|
92
|
-
schemaInfo: { schema }
|
|
93
|
-
}
|
|
94
|
-
} = scope
|
|
95
|
-
|
|
96
|
-
// Schema might be a Schema object or plain object
|
|
97
|
-
const schemaStructure = getSchemaStructure(schema)
|
|
98
|
-
|
|
99
|
-
for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
|
|
100
|
-
if (fieldDef.belongsTo && fieldDef.as) {
|
|
101
|
-
const relName = fieldDef.as
|
|
102
|
-
if (inputRecord.data.relationships[relName] !== undefined) {
|
|
103
|
-
const relData = inputRecord.data.relationships[relName]
|
|
104
|
-
foreignKeyUpdates[fieldName] = relData.data?.id || null
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return foreignKeyUpdates
|
|
110
|
-
}
|
|
@@ -1,93 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* S3 Storage Adapter (mock/demo)
|
|
3
|
-
*
|
|
4
|
-
* Generates S3-style URLs without uploading files to Amazon S3.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* ```javascript
|
|
8
|
-
* import { S3Storage } from 'json-rest-api';
|
|
9
|
-
*
|
|
10
|
-
* const storage = new S3Storage({
|
|
11
|
-
* bucket: 'my-uploads',
|
|
12
|
-
* region: 'us-east-1',
|
|
13
|
-
* prefix: 'uploads/',
|
|
14
|
-
* mockMode: true
|
|
15
|
-
* });
|
|
16
|
-
*
|
|
17
|
-
* const schema = {
|
|
18
|
-
* image: { type: 'file', storage }
|
|
19
|
-
* };
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import crypto from 'crypto'
|
|
24
|
-
import path from 'path'
|
|
25
|
-
|
|
26
|
-
const REAL_S3_NOT_IMPLEMENTED_MESSAGE = 'S3Storage real S3 mode is not implemented. The included adapter only supports mockMode: true; provide a production storage adapter or add an AWS SDK-backed implementation.'
|
|
27
|
-
|
|
28
|
-
function createRealS3NotImplementedError () {
|
|
29
|
-
return new Error(REAL_S3_NOT_IMPLEMENTED_MESSAGE)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export class S3Storage {
|
|
33
|
-
constructor (options = {}) {
|
|
34
|
-
this.bucket = options.bucket
|
|
35
|
-
this.region = options.region || 'us-east-1'
|
|
36
|
-
this.prefix = options.prefix || ''
|
|
37
|
-
this.acl = options.acl || 'public-read'
|
|
38
|
-
|
|
39
|
-
if (!this.bucket) {
|
|
40
|
-
throw new Error('S3Storage requires a bucket name')
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
this.mockMode = options.mockMode !== false // Default to mock mode
|
|
44
|
-
|
|
45
|
-
if (!this.mockMode) {
|
|
46
|
-
throw createRealS3NotImplementedError()
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Upload a file to mock S3 storage.
|
|
52
|
-
* @param {Object} file - File object from detector
|
|
53
|
-
* @returns {Promise<string>} URL of the uploaded file
|
|
54
|
-
*/
|
|
55
|
-
async upload (file) {
|
|
56
|
-
// Generate S3 key
|
|
57
|
-
const ext = path.extname(file.filename)
|
|
58
|
-
const hash = crypto.randomBytes(16).toString('hex')
|
|
59
|
-
const key = `${this.prefix}${hash}${ext}`
|
|
60
|
-
|
|
61
|
-
if (this.mockMode) {
|
|
62
|
-
// Mock mode - just return a fake URL
|
|
63
|
-
const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`
|
|
64
|
-
|
|
65
|
-
// Simulate upload delay
|
|
66
|
-
await new Promise(resolve => setTimeout(resolve, 100))
|
|
67
|
-
|
|
68
|
-
return url
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
throw createRealS3NotImplementedError()
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Delete a file from mock S3 storage.
|
|
76
|
-
* @param {string} url - URL returned by upload()
|
|
77
|
-
* @returns {Promise<void>}
|
|
78
|
-
*/
|
|
79
|
-
async delete (url) {
|
|
80
|
-
if (!this.mockMode) {
|
|
81
|
-
throw createRealS3NotImplementedError()
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const urlObj = new URL(url)
|
|
85
|
-
|
|
86
|
-
if (urlObj.pathname.length === 0) {
|
|
87
|
-
throw new Error('Invalid S3 object URL')
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// Mock mode - just simulate deletion
|
|
91
|
-
await new Promise(resolve => setTimeout(resolve, 50))
|
|
92
|
-
}
|
|
93
|
-
}
|
package/quickTest.js
DELETED
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
//
|
|
2
|
-
// index.js
|
|
3
|
-
//
|
|
4
|
-
import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from './index.js' // Added: ExpressPlugin
|
|
5
|
-
import { Api } from 'hooked-api'
|
|
6
|
-
import knexLib from 'knex'
|
|
7
|
-
import util from 'util'
|
|
8
|
-
import express from 'express' // Added: Express
|
|
9
|
-
|
|
10
|
-
// Utility used throughout this guide
|
|
11
|
-
const inspect = (obj) => util.inspect(obj, { depth: 8 })
|
|
12
|
-
|
|
13
|
-
// Create a Knex instance connected to SQLite in-memory database
|
|
14
|
-
const knex = knexLib({
|
|
15
|
-
client: 'better-sqlite3',
|
|
16
|
-
connection: {
|
|
17
|
-
filename: ':memory:'
|
|
18
|
-
},
|
|
19
|
-
useNullAsDefault: true
|
|
20
|
-
})
|
|
21
|
-
|
|
22
|
-
// Create API instance
|
|
23
|
-
const api = new Api({ name: 'book-catalog-api', logLevel: 'trace' })
|
|
24
|
-
|
|
25
|
-
// Install plugins
|
|
26
|
-
await api.use(RestApiPlugin, { returnBasePath: '/api' })
|
|
27
|
-
await api.use(RestApiKnexPlugin, { knex })
|
|
28
|
-
await api.use(ExpressPlugin, { mountPath: '/api' }) // Added: Express Plugin
|
|
29
|
-
|
|
30
|
-
// Define publishers resource
|
|
31
|
-
await api.addResource('publishers', {
|
|
32
|
-
schema: {
|
|
33
|
-
name: { type: 'string', required: true, max: 255, search: true, indexed: true },
|
|
34
|
-
},
|
|
35
|
-
relationships: {
|
|
36
|
-
// A publisher has many authors
|
|
37
|
-
authors: { hasMany: 'authors', foreignKey: 'publisher_id' },
|
|
38
|
-
},
|
|
39
|
-
searchSchema: { // Adding search schema for publishers
|
|
40
|
-
name: { type: 'string', filterOperator: 'like' }
|
|
41
|
-
}
|
|
42
|
-
})
|
|
43
|
-
await api.resources.publishers.createKnexTable()
|
|
44
|
-
|
|
45
|
-
// Define authors resource, which belongs to a publisher
|
|
46
|
-
await api.addResource('authors', {
|
|
47
|
-
schema: {
|
|
48
|
-
name: { type: 'string', required: true, max: 100, search: true },
|
|
49
|
-
surname: { type: 'string', required: true, max: 100, search: true },
|
|
50
|
-
publisher_id: { type: 'id', belongsTo: 'publishers', as: 'publisher', nullable: true }
|
|
51
|
-
},
|
|
52
|
-
searchSchema: { // Adding search schema for authors
|
|
53
|
-
name: { type: 'string', filterOperator: 'like' },
|
|
54
|
-
surname: { type: 'string', filterOperator: 'like' },
|
|
55
|
-
publisher: { type: 'id', actualField: 'publisher_id', nullable: true },
|
|
56
|
-
publisherName: { type: 'string', actualField: 'publishers.name', filterOperator: 'like' } // Cross-table search
|
|
57
|
-
}
|
|
58
|
-
})
|
|
59
|
-
await api.resources.authors.createKnexTable()
|
|
60
|
-
|
|
61
|
-
// Method 1: Simplified mode without inputRecord (most concise)
|
|
62
|
-
const penguinResult = await api.resources.publishers.post({
|
|
63
|
-
name: 'Penguin Random House'
|
|
64
|
-
})
|
|
65
|
-
console.log('Created publisher:', inspect(penguinResult))
|
|
66
|
-
|
|
67
|
-
// Method 2: Simplified mode with inputRecord (explicit)
|
|
68
|
-
const harperResult = await api.resources.publishers.post({
|
|
69
|
-
inputRecord: {
|
|
70
|
-
name: 'HarperCollins'
|
|
71
|
-
}
|
|
72
|
-
})
|
|
73
|
-
console.log('Created second publisher:', inspect(harperResult))
|
|
74
|
-
|
|
75
|
-
// Method 3: Full JSON:API mode (standards compliant)
|
|
76
|
-
const oxfordResult = await api.resources.publishers.post({
|
|
77
|
-
inputRecord: {
|
|
78
|
-
data: {
|
|
79
|
-
type: 'publishers',
|
|
80
|
-
attributes: {
|
|
81
|
-
name: 'Oxford University Press'
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
},
|
|
85
|
-
simplified: false
|
|
86
|
-
})
|
|
87
|
-
console.log('JSON:API response:', inspect(oxfordResult))
|
|
88
|
-
|
|
89
|
-
// Create an author linked to the first publisher (simplified)
|
|
90
|
-
const authorResult = await api.resources.authors.post({
|
|
91
|
-
name: 'George',
|
|
92
|
-
surname: 'Orwell',
|
|
93
|
-
publisher_id: penguinResult.id
|
|
94
|
-
})
|
|
95
|
-
console.log('Created author:', inspect(authorResult))
|
|
96
|
-
|
|
97
|
-
// Get all publishers
|
|
98
|
-
const allPublishers = await api.resources.publishers.query({})
|
|
99
|
-
console.log('All publishers:', inspect(allPublishers))
|
|
100
|
-
|
|
101
|
-
// Get publisher with included authors
|
|
102
|
-
const publisherWithAuthors = await api.resources.publishers.get({
|
|
103
|
-
id: penguinResult.id,
|
|
104
|
-
include: ['authors']
|
|
105
|
-
})
|
|
106
|
-
console.log('Publisher with authors:', inspect(publisherWithAuthors))
|
|
107
|
-
|
|
108
|
-
// Search authors by name
|
|
109
|
-
const searchResult = await api.resources.authors.query({
|
|
110
|
-
filter: { name: 'George' }
|
|
111
|
-
})
|
|
112
|
-
console.log('Search results:', inspect(searchResult))
|
|
113
|
-
|
|
114
|
-
// Update an author
|
|
115
|
-
const updateResult = await api.resources.authors.patch({
|
|
116
|
-
id: authorResult.id,
|
|
117
|
-
surname: 'Orwell (Eric Blair)'
|
|
118
|
-
})
|
|
119
|
-
console.log('Updated author:', inspect(updateResult))
|
|
120
|
-
|
|
121
|
-
/// *** ...programmatic calls here... ***
|
|
122
|
-
|
|
123
|
-
// Create the express server and add the API's routes
|
|
124
|
-
const app = express()
|
|
125
|
-
app.use(api.http.express.router)
|
|
126
|
-
app.use(api.http.express.notFoundRouter)
|
|
127
|
-
|
|
128
|
-
app.listen(3000, () => {
|
|
129
|
-
console.log('Express server started on port 3000. API available at http://localhost:3000/api')
|
|
130
|
-
}).on('error', (err) => {
|
|
131
|
-
console.error('Failed to start server:', err)
|
|
132
|
-
process.exit(1)
|
|
133
|
-
}).on('error', (err) => {
|
|
134
|
-
console.error('Failed to start server:', err)
|
|
135
|
-
process.exit(1)
|
|
136
|
-
})
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import knexLib from 'knex'
|
|
2
|
-
import { createCustomIdPropertyApi } from '../tests/fixtures/api-configs.js'
|
|
3
|
-
import { createJsonApiDocument } from '../tests/helpers/test-utils.js'
|
|
4
|
-
|
|
5
|
-
async function run () {
|
|
6
|
-
const knex = knexLib({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
|
|
7
|
-
const api = await createCustomIdPropertyApi(knex)
|
|
8
|
-
|
|
9
|
-
const country = await api.resources.countries.post({
|
|
10
|
-
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
|
|
11
|
-
})
|
|
12
|
-
|
|
13
|
-
const book = await api.resources.books.post({
|
|
14
|
-
inputRecord: createJsonApiDocument('books',
|
|
15
|
-
{ title: 'Test Book' },
|
|
16
|
-
{ country: { data: { type: 'countries', id: country.data.id } } }
|
|
17
|
-
)
|
|
18
|
-
})
|
|
19
|
-
|
|
20
|
-
const author = await api.resources.authors.post({
|
|
21
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Author' })
|
|
22
|
-
})
|
|
23
|
-
|
|
24
|
-
await api.resources.reviews.post({
|
|
25
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
26
|
-
rating: 5,
|
|
27
|
-
title: 'Book review',
|
|
28
|
-
content: 'Great book',
|
|
29
|
-
reviewer_name: 'Reader',
|
|
30
|
-
reviewable_type: 'books',
|
|
31
|
-
reviewable_id: book.data.id
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
await api.resources.reviews.post({
|
|
36
|
-
inputRecord: createJsonApiDocument('reviews', {
|
|
37
|
-
rating: 4,
|
|
38
|
-
title: 'Author review',
|
|
39
|
-
content: 'Great author',
|
|
40
|
-
reviewer_name: 'Fan',
|
|
41
|
-
reviewable_type: 'authors',
|
|
42
|
-
reviewable_id: author.data.id
|
|
43
|
-
})
|
|
44
|
-
})
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const res = await api.resources.reviews.query({
|
|
48
|
-
queryParams: { filters: { reviewable_type: 'books' } }
|
|
49
|
-
})
|
|
50
|
-
console.log('Query succeeded with rows:', res.data.length)
|
|
51
|
-
} catch (error) {
|
|
52
|
-
console.error('Query failed:', error)
|
|
53
|
-
if (error?.sql) {
|
|
54
|
-
console.error('SQL:', error.sql)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
await knex.destroy()
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
run().catch((err) => {
|
|
62
|
-
console.error('Unexpected failure:', err)
|
|
63
|
-
})
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import knexLib from 'knex'
|
|
2
|
-
import { createCustomIdPropertyApi } from '../tests/fixtures/api-configs.js'
|
|
3
|
-
|
|
4
|
-
async function run () {
|
|
5
|
-
const knex = knexLib({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
|
|
6
|
-
const api = await createCustomIdPropertyApi(knex)
|
|
7
|
-
const descriptor = await api.anyapi.registry.getDescriptor('default', 'countries')
|
|
8
|
-
console.log(JSON.stringify({
|
|
9
|
-
idProperty: descriptor.idProperty,
|
|
10
|
-
fields: descriptor.fields,
|
|
11
|
-
canonicalFieldMap: descriptor.canonicalFieldMap,
|
|
12
|
-
reverseAttributes: descriptor.reverseAttributes,
|
|
13
|
-
}, null, 2))
|
|
14
|
-
await knex.destroy()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
run().catch((err) => {
|
|
18
|
-
console.error('Failed to inspect descriptor', err)
|
|
19
|
-
})
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import knexLib from 'knex'
|
|
2
|
-
import { createCustomIdPropertyApi } from '../tests/fixtures/api-configs.js'
|
|
3
|
-
|
|
4
|
-
async function run () {
|
|
5
|
-
const knex = knexLib({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
|
|
6
|
-
const api = await createCustomIdPropertyApi(knex)
|
|
7
|
-
const descriptor = await api.anyapi.registry.getDescriptor('default', 'reviews')
|
|
8
|
-
console.log(JSON.stringify({
|
|
9
|
-
idProperty: descriptor.idProperty,
|
|
10
|
-
fields: descriptor.fields,
|
|
11
|
-
canonicalFieldMap: descriptor.canonicalFieldMap,
|
|
12
|
-
reverseAttributes: descriptor.reverseAttributes,
|
|
13
|
-
}, null, 2))
|
|
14
|
-
await knex.destroy()
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
run().catch((err) => {
|
|
18
|
-
console.error('Failed to inspect descriptor', err)
|
|
19
|
-
})
|
package/scripts/run-docs.js
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { spawn } from 'child_process'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
|
-
|
|
7
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
8
|
-
const __dirname = path.dirname(__filename)
|
|
9
|
-
const projectRoot = path.join(__dirname, '..')
|
|
10
|
-
const docsRoot = path.join(projectRoot, 'docs')
|
|
11
|
-
const LOCAL_URL = 'http://localhost:4000/json-rest-api/'
|
|
12
|
-
const BROWSER_DELAY_MS = 3000
|
|
13
|
-
|
|
14
|
-
function parseMode (argv = []) {
|
|
15
|
-
if (argv.includes('--serve') || argv.includes('--dev')) {
|
|
16
|
-
return 'serve'
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
return 'build'
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
async function runCommand (command, args, options = {}) {
|
|
23
|
-
return new Promise((resolve, reject) => {
|
|
24
|
-
const proc = spawn(command, args, {
|
|
25
|
-
stdio: 'inherit',
|
|
26
|
-
shell: true,
|
|
27
|
-
...options
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
proc.on('error', reject)
|
|
31
|
-
proc.on('exit', (code) => {
|
|
32
|
-
if (code === 0) {
|
|
33
|
-
resolve()
|
|
34
|
-
return
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
reject(new Error(`Command failed with code ${code}`))
|
|
38
|
-
})
|
|
39
|
-
})
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
async function ensureBundlerDependencies () {
|
|
43
|
-
try {
|
|
44
|
-
await runCommand('bundle', ['check'], { cwd: docsRoot })
|
|
45
|
-
} catch {
|
|
46
|
-
console.log('Installing Jekyll dependencies...')
|
|
47
|
-
await runCommand('bundle', ['install'], { cwd: docsRoot })
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
async function openBrowser (url) {
|
|
52
|
-
const platform = process.platform
|
|
53
|
-
const command = platform === 'darwin'
|
|
54
|
-
? 'open'
|
|
55
|
-
: platform === 'win32'
|
|
56
|
-
? 'start'
|
|
57
|
-
: 'xdg-open'
|
|
58
|
-
|
|
59
|
-
try {
|
|
60
|
-
await runCommand(command, [url])
|
|
61
|
-
console.log(`Opened browser at ${url}`)
|
|
62
|
-
} catch {
|
|
63
|
-
console.log(`Visit ${url} in your browser`)
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function buildDocs () {
|
|
68
|
-
console.log('Building documentation site...')
|
|
69
|
-
await runCommand('bundle', ['exec', 'jekyll', 'build'], { cwd: docsRoot })
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
async function serveDocs () {
|
|
73
|
-
console.log('Starting documentation server...')
|
|
74
|
-
|
|
75
|
-
const jekyll = spawn('bundle', ['exec', 'jekyll', 'serve', '--watch'], {
|
|
76
|
-
cwd: docsRoot,
|
|
77
|
-
stdio: 'pipe',
|
|
78
|
-
shell: true
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
jekyll.stdout.on('data', (data) => {
|
|
82
|
-
const output = data.toString()
|
|
83
|
-
process.stdout.write(output)
|
|
84
|
-
|
|
85
|
-
if (output.includes('Server running') || output.includes('Server address:')) {
|
|
86
|
-
setTimeout(() => openBrowser(LOCAL_URL), 1000)
|
|
87
|
-
}
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
jekyll.stderr.on('data', (data) => {
|
|
91
|
-
process.stderr.write(data)
|
|
92
|
-
})
|
|
93
|
-
|
|
94
|
-
process.on('SIGINT', () => {
|
|
95
|
-
console.log('\nShutting down Jekyll server...')
|
|
96
|
-
jekyll.kill()
|
|
97
|
-
process.exit(0)
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
jekyll.on('error', (err) => {
|
|
101
|
-
console.error('Jekyll server error:', err)
|
|
102
|
-
process.exit(1)
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
jekyll.on('exit', (code) => {
|
|
106
|
-
if (code !== 0 && code !== null) {
|
|
107
|
-
console.error(`Jekyll server exited with code ${code}`)
|
|
108
|
-
process.exit(code)
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
setTimeout(() => openBrowser(LOCAL_URL), BROWSER_DELAY_MS)
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
async function main () {
|
|
116
|
-
const mode = parseMode(process.argv.slice(2))
|
|
117
|
-
|
|
118
|
-
try {
|
|
119
|
-
await ensureBundlerDependencies()
|
|
120
|
-
|
|
121
|
-
if (mode === 'serve') {
|
|
122
|
-
await serveDocs()
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
await buildDocs()
|
|
127
|
-
} catch (error) {
|
|
128
|
-
console.error('Error:', error.message)
|
|
129
|
-
process.exit(1)
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
main()
|
package/test-debug.js
DELETED
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { createBasicApi } from './tests/fixtures/api-configs.js'
|
|
2
|
-
import { cleanTables, createJsonApiDocument } from './tests/helpers/test-utils.js'
|
|
3
|
-
import knex from 'knex'
|
|
4
|
-
|
|
5
|
-
const dbConfig = {
|
|
6
|
-
client: 'better-sqlite3',
|
|
7
|
-
connection: { filename: ':memory:' },
|
|
8
|
-
useNullAsDefault: true
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
const db = knex(dbConfig)
|
|
12
|
-
|
|
13
|
-
async function test () {
|
|
14
|
-
const api = await createBasicApi(db)
|
|
15
|
-
|
|
16
|
-
// Create test data
|
|
17
|
-
await cleanTables(db, ['basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'])
|
|
18
|
-
|
|
19
|
-
// Create a country first
|
|
20
|
-
const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
|
|
21
|
-
const country = await api.resources.countries.post({ inputRecord: countryDoc })
|
|
22
|
-
|
|
23
|
-
// Create an author
|
|
24
|
-
const authorDoc = createJsonApiDocument('authors', { name: 'Test Author' })
|
|
25
|
-
const author = await api.resources.authors.post({ inputRecord: authorDoc })
|
|
26
|
-
|
|
27
|
-
// Create a book with proper relationships
|
|
28
|
-
const bookDoc = {
|
|
29
|
-
data: {
|
|
30
|
-
type: 'books',
|
|
31
|
-
attributes: { title: 'Test Book' },
|
|
32
|
-
relationships: {
|
|
33
|
-
country: { data: { type: 'countries', id: String(country.data.id) } }
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
const book = await api.resources.books.post({ inputRecord: bookDoc })
|
|
38
|
-
|
|
39
|
-
// Create the many-to-many relationship
|
|
40
|
-
await db('basic_book_authors').insert({
|
|
41
|
-
author_id: author.data.id,
|
|
42
|
-
book_id: book.data.id
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
// Now try to query authors with books included
|
|
46
|
-
console.log('Querying authors with books included...')
|
|
47
|
-
try {
|
|
48
|
-
const result = await api.resources.authors.query({
|
|
49
|
-
queryParams: { include: ['books'] }
|
|
50
|
-
})
|
|
51
|
-
console.log('Success!', JSON.stringify(result, null, 2))
|
|
52
|
-
} catch (error) {
|
|
53
|
-
console.error('Error:', error.message)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
await db.destroy()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
test().catch(console.error)
|