json-rest-api 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +259 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
# Boundary leak checks (non-anyapi)
|
|
2
|
-
|
|
3
|
-
## transport touching db/knex signals
|
|
4
|
-
|
|
5
|
-
## plugins with direct knex access (review for contract bypass)
|
|
6
|
-
plugins/core/rest-api-positioning-plugin.js:23: if (!api.knex?.instance) {
|
|
7
|
-
plugins/core/rest-api-positioning-plugin.js:52: if (scopeVars.schemaInfo && api.knex?.instance) {
|
|
8
|
-
plugins/core/rest-api-positioning-plugin.js:53: const adapter = createStorageAdapter({ knex: api.knex.instance, schemaInfo: scopeVars.schemaInfo })
|
|
9
|
-
plugins/core/rest-api-positioning-plugin.js:197: if (!schemaInfo || !api.knex?.instance) {
|
|
10
|
-
plugins/core/rest-api-positioning-plugin.js:208: const knex = api.knex.instance
|
|
11
|
-
plugins/core/rest-api-positioning-plugin.js:422: const knex = api.knex.instance
|
|
12
|
-
plugins/core/rest-api-plugin.js:158: // Example: helpers.dataGet, helpers.dataPost, etc. will throw "No storage implementation" errors
|
|
13
|
-
plugins/core/rest-api-multihome-plugin.js:6: if (!api.knex?.instance) {
|
|
14
|
-
plugins/core/bulk-operations-plugin.js:420: if (params.atomic && api.knex?.instance && context.bulkOperation === undefined) {
|
|
15
|
-
plugins/core/bulk-operations-plugin.js:443: const knex = api.knex.instance
|
|
16
|
-
plugins/core/bulk-operations-plugin.js:447: const inserted = await knex(tableName)
|
|
17
|
-
plugins/core/rest-api-knex-plugin.js:73: api.knex = {
|
|
18
|
-
plugins/core/rest-api-knex-plugin.js:99: api.knex.helpers.getStorageAdapter = getScopeStorageAdapter
|
|
19
|
-
plugins/core/rest-api-knex-plugin.js:125: api.knex.capabilities = {
|
|
20
|
-
plugins/core/rest-api-knex-plugin.js:168: async ({ api }) => api.knex.instance.destroy()
|
|
21
|
-
plugins/core/rest-api-knex-plugin.js:187: await createKnexTable(api.knex.instance, vars.schemaInfo, tableSchemaInstance, scopeOptions)
|
|
22
|
-
plugins/core/rest-api-knex-plugin.js:198: api.knex.instance,
|
|
23
|
-
plugins/core/rest-api-knex-plugin.js:210: await addKnexFields(api.knex.instance, vars.schemaInfo.tableName, partialTableSchema)
|
|
24
|
-
plugins/core/rest-api-knex-plugin.js:232: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
25
|
-
plugins/core/rest-api-knex-plugin.js:235: helpers.dataExists = async ({ scopeName, context }) => {
|
|
26
|
-
plugins/core/rest-api-knex-plugin.js:245: const db = context.db || api.knex.instance
|
|
27
|
-
plugins/core/rest-api-knex-plugin.js:270: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
28
|
-
plugins/core/rest-api-knex-plugin.js:278: helpers.dataGet = async ({ scopeName, context, runHooks }) => {
|
|
29
|
-
plugins/core/rest-api-knex-plugin.js:299: const db = context.db || api.knex.instance
|
|
30
|
-
plugins/core/rest-api-knex-plugin.js:369: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
31
|
-
plugins/core/rest-api-knex-plugin.js:372: helpers.dataGetMinimal = async ({ scopeName, context }) => {
|
|
32
|
-
plugins/core/rest-api-knex-plugin.js:381: const db = context.db || api.knex.instance
|
|
33
|
-
plugins/core/rest-api-knex-plugin.js:426: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
34
|
-
plugins/core/rest-api-knex-plugin.js:433: helpers.dataQuery = async ({ scopeName, context, runHooks }) => {
|
|
35
|
-
plugins/core/rest-api-knex-plugin.js:442: const db = context.db || api.knex.instance
|
|
36
|
-
plugins/core/rest-api-knex-plugin.js:893: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
37
|
-
plugins/core/rest-api-knex-plugin.js:899: helpers.dataPost = async ({ scopeName, context }) => {
|
|
38
|
-
plugins/core/rest-api-knex-plugin.js:907: const db = context.db || api.knex.instance
|
|
39
|
-
plugins/core/rest-api-knex-plugin.js:937: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
40
|
-
plugins/core/rest-api-knex-plugin.js:946: helpers.dataPut = async ({ scopeName, context }) => {
|
|
41
|
-
plugins/core/rest-api-knex-plugin.js:955: const db = context.db || api.knex.instance
|
|
42
|
-
plugins/core/rest-api-knex-plugin.js:1022: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
43
|
-
plugins/core/rest-api-knex-plugin.js:1030: helpers.dataPatch = async ({ scopeName, context }) => {
|
|
44
|
-
plugins/core/rest-api-knex-plugin.js:1039: const db = context.db || api.knex.instance
|
|
45
|
-
plugins/core/rest-api-knex-plugin.js:1091: * @param {Object} params.context.db - Database connection (knex instance or transaction)
|
|
46
|
-
plugins/core/rest-api-knex-plugin.js:1095: helpers.dataDelete = async ({ scopeName, context }) => {
|
|
47
|
-
plugins/core/rest-api-knex-plugin.js:1105: const db = context.db || api.knex.instance
|
|
48
|
-
|
|
49
|
-
## method-layer to storage-contract call sites
|
|
50
|
-
plugins/core/rest-api-plugin-methods/query.js:126: await scope.checkPermissions({
|
|
51
|
-
plugins/core/rest-api-plugin-methods/query.js:133: context.record = await helpers.dataQuery({
|
|
52
|
-
plugins/core/rest-api-plugin-methods/put.js:95: const minimalRecord = await helpers.dataGetMinimal({
|
|
53
|
-
plugins/core/rest-api-plugin-methods/put.js:171: await scope.checkPermissions({
|
|
54
|
-
plugins/core/rest-api-plugin-methods/put.js:191: await helpers.dataPut({
|
|
55
|
-
plugins/core/rest-api-plugin-methods/post.js:98: await scope.checkPermissions({
|
|
56
|
-
plugins/core/rest-api-plugin-methods/post.js:118: context.id = await helpers.dataPost({
|
|
57
|
-
plugins/core/rest-api-plugin-methods/delete.js:39: (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
58
|
-
plugins/core/rest-api-plugin-methods/delete.js:47: const minimalRecord = await helpers.dataGetMinimal({
|
|
59
|
-
plugins/core/rest-api-plugin-methods/delete.js:68: await scope.checkPermissions({
|
|
60
|
-
plugins/core/rest-api-plugin-methods/delete.js:81: await helpers.dataDelete({
|
|
61
|
-
plugins/core/rest-api-plugin-methods/delete-relationship.js:22: (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
62
|
-
plugins/core/rest-api-plugin-methods/post-relationship.js:23: (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
63
|
-
plugins/core/rest-api-plugin-methods/common.js:128: (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
64
|
-
plugins/core/rest-api-plugin-methods/common.js:450: const record = await helpers.dataGetMinimal({
|
|
65
|
-
plugins/core/rest-api-plugin-methods/common.js:557: const currentRecord = await helpers.dataGetMinimal({
|
|
66
|
-
plugins/core/rest-api-plugin-methods/patch-relationship.js:18: (helpers.newTransaction && !params.transaction ? await helpers.newTransaction() : null)
|
|
67
|
-
plugins/core/rest-api-plugin-methods/get.js:69: const minimalRecord = await helpers.dataGetMinimal({
|
|
68
|
-
plugins/core/rest-api-plugin-methods/get.js:89: await scope.checkPermissions({
|
|
69
|
-
plugins/core/rest-api-plugin-methods/get.js:97: context.record = await helpers.dataGet({
|
|
70
|
-
plugins/core/rest-api-plugin-methods/patch.js:87: const minimalRecord = await helpers.dataGetMinimal({
|
|
71
|
-
plugins/core/rest-api-plugin-methods/patch.js:103: await scope.checkPermissions({
|
|
72
|
-
plugins/core/rest-api-plugin-methods/patch.js:131: await helpers.dataPatch({
|
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
1 import { RestApiValidationError, RestApiResourceError } from '../../lib/rest-api-errors.js'
|
|
2
|
-
2
|
|
3
|
-
3 export const BulkOperationsPlugin = {
|
|
4
|
-
4 name: 'bulk-operations',
|
|
5
|
-
5 dependencies: ['rest-api'],
|
|
6
|
-
6
|
|
7
|
-
7 async install ({ api, log, addHook, addScopeMethod, helpers, pluginOptions }) {
|
|
8
|
-
8 const bulkOptions = pluginOptions || {}
|
|
9
|
-
9 const {
|
|
10
|
-
10 maxBulkOperations = 100,
|
|
11
|
-
11 defaultAtomic = true,
|
|
12
|
-
12 batchSize = 100,
|
|
13
|
-
13 enableOptimizations = true
|
|
14
|
-
14 } = bulkOptions
|
|
15
|
-
15
|
|
16
|
-
16 log.info('Installing Bulk Operations plugin', { maxBulkOperations, defaultAtomic })
|
|
17
|
-
17
|
|
18
|
-
18 // Add bulk methods to each scope
|
|
19
|
-
19 addScopeMethod('bulkPost', async ({ scope, scopeName, params, context, runHooks }) => {
|
|
20
|
-
20 const { inputRecords, atomic = defaultAtomic } = params
|
|
21
|
-
21
|
|
22
|
-
22 // Validate bulk size
|
|
23
|
-
23 if (!Array.isArray(inputRecords) || inputRecords.length === 0) {
|
|
24
|
-
24 throw new RestApiValidationError('Bulk operations require an array of records', {
|
|
25
|
-
25 fields: ['data'],
|
|
26
|
-
26 violations: [{ field: 'data', rule: 'required_array', message: 'Must be a non-empty array' }]
|
|
27
|
-
27 })
|
|
28
|
-
28 }
|
|
29
|
-
29
|
|
30
|
-
30 if (inputRecords.length > maxBulkOperations) {
|
|
31
|
-
31 throw new RestApiValidationError(`Bulk operations limited to ${maxBulkOperations} records`, {
|
|
32
|
-
32 fields: ['data'],
|
|
33
|
-
33 violations: [{
|
|
34
|
-
34 field: 'data',
|
|
35
|
-
35 rule: 'max_items',
|
|
36
|
-
36 message: `Cannot process more than ${maxBulkOperations} records at once`
|
|
37
|
-
37 }]
|
|
38
|
-
38 })
|
|
39
|
-
39 }
|
|
40
|
-
40
|
|
41
|
-
41 const results = []
|
|
42
|
-
42 const errors = []
|
|
43
|
-
43 let transaction = null
|
|
44
|
-
44
|
|
45
|
-
45 try {
|
|
46
|
-
46 // Start transaction if atomic mode
|
|
47
|
-
47 if (atomic && helpers.newTransaction) {
|
|
48
|
-
48 transaction = await helpers.newTransaction()
|
|
49
|
-
49 }
|
|
50
|
-
50
|
|
51
|
-
51 // Process records in batches
|
|
52
|
-
52 for (let i = 0; i < inputRecords.length; i += batchSize) {
|
|
53
|
-
53 const batch = inputRecords.slice(i, i + batchSize)
|
|
54
|
-
54
|
|
55
|
-
55 for (let j = 0; j < batch.length; j++) {
|
|
56
|
-
56 const recordIndex = i + j
|
|
57
|
-
57 const inputRecord = batch[j]
|
|
58
|
-
58
|
|
59
|
-
59 try {
|
|
60
|
-
60 // Create individual context for each record
|
|
61
|
-
61 const recordContext = {
|
|
62
|
-
62 ...context,
|
|
63
|
-
63 bulkOperation: true,
|
|
64
|
-
64 bulkIndex: recordIndex
|
|
65
|
-
65 }
|
|
66
|
-
66
|
|
67
|
-
67 // Use the existing post method with transaction
|
|
68
|
-
68 const result = await scope.post({
|
|
69
|
-
69 inputRecord: inputRecord.data ? inputRecord : { data: inputRecord },
|
|
70
|
-
70 transaction
|
|
71
|
-
71 }, recordContext)
|
|
72
|
-
72
|
|
73
|
-
73 results.push({
|
|
74
|
-
74 index: recordIndex,
|
|
75
|
-
75 status: 'success',
|
|
76
|
-
76 data: result.data
|
|
77
|
-
77 })
|
|
78
|
-
78 } catch (error) {
|
|
79
|
-
79 if (atomic) {
|
|
80
|
-
80 // In atomic mode, rollback and throw
|
|
81
|
-
81 if (transaction) await transaction.rollback()
|
|
82
|
-
82 throw error
|
|
83
|
-
83 } else {
|
|
84
|
-
84 // In non-atomic mode, collect errors
|
|
85
|
-
85 errors.push({
|
|
86
|
-
86 index: recordIndex,
|
|
87
|
-
87 status: 'error',
|
|
88
|
-
88 error: {
|
|
89
|
-
89 code: error.code || 'UNKNOWN_ERROR',
|
|
90
|
-
90 message: error.message,
|
|
91
|
-
91 details: error.details
|
|
92
|
-
92 }
|
|
93
|
-
93 })
|
|
94
|
-
94 }
|
|
95
|
-
95 }
|
|
96
|
-
96 }
|
|
97
|
-
97 }
|
|
98
|
-
98
|
|
99
|
-
99 // Commit transaction if atomic
|
|
100
|
-
100 if (transaction) {
|
|
101
|
-
101 await transaction.commit()
|
|
102
|
-
102 }
|
|
103
|
-
103
|
|
104
|
-
104 // Build response
|
|
105
|
-
105 return {
|
|
106
|
-
106 data: results.filter(r => r.status === 'success').map(r => r.data),
|
|
107
|
-
107 errors: errors.length > 0 ? errors : undefined,
|
|
108
|
-
108 meta: {
|
|
109
|
-
109 total: inputRecords.length,
|
|
110
|
-
110 succeeded: results.length,
|
|
111
|
-
111 failed: errors.length,
|
|
112
|
-
112 atomic
|
|
113
|
-
113 }
|
|
114
|
-
114 }
|
|
115
|
-
115 } catch (error) {
|
|
116
|
-
116 // Ensure rollback on error
|
|
117
|
-
117 if (transaction && !transaction.isCompleted()) {
|
|
118
|
-
118 await transaction.rollback()
|
|
119
|
-
119 }
|
|
120
|
-
120 throw error
|
|
121
|
-
121 }
|
|
122
|
-
122 })
|
|
123
|
-
123
|
|
124
|
-
124 addScopeMethod('bulkPatch', async ({ scope, scopeName, params, context, runHooks }) => {
|
|
125
|
-
125 const { operations, atomic = defaultAtomic } = params
|
|
126
|
-
126
|
|
127
|
-
127 // Validate operations
|
|
128
|
-
128 if (!Array.isArray(operations) || operations.length === 0) {
|
|
129
|
-
129 throw new RestApiValidationError('Bulk patch requires an array of operations', {
|
|
130
|
-
130 fields: ['operations'],
|
|
131
|
-
131 violations: [{ field: 'operations', rule: 'required_array', message: 'Must be a non-empty array' }]
|
|
132
|
-
132 })
|
|
133
|
-
133 }
|
|
134
|
-
134
|
|
135
|
-
135 if (operations.length > maxBulkOperations) {
|
|
136
|
-
136 throw new RestApiValidationError(`Bulk operations limited to ${maxBulkOperations} operations`, {
|
|
137
|
-
137 fields: ['operations'],
|
|
138
|
-
138 violations: [{
|
|
139
|
-
139 field: 'operations',
|
|
140
|
-
140 rule: 'max_items',
|
|
141
|
-
141 message: `Cannot process more than ${maxBulkOperations} operations at once`
|
|
142
|
-
142 }]
|
|
143
|
-
143 })
|
|
144
|
-
144 }
|
|
145
|
-
145
|
|
146
|
-
146 const results = []
|
|
147
|
-
147 const errors = []
|
|
148
|
-
148 let transaction = null
|
|
149
|
-
149
|
|
150
|
-
150 try {
|
|
151
|
-
151 if (atomic && helpers.newTransaction) {
|
|
152
|
-
152 transaction = await helpers.newTransaction()
|
|
153
|
-
153 }
|
|
154
|
-
154
|
|
155
|
-
155 for (let i = 0; i < operations.length; i++) {
|
|
156
|
-
156 const operation = operations[i]
|
|
157
|
-
157
|
|
158
|
-
158 // Validate operation structure
|
|
159
|
-
159 if (!operation.id || !operation.data) {
|
|
160
|
-
160 errors.push({
|
|
161
|
-
161 index: i,
|
|
162
|
-
162 status: 'error',
|
|
163
|
-
163 error: {
|
|
164
|
-
164 code: 'INVALID_OPERATION',
|
|
165
|
-
165 message: 'Operation must include id and data',
|
|
166
|
-
166 details: { operation }
|
|
167
|
-
167 }
|
|
168
|
-
168 })
|
|
169
|
-
169 if (atomic) {
|
|
170
|
-
170 if (transaction) await transaction.rollback()
|
|
171
|
-
171 throw new RestApiValidationError('Invalid operation structure', {
|
|
172
|
-
172 fields: [`operations[${i}]`],
|
|
173
|
-
173 violations: [{
|
|
174
|
-
174 field: `operations[${i}]`,
|
|
175
|
-
175 rule: 'required_fields',
|
|
176
|
-
176 message: 'Operation must include id and data'
|
|
177
|
-
177 }]
|
|
178
|
-
178 })
|
|
179
|
-
179 }
|
|
180
|
-
180 continue
|
|
181
|
-
181 }
|
|
182
|
-
182
|
|
183
|
-
183 try {
|
|
184
|
-
184 const recordContext = {
|
|
185
|
-
185 ...context,
|
|
186
|
-
186 bulkOperation: true,
|
|
187
|
-
187 bulkIndex: i
|
|
188
|
-
188 }
|
|
189
|
-
189
|
|
190
|
-
190 const result = await scope.patch({
|
|
191
|
-
191 id: operation.id,
|
|
192
|
-
192 inputRecord: { data: operation.data },
|
|
193
|
-
193 transaction
|
|
194
|
-
194 }, recordContext)
|
|
195
|
-
195
|
|
196
|
-
196 // If patch doesn't return full record, fetch it
|
|
197
|
-
197 let resultData = result.data
|
|
198
|
-
198 if (!resultData && result.id) {
|
|
199
|
-
199 const fetchedRecord = await scope.get({
|
|
200
|
-
200 id: result.id,
|
|
201
|
-
201 transaction
|
|
202
|
-
202 }, recordContext)
|
|
203
|
-
203 resultData = fetchedRecord.data
|
|
204
|
-
204 }
|
|
205
|
-
205
|
|
206
|
-
206 results.push({
|
|
207
|
-
207 index: i,
|
|
208
|
-
208 id: operation.id,
|
|
209
|
-
209 status: 'success',
|
|
210
|
-
210 data: resultData
|
|
211
|
-
211 })
|
|
212
|
-
212 } catch (error) {
|
|
213
|
-
213 if (atomic) {
|
|
214
|
-
214 if (transaction) await transaction.rollback()
|
|
215
|
-
215 throw error
|
|
216
|
-
216 } else {
|
|
217
|
-
217 errors.push({
|
|
218
|
-
218 index: i,
|
|
219
|
-
219 id: operation.id,
|
|
220
|
-
220 status: 'error',
|
|
221
|
-
221 error: {
|
|
222
|
-
222 code: error.code || 'UNKNOWN_ERROR',
|
|
223
|
-
223 message: error.message,
|
|
224
|
-
224 details: error.details
|
|
225
|
-
225 }
|
|
226
|
-
226 })
|
|
227
|
-
227 }
|
|
228
|
-
228 }
|
|
229
|
-
229 }
|
|
230
|
-
230
|
|
231
|
-
231 if (transaction) {
|
|
232
|
-
232 await transaction.commit()
|
|
233
|
-
233 }
|
|
234
|
-
234
|
|
235
|
-
235 return {
|
|
236
|
-
236 data: results.filter(r => r.status === 'success').map(r => r.data),
|
|
237
|
-
237 errors: errors.length > 0 ? errors : undefined,
|
|
238
|
-
238 meta: {
|
|
239
|
-
239 total: operations.length,
|
|
240
|
-
240 succeeded: results.length,
|
|
241
|
-
241 failed: errors.length,
|
|
242
|
-
242 atomic
|
|
243
|
-
243 }
|
|
244
|
-
244 }
|
|
245
|
-
245 } catch (error) {
|
|
246
|
-
246 if (transaction && !transaction.isCompleted()) {
|
|
247
|
-
247 await transaction.rollback()
|
|
248
|
-
248 }
|
|
249
|
-
249 throw error
|
|
250
|
-
250 }
|
|
251
|
-
251 })
|
|
252
|
-
252
|
|
253
|
-
253 addScopeMethod('bulkDelete', async ({ scope, scopeName, params, context, runHooks }) => {
|
|
254
|
-
254 const { ids, atomic = defaultAtomic } = params
|
|
255
|
-
255
|
|
256
|
-
256 // Validate IDs
|
|
257
|
-
257 if (!Array.isArray(ids) || ids.length === 0) {
|
|
258
|
-
258 throw new RestApiValidationError('Bulk delete requires an array of IDs', {
|
|
259
|
-
259 fields: ['ids'],
|
|
260
|
-
260 violations: [{ field: 'ids', rule: 'required_array', message: 'Must be a non-empty array' }]
|
|
261
|
-
261 })
|
|
262
|
-
262 }
|
|
263
|
-
263
|
|
264
|
-
264 if (ids.length > maxBulkOperations) {
|
|
265
|
-
265 throw new RestApiValidationError(`Bulk operations limited to ${maxBulkOperations} IDs`, {
|
|
266
|
-
266 fields: ['ids'],
|
|
267
|
-
267 violations: [{
|
|
268
|
-
268 field: 'ids',
|
|
269
|
-
269 rule: 'max_items',
|
|
270
|
-
270 message: `Cannot process more than ${maxBulkOperations} IDs at once`
|
|
271
|
-
271 }]
|
|
272
|
-
272 })
|
|
273
|
-
273 }
|
|
274
|
-
274
|
|
275
|
-
275 const results = []
|
|
276
|
-
276 const errors = []
|
|
277
|
-
277 let transaction = null
|
|
278
|
-
278
|
|
279
|
-
279 try {
|
|
280
|
-
280 if (atomic && helpers.newTransaction) {
|
|
281
|
-
281 transaction = await helpers.newTransaction()
|
|
282
|
-
282 }
|
|
283
|
-
283
|
|
284
|
-
284 // Process deletes
|
|
285
|
-
285 for (let i = 0; i < ids.length; i++) {
|
|
286
|
-
286 const id = ids[i]
|
|
287
|
-
287
|
|
288
|
-
288 try {
|
|
289
|
-
289 const recordContext = {
|
|
290
|
-
290 ...context,
|
|
291
|
-
291 bulkOperation: true,
|
|
292
|
-
292 bulkIndex: i
|
|
293
|
-
293 }
|
|
294
|
-
294
|
|
295
|
-
295 await scope.delete({
|
|
296
|
-
296 id,
|
|
297
|
-
297 transaction
|
|
298
|
-
298 }, recordContext)
|
|
299
|
-
299
|
|
300
|
-
300 results.push({
|
|
301
|
-
301 index: i,
|
|
302
|
-
302 id,
|
|
303
|
-
303 status: 'success'
|
|
304
|
-
304 })
|
|
305
|
-
305 } catch (error) {
|
|
306
|
-
306 if (atomic) {
|
|
307
|
-
307 if (transaction) await transaction.rollback()
|
|
308
|
-
308 throw error
|
|
309
|
-
309 } else {
|
|
310
|
-
310 errors.push({
|
|
311
|
-
311 index: i,
|
|
312
|
-
312 id,
|
|
313
|
-
313 status: 'error',
|
|
314
|
-
314 error: {
|
|
315
|
-
315 code: error.code || 'UNKNOWN_ERROR',
|
|
316
|
-
316 message: error.message,
|
|
317
|
-
317 details: error.details
|
|
318
|
-
318 }
|
|
319
|
-
319 })
|
|
320
|
-
320 }
|
|
321
|
-
321 }
|
|
322
|
-
322 }
|
|
323
|
-
323
|
|
324
|
-
324 if (transaction) {
|
|
325
|
-
325 await transaction.commit()
|
|
326
|
-
326 }
|
|
327
|
-
327
|
|
328
|
-
328 return {
|
|
329
|
-
329 meta: {
|
|
330
|
-
330 total: ids.length,
|
|
331
|
-
331 succeeded: results.length,
|
|
332
|
-
332 failed: errors.length,
|
|
333
|
-
333 deleted: results.filter(r => r.status === 'success').map(r => r.id),
|
|
334
|
-
334 atomic
|
|
335
|
-
335 },
|
|
336
|
-
336 errors: errors.length > 0 ? errors : undefined
|
|
337
|
-
337 }
|
|
338
|
-
338 } catch (error) {
|
|
339
|
-
339 if (transaction && !transaction.isCompleted()) {
|
|
340
|
-
340 await transaction.rollback()
|
|
341
|
-
341 }
|
|
342
|
-
342 throw error
|
|
343
|
-
343 }
|
|
344
|
-
344 })
|
|
345
|
-
345
|
|
346
|
-
346 // Hook into scope creation to add bulk routes
|
|
347
|
-
347 addHook('afterAddScope', 'bulkOperationsRoutes', {}, async ({ scopeName }) => {
|
|
348
|
-
348 // Note: context is not available here during route registration
|
|
349
|
-
349 // The urlPrefix will be calculated per-request in the handler
|
|
350
|
-
350 const urlPrefix = api.vars.transport?.mountPath || ''
|
|
351
|
-
351 const scopePath = `${urlPrefix}/${scopeName}`
|
|
352
|
-
352
|
|
353
|
-
353 // Create route handlers
|
|
354
|
-
354 const createBulkRouteHandler = (method) => {
|
|
355
|
-
355 return async ({ context, body, query }) => {
|
|
356
|
-
356 try {
|
|
357
|
-
357 // Parse query params for atomic mode override
|
|
358
|
-
358 const atomic = query?.atomic !== undefined
|
|
359
|
-
359 ? query.atomic === 'true'
|
|
360
|
-
360 : defaultAtomic
|
|
361
|
-
361
|
|
362
|
-
362 let params
|
|
363
|
-
363 if (method === 'bulkPost') {
|
|
364
|
-
364 // For bulk create, expect array in data field (JSON:API style)
|
|
365
|
-
365 params = {
|
|
366
|
-
366 inputRecords: body?.data || body,
|
|
367
|
-
367 atomic
|
|
368
|
-
368 }
|
|
369
|
-
369 } else if (method === 'bulkPatch') {
|
|
370
|
-
370 // For bulk update, expect operations array
|
|
371
|
-
371 params = {
|
|
372
|
-
372 operations: body?.operations || body,
|
|
373
|
-
373 atomic
|
|
374
|
-
374 }
|
|
375
|
-
375 } else if (method === 'bulkDelete') {
|
|
376
|
-
376 // For bulk delete, expect IDs array
|
|
377
|
-
377 params = {
|
|
378
|
-
378 ids: body?.data || body?.ids || body,
|
|
379
|
-
379 atomic
|
|
380
|
-
380 }
|
|
381
|
-
381 }
|
|
382
|
-
382
|
|
383
|
-
383 // Call the scope method
|
|
384
|
-
384 const result = await api.scopes[scopeName][method](params)
|
|
385
|
-
385
|
|
386
|
-
386 return result
|
|
387
|
-
387 } catch (error) {
|
|
388
|
-
388 // Let transport plugin handle error mapping
|
|
389
|
-
389 throw error
|
|
390
|
-
390 }
|
|
391
|
-
391 }
|
|
392
|
-
392 }
|
|
393
|
-
393
|
|
394
|
-
394 // Register bulk routes
|
|
395
|
-
395 await api.addRoute({
|
|
396
|
-
396 method: 'POST',
|
|
397
|
-
397 path: `${scopePath}/bulk`,
|
|
398
|
-
398 handler: createBulkRouteHandler('bulkPost')
|
|
399
|
-
399 })
|
|
400
|
-
400
|
|
401
|
-
401 await api.addRoute({
|
|
402
|
-
402 method: 'PATCH',
|
|
403
|
-
403 path: `${scopePath}/bulk`,
|
|
404
|
-
404 handler: createBulkRouteHandler('bulkPatch')
|
|
405
|
-
405 })
|
|
406
|
-
406
|
|
407
|
-
407 await api.addRoute({
|
|
408
|
-
408 method: 'DELETE',
|
|
409
|
-
409 path: `${scopePath}/bulk`,
|
|
410
|
-
410 handler: createBulkRouteHandler('bulkDelete')
|
|
411
|
-
411 })
|
|
412
|
-
412
|
|
413
|
-
413 log.debug(`Added bulk operation routes for scope: ${scopeName}`)
|
|
414
|
-
414 })
|
|
415
|
-
415
|
|
416
|
-
416 // Add optimized bulk insert for Knex if available
|
|
417
|
-
417 if (enableOptimizations) {
|
|
418
|
-
418 addHook('beforeBulkPost', 'optimizedBulkInsert', {}, async ({ scope, params, context }) => {
|
|
419
|
-
419 // Check if we can use optimized path
|
|
420
|
-
420 if (params.atomic && api.knex?.instance && context.bulkOperation === undefined) {
|
|
421
|
-
421 // This is the main bulk operation, not individual records
|
|
422
|
-
422 const { inputRecords } = params
|
|
423
|
-
423
|
|
424
|
-
424 // Transform records for direct insertion
|
|
425
|
-
425 const recordsToInsert = []
|
|
426
|
-
426 for (const inputRecord of inputRecords) {
|
|
427
|
-
427 // Run validation
|
|
428
|
-
428 const validated = await scope.validateInput({
|
|
429
|
-
429 inputRecord,
|
|
430
|
-
430 operation: 'create'
|
|
431
|
-
431 })
|
|
432
|
-
432
|
|
433
|
-
433 // Transform to database format
|
|
434
|
-
434 const dbRecord = await scope.transformForDatabase({
|
|
435
|
-
435 record: validated,
|
|
436
|
-
436 operation: 'create'
|
|
437
|
-
437 })
|
|
438
|
-
438
|
|
439
|
-
439 recordsToInsert.push(dbRecord)
|
|
440
|
-
440 }
|
|
441
|
-
441
|
|
442
|
-
442 // Perform bulk insert
|
|
443
|
-
443 const knex = api.knex.instance
|
|
444
|
-
444 const tableName = scope.vars.schemaInfo.tableName
|
|
445
|
-
445
|
|
446
|
-
446 try {
|
|
447
|
-
447 const inserted = await knex(tableName)
|
|
448
|
-
448 .insert(recordsToInsert)
|
|
449
|
-
449 .returning('*')
|
|
450
|
-
450
|
|
451
|
-
451 // Transform back to API format
|
|
452
|
-
452 const results = []
|
|
453
|
-
453 for (const record of inserted) {
|
|
454
|
-
454 const apiRecord = await scope.transformFromDatabase({ record })
|
|
455
|
-
455 results.push(apiRecord)
|
|
456
|
-
456 }
|
|
457
|
-
457
|
|
458
|
-
458 // Return optimized result
|
|
459
|
-
459 return {
|
|
460
|
-
460 data: results,
|
|
461
|
-
461 meta: {
|
|
462
|
-
462 total: inputRecords.length,
|
|
463
|
-
463 succeeded: results.length,
|
|
464
|
-
464 failed: 0,
|
|
465
|
-
465 atomic: true,
|
|
466
|
-
466 optimized: true
|
|
467
|
-
467 },
|
|
468
|
-
468 skipDefault: true // Tell the default handler to skip
|
|
469
|
-
469 }
|
|
470
|
-
470 } catch (error) {
|
|
471
|
-
471 // Fall back to default handling
|
|
472
|
-
472 log.warn('Optimized bulk insert failed, falling back to default', { error: error.message })
|
|
473
|
-
473 }
|
|
474
|
-
474 }
|
|
475
|
-
475 })
|
|
476
|
-
476 }
|
|
477
|
-
477
|
|
478
|
-
478 log.info('Bulk Operations plugin installed successfully')
|
|
479
|
-
479 }
|
|
480
|
-
480 }
|