json-rest-api 1.0.22 → 1.0.24
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +1 -2
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
- package/README.md +22 -1
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
- package/docs/API.md +196 -38
- package/docs/COMPARISON.md +2 -2
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +111 -5
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
- package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
- package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +370 -0
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
- package/docs/GUIDE/index.md +18 -9
- package/docs/QUICKSTART.md +5 -1
- package/docs/index.md +1 -1
- package/index.js +3 -2
- package/lib/enhanced-logger.js +1 -1
- package/package.json +6 -2
- package/plugins/core/bulk-operations-plugin.js +26 -34
- package/plugins/core/connectors/express-plugin.js +97 -190
- package/plugins/core/connectors/fastify-plugin.js +206 -0
- package/plugins/core/connectors/lib/busboy-detector.js +4 -4
- package/plugins/core/connectors/lib/connector-core.js +195 -0
- package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
- package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
- package/plugins/core/lib/anyapi/schema-utils.js +43 -4
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +4 -1
- package/plugins/core/lib/dbIntrospection.js +1078 -0
- package/plugins/core/lib/dbTablesOperations.js +1111 -160
- package/plugins/core/lib/querying/knex-cross-table-search.js +0 -7
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -3
- package/plugins/core/lib/querying/knex-process-includes.js +3 -4
- package/plugins/core/lib/querying/knex-query-helpers.js +21 -76
- package/plugins/core/lib/querying/knex-relationship-includes.js +468 -215
- package/plugins/core/lib/querying/query-builder-utils.js +19 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
- package/plugins/core/lib/querying/sort-helpers.js +33 -0
- package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
- package/plugins/core/lib/querying/url-helpers.js +8 -2
- package/plugins/core/lib/querying-writing/compile-schemas.js +20 -5
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -14
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
- package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
- package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +131 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +0 -2
- package/plugins/core/lib/querying-writing/simplified-helpers.js +4 -11
- package/plugins/core/lib/storage/canonical-storage-mapping.js +133 -0
- package/plugins/core/lib/storage/storage-adapter.js +82 -4
- package/plugins/core/lib/storage/storage-mapping.js +274 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +4 -2
- package/plugins/core/rest-api-anyapi-knex-plugin.js +531 -312
- package/plugins/core/rest-api-autofilter-plugin.js +414 -0
- package/plugins/core/rest-api-knex-plugin.js +271 -226
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +1 -0
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
- package/plugins/core/rest-api-plugin-methods/common.js +148 -92
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/delete.js +6 -1
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +7 -2
- package/plugins/core/rest-api-plugin-methods/get.js +29 -14
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +10 -3
- package/plugins/core/rest-api-plugin-methods/patch.js +56 -12
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +11 -2
- package/plugins/core/rest-api-plugin-methods/post.js +21 -8
- package/plugins/core/rest-api-plugin-methods/put.js +73 -21
- package/plugins/core/rest-api-plugin-methods/query.js +15 -53
- package/plugins/core/rest-api-plugin-methods/release.js +0 -2
- package/plugins/core/rest-api-plugin.js +6 -0
- package/plugins/core/rest-api-positioning-plugin.js +2 -5
- package/plugins/core/rest-api-query-projections-plugin.js +130 -0
- package/plugins/core/socketio-plugin.js +1 -4
- package/plugins/storage/local-storage.js +1 -1
- package/plugins/storage/s3-storage.js +5 -2
- package/quickTest.js +1 -0
- package/scripts/run-docs.js +80 -84
- package/test-debug.js +1 -1
- package/tests/README.md +7 -0
- package/tests/anyapi-basic.test.js +3 -0
- package/tests/autofilter.test.js +463 -0
- package/tests/custom-id-mysql-returning-fallback.test.js +128 -0
- package/tests/custom-id-simplified-post.test.js +75 -0
- package/tests/custom-idproperty-relationships.test.js +33 -0
- package/tests/db-introspection.test.js +521 -0
- package/tests/db-table-operations.test.js +786 -0
- package/tests/fastify-plugin.test.js +156 -0
- package/tests/field-getters.test.js +1 -2
- package/tests/field-setters.test.js +0 -4
- package/tests/fixtures/api-configs.js +278 -176
- package/tests/helpers/fake-fastify.js +99 -0
- package/tests/http-connectors-parity.test.js +235 -0
- package/tests/id-normalization.test.js +444 -0
- package/tests/logical-id-write-validation.test.js +151 -0
- package/tests/pagination-cursor-multifield.test.js +11 -11
- package/tests/projected-fields.test.js +319 -0
- package/tests/relationships.test.js +3 -3
- package/tests/searchschema-knex-behavior.test.js +187 -0
- package/tests/searchschema-merge.test.js +21 -69
- package/tests/storage-mapping.test.js +259 -0
- package/audit/00-charter.md +0 -103
- package/audit/01-scope-map.md +0 -84
- package/audit/02-coverage-matrix.csv +0 -174
- package/audit/03-invariants.md +0 -123
- package/audit/04-standards-rubric.md +0 -56
- package/audit/05-competitor-rubric.md +0 -77
- package/audit/06-findings.md +0 -160
- package/audit/07-risk-register.csv +0 -10
- package/audit/08-remediation-roadmap.md +0 -177
- package/audit/09-signoff-check.md +0 -23
- package/audit/10-final-synthesis.md +0 -29
- package/audit/evidence/code/all-files-json-rest-api.txt +0 -168
- package/audit/evidence/code/all-files-json-rest-schema.txt +0 -12
- package/audit/evidence/code/boundary-leak-checks.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__bulk-operations-plugin.js.txt +0 -480
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__express-plugin.js.txt +0 -532
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__busboy-detector.js.txt +0 -175
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__formidable-detector.js.txt +0 -138
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__connectors__lib__request-helpers.js.txt +0 -222
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__file-handling-plugin.js.txt +0 -342
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__dbTablesOperations.js.txt +0 -304
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__fractional-positioning.js.txt +0 -188
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__compile-schemas.js.txt +0 -336
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__connectors-query-parser.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-capabilities.js.txt +0 -265
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__database-value-normalizers.js.txt +0 -369
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__default-data-helpers.js.txt +0 -72
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__field-utils.js.txt +0 -139
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-constants.js.txt +0 -45
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-field-helpers.js.txt +0 -396
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__knex-json-api-transformers.js.txt +0 -162
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__payload-validators.js.txt +0 -1314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__schema-helpers.js.txt +0 -488
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__scope-validations.js.txt +0 -484
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying-writing__simplified-helpers.js.txt +0 -857
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-cross-table-search.js.txt +0 -565
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-json-api-transformers-querying.js.txt +0 -485
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-pagination-helpers.js.txt +0 -560
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-process-includes.js.txt +0 -167
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers-base.js.txt +0 -109
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-query-helpers.js.txt +0 -1064
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-relationship-includes.js.txt +0 -1983
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__knex-window-queries.js.txt +0 -328
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__querying__url-helpers.js.txt +0 -119
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__storage__storage-adapter.js.txt +0 -160
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__many-to-many-manipulations.js.txt +0 -300
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__lib__writing__relationship-processor.js.txt +0 -245
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-access.js.txt +0 -459
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-cors-plugin.js.txt +0 -237
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-knex-plugin.js.txt +0 -1135
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-multihome-plugin.js.txt +0 -314
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__compile-resource-schemas.js.txt +0 -7
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-relationship-routes.js.txt +0 -71
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__register-scope-routes.js.txt +0 -102
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__turn-scope-init-into-vars.js.txt +0 -49
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-hooks__validate-include-configurations.js.txt +0 -39
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__add-route.js.txt +0 -23
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__check-permissions.js.txt +0 -13
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__common.js.txt +0 -639
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete-relationship.js.txt +0 -117
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__delete.js.txt +0 -104
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__enrich-attributes.js.txt +0 -199
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-related.js.txt +0 -353
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get-relationship.js.txt +0 -61
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__get.js.txt +0 -184
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch-relationship.js.txt +0 -55
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__patch.js.txt +0 -191
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post-relationship.js.txt +0 -110
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__post.js.txt +0 -168
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__put.js.txt +0 -253
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__query.js.txt +0 -207
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin-methods__release.js.txt +0 -12
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-plugin.js.txt +0 -165
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__rest-api-positioning-plugin.js.txt +0 -668
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__core__socketio-plugin.js.txt +0 -621
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__local-storage.js.txt +0 -218
- package/audit/evidence/code/critical-pass1/json-rest-api__plugins__storage__s3-storage.js.txt +0 -124
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__CorePlugin.js.txt +0 -269
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__Schema.js.txt +0 -242
- package/audit/evidence/code/critical-pass1/json-rest-schema__src__index.js.txt +0 -77
- package/audit/evidence/code/critical-pass1-risk-keywords.txt +0 -238
- package/audit/evidence/code/critical-pass1-summary.csv +0 -67
- package/audit/evidence/code/critical-pass2-summary.csv +0 -67
- package/audit/evidence/code/early-silent-zones-no-test-token.txt +0 -57
- package/audit/evidence/code/error-model-signals.txt +0 -98
- package/audit/evidence/code/git-files-json-rest-api.txt +0 -171
- package/audit/evidence/code/git-files-json-rest-schema.txt +0 -27
- package/audit/evidence/code/hook-flow-signals.txt +0 -282
- package/audit/evidence/code/idproperty-signals.txt +0 -157
- package/audit/evidence/code/import-cycles-non-anyapi.txt +0 -3
- package/audit/evidence/code/import-hotspots-non-anyapi.txt +0 -96
- package/audit/evidence/code/imports-json-rest-api.txt +0 -354
- package/audit/evidence/code/imports-json-rest-schema.txt +0 -15
- package/audit/evidence/code/in-scope-files-json-rest-api.txt +0 -160
- package/audit/evidence/code/in-scope-files-json-rest-schema.txt +0 -13
- package/audit/evidence/code/non-anyapi-core-files.txt +0 -101
- package/audit/evidence/code/permission-signals.txt +0 -193
- package/audit/evidence/code/query-include-signals.txt +0 -687
- package/audit/evidence/code/transaction-signals.txt +0 -28
- package/audit/evidence/code/validation-signals.txt +0 -650
- package/audit/evidence/code/write-path-signals.txt +0 -61
- package/audit/evidence/docs/docs-claims-signals.txt +0 -1075
- package/audit/evidence/docs/schema-docs-claims-signals.txt +0 -179
- package/audit/evidence/external/corpus-freeze.md +0 -21
- package/audit/evidence/external/npm-competitor-metadata.txt +0 -70
- package/audit/evidence/external/npm-download-signals.txt +0 -23
- package/audit/evidence/external/npm-jsonapi-server.txt +0 -8
- package/audit/evidence/external/npm-modern-jsonapi-metadata.txt +0 -38
- package/audit/evidence/external/readme-fortune-signals.txt +0 -26
- package/audit/evidence/external/readme-fortune.md +0 -165
- package/audit/evidence/external/readme-json-api-serializer-signals.txt +0 -80
- package/audit/evidence/external/readme-json-api-serializer.md +0 -690
- package/audit/evidence/external/readme-jsonapi-serde-server-signals.txt +0 -0
- package/audit/evidence/external/readme-jsonapi-serde-server.md +0 -22
- package/audit/evidence/external/readme-jsonapi-serializer-signals.txt +0 -63
- package/audit/evidence/external/readme-jsonapi-serializer.md +0 -315
- package/audit/evidence/external/readme-jsonapi-server-signals.txt +0 -19
- package/audit/evidence/external/readme-jsonapi-server.md +0 -104
- package/audit/evidence/external/readme-nestjs-json-api-signals.txt +0 -4
- package/audit/evidence/external/readme-nestjs-json-api.md +0 -94
- package/audit/evidence/external/readme-yaser-json-api-nestjs-signals.txt +0 -47
- package/audit/evidence/external/readme-yaser-json-api-nestjs.md +0 -447
- package/audit/evidence/external/standard-jsonapi-snippets.txt +0 -84
- package/audit/evidence/external/standard-rfc-snippets.txt +0 -168
- package/audit/evidence/external/standards-sources.txt +0 -6
- package/audit/evidence/runtime/baseline-commits.txt +0 -2
- package/audit/evidence/runtime/db-adapter-assumptions.txt +0 -3
- package/audit/evidence/runtime/environment.txt +0 -4
- package/audit/evidence/runtime/invariant-runtime-mapping.md +0 -38
- package/audit/evidence/runtime/probe-patch-relationship-error.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-error.log +0 -76
- package/audit/evidence/runtime/probe-patch-relationship-error.mjs +0 -67
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.exitcode +0 -1
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.log +0 -62
- package/audit/evidence/runtime/probe-patch-relationship-referenceerror.mjs +0 -47
- package/audit/evidence/runtime/test-exitcodes.txt +0 -4
- package/audit/evidence/runtime/test-summary.md +0 -107
- package/audit/evidence/runtime/trace-anchors.txt +0 -61
- package/audit/evidence/tests/access-multitenancy-test-signals.txt +0 -125
- package/audit/evidence/tests/failing-tests-summary.txt +0 -25
- package/audit/evidence/tests/npm-test-json-rest-api.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-api.log +0 -5885
- package/audit/evidence/tests/npm-test-json-rest-schema.exitcode +0 -1
- package/audit/evidence/tests/npm-test-json-rest-schema.log +0 -356
- package/audit/evidence/tests/query-include-test-signals.txt +0 -948
- package/audit/evidence/tests/relationship-test-signals.txt +0 -223
- package/audit/evidence/tests/skipped-tests.txt +0 -10
- package/audit/evidence/tests/test-suite-list-non-anyapi.txt +0 -30
- package/audit/evidence/tests/test-suite-list.txt +0 -32
- package/audit/subsystems/docs-drift.md +0 -49
- package/audit/subsystems/pass2-cross-review.md +0 -131
- package/audit/subsystems/plugins-access.md +0 -51
- package/audit/subsystems/plugins-bulk.md +0 -50
- package/audit/subsystems/plugins-cors.md +0 -48
- package/audit/subsystems/plugins-file.md +0 -53
- package/audit/subsystems/plugins-multihome.md +0 -50
- package/audit/subsystems/plugins-positioning.md +0 -54
- package/audit/subsystems/plugins-socketio.md +0 -50
- package/audit/subsystems/query-engine.md +0 -61
- package/audit/subsystems/relationships.md +0 -55
- package/audit/subsystems/rest-orchestration.md +0 -66
- package/audit/subsystems/schema-core.md +0 -64
- package/audit/subsystems/storage-knex.md +0 -57
- package/audit/subsystems/tests.md +0 -48
- package/audit/subsystems/transport-express.md +0 -55
- package/audit/traces/trace-read-get.md +0 -22
- package/audit/traces/trace-read-query.md +0 -25
- package/audit/traces/trace-relationship-endpoints.md +0 -31
- package/audit/traces/trace-write-delete.md +0 -17
- package/audit/traces/trace-write-patch.md +0 -26
- package/audit/traces/trace-write-post.md +0 -22
- package/audit/traces/trace-write-put.md +0 -23
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
- package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
- package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
- package/plugins/core/rest-api-access.js +0 -459
- package/plugins/core/rest-api-multihome-plugin.js +0 -314
- package/tests/multihome.test.js +0 -553
- package/tests/multihome.test.js.bak +0 -550
- package/tests/ownership-optout.test.js +0 -76
- package/tests/rest-api-access.test.js +0 -206
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import knexLib from 'knex'
|
|
4
|
+
import {
|
|
5
|
+
cleanTables,
|
|
6
|
+
createJsonApiDocument,
|
|
7
|
+
} from './helpers/test-utils.js'
|
|
8
|
+
import { storageMode } from './helpers/storage-mode.js'
|
|
9
|
+
import { createSearchSchemaMergeApi } from './fixtures/api-configs.js'
|
|
10
|
+
|
|
11
|
+
const maybeDescribe = storageMode.isAnyApi() ? describe.skip : describe
|
|
12
|
+
|
|
13
|
+
// Create Knex instance for tests - always use SQLite in-memory
|
|
14
|
+
const knex = knexLib({
|
|
15
|
+
client: 'better-sqlite3',
|
|
16
|
+
connection: {
|
|
17
|
+
filename: ':memory:'
|
|
18
|
+
},
|
|
19
|
+
useNullAsDefault: true
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
maybeDescribe('SearchSchema Merge Behavior (Knex execution)', () => {
|
|
23
|
+
let api
|
|
24
|
+
|
|
25
|
+
before(async () => {
|
|
26
|
+
api = await createSearchSchemaMergeApi(knex)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
after(async () => {
|
|
30
|
+
await knex.destroy()
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
beforeEach(async () => {
|
|
34
|
+
await cleanTables(knex, ['searchmerge_products', 'searchmerge_users', 'searchmerge_orders'])
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('should use exact matching for search:true strings unless filterOperator is explicit', async () => {
|
|
38
|
+
const context = {}
|
|
39
|
+
|
|
40
|
+
await api.resources.users.post({
|
|
41
|
+
inputRecord: createJsonApiDocument('users', {
|
|
42
|
+
username: 'ann',
|
|
43
|
+
email: 'ann@example.com',
|
|
44
|
+
bio: 'Writes API guides'
|
|
45
|
+
})
|
|
46
|
+
}, context)
|
|
47
|
+
|
|
48
|
+
await api.resources.users.post({
|
|
49
|
+
inputRecord: createJsonApiDocument('users', {
|
|
50
|
+
username: 'anna',
|
|
51
|
+
email: 'anna@example.com',
|
|
52
|
+
bio: 'Writing API tutorials'
|
|
53
|
+
})
|
|
54
|
+
}, context)
|
|
55
|
+
|
|
56
|
+
const usernameResults = await api.resources.users.query({
|
|
57
|
+
queryParams: {
|
|
58
|
+
filters: {
|
|
59
|
+
username: 'ann'
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}, context)
|
|
63
|
+
|
|
64
|
+
assert.equal(usernameResults.data.length, 1, 'search:true username should use exact matching')
|
|
65
|
+
assert.equal(usernameResults.data[0].attributes.username, 'ann')
|
|
66
|
+
|
|
67
|
+
const bioResults = await api.resources.users.query({
|
|
68
|
+
queryParams: {
|
|
69
|
+
filters: {
|
|
70
|
+
bio: 'Writ'
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}, context)
|
|
74
|
+
|
|
75
|
+
assert.equal(bioResults.data.length, 2, 'explicit like operator should still support partial matching')
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
it('should allow filtering with merged searchSchema fields', async () => {
|
|
79
|
+
const context = {}
|
|
80
|
+
|
|
81
|
+
await api.resources.products.post({
|
|
82
|
+
inputRecord: createJsonApiDocument('products', {
|
|
83
|
+
name: 'Widget A',
|
|
84
|
+
description: 'A great widget',
|
|
85
|
+
price: 50,
|
|
86
|
+
sku: 'WGT-001',
|
|
87
|
+
status: 'active'
|
|
88
|
+
})
|
|
89
|
+
}, context)
|
|
90
|
+
|
|
91
|
+
await api.resources.products.post({
|
|
92
|
+
inputRecord: createJsonApiDocument('products', {
|
|
93
|
+
name: 'Widget B',
|
|
94
|
+
description: 'Another widget',
|
|
95
|
+
price: 150,
|
|
96
|
+
sku: 'WGT-002',
|
|
97
|
+
status: 'inactive'
|
|
98
|
+
})
|
|
99
|
+
}, context)
|
|
100
|
+
|
|
101
|
+
const nameResults = await api.resources.products.query({
|
|
102
|
+
queryParams: {
|
|
103
|
+
filters: {
|
|
104
|
+
name: 'Widget A'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}, context)
|
|
108
|
+
assert.equal(nameResults.data.length, 1, 'Should find one product by name')
|
|
109
|
+
assert.equal(nameResults.data[0].attributes.name, 'Widget A')
|
|
110
|
+
|
|
111
|
+
const priceResults = await api.resources.products.query({
|
|
112
|
+
queryParams: {
|
|
113
|
+
filters: {
|
|
114
|
+
price: [40, 60]
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}, context)
|
|
118
|
+
assert.equal(priceResults.data.length, 1, 'Should find one product in price range')
|
|
119
|
+
assert.equal(priceResults.data[0].attributes.price, 50)
|
|
120
|
+
|
|
121
|
+
const statusResults = await api.resources.products.query({
|
|
122
|
+
queryParams: {
|
|
123
|
+
filters: {
|
|
124
|
+
status: ['active']
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}, context)
|
|
128
|
+
assert.equal(statusResults.data.length, 1, 'Should find one active product')
|
|
129
|
+
assert.equal(statusResults.data[0].attributes.status, 'active')
|
|
130
|
+
|
|
131
|
+
const skuResults = await api.resources.products.query({
|
|
132
|
+
queryParams: {
|
|
133
|
+
filters: {
|
|
134
|
+
sku: 'WGT-002'
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}, context)
|
|
138
|
+
assert.equal(skuResults.data.length, 1, 'Should find one product by SKU')
|
|
139
|
+
assert.equal(skuResults.data[0].attributes.sku, 'WGT-002')
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
it('should support arbitrary public filter keys when searchSchema maps them intentionally', async () => {
|
|
143
|
+
const context = {}
|
|
144
|
+
|
|
145
|
+
await api.resources.products.post({
|
|
146
|
+
inputRecord: createJsonApiDocument('products', {
|
|
147
|
+
name: 'Widget Alpha',
|
|
148
|
+
description: 'The great all-purpose widget',
|
|
149
|
+
price: 50,
|
|
150
|
+
sku: 'WGT-101',
|
|
151
|
+
status: 'active'
|
|
152
|
+
})
|
|
153
|
+
}, context)
|
|
154
|
+
|
|
155
|
+
await api.resources.products.post({
|
|
156
|
+
inputRecord: createJsonApiDocument('products', {
|
|
157
|
+
name: 'Widget Beta',
|
|
158
|
+
description: 'A quiet replacement unit',
|
|
159
|
+
price: 60,
|
|
160
|
+
sku: 'WGT-102',
|
|
161
|
+
status: 'inactive'
|
|
162
|
+
})
|
|
163
|
+
}, context)
|
|
164
|
+
|
|
165
|
+
const aliasResults = await api.resources.products.query({
|
|
166
|
+
queryParams: {
|
|
167
|
+
filters: {
|
|
168
|
+
term: 'great'
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}, context)
|
|
172
|
+
|
|
173
|
+
assert.equal(aliasResults.data.length, 1, 'term should be resolved by backend search mapping')
|
|
174
|
+
assert.equal(aliasResults.data[0].attributes.name, 'Widget Alpha')
|
|
175
|
+
|
|
176
|
+
const customResults = await api.resources.products.query({
|
|
177
|
+
queryParams: {
|
|
178
|
+
filters: {
|
|
179
|
+
availability: true
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}, context)
|
|
183
|
+
|
|
184
|
+
assert.equal(customResults.data.length, 1, 'availability should be interpreted by backend custom filter logic')
|
|
185
|
+
assert.equal(customResults.data[0].attributes.status, 'active')
|
|
186
|
+
})
|
|
187
|
+
})
|
|
@@ -3,8 +3,6 @@ import assert from 'node:assert/strict'
|
|
|
3
3
|
import knexLib from 'knex'
|
|
4
4
|
import {
|
|
5
5
|
cleanTables,
|
|
6
|
-
createJsonApiDocument,
|
|
7
|
-
createRelationship
|
|
8
6
|
} from './helpers/test-utils.js'
|
|
9
7
|
import { createSearchSchemaMergeApi } from './fixtures/api-configs.js'
|
|
10
8
|
|
|
@@ -63,11 +61,18 @@ describe('SearchSchema Merge Behavior', () => {
|
|
|
63
61
|
assert.ok(searchSchema.structure.price, 'price field should be searchable')
|
|
64
62
|
assert.equal(searchSchema.structure.price.filterOperator, 'between', 'price should use between operator from searchSchema')
|
|
65
63
|
|
|
64
|
+
// Check that arbitrary public filter keys are allowed when defined intentionally
|
|
65
|
+
assert.ok(searchSchema.structure.term, 'term alias should be searchable')
|
|
66
|
+
assert.deepEqual(searchSchema.structure.term.oneOf, ['name', 'description'], 'term should map to multiple fields')
|
|
67
|
+
|
|
66
68
|
// Check that virtual fields from searchSchema are present
|
|
67
69
|
assert.ok(searchSchema.structure.category_name, 'category_name virtual field should be searchable')
|
|
68
70
|
assert.equal(searchSchema.structure.category_name.filterOperator, 'like', 'category_name should use like operator')
|
|
69
71
|
assert.equal(searchSchema.structure.category_name.actualField, 'category.name', 'category_name should map to category.name')
|
|
70
72
|
|
|
73
|
+
assert.ok(searchSchema.structure.availability, 'availability custom filter should be searchable')
|
|
74
|
+
assert.equal(typeof searchSchema.structure.availability.applyFilter, 'function', 'availability should use custom backend logic')
|
|
75
|
+
|
|
71
76
|
// Check explicit searchSchema field not marked with search:true
|
|
72
77
|
assert.ok(searchSchema.structure.status, 'status field should be searchable from searchSchema')
|
|
73
78
|
assert.equal(searchSchema.structure.status.filterOperator, 'in', 'status should use in operator')
|
|
@@ -111,72 +116,19 @@ describe('SearchSchema Merge Behavior', () => {
|
|
|
111
116
|
assert.ok(!searchSchema.structure.total, 'total should not be searchable')
|
|
112
117
|
})
|
|
113
118
|
|
|
114
|
-
it('should
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const product2 = await api.resources.products.post({
|
|
129
|
-
inputRecord: createJsonApiDocument('products', {
|
|
130
|
-
name: 'Widget B',
|
|
131
|
-
description: 'Another widget',
|
|
132
|
-
price: 150,
|
|
133
|
-
sku: 'WGT-002',
|
|
134
|
-
status: 'inactive'
|
|
135
|
-
})
|
|
136
|
-
}, context)
|
|
137
|
-
|
|
138
|
-
// Test filtering with field from search:true
|
|
139
|
-
const nameResults = await api.resources.products.query({
|
|
140
|
-
queryParams: {
|
|
141
|
-
filters: {
|
|
142
|
-
name: 'Widget A'
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}, context)
|
|
146
|
-
assert.equal(nameResults.data.length, 1, 'Should find one product by name')
|
|
147
|
-
assert.equal(nameResults.data[0].attributes.name, 'Widget A')
|
|
148
|
-
|
|
149
|
-
// Test filtering with field from explicit searchSchema (between operator)
|
|
150
|
-
const priceResults = await api.resources.products.query({
|
|
151
|
-
queryParams: {
|
|
152
|
-
filters: {
|
|
153
|
-
price: [40, 60] // Between 40 and 60
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
}, context)
|
|
157
|
-
assert.equal(priceResults.data.length, 1, 'Should find one product in price range')
|
|
158
|
-
assert.equal(priceResults.data[0].attributes.price, 50)
|
|
159
|
-
|
|
160
|
-
// Test filtering with explicit searchSchema field (in operator)
|
|
161
|
-
const statusResults = await api.resources.products.query({
|
|
162
|
-
queryParams: {
|
|
163
|
-
filters: {
|
|
164
|
-
status: ['active']
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}, context)
|
|
168
|
-
assert.equal(statusResults.data.length, 1, 'Should find one active product')
|
|
169
|
-
assert.equal(statusResults.data[0].attributes.status, 'active')
|
|
170
|
-
|
|
171
|
-
// Test filtering with search:true field that wasn't overridden
|
|
172
|
-
const skuResults = await api.resources.products.query({
|
|
173
|
-
queryParams: {
|
|
174
|
-
filters: {
|
|
175
|
-
sku: 'WGT-002'
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
}, context)
|
|
179
|
-
assert.equal(skuResults.data.length, 1, 'Should find one product by SKU')
|
|
180
|
-
assert.equal(skuResults.data[0].attributes.sku, 'WGT-002')
|
|
119
|
+
it('should reject invalid filter keys that are not part of the public searchSchema contract', async () => {
|
|
120
|
+
await assert.rejects(
|
|
121
|
+
async () => {
|
|
122
|
+
await api.resources.products.query({
|
|
123
|
+
queryParams: {
|
|
124
|
+
filters: {
|
|
125
|
+
invalid_field: 'nope'
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
},
|
|
130
|
+
(error) => error?.code === 'REST_API_VALIDATION',
|
|
131
|
+
'Should reject fields that are not defined in searchSchema'
|
|
132
|
+
)
|
|
181
133
|
})
|
|
182
134
|
})
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import knexLib from 'knex'
|
|
4
|
+
import { Api } from 'hooked-api'
|
|
5
|
+
import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
|
|
6
|
+
import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.js'
|
|
7
|
+
import {
|
|
8
|
+
cleanTables,
|
|
9
|
+
createJsonApiDocument,
|
|
10
|
+
createRelationship,
|
|
11
|
+
resourceIdentifier,
|
|
12
|
+
assertResourceAttributes,
|
|
13
|
+
assertResourceRelationship,
|
|
14
|
+
validateJsonApiStructure,
|
|
15
|
+
} from './helpers/test-utils.js'
|
|
16
|
+
|
|
17
|
+
const knex = knexLib({
|
|
18
|
+
client: 'better-sqlite3',
|
|
19
|
+
connection: {
|
|
20
|
+
filename: ':memory:'
|
|
21
|
+
},
|
|
22
|
+
useNullAsDefault: true
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
let api
|
|
26
|
+
|
|
27
|
+
const serializeDateTimeForSql = (value) => {
|
|
28
|
+
if (value == null) return null
|
|
29
|
+
if (!(value instanceof Date)) {
|
|
30
|
+
throw new Error('serializeDateTimeForSql expects a Date value after schema normalization')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return value.toISOString().replace('T', ' ').slice(0, 19)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
describe('Storage mapping', () => {
|
|
37
|
+
before(async () => {
|
|
38
|
+
api = new Api({
|
|
39
|
+
name: 'storage-mapping-test',
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
await api.use(RestApiPlugin, {
|
|
43
|
+
simplifiedApi: false,
|
|
44
|
+
simplifiedTransport: false,
|
|
45
|
+
returnRecordApi: {
|
|
46
|
+
post: 'full',
|
|
47
|
+
put: 'full',
|
|
48
|
+
patch: 'full'
|
|
49
|
+
},
|
|
50
|
+
returnRecordTransport: {
|
|
51
|
+
post: 'full',
|
|
52
|
+
put: 'full',
|
|
53
|
+
patch: 'full'
|
|
54
|
+
}
|
|
55
|
+
})
|
|
56
|
+
await api.use(RestApiKnexPlugin, { knex })
|
|
57
|
+
|
|
58
|
+
await api.addResource('countries', {
|
|
59
|
+
schema: {
|
|
60
|
+
id: { type: 'id' },
|
|
61
|
+
name: { type: 'string', required: true }
|
|
62
|
+
},
|
|
63
|
+
relationships: {
|
|
64
|
+
profiles: { type: 'hasMany', target: 'profiles', foreignKey: 'country_id' }
|
|
65
|
+
},
|
|
66
|
+
tableName: 'mapped_countries'
|
|
67
|
+
})
|
|
68
|
+
await api.resources.countries.createKnexTable()
|
|
69
|
+
|
|
70
|
+
await api.addResource('profiles', {
|
|
71
|
+
schema: {
|
|
72
|
+
id: { type: 'id' },
|
|
73
|
+
displayName: { type: 'string', required: true, search: true },
|
|
74
|
+
loginCount: { type: 'number', defaultTo: 0 },
|
|
75
|
+
lastSeenAt: {
|
|
76
|
+
type: 'dateTime',
|
|
77
|
+
nullable: true,
|
|
78
|
+
storage: {
|
|
79
|
+
serialize: serializeDateTimeForSql
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
countryId: {
|
|
83
|
+
type: 'id',
|
|
84
|
+
nullable: true,
|
|
85
|
+
belongsTo: 'countries',
|
|
86
|
+
as: 'country'
|
|
87
|
+
},
|
|
88
|
+
externalRef: { type: 'string', nullable: true, search: true, storage: { column: 'legacy_ref' } },
|
|
89
|
+
confirmationToken: { type: 'string', virtual: true }
|
|
90
|
+
},
|
|
91
|
+
tableName: 'mapped_profiles'
|
|
92
|
+
})
|
|
93
|
+
await api.resources.profiles.createKnexTable()
|
|
94
|
+
|
|
95
|
+
await api.addResource('verbatim_profiles', {
|
|
96
|
+
storage: { naming: 'exact' },
|
|
97
|
+
schema: {
|
|
98
|
+
id: { type: 'id' },
|
|
99
|
+
displayName: { type: 'string', required: true },
|
|
100
|
+
loginCount: { type: 'number', defaultTo: 0 }
|
|
101
|
+
},
|
|
102
|
+
tableName: 'verbatim_profiles'
|
|
103
|
+
})
|
|
104
|
+
await api.resources.verbatim_profiles.createKnexTable()
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
after(async () => {
|
|
108
|
+
await knex.destroy()
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
beforeEach(async () => {
|
|
112
|
+
await cleanTables(knex, ['mapped_profiles', 'mapped_countries', 'verbatim_profiles'])
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
it('persists mapped columns while exposing logical field names', async () => {
|
|
116
|
+
const country = await api.resources.countries.post({
|
|
117
|
+
inputRecord: createJsonApiDocument('countries', {
|
|
118
|
+
name: 'Australia'
|
|
119
|
+
})
|
|
120
|
+
})
|
|
121
|
+
|
|
122
|
+
const createResult = await api.resources.profiles.post({
|
|
123
|
+
inputRecord: createJsonApiDocument(
|
|
124
|
+
'profiles',
|
|
125
|
+
{
|
|
126
|
+
displayName: 'Mercury',
|
|
127
|
+
loginCount: 7,
|
|
128
|
+
lastSeenAt: '2026-01-02T03:04:05Z',
|
|
129
|
+
externalRef: 'legacy-001',
|
|
130
|
+
confirmationToken: 'secret-token'
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
country: createRelationship(resourceIdentifier('countries', country.data.id))
|
|
134
|
+
}
|
|
135
|
+
)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
validateJsonApiStructure(createResult)
|
|
139
|
+
assertResourceAttributes(createResult.data, {
|
|
140
|
+
displayName: 'Mercury',
|
|
141
|
+
loginCount: 7
|
|
142
|
+
})
|
|
143
|
+
assert.equal(createResult.data.attributes.confirmationToken, 'secret-token')
|
|
144
|
+
assert.ok(createResult.data.attributes.lastSeenAt instanceof Date)
|
|
145
|
+
assert.equal(createResult.data.attributes.lastSeenAt.toISOString(), '2026-01-02T03:04:05.000Z')
|
|
146
|
+
assert.ok(!('display_name' in createResult.data.attributes))
|
|
147
|
+
assert.ok(!('country_id' in createResult.data.attributes))
|
|
148
|
+
|
|
149
|
+
const dbRecord = await knex('mapped_profiles').first()
|
|
150
|
+
assert.equal(dbRecord.display_name, 'Mercury')
|
|
151
|
+
assert.equal(dbRecord.login_count, 7)
|
|
152
|
+
assert.equal(dbRecord.last_seen_at, '2026-01-02 03:04:05')
|
|
153
|
+
assert.equal(String(dbRecord.country_id), country.data.id)
|
|
154
|
+
assert.equal(dbRecord.legacy_ref, 'legacy-001')
|
|
155
|
+
assert.ok(!('displayName' in dbRecord))
|
|
156
|
+
assert.ok(!('countryId' in dbRecord))
|
|
157
|
+
assert.ok(!('confirmationToken' in dbRecord))
|
|
158
|
+
|
|
159
|
+
const getResult = await api.resources.profiles.get({
|
|
160
|
+
id: createResult.data.id,
|
|
161
|
+
queryParams: {
|
|
162
|
+
include: ['country']
|
|
163
|
+
}
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
validateJsonApiStructure(getResult)
|
|
167
|
+
assertResourceAttributes(getResult.data, {
|
|
168
|
+
displayName: 'Mercury',
|
|
169
|
+
loginCount: 7
|
|
170
|
+
})
|
|
171
|
+
assert.ok(getResult.data.attributes.lastSeenAt instanceof Date)
|
|
172
|
+
assert.equal(getResult.data.attributes.lastSeenAt.toISOString(), '2026-01-02T03:04:05.000Z')
|
|
173
|
+
assert.ok(!('display_name' in getResult.data.attributes))
|
|
174
|
+
assertResourceRelationship(
|
|
175
|
+
getResult.data,
|
|
176
|
+
'country',
|
|
177
|
+
resourceIdentifier('countries', country.data.id)
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
const queryResult = await api.resources.profiles.query({
|
|
181
|
+
queryParams: {
|
|
182
|
+
filters: {
|
|
183
|
+
displayName: 'Mercury',
|
|
184
|
+
externalRef: 'legacy-001'
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
validateJsonApiStructure(queryResult, true)
|
|
190
|
+
assert.equal(queryResult.data.length, 1)
|
|
191
|
+
assertResourceAttributes(queryResult.data[0], {
|
|
192
|
+
displayName: 'Mercury',
|
|
193
|
+
externalRef: 'legacy-001'
|
|
194
|
+
})
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
it('applies storage mapping on PATCH updates and sparse fieldsets', async () => {
|
|
198
|
+
const created = await api.resources.profiles.post({
|
|
199
|
+
inputRecord: createJsonApiDocument('profiles', {
|
|
200
|
+
displayName: 'Before',
|
|
201
|
+
loginCount: 1,
|
|
202
|
+
lastSeenAt: '2026-01-01T00:00:00Z'
|
|
203
|
+
})
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
const patchResult = await api.resources.profiles.patch({
|
|
207
|
+
id: created.data.id,
|
|
208
|
+
inputRecord: {
|
|
209
|
+
data: {
|
|
210
|
+
type: 'profiles',
|
|
211
|
+
id: created.data.id,
|
|
212
|
+
attributes: {
|
|
213
|
+
displayName: 'After',
|
|
214
|
+
lastSeenAt: '2026-02-03T04:05:06Z',
|
|
215
|
+
confirmationToken: 'updated-token'
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
},
|
|
219
|
+
queryParams: {
|
|
220
|
+
fields: {
|
|
221
|
+
profiles: 'displayName,lastSeenAt,confirmationToken'
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
})
|
|
225
|
+
|
|
226
|
+
validateJsonApiStructure(patchResult)
|
|
227
|
+
assert.deepEqual(Object.keys(patchResult.data.attributes).sort(), ['confirmationToken', 'displayName', 'lastSeenAt'])
|
|
228
|
+
assert.equal(patchResult.data.attributes.displayName, 'After')
|
|
229
|
+
assert.equal(patchResult.data.attributes.confirmationToken, 'updated-token')
|
|
230
|
+
assert.ok(patchResult.data.attributes.lastSeenAt instanceof Date)
|
|
231
|
+
assert.equal(patchResult.data.attributes.lastSeenAt.toISOString(), '2026-02-03T04:05:06.000Z')
|
|
232
|
+
|
|
233
|
+
const dbRecord = await knex('mapped_profiles').where('id', created.data.id).first()
|
|
234
|
+
assert.equal(dbRecord.display_name, 'After')
|
|
235
|
+
assert.equal(dbRecord.last_seen_at, '2026-02-03 04:05:06')
|
|
236
|
+
assert.ok(!('displayName' in dbRecord))
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
it('can opt out of snake_case translation for an entire resource', async () => {
|
|
240
|
+
const created = await api.resources.verbatim_profiles.post({
|
|
241
|
+
inputRecord: createJsonApiDocument('verbatim_profiles', {
|
|
242
|
+
displayName: 'Exact Mode',
|
|
243
|
+
loginCount: 3
|
|
244
|
+
})
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
validateJsonApiStructure(created)
|
|
248
|
+
assertResourceAttributes(created.data, {
|
|
249
|
+
displayName: 'Exact Mode',
|
|
250
|
+
loginCount: 3
|
|
251
|
+
})
|
|
252
|
+
|
|
253
|
+
const dbRecord = await knex('verbatim_profiles').where('id', created.data.id).first()
|
|
254
|
+
assert.equal(dbRecord.displayName, 'Exact Mode')
|
|
255
|
+
assert.equal(dbRecord.loginCount, 3)
|
|
256
|
+
assert.equal(Object.hasOwn(dbRecord, 'display_name'), false)
|
|
257
|
+
assert.equal(Object.hasOwn(dbRecord, 'login_count'), false)
|
|
258
|
+
})
|
|
259
|
+
})
|
package/audit/00-charter.md
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
# Technical Audit Charter
|
|
2
|
-
|
|
3
|
-
## Mission
|
|
4
|
-
Produce a formal, evidence-driven audit of architecture quality, code quality, standards alignment, competitive positioning, and operational risk for:
|
|
5
|
-
- `/home/merc/Development/current/json-rest-api`
|
|
6
|
-
- `/home/merc/Development/current/json-rest-schema`
|
|
7
|
-
|
|
8
|
-
## Scope
|
|
9
|
-
### Included
|
|
10
|
-
- Runtime architecture, data/control flow, and plugin interactions for non-`anyapi` code paths.
|
|
11
|
-
- Schema compilation/validation behavior in `json-rest-schema` and its integration surface in `json-rest-api`.
|
|
12
|
-
- Test and documentation alignment for critical behavior.
|
|
13
|
-
- Runtime/test corroboration for risk classification.
|
|
14
|
-
|
|
15
|
-
### Explicit Exclusions
|
|
16
|
-
- `anyapi` implementation and behavior are fully out-of-scope for conclusions, scoring, and recommendations.
|
|
17
|
-
- Excluded path classes (json-rest-api):
|
|
18
|
-
- `plugins/core/lib/anyapi/**`
|
|
19
|
-
- `plugins/core/rest-api-anyapi-knex-plugin.js`
|
|
20
|
-
- `tests/anyapi-*.test.js`
|
|
21
|
-
- `scripts/*anyapi*`
|
|
22
|
-
|
|
23
|
-
## Baseline Freeze
|
|
24
|
-
- Baseline commits: `audit/evidence/runtime/baseline-commits.txt`
|
|
25
|
-
- Environment metadata: `audit/evidence/runtime/environment.txt`
|
|
26
|
-
- DB adapter assumptions: `audit/evidence/runtime/db-adapter-assumptions.txt`
|
|
27
|
-
- Standards/competitor corpus freeze: `audit/evidence/external/corpus-freeze.md`
|
|
28
|
-
|
|
29
|
-
## Evidence Policy
|
|
30
|
-
1. No claim without file-level and/or runtime/test evidence reference.
|
|
31
|
-
2. No high-severity risk without corroborating runtime/test signal.
|
|
32
|
-
3. Critical files require dual-pass review (pass 1 + cross-review pass 2).
|
|
33
|
-
4. Assertions must be tagged as:
|
|
34
|
-
- `Verified`: directly confirmed by code/runtime/test evidence.
|
|
35
|
-
- `Inferred`: logically derived from verified evidence.
|
|
36
|
-
- `Unknown`: evidence missing or inconclusive.
|
|
37
|
-
5. Unknowns are tracked explicitly in subsystem reports and findings.
|
|
38
|
-
|
|
39
|
-
## Severity Model
|
|
40
|
-
- Inputs:
|
|
41
|
-
- Impact: `1-5`
|
|
42
|
-
- Likelihood: `1-5`
|
|
43
|
-
- Detectability: `1-5` (higher means easier to detect)
|
|
44
|
-
- Formula:
|
|
45
|
-
- `severity_score = impact * likelihood * (6 - detectability)`
|
|
46
|
-
- Range: `1..125`
|
|
47
|
-
- Bands:
|
|
48
|
-
- `High`: `>= 60`
|
|
49
|
-
- `Medium`: `30-59`
|
|
50
|
-
- `Low`: `< 30`
|
|
51
|
-
|
|
52
|
-
## Confidence Model
|
|
53
|
-
- `C1 Very Low`: weak signal, mostly inferred.
|
|
54
|
-
- `C2 Low`: limited direct evidence.
|
|
55
|
-
- `C3 Medium`: direct code evidence + partial corroboration.
|
|
56
|
-
- `C4 High`: direct code evidence + test/runtime corroboration.
|
|
57
|
-
- `C5 Very High`: repeated corroboration across code, runtime, and tests.
|
|
58
|
-
|
|
59
|
-
## Scoring Model
|
|
60
|
-
Each major dimension scored `1-5`, evidence-backed:
|
|
61
|
-
- Correctness
|
|
62
|
-
- Security
|
|
63
|
-
- Resilience
|
|
64
|
-
- Performance
|
|
65
|
-
- Maintainability
|
|
66
|
-
- Observability
|
|
67
|
-
- Test quality
|
|
68
|
-
|
|
69
|
-
Aggregate score is reported as profile (not single opaque number) plus weighted composite:
|
|
70
|
-
- Correctness `20%`
|
|
71
|
-
- Security `20%`
|
|
72
|
-
- Resilience `15%`
|
|
73
|
-
- Performance `15%`
|
|
74
|
-
- Maintainability `15%`
|
|
75
|
-
- Observability `5%`
|
|
76
|
-
- Test quality `10%`
|
|
77
|
-
|
|
78
|
-
## Deliverables
|
|
79
|
-
- `audit/00-charter.md`
|
|
80
|
-
- `audit/01-scope-map.md`
|
|
81
|
-
- `audit/02-coverage-matrix.csv`
|
|
82
|
-
- `audit/03-invariants.md`
|
|
83
|
-
- `audit/04-standards-rubric.md`
|
|
84
|
-
- `audit/05-competitor-rubric.md`
|
|
85
|
-
- `audit/06-findings.md`
|
|
86
|
-
- `audit/07-risk-register.csv`
|
|
87
|
-
- `audit/08-remediation-roadmap.md`
|
|
88
|
-
- `audit/subsystems/*.md`
|
|
89
|
-
- `audit/traces/*.md`
|
|
90
|
-
- `audit/evidence/**`
|
|
91
|
-
|
|
92
|
-
## Signoff Criteria
|
|
93
|
-
1. 100% critical-file dual-pass coverage.
|
|
94
|
-
2. 100% invariants not left unknown in final state.
|
|
95
|
-
3. 100% high-severity risks have mitigation + verification test.
|
|
96
|
-
4. Standards and competitor matrices complete with evidence.
|
|
97
|
-
5. Prioritized remediation roadmap with exit criteria.
|
|
98
|
-
6. Final synthesis includes residual risk and confidence summary.
|
|
99
|
-
|
|
100
|
-
## Reproducibility Commands (Baseline)
|
|
101
|
-
- `git rev-parse HEAD` in each repo.
|
|
102
|
-
- `node -v && npm -v && uname -a`
|
|
103
|
-
- `git ls-files` inventories captured under `audit/evidence/code/`.
|