json-rest-api 1.0.22 → 1.0.24
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.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +259 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
1 export const MultiHomePlugin = {
|
|
2
|
-
2 name: 'multihome',
|
|
3
|
-
3 dependencies: ['rest-api'],
|
|
4
|
-
4
|
|
5
|
-
5 install ({ api, addHook, vars, helpers, log, scopes, pluginOptions }) {
|
|
6
|
-
6 if (!api.knex?.instance) {
|
|
7
|
-
7 throw new Error('Multihome plugin requires a storage plugin with knex support (rest-api-knex or rest-api-anyapi-knex)')
|
|
8
|
-
8 }
|
|
9
|
-
9
|
|
10
|
-
10 // Get configuration - hooked-api namespaces options by plugin name
|
|
11
|
-
11 const multihomeOptions = pluginOptions || {}
|
|
12
|
-
12
|
|
13
|
-
13 // Store configuration in vars (data only)
|
|
14
|
-
14 vars.multihome = {
|
|
15
|
-
15 field: multihomeOptions.field || 'multihome_id',
|
|
16
|
-
16 excludeResources: multihomeOptions.excludeResources || ['system_migrations', 'system_logs'],
|
|
17
|
-
17 requireAuth: multihomeOptions.requireAuth !== undefined ? multihomeOptions.requireAuth : true,
|
|
18
|
-
18 allowMissing: multihomeOptions.allowMissing || false
|
|
19
|
-
19 }
|
|
20
|
-
20
|
|
21
|
-
21 const resolveStorageAdapter = (scopeName, hookContext = {}) => {
|
|
22
|
-
22 if (!scopeName) return null
|
|
23
|
-
23 if (hookContext.storageAdapter) return hookContext.storageAdapter
|
|
24
|
-
24 if (hookContext.knexQuery?.storageAdapter) return hookContext.knexQuery.storageAdapter
|
|
25
|
-
25 if (helpers.getStorageAdapter) {
|
|
26
|
-
26 return helpers.getStorageAdapter(scopeName)
|
|
27
|
-
27 }
|
|
28
|
-
28 return null
|
|
29
|
-
29 }
|
|
30
|
-
30
|
|
31
|
-
31 const translateColumnReference = ({ field, tableName, scopeName, hookContext }) => {
|
|
32
|
-
32 const storageAdapter = resolveStorageAdapter(scopeName, hookContext)
|
|
33
|
-
33 const translated = storageAdapter?.translateColumn?.(field) ?? field
|
|
34
|
-
34 if (tableName) {
|
|
35
|
-
35 return `${tableName}.${translated}`
|
|
36
|
-
36 }
|
|
37
|
-
37 return translated
|
|
38
|
-
38 }
|
|
39
|
-
39
|
|
40
|
-
40 const translateFilterValue = ({ field, value, scopeName, hookContext }) => {
|
|
41
|
-
41 const storageAdapter = resolveStorageAdapter(scopeName, hookContext)
|
|
42
|
-
42 if (!storageAdapter?.translateFilterValue) return value
|
|
43
|
-
43 return storageAdapter.translateFilterValue(field, value)
|
|
44
|
-
44 }
|
|
45
|
-
45
|
|
46
|
-
46 // Default extractor function
|
|
47
|
-
47 function defaultSubdomainExtractor (request) {
|
|
48
|
-
48 // Extract from subdomain (e.g., 'mobily' from 'mobily.app.com')
|
|
49
|
-
49 const host = request.headers?.host || request.hostname || ''
|
|
50
|
-
50 const subdomain = host.split('.')[0]
|
|
51
|
-
51
|
|
52
|
-
52 // Don't use 'www' or 'api' as tenant IDs
|
|
53
|
-
53 if (subdomain && !['www', 'api', 'app'].includes(subdomain)) {
|
|
54
|
-
54 return subdomain
|
|
55
|
-
55 }
|
|
56
|
-
56
|
|
57
|
-
57 // Fallback to header
|
|
58
|
-
58 return request.headers?.['x-multihome-id'] || null
|
|
59
|
-
59 }
|
|
60
|
-
60
|
|
61
|
-
61 // Store extractor function in helpers
|
|
62
|
-
62 helpers.extractMultihomeId = multihomeOptions.extractor || defaultSubdomainExtractor
|
|
63
|
-
63
|
|
64
|
-
64 // Hook into transport layer to extract multihome_id
|
|
65
|
-
65 addHook('transport:request', 'extract-multihome-id', {}, async ({ context, request, helpers }) => {
|
|
66
|
-
66 // Extract multihome_id using configured extractor
|
|
67
|
-
67 const multihomeId = helpers.extractMultihomeId(request)
|
|
68
|
-
68
|
|
69
|
-
69 if (multihomeId) {
|
|
70
|
-
70 // Store in auth context (even if no user auth)
|
|
71
|
-
71 context.auth = context.auth || {}
|
|
72
|
-
72 context.auth.multihome_id = multihomeId
|
|
73
|
-
73
|
|
74
|
-
74 log.debug('Multihome ID extracted', {
|
|
75
|
-
75 multihome_id: multihomeId,
|
|
76
|
-
76 source: request.headers?.host
|
|
77
|
-
77 })
|
|
78
|
-
78 } else if (vars.multihome.requireAuth) {
|
|
79
|
-
79 log.warn('No multihome ID found in request', {
|
|
80
|
-
80 host: request.headers?.host,
|
|
81
|
-
81 headers: Object.keys(request.headers || {})
|
|
82
|
-
82 })
|
|
83
|
-
83 }
|
|
84
|
-
84 })
|
|
85
|
-
85
|
|
86
|
-
86 // Validate that resources have multihome field when added
|
|
87
|
-
87 addHook('scope:added', 'validate-multihome-field', {}, ({ context, vars }) => {
|
|
88
|
-
88 const { scopeName, scopeOptions } = context
|
|
89
|
-
89
|
|
90
|
-
90 // Skip excluded resources
|
|
91
|
-
91 if (vars.multihome.excludeResources.includes(scopeName)) {
|
|
92
|
-
92 log.debug(`Resource ${scopeName} excluded from multihome validation`)
|
|
93
|
-
93 return
|
|
94
|
-
94 }
|
|
95
|
-
95
|
|
96
|
-
96 // Check if schema has multihome field
|
|
97
|
-
97 const schema = scopeOptions.schema
|
|
98
|
-
98 if (schema && !schema[vars.multihome.field]) {
|
|
99
|
-
99 if (vars.multihome.allowMissing) {
|
|
100
|
-
100 log.warn(`Resource ${scopeName} missing ${vars.multihome.field} field - multihome filtering disabled for this
|
|
101
|
-
101 resource`)
|
|
102
|
-
102 } else {
|
|
103
|
-
103 throw new Error(
|
|
104
|
-
104 `Resource '${scopeName}' must have '${vars.multihome.field}' field in schema for multi-tenancy`
|
|
105
|
-
105 )
|
|
106
|
-
106 }
|
|
107
|
-
107 }
|
|
108
|
-
108 })
|
|
109
|
-
109
|
|
110
|
-
110 // Main filtering hook - adds WHERE clause to all queries
|
|
111
|
-
111 log.debug('Registering multihome filter hook')
|
|
112
|
-
112 addHook('knexQueryFiltering', 'multihome-filter', {}, async ({ context, vars }) => {
|
|
113
|
-
113 // Get query info from context
|
|
114
|
-
114 const { query, tableName, scopeName } = context.knexQuery
|
|
115
|
-
115
|
|
116
|
-
116 // Skip excluded resources first
|
|
117
|
-
117 if (vars.multihome.excludeResources.includes(scopeName)) {
|
|
118
|
-
118 log.trace(`Skipping multihome filter for excluded resource: ${scopeName}`)
|
|
119
|
-
119 return
|
|
120
|
-
120 }
|
|
121
|
-
121
|
|
122
|
-
122 // Skip if no multihome context
|
|
123
|
-
123 if (!context.auth?.multihome_id) {
|
|
124
|
-
124 if (vars.multihome.requireAuth) {
|
|
125
|
-
125 throw new Error('No multihome context available - cannot execute query')
|
|
126
|
-
126 }
|
|
127
|
-
127 return
|
|
128
|
-
128 }
|
|
129
|
-
129
|
|
130
|
-
130 log.debug('Applying multihome tenant filter', {
|
|
131
|
-
131 scopeName,
|
|
132
|
-
132 tableName,
|
|
133
|
-
133 tenant: context.auth.multihome_id,
|
|
134
|
-
134 })
|
|
135
|
-
135
|
|
136
|
-
136 // Check if this resource has multihome field
|
|
137
|
-
137 const scope = scopes[scopeName]
|
|
138
|
-
138 const hasMultihomeField = scope?.vars?.schemaInfo?.schemaStructure?.[vars.multihome.field]
|
|
139
|
-
139
|
|
140
|
-
140 if (!hasMultihomeField) {
|
|
141
|
-
141 if (vars.multihome.allowMissing) {
|
|
142
|
-
142 log.trace(`Resource ${scopeName} has no ${vars.multihome.field} field - skipping filter`)
|
|
143
|
-
143 return
|
|
144
|
-
144 } else {
|
|
145
|
-
145 throw new Error(`Resource ${scopeName} missing required ${vars.multihome.field} field`)
|
|
146
|
-
146 }
|
|
147
|
-
147 }
|
|
148
|
-
148
|
|
149
|
-
149 const storageAdapter = resolveStorageAdapter(scopeName, context)
|
|
150
|
-
150 if (storageAdapter && !context.storageAdapter) {
|
|
151
|
-
151 context.storageAdapter = storageAdapter
|
|
152
|
-
152 }
|
|
153
|
-
153
|
|
154
|
-
154 const columnRef = translateColumnReference({
|
|
155
|
-
155 field: vars.multihome.field,
|
|
156
|
-
156 tableName,
|
|
157
|
-
157 scopeName,
|
|
158
|
-
158 hookContext: context,
|
|
159
|
-
159 })
|
|
160
|
-
160
|
|
161
|
-
161 const tenantValue = translateFilterValue({
|
|
162
|
-
162 field: vars.multihome.field,
|
|
163
|
-
163 value: context.auth.multihome_id,
|
|
164
|
-
164 scopeName,
|
|
165
|
-
165 hookContext: context,
|
|
166
|
-
166 })
|
|
167
|
-
167
|
|
168
|
-
168 if (tenantValue === null) {
|
|
169
|
-
169 query.whereNull(columnRef)
|
|
170
|
-
170 } else {
|
|
171
|
-
171 query.where(columnRef, tenantValue)
|
|
172
|
-
172 }
|
|
173
|
-
173
|
|
174
|
-
174 log.trace('Added multihome filter', {
|
|
175
|
-
175 scopeName,
|
|
176
|
-
176 tableName,
|
|
177
|
-
177 multihome_id: context.auth.multihome_id
|
|
178
|
-
178 })
|
|
179
|
-
179 })
|
|
180
|
-
180
|
|
181
|
-
181 // Set multihome_id on new records
|
|
182
|
-
182 addHook('beforeSchemaValidate', 'set-multihome-id', {}, async ({ context, scopeName, vars }) => {
|
|
183
|
-
183 // Skip excluded resources first
|
|
184
|
-
184 if (vars.multihome.excludeResources.includes(scopeName)) {
|
|
185
|
-
185 return
|
|
186
|
-
186 }
|
|
187
|
-
187
|
|
188
|
-
188 // Skip if no multihome context
|
|
189
|
-
189 if (!context.auth?.multihome_id) {
|
|
190
|
-
190 if (vars.multihome.requireAuth) {
|
|
191
|
-
191 throw new Error('Cannot create record without multihome context')
|
|
192
|
-
192 }
|
|
193
|
-
193 return
|
|
194
|
-
194 }
|
|
195
|
-
195
|
|
196
|
-
196 // Check if this resource has multihome field
|
|
197
|
-
197 const scope = scopes[scopeName]
|
|
198
|
-
198 const hasMultihomeField = scope?.vars?.schemaInfo?.schemaStructure?.[vars.multihome.field]
|
|
199
|
-
199
|
|
200
|
-
200 if (!hasMultihomeField) {
|
|
201
|
-
201 if (!vars.multihome.allowMissing) {
|
|
202
|
-
202 throw new Error(`Resource ${scopeName} missing required ${vars.multihome.field} field`)
|
|
203
|
-
203 }
|
|
204
|
-
204 return
|
|
205
|
-
205 }
|
|
206
|
-
206
|
|
207
|
-
207 // For POST, always set multihome_id
|
|
208
|
-
208 if (context.method === 'post') {
|
|
209
|
-
209 context.inputRecord.data.attributes = context.inputRecord.data.attributes || {}
|
|
210
|
-
210 context.inputRecord.data.attributes[vars.multihome.field] = context.auth.multihome_id
|
|
211
|
-
211
|
|
212
|
-
212 log.debug('Set multihome_id on new record', {
|
|
213
|
-
213 scopeName,
|
|
214
|
-
214 multihome_id: context.auth.multihome_id
|
|
215
|
-
215 })
|
|
216
|
-
216 }
|
|
217
|
-
217
|
|
218
|
-
218 // For PUT/PATCH, validate multihome_id if provided
|
|
219
|
-
219 if ((context.method === 'put' || context.method === 'patch') &&
|
|
220
|
-
220 context.inputRecord.data.attributes?.[vars.multihome.field]) {
|
|
221
|
-
221 const providedId = context.inputRecord.data.attributes[vars.multihome.field]
|
|
222
|
-
222 if (providedId !== context.auth.multihome_id) {
|
|
223
|
-
223 throw new Error(
|
|
224
|
-
224 `Cannot set ${vars.multihome.field} to '${providedId}' - must match current context '${context.auth.multihome_id}'`
|
|
225
|
-
225 )
|
|
226
|
-
226 }
|
|
227
|
-
227 }
|
|
228
|
-
228 })
|
|
229
|
-
229
|
|
230
|
-
230 // Add checkPermissions hook to enforce tenant isolation
|
|
231
|
-
231 // This is the single source of truth for single-record access control
|
|
232
|
-
232 addHook('checkPermissions', 'multihome-check-permissions', {}, async ({ context, scopeName }) => {
|
|
233
|
-
233 // Extract the needed values from originalContext
|
|
234
|
-
234 const auth = context.originalContext?.auth
|
|
235
|
-
235 const minimalRecord = context.originalContext?.minimalRecord
|
|
236
|
-
236 const id = context.originalContext?.id
|
|
237
|
-
237
|
|
238
|
-
238 // Skip excluded resources
|
|
239
|
-
239 if (vars.multihome.excludeResources.includes(scopeName)) {
|
|
240
|
-
240 return
|
|
241
|
-
241 }
|
|
242
|
-
242
|
|
243
|
-
243 // Skip if no multihome context
|
|
244
|
-
244 if (!auth?.multihome_id) {
|
|
245
|
-
245 if (vars.multihome.requireAuth) {
|
|
246
|
-
246 throw new Error('No multihome context available')
|
|
247
|
-
247 }
|
|
248
|
-
248 return
|
|
249
|
-
249 }
|
|
250
|
-
250
|
|
251
|
-
251 // Get the scope to check if it has multihome field
|
|
252
|
-
252 const scope = scopes[scopeName]
|
|
253
|
-
253 const hasMultihomeField = scope?.vars?.schemaInfo?.schemaStructure?.[vars.multihome.field]
|
|
254
|
-
254
|
|
255
|
-
255 if (!hasMultihomeField) {
|
|
256
|
-
256 return // Resource doesn't support multihome
|
|
257
|
-
257 }
|
|
258
|
-
258
|
|
259
|
-
259 // For operations on existing records, verify tenant ownership
|
|
260
|
-
260 if (minimalRecord) {
|
|
261
|
-
261 // minimalRecord is in JSON:API format, so tenant_id is in attributes
|
|
262
|
-
262 const recordTenant = minimalRecord.attributes?.[vars.multihome.field]
|
|
263
|
-
263 const userTenant = auth.multihome_id
|
|
264
|
-
264
|
|
265
|
-
265 if (recordTenant !== userTenant) {
|
|
266
|
-
266 log.error('Multihome permission violation', {
|
|
267
|
-
267 scopeName,
|
|
268
|
-
268 recordId: id,
|
|
269
|
-
269 recordTenant,
|
|
270
|
-
270 userTenant,
|
|
271
|
-
271 method: context.method
|
|
272
|
-
272 })
|
|
273
|
-
273
|
|
274
|
-
274 // Return 404 for GET to prevent information leakage
|
|
275
|
-
275 // Return 403 for other operations
|
|
276
|
-
276 if (context.method === 'get') {
|
|
277
|
-
277 const error = new Error('Resource not found')
|
|
278
|
-
278 error.code = 'REST_API_RESOURCE'
|
|
279
|
-
279 throw error
|
|
280
|
-
280 } else {
|
|
281
|
-
281 const error = new Error('Access denied - insufficient permissions')
|
|
282
|
-
282 error.code = 'REST_API_FORBIDDEN'
|
|
283
|
-
283 throw error
|
|
284
|
-
284 }
|
|
285
|
-
285 }
|
|
286
|
-
286 }
|
|
287
|
-
287 })
|
|
288
|
-
288
|
|
289
|
-
289 // Note: PUT, PATCH, and DELETE operations will fail naturally if the record
|
|
290
|
-
290 // doesn't exist due to tenant filtering in the GET operation that precedes them
|
|
291
|
-
291
|
|
292
|
-
292 // Add API method to get current multihome context
|
|
293
|
-
293 api.multihome = {
|
|
294
|
-
294 getCurrentTenant: () => {
|
|
295
|
-
295 // This would need to be called within a request context
|
|
296
|
-
296 // Real implementation would need access to current request context
|
|
297
|
-
297 return null
|
|
298
|
-
298 },
|
|
299
|
-
299
|
|
300
|
-
300 // Runtime configuration helper (for debugging/testing)
|
|
301
|
-
301 getConfig: () => ({
|
|
302
|
-
302 ...vars.multihome,
|
|
303
|
-
303 hasCustomExtractor: helpers.extractMultihomeId !== defaultSubdomainExtractor
|
|
304
|
-
304 })
|
|
305
|
-
305 }
|
|
306
|
-
306
|
|
307
|
-
307 log.info('MultiHome plugin installed', {
|
|
308
|
-
308 field: vars.multihome.field,
|
|
309
|
-
309 excludedResources: vars.multihome.excludeResources,
|
|
310
|
-
310 requireAuth: vars.multihome.requireAuth,
|
|
311
|
-
311 hasCustomExtractor: helpers.extractMultihomeId !== defaultSubdomainExtractor
|
|
312
|
-
312 })
|
|
313
|
-
313 }
|
|
314
|
-
314 }
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
1 import { compileSchemas } from '../lib/querying-writing/compile-schemas.js'
|
|
2
|
-
2
|
|
3
|
-
3 export default async function compileResourceSchemas ({ context, scopes, runHooks }) {
|
|
4
|
-
4 const scope = scopes[context.scopeName]
|
|
5
|
-
5 // Pass scopeOptions and vars from context since scope object structure is different
|
|
6
|
-
6 return compileSchemas({ ...scope, scopeOptions: context.scopeOptions, vars: context.vars }, { context: { scopeName: context.scopeName }, runHooks })
|
|
7
|
-
7 }
|
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
1 import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
|
|
2
|
-
2
|
|
3
|
-
3 export default async function registerRelationshipRoutes ({ context, api, log }) {
|
|
4
|
-
4 const { scopeName } = context
|
|
5
|
-
5 const basePath = api.scopes[scopeName].vars.transport?.mountPath || ''
|
|
6
|
-
6 const scopePath = `${basePath}/${scopeName}`
|
|
7
|
-
7
|
|
8
|
-
8 // Helper to create route handlers
|
|
9
|
-
9 const createRouteHandler = (methodName) => {
|
|
10
|
-
10 return async ({ params, body, queryString }) => {
|
|
11
|
-
11 const scope = api.scopes[scopeName]
|
|
12
|
-
12
|
|
13
|
-
13 const methodParams = {
|
|
14
|
-
14 id: params.id,
|
|
15
|
-
15 relationshipName: params.relationshipName,
|
|
16
|
-
16 isTransport: true
|
|
17
|
-
17 }
|
|
18
|
-
18
|
|
19
|
-
19 // Add query params for getRelated
|
|
20
|
-
20 if (methodName === 'getRelated' && queryString) {
|
|
21
|
-
21 methodParams.queryParams = parseJsonApiQuery(queryString)
|
|
22
|
-
22 }
|
|
23
|
-
23
|
|
24
|
-
24 // Add body data for write operations
|
|
25
|
-
25 if (body && body.data !== undefined) {
|
|
26
|
-
26 methodParams.relationshipData = body.data
|
|
27
|
-
27 }
|
|
28
|
-
28
|
|
29
|
-
29 return await scope[methodName](methodParams)
|
|
30
|
-
30 }
|
|
31
|
-
31 }
|
|
32
|
-
32
|
|
33
|
-
33 // Register relationship routes
|
|
34
|
-
34
|
|
35
|
-
35 // GET /api/{scope}/{id}/relationships/{relationshipName}
|
|
36
|
-
36 await api.addRoute({
|
|
37
|
-
37 method: 'GET',
|
|
38
|
-
38 path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
39
|
-
39 handler: createRouteHandler('getRelationship')
|
|
40
|
-
40 })
|
|
41
|
-
41
|
|
42
|
-
42 // GET /api/{scope}/{id}/{relationshipName}
|
|
43
|
-
43 await api.addRoute({
|
|
44
|
-
44 method: 'GET',
|
|
45
|
-
45 path: `${scopePath}/:id/:relationshipName`,
|
|
46
|
-
46 handler: createRouteHandler('getRelated')
|
|
47
|
-
47 })
|
|
48
|
-
48
|
|
49
|
-
49 // POST /api/{scope}/{id}/relationships/{relationshipName}
|
|
50
|
-
50 await api.addRoute({
|
|
51
|
-
51 method: 'POST',
|
|
52
|
-
52 path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
53
|
-
53 handler: createRouteHandler('postRelationship')
|
|
54
|
-
54 })
|
|
55
|
-
55
|
|
56
|
-
56 // PATCH /api/{scope}/{id}/relationships/{relationshipName}
|
|
57
|
-
57 await api.addRoute({
|
|
58
|
-
58 method: 'PATCH',
|
|
59
|
-
59 path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
60
|
-
60 handler: createRouteHandler('patchRelationship')
|
|
61
|
-
61 })
|
|
62
|
-
62
|
|
63
|
-
63 // DELETE /api/{scope}/{id}/relationships/{relationshipName}
|
|
64
|
-
64 await api.addRoute({
|
|
65
|
-
65 method: 'DELETE',
|
|
66
|
-
66 path: `${scopePath}/:id/relationships/:relationshipName`,
|
|
67
|
-
67 handler: createRouteHandler('deleteRelationship')
|
|
68
|
-
68 })
|
|
69
|
-
69
|
|
70
|
-
70 log.trace(`Registered relationship routes for scope: ${scopeName}`)
|
|
71
|
-
71 }
|
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
1 import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
|
-
2 import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
|
|
3
|
-
3
|
|
4
|
-
4 export default async function registerScopeRoutes ({ context, api, vars, log }) {
|
|
5
|
-
5 const { scopeName } = context
|
|
6
|
-
6 const basePath = vars.transport?.mountPath || ''
|
|
7
|
-
7
|
|
8
|
-
8 // Helper to create route handlers
|
|
9
|
-
9 const createRouteHandler = (scopeName, methodName) => {
|
|
10
|
-
10 return async ({ queryString, headers, params, body, context }) => {
|
|
11
|
-
11 const scope = api.scopes[scopeName]
|
|
12
|
-
12 if (!scope) {
|
|
13
|
-
13 throw new RestApiResourceError(
|
|
14
|
-
14 `Scope '${scopeName}' not found`,
|
|
15
|
-
15 {
|
|
16
|
-
16 subtype: 'not_found',
|
|
17
|
-
17 resourceType: 'scope',
|
|
18
|
-
18 resourceId: scopeName
|
|
19
|
-
19 }
|
|
20
|
-
20 )
|
|
21
|
-
21 }
|
|
22
|
-
22
|
|
23
|
-
23 // Build parameters for the scope method
|
|
24
|
-
24 const methodParams = {}
|
|
25
|
-
25
|
|
26
|
-
26 // Add ID for single-resource operations
|
|
27
|
-
27 if (['get', 'put', 'patch', 'delete'].includes(methodName)) {
|
|
28
|
-
28 methodParams.id = params.id
|
|
29
|
-
29 }
|
|
30
|
-
30
|
|
31
|
-
31 // Parse query parameters for read operations
|
|
32
|
-
32 if (['query', 'get'].includes(methodName)) {
|
|
33
|
-
33 methodParams.queryParams = parseJsonApiQuery(queryString)
|
|
34
|
-
34 methodParams.isTransport = true
|
|
35
|
-
35 }
|
|
36
|
-
36
|
|
37
|
-
37 // Add body for write operations
|
|
38
|
-
38 if (['post', 'put', 'patch'].includes(methodName)) {
|
|
39
|
-
39 methodParams.inputRecord = body
|
|
40
|
-
40 methodParams.isTransport = true
|
|
41
|
-
41
|
|
42
|
-
42 // Add query params for includes/fields on write operations
|
|
43
|
-
43 if (queryString) {
|
|
44
|
-
44 methodParams.queryParams = parseJsonApiQuery(queryString)
|
|
45
|
-
45 }
|
|
46
|
-
46 }
|
|
47
|
-
47
|
|
48
|
-
48 // Call the scope method
|
|
49
|
-
49 const result = await scope[methodName](methodParams, context)
|
|
50
|
-
50
|
|
51
|
-
51 // Return the result (transport plugin handles response formatting)
|
|
52
|
-
52 return result
|
|
53
|
-
53 }
|
|
54
|
-
54 }
|
|
55
|
-
55
|
|
56
|
-
56 const scopePath = `${basePath}/${scopeName}`
|
|
57
|
-
57
|
|
58
|
-
58 // Register routes for each HTTP method
|
|
59
|
-
59 // GET /api/{scope} - Query collection
|
|
60
|
-
60 await api.addRoute({
|
|
61
|
-
61 method: 'GET',
|
|
62
|
-
62 path: scopePath,
|
|
63
|
-
63 handler: createRouteHandler(scopeName, 'query')
|
|
64
|
-
64 })
|
|
65
|
-
65
|
|
66
|
-
66 // GET /api/{scope}/{id} - Get single resource
|
|
67
|
-
67 await api.addRoute({
|
|
68
|
-
68 method: 'GET',
|
|
69
|
-
69 path: `${scopePath}/:id`,
|
|
70
|
-
70 handler: createRouteHandler(scopeName, 'get')
|
|
71
|
-
71 })
|
|
72
|
-
72
|
|
73
|
-
73 // POST /api/{scope} - Create resource
|
|
74
|
-
74 await api.addRoute({
|
|
75
|
-
75 method: 'POST',
|
|
76
|
-
76 path: scopePath,
|
|
77
|
-
77 handler: createRouteHandler(scopeName, 'post')
|
|
78
|
-
78 })
|
|
79
|
-
79
|
|
80
|
-
80 // PUT /api/{scope}/{id} - Replace resource
|
|
81
|
-
81 await api.addRoute({
|
|
82
|
-
82 method: 'PUT',
|
|
83
|
-
83 path: `${scopePath}/:id`,
|
|
84
|
-
84 handler: createRouteHandler(scopeName, 'put')
|
|
85
|
-
85 })
|
|
86
|
-
86
|
|
87
|
-
87 // PATCH /api/{scope}/{id} - Update resource
|
|
88
|
-
88 await api.addRoute({
|
|
89
|
-
89 method: 'PATCH',
|
|
90
|
-
90 path: `${scopePath}/:id`,
|
|
91
|
-
91 handler: createRouteHandler(scopeName, 'patch')
|
|
92
|
-
92 })
|
|
93
|
-
93
|
|
94
|
-
94 // DELETE /api/{scope}/{id} - Delete resource
|
|
95
|
-
95 await api.addRoute({
|
|
96
|
-
96 method: 'DELETE',
|
|
97
|
-
97 path: `${scopePath}/:id`,
|
|
98
|
-
98 handler: createRouteHandler(scopeName, 'delete')
|
|
99
|
-
99 })
|
|
100
|
-
100
|
|
101
|
-
101 log.info(`Routes registered for scope '${scopeName}'`)
|
|
102
|
-
102 }
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
1 export default async function turnScopeInitIntoVars ({ context, scopes, vars: apiVars }) {
|
|
2
|
-
2 // Refer to the scope's vars
|
|
3
|
-
3 const scope = scopes[context.scopeName]
|
|
4
|
-
4 const scopeOptions = scope?.scopeOptions || context.scopeOptions || {}
|
|
5
|
-
5 const vars = scope?.vars || apiVars
|
|
6
|
-
6
|
|
7
|
-
7 // The scope-specific ones
|
|
8
|
-
8 vars.sortableFields = scopeOptions.sortableFields || []
|
|
9
|
-
9 vars.defaultSort = scopeOptions.defaultSort || null
|
|
10
|
-
10
|
|
11
|
-
11 // The general ones that are also set at api level, but overrideable
|
|
12
|
-
12 if (typeof scopeOptions.queryDefaultLimit !== 'undefined') vars.queryDefaultLimit = scopeOptions.queryDefaultLimit
|
|
13
|
-
13 if (typeof scopeOptions.queryMaxLimit !== 'undefined') vars.queryMaxLimit = scopeOptions.queryMaxLimit
|
|
14
|
-
14 if (typeof scopeOptions.includeDepthLimit !== 'undefined') vars.includeDepthLimit = scopeOptions.includeDepthLimit
|
|
15
|
-
15 if (typeof scopeOptions.enablePaginationCounts !== 'undefined') vars.enablePaginationCounts = scopeOptions.enablePaginationCounts
|
|
16
|
-
16
|
|
17
|
-
17 // Set simplified settings as scope vars
|
|
18
|
-
18 if (typeof scopeOptions.simplifiedApi !== 'undefined') vars.simplifiedApi = scopeOptions.simplifiedApi
|
|
19
|
-
19 if (typeof scopeOptions.simplifiedTransport !== 'undefined') vars.simplifiedTransport = scopeOptions.simplifiedTransport
|
|
20
|
-
20
|
|
21
|
-
21 // Set returnRecord settings as scope vars
|
|
22
|
-
22 if (typeof scopeOptions.returnRecordApi !== 'undefined') vars.returnRecordApi = scopeOptions.returnRecordApi
|
|
23
|
-
23 if (typeof scopeOptions.returnRecordTransport !== 'undefined') vars.returnRecordTransport = scopeOptions.returnRecordTransport
|
|
24
|
-
24
|
|
25
|
-
25 // Set idProperty as scope var
|
|
26
|
-
26 if (typeof scopeOptions.idProperty !== 'undefined') vars.idProperty = scopeOptions.idProperty
|
|
27
|
-
27
|
|
28
|
-
28 // Add validation for query limits
|
|
29
|
-
29 if (vars.queryDefaultLimit && vars.queryMaxLimit) {
|
|
30
|
-
30 if (vars.queryDefaultLimit > vars.queryMaxLimit) {
|
|
31
|
-
31 throw new Error(
|
|
32
|
-
32 `Invalid scope '${context.scopeName}' configuration: ` +
|
|
33
|
-
33 `queryDefaultLimit (${vars.queryDefaultLimit}) cannot exceed queryMaxLimit (${vars.queryMaxLimit})`
|
|
34
|
-
34 )
|
|
35
|
-
35 }
|
|
36
|
-
36 }
|
|
37
|
-
37
|
|
38
|
-
38 // Validate relationship include limits at scope creation time
|
|
39
|
-
39 Object.entries(scopeOptions.relationships || {}).forEach(([relName, relDef]) => {
|
|
40
|
-
40 if (relDef.include?.limit && vars.queryMaxLimit) {
|
|
41
|
-
41 if (relDef.include.limit > vars.queryMaxLimit) {
|
|
42
|
-
42 throw new Error(
|
|
43
|
-
43 `Invalid relationship '${context.scopeName}.${relName}' configuration: ` +
|
|
44
|
-
44 `include.limit (${relDef.include.limit}) cannot exceed queryMaxLimit (${vars.queryMaxLimit})`
|
|
45
|
-
45 )
|
|
46
|
-
46 }
|
|
47
|
-
47 }
|
|
48
|
-
48 })
|
|
49
|
-
49 }
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
1 export default async function validateIncludeConfigurations ({ context, scopes, log }) {
|
|
2
|
-
2 const { scopeName } = context
|
|
3
|
-
3 const scope = scopes[scopeName]
|
|
4
|
-
4 const relationships = scope.vars.schemaInfo?.schemaRelationships
|
|
5
|
-
5
|
|
6
|
-
6 if (!relationships) return
|
|
7
|
-
7
|
|
8
|
-
8 // Check each relationship for include configuration
|
|
9
|
-
9 for (const [relName, relDef] of Object.entries(relationships)) {
|
|
10
|
-
10 if (relDef.include?.strategy === 'window') {
|
|
11
|
-
11 // This relationship requires window functions
|
|
12
|
-
12 // We'll validate this at query time since the database might not be connected yet
|
|
13
|
-
13 log.debug(`Relationship ${scopeName}.${relName} configured for window function includes`)
|
|
14
|
-
14 }
|
|
15
|
-
15
|
|
16
|
-
16 // Validate include configuration
|
|
17
|
-
17 if (relDef.include?.limit) {
|
|
18
|
-
18 if (typeof relDef.include.limit !== 'number') {
|
|
19
|
-
19 throw new Error(
|
|
20
|
-
20 `Invalid include limit for ${scopeName}.${relName}: limit must be a number`
|
|
21
|
-
21 )
|
|
22
|
-
22 }
|
|
23
|
-
23 // Check against queryMaxLimit if available
|
|
24
|
-
24 const maxLimit = scope.vars?.queryMaxLimit
|
|
25
|
-
25 if (maxLimit && relDef.include.limit > maxLimit) {
|
|
26
|
-
26 throw new Error(
|
|
27
|
-
27 `Invalid include limit for ${scopeName}.${relName}: ` +
|
|
28
|
-
28 `limit (${relDef.include.limit}) exceeds queryMaxLimit (${maxLimit})`
|
|
29
|
-
29 )
|
|
30
|
-
30 }
|
|
31
|
-
31 }
|
|
32
|
-
32
|
|
33
|
-
33 if (relDef.include?.orderBy && !Array.isArray(relDef.include.orderBy)) {
|
|
34
|
-
34 throw new Error(
|
|
35
|
-
35 `Invalid include orderBy for ${scopeName}.${relName}: orderBy must be an array`
|
|
36
|
-
36 )
|
|
37
|
-
37 }
|
|
38
|
-
38 }
|
|
39
|
-
39 }
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
1 import { ValidationError } from 'hooked-api'
|
|
2
|
-
2
|
|
3
|
-
3 /**
|
|
4
|
-
4 * addRoute
|
|
5
|
-
5 * Registers a new route with the transport layer
|
|
6
|
-
6 */
|
|
7
|
-
7 export default async ({ params, context, runHooks }) => {
|
|
8
|
-
8 const { method, path, handler } = params
|
|
9
|
-
9
|
|
10
|
-
10 // Validate route configuration
|
|
11
|
-
11 if (!method || !path || !handler) {
|
|
12
|
-
12 throw new ValidationError('Route requires method, path, and handler')
|
|
13
|
-
13 }
|
|
14
|
-
14
|
|
15
|
-
15 // debugger
|
|
16
|
-
16 // Create context for enrichAttributes hooks
|
|
17
|
-
17 Object.assign(context, params)
|
|
18
|
-
18
|
|
19
|
-
19 // Run the addRoute hook to notify transport plugins
|
|
20
|
-
20 await runHooks('addRoute')
|
|
21
|
-
21
|
|
22
|
-
22 return { registered: true, method, path }
|
|
23
|
-
23 }
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * checkPermissions
|
|
3
|
-
3 * Check if ther are permissions to access a resource.
|
|
4
|
-
4 *
|
|
5
|
-
5 */
|
|
6
|
-
6 export default async function checkPermissionsMethod ({ context, params, runHooks, scopeName, scopes, helpers }) {
|
|
7
|
-
7 Object.assign(context, {
|
|
8
|
-
8 method: params.method,
|
|
9
|
-
9 originalContext: params.originalContext,
|
|
10
|
-
10 })
|
|
11
|
-
11
|
|
12
|
-
12 await runHooks('checkPermissions')
|
|
13
|
-
13 }
|