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
|
@@ -0,0 +1,370 @@
|
|
|
1
|
+
# Knex Schema and Migrations
|
|
2
|
+
|
|
3
|
+
`RestApiKnexPlugin` exposes a small schema-management surface for table-backed resources.
|
|
4
|
+
|
|
5
|
+
Use it when you want to:
|
|
6
|
+
|
|
7
|
+
- create tables directly from resource definitions
|
|
8
|
+
- add or alter plain columns on existing tables
|
|
9
|
+
- inspect a live table snapshot
|
|
10
|
+
- generate a create migration from a resource
|
|
11
|
+
- generate an additive diff migration from a live table
|
|
12
|
+
|
|
13
|
+
This is a Knex/table feature surface. It does not exist for non-table resources.
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
Once a resource is backed by `RestApiKnexPlugin`, these scope methods are available:
|
|
18
|
+
|
|
19
|
+
- `createKnexTable()`
|
|
20
|
+
- `addKnexFields({ fields })`
|
|
21
|
+
- `alterKnexFields({ fields })`
|
|
22
|
+
- `introspectKnexTableSnapshot()`
|
|
23
|
+
- `generateKnexMigration()`
|
|
24
|
+
- `generateKnexMigrationDiff()`
|
|
25
|
+
|
|
26
|
+
Example:
|
|
27
|
+
|
|
28
|
+
```js
|
|
29
|
+
await api.addResource('memberships', {
|
|
30
|
+
schema: {
|
|
31
|
+
id: { type: 'id' },
|
|
32
|
+
workspaceId: { type: 'id', required: true },
|
|
33
|
+
userId: { type: 'id', required: true },
|
|
34
|
+
role: { type: 'string', enum: ['owner', 'member'], required: true, defaultTo: 'member' }
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
indexes: [
|
|
38
|
+
{
|
|
39
|
+
name: 'uq_memberships_workspace_user',
|
|
40
|
+
unique: true,
|
|
41
|
+
columns: ['workspaceId', 'userId']
|
|
42
|
+
}
|
|
43
|
+
],
|
|
44
|
+
|
|
45
|
+
foreignKeys: [
|
|
46
|
+
{
|
|
47
|
+
name: 'fk_memberships_workspace_user',
|
|
48
|
+
columns: ['workspaceId', 'userId'],
|
|
49
|
+
referencedTableName: 'workspace_users',
|
|
50
|
+
referencedColumns: ['workspace_id', 'user_id'],
|
|
51
|
+
deleteRule: 'CASCADE',
|
|
52
|
+
updateRule: 'RESTRICT'
|
|
53
|
+
}
|
|
54
|
+
],
|
|
55
|
+
|
|
56
|
+
checkConstraints: [
|
|
57
|
+
{
|
|
58
|
+
name: 'chk_memberships_workspace_positive',
|
|
59
|
+
clause: 'workspace_id > 0'
|
|
60
|
+
}
|
|
61
|
+
],
|
|
62
|
+
|
|
63
|
+
tableName: 'memberships'
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Default Column Naming
|
|
68
|
+
|
|
69
|
+
Table-backed resources keep logical field names in the resource schema and map them to physical columns for Knex operations.
|
|
70
|
+
|
|
71
|
+
By default, physical columns use snake_case:
|
|
72
|
+
|
|
73
|
+
- `workspaceId` -> `workspace_id`
|
|
74
|
+
- `userId` -> `user_id`
|
|
75
|
+
- `createdAt` -> `created_at`
|
|
76
|
+
|
|
77
|
+
This keeps the API surface expressive without repeating `storage.column` on every camelCase field.
|
|
78
|
+
|
|
79
|
+
You only need `storage.column` when a field should use a non-standard column name:
|
|
80
|
+
|
|
81
|
+
```js
|
|
82
|
+
await api.addResource('profiles', {
|
|
83
|
+
schema: {
|
|
84
|
+
id: { type: 'id' },
|
|
85
|
+
displayName: { type: 'string', required: true },
|
|
86
|
+
legacyRef: { type: 'string', storage: { column: 'legacy_profile_ref' } }
|
|
87
|
+
},
|
|
88
|
+
tableName: 'profiles'
|
|
89
|
+
})
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
If you need physical columns to match the logical field names exactly for a whole resource, opt out explicitly:
|
|
93
|
+
|
|
94
|
+
```js
|
|
95
|
+
await api.addResource('verbatim_profiles', {
|
|
96
|
+
storage: { naming: 'exact' },
|
|
97
|
+
schema: {
|
|
98
|
+
id: { type: 'id' },
|
|
99
|
+
displayName: { type: 'string', required: true }
|
|
100
|
+
},
|
|
101
|
+
tableName: 'verbatim_profiles'
|
|
102
|
+
})
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Logical IDs and `idProperty`
|
|
106
|
+
|
|
107
|
+
`idProperty` names the physical primary-key column for table-backed resources. The API contract still uses the logical resource id.
|
|
108
|
+
|
|
109
|
+
```js
|
|
110
|
+
await api.addResource('profiles', {
|
|
111
|
+
idProperty: 'user_id',
|
|
112
|
+
schema: {
|
|
113
|
+
id: { type: 'id', required: true, storage: { column: 'user_id' } },
|
|
114
|
+
displayName: { type: 'string', required: true },
|
|
115
|
+
loginCount: { type: 'number', defaultTo: 0 }
|
|
116
|
+
},
|
|
117
|
+
tableName: 'profiles'
|
|
118
|
+
})
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
With this definition:
|
|
122
|
+
|
|
123
|
+
- writes send the resource id as `id` or `data.id`
|
|
124
|
+
- reads return the resource id as `id` or `data.id`
|
|
125
|
+
- the primary key is stored in `user_id`
|
|
126
|
+
- `displayName` and `loginCount` are stored in `display_name` and `login_count`
|
|
127
|
+
|
|
128
|
+
The resource id is not part of `attributes`:
|
|
129
|
+
|
|
130
|
+
```js
|
|
131
|
+
await api.resources.profiles.post({
|
|
132
|
+
inputRecord: {
|
|
133
|
+
data: {
|
|
134
|
+
type: 'profiles',
|
|
135
|
+
id: '42',
|
|
136
|
+
attributes: {
|
|
137
|
+
displayName: 'Mercury'
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
})
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### ID normalization
|
|
145
|
+
|
|
146
|
+
If your primary keys need canonicalization beyond the default trim-and-stringify behavior, configure `normalizeId`.
|
|
147
|
+
|
|
148
|
+
```js
|
|
149
|
+
await api.use(RestApiPlugin, {
|
|
150
|
+
normalizeId: (value) => {
|
|
151
|
+
if (value === null || value === undefined) return null
|
|
152
|
+
|
|
153
|
+
const normalized = String(value).trim()
|
|
154
|
+
return normalized ? normalized.toUpperCase() : null
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
You can override that per resource:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
await api.addResource('profiles', {
|
|
163
|
+
idProperty: 'user_id',
|
|
164
|
+
normalizeId: (value) => {
|
|
165
|
+
if (value === null || value === undefined) return null
|
|
166
|
+
|
|
167
|
+
const normalized = String(value).trim()
|
|
168
|
+
return normalized ? normalized.toLowerCase() : null
|
|
169
|
+
},
|
|
170
|
+
schema: {
|
|
171
|
+
id: { type: 'id', required: true, storage: { column: 'user_id' } },
|
|
172
|
+
displayName: { type: 'string', required: true }
|
|
173
|
+
},
|
|
174
|
+
tableName: 'profiles'
|
|
175
|
+
})
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
For table-backed resources, `normalizeId` is applied before:
|
|
179
|
+
|
|
180
|
+
- reading a record by id
|
|
181
|
+
- replacing, patching, or deleting a record
|
|
182
|
+
- persisting an explicit `POST` resource id
|
|
183
|
+
- validating or writing relationship identifiers that point at the resource
|
|
184
|
+
|
|
185
|
+
If the normalizer returns an empty value, existing-resource operations fail as `not_found`, while explicit `POST` document ids fail validation on `data.id`.
|
|
186
|
+
|
|
187
|
+
## Create Tables
|
|
188
|
+
|
|
189
|
+
Create the table directly from the resource definition:
|
|
190
|
+
|
|
191
|
+
```js
|
|
192
|
+
await api.resources.memberships.createKnexTable()
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
`createKnexTable()` understands:
|
|
196
|
+
|
|
197
|
+
- storage-mapped column names
|
|
198
|
+
- mapped logical id columns
|
|
199
|
+
- top-level `indexes`
|
|
200
|
+
- top-level `foreignKeys`
|
|
201
|
+
- top-level `checkConstraints`
|
|
202
|
+
- enum columns
|
|
203
|
+
- MySQL-style `setValues`
|
|
204
|
+
|
|
205
|
+
This is the same table metadata used by migration generation and diffing.
|
|
206
|
+
|
|
207
|
+
## Field-Only Helpers
|
|
208
|
+
|
|
209
|
+
Two helpers only operate on columns:
|
|
210
|
+
|
|
211
|
+
```js
|
|
212
|
+
await api.resources.memberships.addKnexFields({
|
|
213
|
+
fields: {
|
|
214
|
+
noteCount: { type: 'number', defaultTo: 0, storage: { column: 'note_count' } }
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
await api.resources.memberships.alterKnexFields({
|
|
219
|
+
fields: {
|
|
220
|
+
role: { type: 'string', enum: ['owner', 'member', 'viewer'] }
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Important:
|
|
226
|
+
|
|
227
|
+
- these helpers are field-only
|
|
228
|
+
- they reject top-level `indexes`, `foreignKeys`, and `checkConstraints`
|
|
229
|
+
- use `createKnexTable()`, `generateKnexMigration()`, or `generateKnexMigrationDiff()` for table metadata
|
|
230
|
+
|
|
231
|
+
## Live Table Snapshots
|
|
232
|
+
|
|
233
|
+
Use `introspectKnexTableSnapshot()` to inspect the physical table shape:
|
|
234
|
+
|
|
235
|
+
```js
|
|
236
|
+
const snapshot = await api.resources.memberships.introspectKnexTableSnapshot()
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Returned shape:
|
|
240
|
+
|
|
241
|
+
```js
|
|
242
|
+
{
|
|
243
|
+
dialect: 'sqlite',
|
|
244
|
+
schemaName: 'main',
|
|
245
|
+
tableName: 'memberships',
|
|
246
|
+
tableCollation: '',
|
|
247
|
+
idColumn: 'id',
|
|
248
|
+
primaryKeyColumns: ['id'],
|
|
249
|
+
hasWorkspaceIdColumn: true,
|
|
250
|
+
hasUserIdColumn: true,
|
|
251
|
+
columns: [...],
|
|
252
|
+
indexes: [...],
|
|
253
|
+
foreignKeys: [...],
|
|
254
|
+
checkConstraints: [...]
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Column entries are normalized and include information such as:
|
|
259
|
+
|
|
260
|
+
- physical column name
|
|
261
|
+
- logical key
|
|
262
|
+
- type kind
|
|
263
|
+
- nullability
|
|
264
|
+
- default value
|
|
265
|
+
- precision / scale / datetime precision
|
|
266
|
+
- enum values
|
|
267
|
+
- set values
|
|
268
|
+
|
|
269
|
+
Current live introspection support:
|
|
270
|
+
|
|
271
|
+
- SQLite
|
|
272
|
+
- MySQL / MariaDB via `mysql2`
|
|
273
|
+
|
|
274
|
+
## Create Migrations
|
|
275
|
+
|
|
276
|
+
Use `generateKnexMigration()` to emit a Knex migration string from the resource definition:
|
|
277
|
+
|
|
278
|
+
```js
|
|
279
|
+
const migration = await api.resources.memberships.generateKnexMigration()
|
|
280
|
+
console.log(migration)
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
This generates `exports.up` and `exports.down` for full table creation/drop.
|
|
284
|
+
|
|
285
|
+
It includes the same table metadata as `createKnexTable()`:
|
|
286
|
+
|
|
287
|
+
- mapped columns
|
|
288
|
+
- mapped id column
|
|
289
|
+
- indexes
|
|
290
|
+
- foreign keys
|
|
291
|
+
- check constraints
|
|
292
|
+
- enum columns
|
|
293
|
+
- supported MySQL-specific set columns
|
|
294
|
+
|
|
295
|
+
## Diff Migrations
|
|
296
|
+
|
|
297
|
+
Use `generateKnexMigrationDiff()` to compare the desired schema against the live table snapshot:
|
|
298
|
+
|
|
299
|
+
```js
|
|
300
|
+
const diff = await api.resources.memberships.generateKnexMigrationDiff()
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Returned shape:
|
|
304
|
+
|
|
305
|
+
```js
|
|
306
|
+
{
|
|
307
|
+
migration,
|
|
308
|
+
warnings,
|
|
309
|
+
plan
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`plan` is normalized into:
|
|
314
|
+
|
|
315
|
+
- `addColumns`
|
|
316
|
+
- `alterColumns`
|
|
317
|
+
- `dropColumns`
|
|
318
|
+
- `addIndexes`
|
|
319
|
+
- `dropIndexes`
|
|
320
|
+
- `addForeignKeys`
|
|
321
|
+
- `dropForeignKeys`
|
|
322
|
+
- `addCheckConstraints`
|
|
323
|
+
|
|
324
|
+
The generated migration is intentionally additive-first:
|
|
325
|
+
|
|
326
|
+
- create missing columns
|
|
327
|
+
- alter columns when supported
|
|
328
|
+
- recreate changed indexes when needed
|
|
329
|
+
- add missing foreign keys
|
|
330
|
+
- add supported check constraints
|
|
331
|
+
|
|
332
|
+
## Important Limits
|
|
333
|
+
|
|
334
|
+
This is a schema-vs-live-table diff, not a migration-history engine.
|
|
335
|
+
|
|
336
|
+
It answers:
|
|
337
|
+
|
|
338
|
+
- what the table looks like now
|
|
339
|
+
- what the resource says it should look like
|
|
340
|
+
- what Knex migration can safely move the table toward that shape
|
|
341
|
+
|
|
342
|
+
It does not answer:
|
|
343
|
+
|
|
344
|
+
- what changed since a previous migration file
|
|
345
|
+
- how to reconstruct migration history
|
|
346
|
+
|
|
347
|
+
Other important limits:
|
|
348
|
+
|
|
349
|
+
- destructive changes are surfaced as warnings
|
|
350
|
+
- dropped columns are skipped unless explicitly allowed by diff options
|
|
351
|
+
- SQLite check-constraint add/drop/alter support is warning-only
|
|
352
|
+
- `setValues` diffing is only emitted for MySQL-compatible targets
|
|
353
|
+
- diff migrations do not auto-generate a destructive `down`
|
|
354
|
+
|
|
355
|
+
## When to Use Which Helper
|
|
356
|
+
|
|
357
|
+
- `createKnexTable()`
|
|
358
|
+
- bootstrap a table directly from the resource
|
|
359
|
+
|
|
360
|
+
- `addKnexFields()` / `alterKnexFields()`
|
|
361
|
+
- small column-only local changes
|
|
362
|
+
|
|
363
|
+
- `introspectKnexTableSnapshot()`
|
|
364
|
+
- inspect the real physical table
|
|
365
|
+
|
|
366
|
+
- `generateKnexMigration()`
|
|
367
|
+
- emit a full create migration from the current resource definition
|
|
368
|
+
|
|
369
|
+
- `generateKnexMigrationDiff()`
|
|
370
|
+
- emit an additive migration against the live table
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Query Projections
|
|
2
|
+
|
|
3
|
+
Query projections are SQL-backed, output-only query fields provided by the optional `QueryProjectionsPlugin`. They are different from normal computed fields.
|
|
4
|
+
|
|
5
|
+
If you want to build your own plugin on the same query-field seam, see [Writing Plugins](GUIDE_X_Writing_Plugins.md).
|
|
6
|
+
|
|
7
|
+
- **Computed fields** run after the row is fetched, in JavaScript.
|
|
8
|
+
- **Projected fields** are selected inside the query, so they can participate in `SELECT`, `ORDER BY`, and cursor pagination.
|
|
9
|
+
|
|
10
|
+
Use query projection fields when a derived value must behave like a real list field.
|
|
11
|
+
|
|
12
|
+
Typical cases:
|
|
13
|
+
- sorting by a derived label such as `full_name`
|
|
14
|
+
- cursor pagination over a derived value
|
|
15
|
+
- returning a derived SQL value without storing it
|
|
16
|
+
|
|
17
|
+
Do **not** use query projection fields for normal response decoration. Regular computed fields are simpler and remain the right default for that.
|
|
18
|
+
|
|
19
|
+
## Setup
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
import { RestApiPlugin, QueryProjectionsPlugin } from 'json-rest-api'
|
|
23
|
+
|
|
24
|
+
await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
|
|
25
|
+
await api.use(QueryProjectionsPlugin)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Defining a query field
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
await api.addResource('authors', {
|
|
32
|
+
schema: {
|
|
33
|
+
id: { type: 'id' },
|
|
34
|
+
first_name: { type: 'string', required: true },
|
|
35
|
+
last_name: { type: 'string', required: true }
|
|
36
|
+
},
|
|
37
|
+
|
|
38
|
+
queryFields: {
|
|
39
|
+
full_name: {
|
|
40
|
+
type: 'string',
|
|
41
|
+
sortable: true,
|
|
42
|
+
select: ({ knex, column }) => knex.raw(
|
|
43
|
+
"trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
|
|
44
|
+
[column('first_name'), column('last_name')]
|
|
45
|
+
)
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
sortableFields: ['id', 'first_name', 'last_name'],
|
|
50
|
+
defaultSort: ['full_name']
|
|
51
|
+
})
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`select()` is called at query time. It can return:
|
|
55
|
+
|
|
56
|
+
- a `knex.raw(...)` expression
|
|
57
|
+
- a Knex reference
|
|
58
|
+
- a Knex query/subquery expression
|
|
59
|
+
|
|
60
|
+
The callback receives:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
{
|
|
64
|
+
knex, // current knex/db handle
|
|
65
|
+
db, // same as knex
|
|
66
|
+
context, // request context
|
|
67
|
+
scopeName,
|
|
68
|
+
tableName,
|
|
69
|
+
fieldName,
|
|
70
|
+
schemaInfo,
|
|
71
|
+
adapter, // storage adapter
|
|
72
|
+
column, // helper: logical field -> qualified storage column
|
|
73
|
+
ref // helper: logical field -> knex ref
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Behavior
|
|
78
|
+
|
|
79
|
+
Query projection fields are:
|
|
80
|
+
|
|
81
|
+
- returned by default in `get()` and `query()`
|
|
82
|
+
- available in sparse fieldsets
|
|
83
|
+
- usable in sorting and cursor pagination
|
|
84
|
+
- available on included resources
|
|
85
|
+
- ignored on writes
|
|
86
|
+
|
|
87
|
+
They are **not** stored columns, and they are **not** part of write validation.
|
|
88
|
+
|
|
89
|
+
## Sparse fieldsets
|
|
90
|
+
|
|
91
|
+
Query projection fields can be requested explicitly:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
const result = await api.resources.authors.get({
|
|
95
|
+
id: '1',
|
|
96
|
+
queryParams: {
|
|
97
|
+
fields: {
|
|
98
|
+
authors: 'first_name,full_name'
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
})
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
If a query field is needed only for sorting, it is still selected internally, but it is removed from the response unless the sparse fieldset asked for it.
|
|
105
|
+
|
|
106
|
+
## Sorting and cursor pagination
|
|
107
|
+
|
|
108
|
+
Query projection fields are useful when a derived value must drive list behavior:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
const result = await api.resources.authors.query({
|
|
112
|
+
queryParams: {
|
|
113
|
+
sort: ['full_name'],
|
|
114
|
+
page: { size: 20 }
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
`json-rest-api` automatically appends `id` as a stable tie-breaker for cursor ordering. That prevents duplicate or skipped rows when multiple records share the same projected value.
|
|
120
|
+
|
|
121
|
+
## Included resources
|
|
122
|
+
|
|
123
|
+
Query projection fields can be selected for included resources too:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
const result = await api.resources.books.get({
|
|
127
|
+
id: '1',
|
|
128
|
+
queryParams: {
|
|
129
|
+
include: ['author'],
|
|
130
|
+
fields: {
|
|
131
|
+
books: 'title',
|
|
132
|
+
authors: 'full_name'
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Write behavior
|
|
139
|
+
|
|
140
|
+
Query projection fields are output-only. If a client sends them in `POST`, `PUT`, or `PATCH`, they are ignored.
|
|
141
|
+
|
|
142
|
+
That keeps the boundary clean:
|
|
143
|
+
|
|
144
|
+
- persisted fields own storage
|
|
145
|
+
- computed fields own response-layer enrichment
|
|
146
|
+
- query projection fields own query-layer derivation
|
|
147
|
+
|
|
148
|
+
## Limits
|
|
149
|
+
|
|
150
|
+
Projected fields are an advanced query feature. They are intentionally **backend-sensitive**.
|
|
151
|
+
|
|
152
|
+
Important limits:
|
|
153
|
+
|
|
154
|
+
- projection SQL can be dialect-specific
|
|
155
|
+
- this is not a portability layer across every SQL engine
|
|
156
|
+
- query projection fields are not auto-searchable
|
|
157
|
+
- if you want to sort by one, mark it `sortable: true`
|
|
158
|
+
|
|
159
|
+
For display-only derived values, prefer normal computed fields from [Field Transformations](GUIDE_3_Field_Transformations.md).
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# Writing Plugins
|
|
2
|
+
|
|
3
|
+
`json-rest-api` is built as a set of plugins on top of `hooked-api`. This guide covers the public extension surface for plugin authors.
|
|
4
|
+
|
|
5
|
+
Use this guide when you want to:
|
|
6
|
+
|
|
7
|
+
- add resource-level behavior across many resources
|
|
8
|
+
- add new scope methods
|
|
9
|
+
- compile resource metadata into reusable runtime state
|
|
10
|
+
- hook into query or write processing
|
|
11
|
+
|
|
12
|
+
For internal architecture notes and maintainer-oriented details, see [docs/ONBOARDING.md](../ONBOARDING.md).
|
|
13
|
+
|
|
14
|
+
## Minimal plugin shape
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
export const MyPlugin = {
|
|
18
|
+
name: 'my-plugin',
|
|
19
|
+
dependencies: ['rest-api'],
|
|
20
|
+
|
|
21
|
+
install ({ addHook, addScopeMethod, helpers, log, pluginOptions = {} }) {
|
|
22
|
+
addHook('scope:added', 'compile-my-plugin', {}, ({ context, scopes }) => {
|
|
23
|
+
const scope = scopes[context.scopeName]
|
|
24
|
+
scope.vars.myPlugin = { enabled: true }
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
addScopeMethod('doSomething', async ({ scopeName, scope, params, context }) => {
|
|
28
|
+
return { scopeName, enabled: scope.vars.myPlugin?.enabled === true }
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
The usual pattern is:
|
|
35
|
+
|
|
36
|
+
1. compile resource metadata during `scope:added`
|
|
37
|
+
2. store normalized state in `scope.vars`
|
|
38
|
+
3. use hooks or scope methods to apply behavior at runtime
|
|
39
|
+
|
|
40
|
+
## The stable extension points
|
|
41
|
+
|
|
42
|
+
### `scope:added`
|
|
43
|
+
|
|
44
|
+
Use this to inspect `scopeOptions`, validate configuration, and compile resource-specific runtime state into `scope.vars`.
|
|
45
|
+
|
|
46
|
+
Typical uses:
|
|
47
|
+
|
|
48
|
+
- compile autofilter presets
|
|
49
|
+
- compile query-field definitions
|
|
50
|
+
- validate resource-specific options
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
addHook('scope:added', 'compile-example', {}, ({ context, scopes }) => {
|
|
56
|
+
const scope = scopes[context.scopeName]
|
|
57
|
+
const options = scope.scopeOptions || {}
|
|
58
|
+
|
|
59
|
+
scope.vars.example = {
|
|
60
|
+
flag: options.exampleFlag === true
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### `beforeSchemaValidate`
|
|
66
|
+
|
|
67
|
+
Use this to normalize or strip write input before schema validation runs.
|
|
68
|
+
|
|
69
|
+
Typical uses:
|
|
70
|
+
|
|
71
|
+
- inject derived input values
|
|
72
|
+
- remove output-only fields from `POST`/`PUT`/`PATCH`
|
|
73
|
+
- translate alternate request forms into canonical attributes/relationships
|
|
74
|
+
|
|
75
|
+
Example:
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
addHook('beforeSchemaValidate', 'strip-output-only-input', {}, ({ context }) => {
|
|
79
|
+
const attributes = context.inputRecord?.data?.attributes
|
|
80
|
+
if (!attributes) return
|
|
81
|
+
|
|
82
|
+
delete attributes.output_only_field
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### `knexQueryFiltering`
|
|
87
|
+
|
|
88
|
+
Use this to add query constraints to the generated Knex query.
|
|
89
|
+
|
|
90
|
+
Typical uses:
|
|
91
|
+
|
|
92
|
+
- automatic scoping
|
|
93
|
+
- cross-table filters
|
|
94
|
+
- custom public filter semantics
|
|
95
|
+
|
|
96
|
+
Example:
|
|
97
|
+
|
|
98
|
+
```javascript
|
|
99
|
+
addHook('knexQueryFiltering', 'scope-by-workspace', {}, async ({ context }) => {
|
|
100
|
+
const query = context.knexQuery?.query
|
|
101
|
+
if (!query) return
|
|
102
|
+
|
|
103
|
+
query.where('workspace_id', context.session.workspaceId)
|
|
104
|
+
})
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`knexQueryFiltering` is the right seam for filtering. It is **not** the right seam for turning ad hoc SQL aliases into first-class fields.
|
|
108
|
+
|
|
109
|
+
### `addScopeMethod`
|
|
110
|
+
|
|
111
|
+
Use this when a plugin needs a reusable method on every resource or selected resources.
|
|
112
|
+
|
|
113
|
+
Example:
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
addScopeMethod('introspect', async ({ vars }) => {
|
|
117
|
+
return {
|
|
118
|
+
tableName: vars.schemaInfo?.tableName,
|
|
119
|
+
fields: Object.keys(vars.schemaInfo?.schemaStructure || {})
|
|
120
|
+
}
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## The query-field seam
|
|
125
|
+
|
|
126
|
+
`json-rest-api` now supports a small, explicit seam for **query-only read fields**.
|
|
127
|
+
|
|
128
|
+
This is the seam used by `QueryProjectionsPlugin`, and it is the recommended pattern for plugins that need derived SQL-backed fields.
|
|
129
|
+
|
|
130
|
+
### What a plugin should provide
|
|
131
|
+
|
|
132
|
+
Compile resource-level definitions into:
|
|
133
|
+
|
|
134
|
+
```javascript
|
|
135
|
+
scope.vars.queryFields = {
|
|
136
|
+
full_name: {
|
|
137
|
+
type: 'string',
|
|
138
|
+
sortable: true,
|
|
139
|
+
hidden: false,
|
|
140
|
+
normallyHidden: false,
|
|
141
|
+
select: ({ knex, db, context, scopeName, tableName, fieldName, schemaInfo, adapter, column, ref }) => {
|
|
142
|
+
return knex.raw(
|
|
143
|
+
"trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
|
|
144
|
+
[column('first_name'), column('last_name')]
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### What core will do with `scope.vars.queryFields`
|
|
152
|
+
|
|
153
|
+
Once a plugin sets `scope.vars.queryFields`, core will:
|
|
154
|
+
|
|
155
|
+
- include visible query fields by default in `get()` and `query()`
|
|
156
|
+
- allow them in sparse fieldsets
|
|
157
|
+
- allow them in sorting when the plugin marks them sortable
|
|
158
|
+
- use them in cursor pagination with a stable `id` tie-breaker
|
|
159
|
+
- carry them through included-resource selection
|
|
160
|
+
- only expose declared query-field aliases on the AnyAPI path
|
|
161
|
+
|
|
162
|
+
### What this seam is for
|
|
163
|
+
|
|
164
|
+
Use it for:
|
|
165
|
+
|
|
166
|
+
- SQL-backed projections
|
|
167
|
+
- aggregate list fields
|
|
168
|
+
- joined display labels
|
|
169
|
+
- derived fields that must participate in `ORDER BY`
|
|
170
|
+
|
|
171
|
+
Do **not** use it for:
|
|
172
|
+
|
|
173
|
+
- writable fields
|
|
174
|
+
- storage/schema ownership
|
|
175
|
+
- migrations or introspection
|
|
176
|
+
- normal response-only computed fields
|
|
177
|
+
|
|
178
|
+
For the concrete projection example, see [Query Projections](GUIDE_X_Query_Projections.md).
|
|
179
|
+
|
|
180
|
+
## Recommended plugin pattern
|
|
181
|
+
|
|
182
|
+
When you add a new plugin feature, prefer this flow:
|
|
183
|
+
|
|
184
|
+
1. read plugin options at install time
|
|
185
|
+
2. validate and compile per-resource config in `scope:added`
|
|
186
|
+
3. store only normalized runtime state in `scope.vars`
|
|
187
|
+
4. use hooks or scope methods to apply behavior
|
|
188
|
+
|
|
189
|
+
That keeps the plugin declarative and avoids re-parsing configuration during requests.
|
|
190
|
+
|
|
191
|
+
## Boundaries to keep clean
|
|
192
|
+
|
|
193
|
+
- Keep **query-layer** extensions out of `schema`.
|
|
194
|
+
- Keep **write/input** reshaping in `beforeSchemaValidate`.
|
|
195
|
+
- Keep **filtering** in `knexQueryFiltering`.
|
|
196
|
+
- Keep **response-only** enrichment in `enrichAttributes`.
|
|
197
|
+
- Do not rely on arbitrary internal helper filenames as public API.
|
|
198
|
+
|
|
199
|
+
If a feature requires deeper integration than the public seams provide, treat that as a core extension discussion rather than a plugin hack.
|