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
package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt
DELETED
|
@@ -1,342 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * File Handling Plugin for JSON REST API
|
|
3
|
-
3 *
|
|
4
|
-
4 * This plugin provides automatic file upload handling based on schema definitions.
|
|
5
|
-
5 * It works with any protocol connector (HTTP, Express, WebSocket, etc.) by using
|
|
6
|
-
6 * a detector registry pattern.
|
|
7
|
-
7 *
|
|
8
|
-
8 * Features:
|
|
9
|
-
9 * - Schema-driven: Detects file fields from type: 'file' in schemas
|
|
10
|
-
10 * - Protocol-agnostic: Works with any connector that registers a detector
|
|
11
|
-
11 * - Storage pluggable: Different fields can use different storage backends
|
|
12
|
-
12 * - Zero configuration: Just define file fields in your schema
|
|
13
|
-
13 *
|
|
14
|
-
14 * Usage:
|
|
15
|
-
15 * ```javascript
|
|
16
|
-
16 * // 1. Define schema with file fields
|
|
17
|
-
17 * const imageSchema = {
|
|
18
|
-
18 * title: { type: 'string' },
|
|
19
|
-
19 * uploadedImage: {
|
|
20
|
-
20 * type: 'file',
|
|
21
|
-
21 * storage: S3Storage,
|
|
22
|
-
22 * accepts: ['image/jpeg', 'image/png']
|
|
23
|
-
23 * }
|
|
24
|
-
24 * };
|
|
25
|
-
25 *
|
|
26
|
-
26 * // 2. Use plugins (order matters - file-handling depends on rest-api)
|
|
27
|
-
27 * api.use(RestApiPlugin);
|
|
28
|
-
28 * api.use(FileHandlingPlugin);
|
|
29
|
-
29 * api.use(ExpressPlugin); // Or any other connector
|
|
30
|
-
30 *
|
|
31
|
-
31 * // 3. Files are automatically handled!
|
|
32
|
-
32 * ```
|
|
33
|
-
33 */
|
|
34
|
-
34
|
|
35
|
-
35 import { RestApiValidationError } from '../../lib/rest-api-errors.js'
|
|
36
|
-
36
|
|
37
|
-
37 export const FileHandlingPlugin = {
|
|
38
|
-
38 name: 'file-handling',
|
|
39
|
-
39 dependencies: ['rest-api'],
|
|
40
|
-
40
|
|
41
|
-
41 install ({ addHook, helpers, scopes, log, vars, on, api }) {
|
|
42
|
-
42 // Track which scopes have file fields
|
|
43
|
-
43 const fileScopes = new Map() // scopeName -> fileField[]
|
|
44
|
-
44
|
|
45
|
-
45 // Registry of file detectors from various protocols
|
|
46
|
-
46 const detectorRegistry = []
|
|
47
|
-
47
|
|
48
|
-
48 /**
|
|
49
|
-
49 * Register a file detector from a protocol plugin
|
|
50
|
-
50 *
|
|
51
|
-
51 * @param {Object} detector - The detector object
|
|
52
|
-
52 * @param {string} detector.name - Name of the detector (e.g., 'http-multipart')
|
|
53
|
-
53 * @param {Function} detector.detect - Function to check if this detector applies
|
|
54
|
-
54 * @param {Function} detector.parse - Function to parse files from the request
|
|
55
|
-
55 */
|
|
56
|
-
56 api.rest.registerFileDetector = (detector) => {
|
|
57
|
-
57 if (!detector || !detector.name || !detector.detect || !detector.parse) {
|
|
58
|
-
58 throw new Error('File detector must have name, detect(), and parse() properties')
|
|
59
|
-
59 }
|
|
60
|
-
60
|
|
61
|
-
61 detectorRegistry.push(detector)
|
|
62
|
-
62 log.debug(`Registered file detector: ${detector.name}`)
|
|
63
|
-
63 }
|
|
64
|
-
64
|
|
65
|
-
65 // Store detectors array for inspection
|
|
66
|
-
66 api.rest.fileDetectors = detectorRegistry
|
|
67
|
-
67
|
|
68
|
-
68 /**
|
|
69
|
-
69 * Analyze a scope's schema to find file fields
|
|
70
|
-
70 */
|
|
71
|
-
71 const analyzeScopeSchema = (scopeName, scopeOptions) => {
|
|
72
|
-
72 const schema = scopeOptions?.schema
|
|
73
|
-
73 if (!schema) return
|
|
74
|
-
74
|
|
75
|
-
75 const fileFields = []
|
|
76
|
-
76
|
|
77
|
-
77 // Look for fields with type: 'file'
|
|
78
|
-
78 for (const [fieldName, fieldConfig] of Object.entries(schema)) {
|
|
79
|
-
79 if (fieldConfig && fieldConfig.type === 'file') {
|
|
80
|
-
80 fileFields.push({
|
|
81
|
-
81 field: fieldName,
|
|
82
|
-
82 storage: fieldConfig.storage,
|
|
83
|
-
83 accepts: fieldConfig.accepts || ['*'],
|
|
84
|
-
84 maxSize: fieldConfig.maxSize,
|
|
85
|
-
85 required: fieldConfig.required || false
|
|
86
|
-
86 })
|
|
87
|
-
87 }
|
|
88
|
-
88 }
|
|
89
|
-
89
|
|
90
|
-
90 if (fileFields.length > 0) {
|
|
91
|
-
91 fileScopes.set(scopeName, fileFields)
|
|
92
|
-
92 log.info(`Scope '${scopeName}' has ${fileFields.length} file field(s): ${fileFields.map(f => f.field).join(', ')}`)
|
|
93
|
-
93 }
|
|
94
|
-
94 }
|
|
95
|
-
95
|
|
96
|
-
96 // Analyze existing scopes
|
|
97
|
-
97 for (const [scopeName, scope] of Object.entries(scopes)) {
|
|
98
|
-
98 if (scope._scopeOptions) {
|
|
99
|
-
99 analyzeScopeSchema(scopeName, scope._scopeOptions)
|
|
100
|
-
100 }
|
|
101
|
-
101 }
|
|
102
|
-
102
|
|
103
|
-
103 // Listen for new scopes being added
|
|
104
|
-
104 addHook('scope:added', 'analyzeFileFields', {}, ({ context }) => {
|
|
105
|
-
105 const { scopeName, scopeOptions } = context
|
|
106
|
-
106 analyzeScopeSchema(scopeName, scopeOptions)
|
|
107
|
-
107 })
|
|
108
|
-
108
|
|
109
|
-
109 /**
|
|
110
|
-
110 * Process files for a scope if it has file fields
|
|
111
|
-
111 */
|
|
112
|
-
112 const processFiles = async (scopeName, params, context) => {
|
|
113
|
-
113 const fileFields = fileScopes.get(scopeName)
|
|
114
|
-
114 if (!fileFields || fileFields.length === 0) {
|
|
115
|
-
115 return // This scope doesn't have file fields
|
|
116
|
-
116 }
|
|
117
|
-
117
|
|
118
|
-
118 // Try each detector to see if we have files
|
|
119
|
-
119 let parsed = null
|
|
120
|
-
120 let detectorUsed = null
|
|
121
|
-
121
|
|
122
|
-
122 for (const detector of detectorRegistry) {
|
|
123
|
-
123 try {
|
|
124
|
-
124 if (await detector.detect(params, context)) {
|
|
125
|
-
125 log.debug(`Detector '${detector.name}' matched for scope '${scopeName}'`)
|
|
126
|
-
126 parsed = await detector.parse(params, context)
|
|
127
|
-
127 detectorUsed = detector.name
|
|
128
|
-
128 break
|
|
129
|
-
129 }
|
|
130
|
-
130 } catch (error) {
|
|
131
|
-
131 log.warn(`Detector '${detector.name}' failed:`, error)
|
|
132
|
-
132 }
|
|
133
|
-
133 }
|
|
134
|
-
134
|
|
135
|
-
135 if (!parsed) {
|
|
136
|
-
136 // No files detected - check if any were required
|
|
137
|
-
137 for (const fieldConfig of fileFields) {
|
|
138
|
-
138 if (fieldConfig.required && !params.inputRecord?.data?.attributes?.[fieldConfig.field]) {
|
|
139
|
-
139 throw new RestApiValidationError(
|
|
140
|
-
140 `Required file field '${fieldConfig.field}' is missing`,
|
|
141
|
-
141 {
|
|
142
|
-
142 fields: [fieldConfig.field],
|
|
143
|
-
143 violations: [{
|
|
144
|
-
144 field: fieldConfig.field,
|
|
145
|
-
145 message: 'This field is required'
|
|
146
|
-
146 }]
|
|
147
|
-
147 }
|
|
148
|
-
148 )
|
|
149
|
-
149 }
|
|
150
|
-
150 }
|
|
151
|
-
151 return
|
|
152
|
-
152 }
|
|
153
|
-
153
|
|
154
|
-
154 log.debug(`Processing files with detector '${detectorUsed}'`)
|
|
155
|
-
155 const { fields, files } = parsed
|
|
156
|
-
156
|
|
157
|
-
157 // Process each file field defined in schema
|
|
158
|
-
158 for (const fieldConfig of fileFields) {
|
|
159
|
-
159 const file = files[fieldConfig.field]
|
|
160
|
-
160
|
|
161
|
-
161 if (!file) {
|
|
162
|
-
162 if (fieldConfig.required) {
|
|
163
|
-
163 throw new RestApiValidationError(
|
|
164
|
-
164 `Required file field '${fieldConfig.field}' is missing`,
|
|
165
|
-
165 {
|
|
166
|
-
166 fields: [fieldConfig.field],
|
|
167
|
-
167 violations: [{
|
|
168
|
-
168 field: fieldConfig.field,
|
|
169
|
-
169 message: 'This field is required'
|
|
170
|
-
170 }]
|
|
171
|
-
171 }
|
|
172
|
-
172 )
|
|
173
|
-
173 }
|
|
174
|
-
174 continue
|
|
175
|
-
175 }
|
|
176
|
-
176
|
|
177
|
-
177 // Validate mime type
|
|
178
|
-
178 if (fieldConfig.accepts[0] !== '*') {
|
|
179
|
-
179 const acceptable = fieldConfig.accepts.some(pattern => {
|
|
180
|
-
180 if (pattern.endsWith('/*')) {
|
|
181
|
-
181 // e.g., 'image/*'
|
|
182
|
-
182 const prefix = pattern.slice(0, -2)
|
|
183
|
-
183 return file.mimetype.startsWith(prefix + '/')
|
|
184
|
-
184 }
|
|
185
|
-
185 return file.mimetype === pattern
|
|
186
|
-
186 })
|
|
187
|
-
187
|
|
188
|
-
188 if (!acceptable) {
|
|
189
|
-
189 throw new RestApiValidationError(
|
|
190
|
-
190 `Invalid file type for field '${fieldConfig.field}'`,
|
|
191
|
-
191 {
|
|
192
|
-
192 fields: [fieldConfig.field],
|
|
193
|
-
193 violations: [{
|
|
194
|
-
194 field: fieldConfig.field,
|
|
195
|
-
195 message: `Expected ${fieldConfig.accepts.join(' or ')}, got ${file.mimetype}`
|
|
196
|
-
196 }]
|
|
197
|
-
197 }
|
|
198
|
-
198 )
|
|
199
|
-
199 }
|
|
200
|
-
200 }
|
|
201
|
-
201
|
|
202
|
-
202 // Validate file size
|
|
203
|
-
203 if (fieldConfig.maxSize) {
|
|
204
|
-
204 const maxBytes = parseSize(fieldConfig.maxSize)
|
|
205
|
-
205 if (file.size > maxBytes) {
|
|
206
|
-
206 throw new RestApiValidationError(
|
|
207
|
-
207 `File too large for field '${fieldConfig.field}'`,
|
|
208
|
-
208 {
|
|
209
|
-
209 fields: [fieldConfig.field],
|
|
210
|
-
210 violations: [{
|
|
211
|
-
211 field: fieldConfig.field,
|
|
212
|
-
212 message: `Maximum size is ${fieldConfig.maxSize}, got ${formatSize(file.size)}`
|
|
213
|
-
213 }]
|
|
214
|
-
214 }
|
|
215
|
-
215 )
|
|
216
|
-
216 }
|
|
217
|
-
217 }
|
|
218
|
-
218
|
|
219
|
-
219 // Upload to storage
|
|
220
|
-
220 if (!fieldConfig.storage) {
|
|
221
|
-
221 throw new Error(`No storage configured for file field '${fieldConfig.field}'`)
|
|
222
|
-
222 }
|
|
223
|
-
223
|
|
224
|
-
224 try {
|
|
225
|
-
225 const storedUrl = await fieldConfig.storage.upload(file)
|
|
226
|
-
226 fields[fieldConfig.field] = storedUrl
|
|
227
|
-
227 log.debug(`Uploaded file for field '${fieldConfig.field}' to: ${storedUrl}`)
|
|
228
|
-
228 } catch (error) {
|
|
229
|
-
229 // Cleanup if file has cleanup function
|
|
230
|
-
230 if (file.cleanup) {
|
|
231
|
-
231 try {
|
|
232
|
-
232 await file.cleanup()
|
|
233
|
-
233 } catch (cleanupError) {
|
|
234
|
-
234 log.warn('Failed to cleanup file after upload error:', cleanupError)
|
|
235
|
-
235 }
|
|
236
|
-
236 }
|
|
237
|
-
237
|
|
238
|
-
238 throw new RestApiValidationError(
|
|
239
|
-
239 `Failed to upload file for field '${fieldConfig.field}': ${error.message}`,
|
|
240
|
-
240 {
|
|
241
|
-
241 fields: [fieldConfig.field],
|
|
242
|
-
242 violations: [{
|
|
243
|
-
243 field: fieldConfig.field,
|
|
244
|
-
244 message: error.message
|
|
245
|
-
245 }]
|
|
246
|
-
246 }
|
|
247
|
-
247 )
|
|
248
|
-
248 }
|
|
249
|
-
249 }
|
|
250
|
-
250
|
|
251
|
-
251 // Replace inputRecord with processed data
|
|
252
|
-
252 if (!params.inputRecord) {
|
|
253
|
-
253 params.inputRecord = { data: { attributes: {} } }
|
|
254
|
-
254 }
|
|
255
|
-
255 if (!params.inputRecord.data) {
|
|
256
|
-
256 params.inputRecord.data = { attributes: {} }
|
|
257
|
-
257 }
|
|
258
|
-
258 if (!params.inputRecord.data.attributes) {
|
|
259
|
-
259 params.inputRecord.data.attributes = {}
|
|
260
|
-
260 }
|
|
261
|
-
261
|
|
262
|
-
262 // Merge fields into attributes
|
|
263
|
-
263 Object.assign(params.inputRecord.data.attributes, fields)
|
|
264
|
-
264
|
|
265
|
-
265 // Cleanup any remaining temp files
|
|
266
|
-
266 for (const file of Object.values(files)) {
|
|
267
|
-
267 if (file.cleanup) {
|
|
268
|
-
268 try {
|
|
269
|
-
269 await file.cleanup()
|
|
270
|
-
270 } catch (error) {
|
|
271
|
-
271 log.warn('Failed to cleanup temp file:', error)
|
|
272
|
-
272 }
|
|
273
|
-
273 }
|
|
274
|
-
274 }
|
|
275
|
-
275 }
|
|
276
|
-
276
|
|
277
|
-
277 /**
|
|
278
|
-
278 * Hook into REST API methods to process files
|
|
279
|
-
279 */
|
|
280
|
-
280 addHook('beforeProcessing', 'processFiles', {}, async ({ context }) => {
|
|
281
|
-
281 const method = context.method
|
|
282
|
-
282 const scopeName = context.scopeName
|
|
283
|
-
283 const params = context.params
|
|
284
|
-
284
|
|
285
|
-
285 // Only process for mutation methods
|
|
286
|
-
286 if (!['post', 'put', 'patch'].includes(method)) {
|
|
287
|
-
287 return
|
|
288
|
-
288 }
|
|
289
|
-
289
|
|
290
|
-
290 // Process files if this scope has file fields
|
|
291
|
-
291 await processFiles(scopeName, params, context)
|
|
292
|
-
292 })
|
|
293
|
-
293
|
|
294
|
-
294 log.info('File handling plugin initialized successfully')
|
|
295
|
-
295 }
|
|
296
|
-
296 }
|
|
297
|
-
297
|
|
298
|
-
298 /**
|
|
299
|
-
299 * Parse size string to bytes
|
|
300
|
-
300 * @param {string} size - Size string like '10mb', '1.5GB'
|
|
301
|
-
301 * @returns {number} Size in bytes
|
|
302
|
-
302 */
|
|
303
|
-
303 function parseSize (size) {
|
|
304
|
-
304 const units = {
|
|
305
|
-
305 b: 1,
|
|
306
|
-
306 kb: 1024,
|
|
307
|
-
307 mb: 1024 * 1024,
|
|
308
|
-
308 gb: 1024 * 1024 * 1024
|
|
309
|
-
309 }
|
|
310
|
-
310
|
|
311
|
-
311 const match = size.toLowerCase().match(/^(\d+(?:\.\d+)?)\s*([a-z]+)$/)
|
|
312
|
-
312 if (!match) {
|
|
313
|
-
313 throw new Error(`Invalid size format: ${size}`)
|
|
314
|
-
314 }
|
|
315
|
-
315
|
|
316
|
-
316 const [, num, unit] = match
|
|
317
|
-
317 const multiplier = units[unit]
|
|
318
|
-
318
|
|
319
|
-
319 if (!multiplier) {
|
|
320
|
-
320 throw new Error(`Unknown size unit: ${unit}`)
|
|
321
|
-
321 }
|
|
322
|
-
322
|
|
323
|
-
323 return parseFloat(num) * multiplier
|
|
324
|
-
324 }
|
|
325
|
-
325
|
|
326
|
-
326 /**
|
|
327
|
-
327 * Format bytes to human readable size
|
|
328
|
-
328 * @param {number} bytes - Size in bytes
|
|
329
|
-
329 * @returns {string} Human readable size
|
|
330
|
-
330 */
|
|
331
|
-
331 function formatSize (bytes) {
|
|
332
|
-
332 const units = ['B', 'KB', 'MB', 'GB']
|
|
333
|
-
333 let size = bytes
|
|
334
|
-
334 let unitIndex = 0
|
|
335
|
-
335
|
|
336
|
-
336 while (size >= 1024 && unitIndex < units.length - 1) {
|
|
337
|
-
337 size /= 1024
|
|
338
|
-
338 unitIndex++
|
|
339
|
-
339 }
|
|
340
|
-
340
|
|
341
|
-
341 return `${size.toFixed(1)}${units[unitIndex]}`
|
|
342
|
-
342 }
|
|
@@ -1,304 +0,0 @@
|
|
|
1
|
-
1 /**
|
|
2
|
-
2 * @file Creates Knex table definitions from json-rest-schema definitions
|
|
3
|
-
3 */
|
|
4
|
-
4
|
|
5
|
-
5 /**
|
|
6
|
-
6 * Maps json-rest-schema types to Knex column types
|
|
7
|
-
7 * @param {object} table - The Knex table builder instance
|
|
8
|
-
8 * @param {string} columnName - The name of the column
|
|
9
|
-
9 * @param {object} definition - The schema definition for the field
|
|
10
|
-
10 * @returns {object} The Knex column builder instance
|
|
11
|
-
11 */
|
|
12
|
-
12 function mapTypeToKnex (table, columnName, definition) {
|
|
13
|
-
13 const { type, precision, scale, maxLength, unsigned } = definition
|
|
14
|
-
14
|
|
15
|
-
15 switch (type) {
|
|
16
|
-
16 case 'string':
|
|
17
|
-
17 return maxLength ? table.string(columnName, maxLength) : table.string(columnName)
|
|
18
|
-
18
|
|
19
|
-
19 case 'number':
|
|
20
|
-
20 if (precision !== undefined && scale !== undefined) {
|
|
21
|
-
21 return table.decimal(columnName, precision, scale)
|
|
22
|
-
22 }
|
|
23
|
-
23 return table.float(columnName)
|
|
24
|
-
24
|
|
25
|
-
25 case 'id':
|
|
26
|
-
26 const col = table.integer(columnName)
|
|
27
|
-
27 return unsigned !== false ? col.unsigned() : col
|
|
28
|
-
28
|
|
29
|
-
29 case 'boolean':
|
|
30
|
-
30 return table.boolean(columnName)
|
|
31
|
-
31
|
|
32
|
-
32 case 'date':
|
|
33
|
-
33 return table.date(columnName)
|
|
34
|
-
34
|
|
35
|
-
35 case 'dateTime':
|
|
36
|
-
36 return table.datetime(columnName)
|
|
37
|
-
37
|
|
38
|
-
38 case 'time':
|
|
39
|
-
39 return table.time(columnName)
|
|
40
|
-
40
|
|
41
|
-
41 case 'timestamp':
|
|
42
|
-
42 return table.integer(columnName)
|
|
43
|
-
43
|
|
44
|
-
44 case 'array':
|
|
45
|
-
45 case 'object':
|
|
46
|
-
46 case 'serialize':
|
|
47
|
-
47 return table.json(columnName)
|
|
48
|
-
48
|
|
49
|
-
49 case 'blob':
|
|
50
|
-
50 case 'file':
|
|
51
|
-
51 return table.binary(columnName)
|
|
52
|
-
52
|
|
53
|
-
53 case 'none':
|
|
54
|
-
54 default:
|
|
55
|
-
55 return table.string(columnName)
|
|
56
|
-
56 }
|
|
57
|
-
57 }
|
|
58
|
-
58
|
|
59
|
-
59 /**
|
|
60
|
-
60 * Applies schema constraints to a Knex column
|
|
61
|
-
61 * @param {object} column - The Knex column builder instance
|
|
62
|
-
62 * @param {object} definition - The schema definition for the field
|
|
63
|
-
63 */
|
|
64
|
-
64 function applyConstraints (column, definition) {
|
|
65
|
-
65 // Nullability
|
|
66
|
-
66 if (definition.nullable === true) {
|
|
67
|
-
67 column.nullable()
|
|
68
|
-
68 } else if (definition.required === true || definition.nullable === false) {
|
|
69
|
-
69 column.notNullable()
|
|
70
|
-
70 }
|
|
71
|
-
71
|
|
72
|
-
72 // Default value
|
|
73
|
-
73 if (definition.defaultTo !== undefined) {
|
|
74
|
-
74 column.defaultTo(definition.defaultTo)
|
|
75
|
-
75 }
|
|
76
|
-
76
|
|
77
|
-
77 // Database-specific constraints
|
|
78
|
-
78 if (definition.unique === true) {
|
|
79
|
-
79 column.unique()
|
|
80
|
-
80 }
|
|
81
|
-
81
|
|
82
|
-
82 if (definition.primary === true) {
|
|
83
|
-
83 column.primary()
|
|
84
|
-
84 }
|
|
85
|
-
85
|
|
86
|
-
86 if (definition.index === true) {
|
|
87
|
-
87 column.index()
|
|
88
|
-
88 }
|
|
89
|
-
89
|
|
90
|
-
90 if (definition.references) {
|
|
91
|
-
91 column.references(definition.references.column || 'id')
|
|
92
|
-
92 .inTable(definition.references.table)
|
|
93
|
-
93
|
|
94
|
-
94 if (definition.references.onDelete) {
|
|
95
|
-
95 column.onDelete(definition.references.onDelete)
|
|
96
|
-
96 }
|
|
97
|
-
97
|
|
98
|
-
98 if (definition.references.onUpdate) {
|
|
99
|
-
99 column.onUpdate(definition.references.onUpdate)
|
|
100
|
-
100 }
|
|
101
|
-
101 }
|
|
102
|
-
102
|
|
103
|
-
103 if (definition.comment) {
|
|
104
|
-
104 column.comment(definition.comment)
|
|
105
|
-
105 }
|
|
106
|
-
106 }
|
|
107
|
-
107
|
|
108
|
-
108 /**
|
|
109
|
-
109 * Creates a Knex table from a json-rest-schema definition
|
|
110
|
-
110 * @param {object} knex - The Knex instance
|
|
111
|
-
111 * @param {string} tableName - The name of the table to create
|
|
112
|
-
112 * @param {object} schema - The json-rest-schema instance
|
|
113
|
-
113 * @param {string} [idProperty='id'] - The name of the ID column
|
|
114
|
-
114 * @param {object} [options={}] - Additional options
|
|
115
|
-
115 * @param {boolean} [options.autoIncrement=true] - Whether to use auto-incrementing IDs
|
|
116
|
-
116 * @param {boolean} [options.timestamps=false] - Whether to add created_at/updated_at columns
|
|
117
|
-
117 * @returns {Promise} A promise that resolves when the table is created
|
|
118
|
-
118 */
|
|
119
|
-
119 export async function createKnexTable (knex, schemaInfo, tableSchemaInstance, options = {}) {
|
|
120
|
-
120 const { autoIncrement = true, timestamps = false } = options
|
|
121
|
-
121
|
|
122
|
-
122 const tableName = schemaInfo.tableName
|
|
123
|
-
123 const idProperty = schemaInfo.idProperty
|
|
124
|
-
124 const tableSchemaStructure = tableSchemaInstance.structure
|
|
125
|
-
125
|
|
126
|
-
126 return knex.schema.createTable(tableName, (table) => {
|
|
127
|
-
127 // Check if schema has the idProperty field with primary key
|
|
128
|
-
128 const hasIdField = tableSchemaStructure[idProperty] && tableSchemaStructure[idProperty].primary === true
|
|
129
|
-
129
|
|
130
|
-
130 // Add auto-incrementing ID if no primary key is defined and autoIncrement is true
|
|
131
|
-
131 if (!hasIdField && autoIncrement) {
|
|
132
|
-
132 table.increments(idProperty).primary()
|
|
133
|
-
133 }
|
|
134
|
-
134
|
|
135
|
-
135 // Process each field in the schema
|
|
136
|
-
136 for (const [fieldName, definition] of Object.entries(tableSchemaStructure)) {
|
|
137
|
-
137 // Skip if this is the ID field and we already handled it
|
|
138
|
-
138 if (fieldName === idProperty && !hasIdField && autoIncrement) {
|
|
139
|
-
139 continue
|
|
140
|
-
140 }
|
|
141
|
-
141
|
|
142
|
-
142 // Create the column with the appropriate type
|
|
143
|
-
143 const column = mapTypeToKnex(table, fieldName, definition)
|
|
144
|
-
144
|
|
145
|
-
145 // Apply constraints
|
|
146
|
-
146 applyConstraints(column, definition)
|
|
147
|
-
147 }
|
|
148
|
-
148
|
|
149
|
-
149 // Add timestamps if requested
|
|
150
|
-
150 if (timestamps) {
|
|
151
|
-
151 table.timestamps(true, true)
|
|
152
|
-
152 }
|
|
153
|
-
153 })
|
|
154
|
-
154 }
|
|
155
|
-
155
|
|
156
|
-
156 export async function addKnexFields (knex, tableName, schema) {
|
|
157
|
-
157 return knex.schema.alterTable(tableName, (table) => {
|
|
158
|
-
158 for (const [fieldName, definition] of Object.entries(schema.structure)) {
|
|
159
|
-
159 const column = mapTypeToKnex(table, fieldName, definition)
|
|
160
|
-
160 applyConstraints(column, definition)
|
|
161
|
-
161 }
|
|
162
|
-
162 })
|
|
163
|
-
163 }
|
|
164
|
-
164
|
|
165
|
-
165 // Helper function to alter multiple fields in an existing table
|
|
166
|
-
166 export async function alterKnexFields (knex, tableName, fields, options = {}) {
|
|
167
|
-
167 return knex.schema.alterTable(tableName, (table) => {
|
|
168
|
-
168 for (const [fieldName, definition] of Object.entries(fields)) {
|
|
169
|
-
169 // Create the column with alter flag
|
|
170
|
-
170 const column = mapTypeToKnex(table, fieldName, definition)
|
|
171
|
-
171
|
|
172
|
-
172 // Mark this as an alteration
|
|
173
|
-
173 column.alter()
|
|
174
|
-
174
|
|
175
|
-
175 // Apply constraints (these will be reapplied with the alter)
|
|
176
|
-
176 applyConstraints(column, definition)
|
|
177
|
-
177 }
|
|
178
|
-
178 })
|
|
179
|
-
179 }
|
|
180
|
-
180
|
|
181
|
-
181 /**
|
|
182
|
-
182 * Generates a Knex migration string from a json-rest-schema definition
|
|
183
|
-
183 * @param {string} tableName - The name of the table
|
|
184
|
-
184 * @param {object} schema - The json-rest-schema instance
|
|
185
|
-
185 * @param {object} [options={}] - Additional options
|
|
186
|
-
186 * @returns {string} The migration code as a string
|
|
187
|
-
187 */
|
|
188
|
-
188 export function generateKnexMigration (tableName, schema, options = {}) {
|
|
189
|
-
189 const { autoIncrement = true, timestamps = false } = options
|
|
190
|
-
190
|
|
191
|
-
191 let migration = `exports.up = function(knex) {
|
|
192
|
-
192 return knex.schema.createTable('${tableName}', (table) => {\n`
|
|
193
|
-
193
|
|
194
|
-
194 // Check if schema has an 'id' field with primary key
|
|
195
|
-
195 const hasIdField = schema.structure.id && schema.structure.id.primary === true
|
|
196
|
-
196
|
|
197
|
-
197 // Add auto-incrementing ID if needed
|
|
198
|
-
198 if (!hasIdField && autoIncrement) {
|
|
199
|
-
199 migration += ' table.increments(\'id\').primary();\n'
|
|
200
|
-
200 }
|
|
201
|
-
201
|
|
202
|
-
202 // Process each field
|
|
203
|
-
203 for (const [fieldName, definition] of Object.entries(schema.structure)) {
|
|
204
|
-
204 if (fieldName === 'id' && !hasIdField && autoIncrement) {
|
|
205
|
-
205 continue
|
|
206
|
-
206 }
|
|
207
|
-
207
|
|
208
|
-
208 let line = ' '
|
|
209
|
-
209
|
|
210
|
-
210 // Map type
|
|
211
|
-
211 switch (definition.type) {
|
|
212
|
-
212 case 'string':
|
|
213
|
-
213 line += definition.maxLength
|
|
214
|
-
214 ? `table.string('${fieldName}', ${definition.maxLength})`
|
|
215
|
-
215 : `table.string('${fieldName}')`
|
|
216
|
-
216 break
|
|
217
|
-
217 case 'number':
|
|
218
|
-
218 if (definition.precision !== undefined && definition.scale !== undefined) {
|
|
219
|
-
219 line += `table.decimal('${fieldName}', ${definition.precision}, ${definition.scale})`
|
|
220
|
-
220 } else {
|
|
221
|
-
221 line += `table.float('${fieldName}')`
|
|
222
|
-
222 }
|
|
223
|
-
223 break
|
|
224
|
-
224 case 'id':
|
|
225
|
-
225 line += `table.integer('${fieldName}')`
|
|
226
|
-
226 if (definition.unsigned !== false) line += '.unsigned()'
|
|
227
|
-
227 break
|
|
228
|
-
228 case 'boolean':
|
|
229
|
-
229 line += `table.boolean('${fieldName}')`
|
|
230
|
-
230 break
|
|
231
|
-
231 case 'date':
|
|
232
|
-
232 line += `table.date('${fieldName}')`
|
|
233
|
-
233 break
|
|
234
|
-
234 case 'dateTime':
|
|
235
|
-
235 line += `table.datetime('${fieldName}')`
|
|
236
|
-
236 break
|
|
237
|
-
237 case 'time':
|
|
238
|
-
238 line += `table.time('${fieldName}')`
|
|
239
|
-
239 break
|
|
240
|
-
240 case 'timestamp':
|
|
241
|
-
241 line += `table.integer('${fieldName}')`
|
|
242
|
-
242 break
|
|
243
|
-
243 case 'array':
|
|
244
|
-
244 case 'object':
|
|
245
|
-
245 case 'serialize':
|
|
246
|
-
246 line += `table.json('${fieldName}')`
|
|
247
|
-
247 break
|
|
248
|
-
248 case 'blob':
|
|
249
|
-
249 case 'file':
|
|
250
|
-
250 line += `table.binary('${fieldName}')`
|
|
251
|
-
251 break
|
|
252
|
-
252 default:
|
|
253
|
-
253 line += `table.string('${fieldName}')`
|
|
254
|
-
254 }
|
|
255
|
-
255
|
|
256
|
-
256 // Add constraints
|
|
257
|
-
257 if (definition.nullable === true) {
|
|
258
|
-
258 line += '.nullable()'
|
|
259
|
-
259 } else if (definition.required === true || definition.nullable === false) {
|
|
260
|
-
260 line += '.notNullable()'
|
|
261
|
-
261 }
|
|
262
|
-
262
|
|
263
|
-
263 if (definition.defaultTo !== undefined) {
|
|
264
|
-
264 const defaultValue = typeof definition.defaultTo === 'string'
|
|
265
|
-
265 ? `'${definition.defaultTo}'`
|
|
266
|
-
266 : definition.defaultTo
|
|
267
|
-
267 line += `.defaultTo(${defaultValue})`
|
|
268
|
-
268 }
|
|
269
|
-
269
|
|
270
|
-
270 if (definition.unique === true) line += '.unique()'
|
|
271
|
-
271 if (definition.primary === true) line += '.primary()'
|
|
272
|
-
272 if (definition.index === true) line += '.index()'
|
|
273
|
-
273
|
|
274
|
-
274 if (definition.references) {
|
|
275
|
-
275 const refCol = definition.references.column || 'id'
|
|
276
|
-
276 line += `.references('${refCol}').inTable('${definition.references.table}')`
|
|
277
|
-
277 if (definition.references.onDelete) {
|
|
278
|
-
278 line += `.onDelete('${definition.references.onDelete}')`
|
|
279
|
-
279 }
|
|
280
|
-
280 if (definition.references.onUpdate) {
|
|
281
|
-
281 line += `.onUpdate('${definition.references.onUpdate}')`
|
|
282
|
-
282 }
|
|
283
|
-
283 }
|
|
284
|
-
284
|
|
285
|
-
285 if (definition.comment) {
|
|
286
|
-
286 line += `.comment('${definition.comment}')`
|
|
287
|
-
287 }
|
|
288
|
-
288
|
|
289
|
-
289 migration += line + ';\n'
|
|
290
|
-
290 }
|
|
291
|
-
291
|
|
292
|
-
292 if (timestamps) {
|
|
293
|
-
293 migration += ' table.timestamps(true, true);\n'
|
|
294
|
-
294 }
|
|
295
|
-
295
|
|
296
|
-
296 migration += ` });
|
|
297
|
-
297 };
|
|
298
|
-
298
|
|
299
|
-
299 exports.down = function(knex) {
|
|
300
|
-
300 return knex.schema.dropTable('${tableName}');
|
|
301
|
-
301 };`
|
|
302
|
-
302
|
|
303
|
-
303 return migration
|
|
304
|
-
304 }
|