json-rest-api 1.0.28 → 2.0.0
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/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +746 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -174
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +20 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +142 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1069
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -385
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +38 -69
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +39 -82
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -150
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -118
- package/tests/fixtures/api-configs.js +0 -2509
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-cursor-multifield.test.js +0 -643
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ResourceConversionSchema, StorageAdapter, StorageContext, StorageDatabase, StorageFieldDefinition, StorageQuery } from '../storage/storage-types.js'
|
|
2
|
+
import type { CompiledQueryField, ProjectionRuntime } from './query-field-types.js'
|
|
3
|
+
import type { Fieldsets } from './field-utils.js'
|
|
4
|
+
|
|
5
|
+
export interface SelectionSchema extends Partial<ResourceConversionSchema> {
|
|
6
|
+
queryFields?: Record<string, CompiledQueryField>
|
|
7
|
+
outputRelationships?: Record<string, StorageFieldDefinition>
|
|
8
|
+
readDependencies?: Record<string, readonly string[]>
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface SelectionScope {
|
|
12
|
+
vars?: { schemaInfo?: SelectionSchema }
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SelectionContext extends StorageContext {
|
|
16
|
+
scopeName: string
|
|
17
|
+
queryParams?: { fields?: Fieldsets; sort?: unknown }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface FieldSelection {
|
|
21
|
+
fieldsToSelect: string[]
|
|
22
|
+
queryFieldsToSelect: string[]
|
|
23
|
+
requestedFields: readonly string[] | null
|
|
24
|
+
computedDependencies: string[]
|
|
25
|
+
idProperty: 'id'
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface SelectionQueryRequest {
|
|
29
|
+
query: StorageQuery
|
|
30
|
+
scope?: SelectionScope | null
|
|
31
|
+
fieldSelectionInfo?: Partial<FieldSelection> | null
|
|
32
|
+
tableName: string
|
|
33
|
+
useTablePrefix?: boolean
|
|
34
|
+
storageAdapter?: StorageAdapter | null
|
|
35
|
+
db?: StorageDatabase | null
|
|
36
|
+
context?: StorageContext | null
|
|
37
|
+
scopeName?: string
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SelectedQuery {
|
|
41
|
+
query: StorageQuery
|
|
42
|
+
queryFieldRuntimeByField: Map<string, ProjectionRuntime>
|
|
43
|
+
}
|
|
@@ -1,71 +1,122 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import { StorageFieldDefinition } from '../storage/storage-types.js' */
|
|
3
|
+
/** @import { JsonApiDocument } from '../../../../types/representations.js' */
|
|
4
|
+
/** @typedef {string | readonly string[] | null | undefined} Fieldset */
|
|
5
|
+
/** @typedef {Record<string, Fieldset>} Fieldsets */
|
|
6
|
+
import { RestApiValidationError } from '../../../../lib/rest-api-errors.js'
|
|
7
|
+
import { SERIALIZATION_CAPABILITIES } from './database-capabilities.js'
|
|
8
|
+
|
|
9
|
+
/** @param {string} name @param {string} location */
|
|
10
|
+
export function assertFieldName (name, location) {
|
|
11
|
+
if (name === '__proto__') {
|
|
12
|
+
throw new Error(`Invalid ${location}: '__proto__' cannot be a field, filter or relationship name. Choose another name.`)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** @param {unknown} declarations @param {string} location */
|
|
17
|
+
export function assertFieldNameMap (declarations, location) {
|
|
18
|
+
if (declarations == null) return
|
|
19
|
+
const prototype = typeof declarations === 'object' ? Object.getPrototypeOf(declarations) : undefined
|
|
20
|
+
if (typeof declarations !== 'object' || Array.isArray(declarations) || (prototype !== null && prototype !== Object.prototype)) {
|
|
21
|
+
throw new Error(`Invalid ${location}: Expected an object with own field declarations and a plain or null prototype.`)
|
|
22
|
+
}
|
|
23
|
+
for (const name of Object.keys(declarations)) assertFieldName(name, location)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** @param {StorageFieldDefinition | null | undefined} definition @param {string} fieldName @param {string} operation */
|
|
27
|
+
export function assertScalarQueryField (definition, fieldName, operation) {
|
|
28
|
+
const type = definition?.type
|
|
29
|
+
if (type === undefined || !SERIALIZATION_CAPABILITIES.structuredTypes.includes(type)) return
|
|
30
|
+
const parameter = operation === 'sort' ? 'sort' : `filters.${fieldName}`
|
|
31
|
+
const message = `Field '${fieldName}' has type '${type}' and cannot be used in whole-document ${operation} operations. Use a scalar field or projection for sorting, or a custom applyFilter function for JSON queries.`
|
|
32
|
+
throw new RestApiValidationError(message, {
|
|
33
|
+
fields: [parameter],
|
|
34
|
+
violations: [{ field: parameter, rule: 'structured_query', message }]
|
|
35
|
+
})
|
|
36
|
+
}
|
|
37
|
+
|
|
1
38
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* @param {
|
|
5
|
-
* @returns {Set<string>}
|
|
6
|
-
*
|
|
7
|
-
* @example
|
|
8
|
-
* // Input: Schema with belongsTo relationships
|
|
9
|
-
* const schema = {
|
|
10
|
-
* title: { type: 'string' },
|
|
11
|
-
* content: { type: 'string' },
|
|
12
|
-
* author_id: { type: 'number', belongsTo: 'users', as: 'author' },
|
|
13
|
-
* category_id: { type: 'number', belongsTo: 'categories', as: 'category' },
|
|
14
|
-
* status: { type: 'string' }
|
|
15
|
-
* };
|
|
16
|
-
*
|
|
17
|
-
* const foreignKeys = getForeignKeyFields(schema);
|
|
18
|
-
*
|
|
19
|
-
* // Output: Set containing only foreign key fields
|
|
20
|
-
* // Set(['author_id', 'category_id'])
|
|
21
|
-
*
|
|
22
|
-
* @example
|
|
23
|
-
* // Input: Works with compiled Schema objects too
|
|
24
|
-
* const schemaObject = {
|
|
25
|
-
* structure: {
|
|
26
|
-
* post_id: { type: 'id', belongsTo: 'posts', as: 'post' },
|
|
27
|
-
* user_id: { type: 'id', belongsTo: 'users', as: 'user' }
|
|
28
|
-
* }
|
|
29
|
-
* };
|
|
30
|
-
*
|
|
31
|
-
* const foreignKeys = getForeignKeyFields(schemaObject);
|
|
32
|
-
*
|
|
33
|
-
* // Output: Handles both formats
|
|
34
|
-
* // Set(['post_id', 'user_id'])
|
|
35
|
-
*
|
|
36
|
-
* @description
|
|
37
|
-
* Used by:
|
|
38
|
-
* - toJsonApi to filter foreign keys from attributes
|
|
39
|
-
* - buildFieldSelection to ensure foreign keys are always selected
|
|
40
|
-
* - knex-json-api-helpers for field filtering
|
|
41
|
-
*
|
|
42
|
-
* Purpose:
|
|
43
|
-
* - Identifies fields that should be relationships, not attributes
|
|
44
|
-
* - Foreign keys are stored in DB but not exposed as JSON:API attributes
|
|
45
|
-
* - Ensures these fields are included in SELECT for relationship building
|
|
46
|
-
* - Helps transform database structure to JSON:API format
|
|
47
|
-
*
|
|
48
|
-
* Data flow:
|
|
49
|
-
* 1. Accepts schema in either format (plain or with structure property)
|
|
50
|
-
* 2. Iterates through all field definitions
|
|
51
|
-
* 3. Collects fields with belongsTo property
|
|
52
|
-
* 4. Returns Set for efficient lookups
|
|
39
|
+
* Collect logical relationship backing fields from an explicit field map.
|
|
40
|
+
* @param {Record<string, StorageFieldDefinition> | null | undefined} schemaStructure - Compiled field definitions, not a Schema wrapper.
|
|
41
|
+
* @param {Record<string, { belongsToPolymorphic?: { typeField: string, idField: string } }>} [relationships] - Includes polymorphic backing fields.
|
|
42
|
+
* @returns {Set<string>}
|
|
53
43
|
*/
|
|
54
|
-
export const getForeignKeyFields = (
|
|
44
|
+
export const getForeignKeyFields = (schemaStructure, relationships = {}) => {
|
|
45
|
+
/** @type {Set<string>} */
|
|
55
46
|
const foreignKeys = new Set()
|
|
56
|
-
if (!
|
|
57
|
-
|
|
58
|
-
// Handle both Schema objects and plain objects
|
|
59
|
-
const schemaStructure = schema.structure || schema
|
|
47
|
+
if (!schemaStructure) return foreignKeys
|
|
60
48
|
|
|
61
49
|
Object.entries(schemaStructure).forEach(([field, def]) => {
|
|
62
50
|
if (def.belongsTo) {
|
|
63
51
|
foreignKeys.add(field)
|
|
64
52
|
}
|
|
65
53
|
})
|
|
54
|
+
for (const definition of Object.values(relationships)) {
|
|
55
|
+
if (definition.belongsToPolymorphic) {
|
|
56
|
+
foreignKeys.add(definition.belongsToPolymorphic.typeField)
|
|
57
|
+
foreignKeys.add(definition.belongsToPolymorphic.idField)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
66
60
|
return foreignKeys
|
|
67
61
|
}
|
|
68
62
|
|
|
63
|
+
/** @param {Fieldset} requestedFields */
|
|
64
|
+
export const parseFieldset = requestedFields => requestedFields == null
|
|
65
|
+
? null
|
|
66
|
+
: typeof requestedFields === 'string'
|
|
67
|
+
? requestedFields.split(',').map(field => field.trim()).filter(Boolean)
|
|
68
|
+
: requestedFields
|
|
69
|
+
|
|
70
|
+
/** @param {Fieldsets | null | undefined} fieldsets @param {string} resourceType */
|
|
71
|
+
export const getResourceFieldset = (fieldsets, resourceType) => fieldsets && Object.hasOwn(fieldsets, resourceType)
|
|
72
|
+
? fieldsets[resourceType]
|
|
73
|
+
: undefined
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* @overload
|
|
77
|
+
* @param {JsonApiDocument | null | undefined} record
|
|
78
|
+
* @param {Fieldsets} [fieldsets]
|
|
79
|
+
* @param {{ simplified?: false, resourceType?: string }} [options]
|
|
80
|
+
* @returns {void}
|
|
81
|
+
*/
|
|
82
|
+
/**
|
|
83
|
+
* @overload
|
|
84
|
+
* @param {Record<string, unknown> | null | undefined} record
|
|
85
|
+
* @param {Fieldsets | undefined} fieldsets
|
|
86
|
+
* @param {{ simplified: true, resourceType: string }} options
|
|
87
|
+
* @returns {void}
|
|
88
|
+
*/
|
|
89
|
+
/**
|
|
90
|
+
* @param {JsonApiDocument | Record<string, unknown> | null | undefined} record
|
|
91
|
+
* @param {Fieldsets} [fieldsets]
|
|
92
|
+
* @param {{ simplified?: boolean, resourceType?: string }} [options]
|
|
93
|
+
*/
|
|
94
|
+
export const filterResponseFields = (record, fieldsets = {}, { simplified = false, resourceType } = {}) => {
|
|
95
|
+
if (simplified) {
|
|
96
|
+
const fields = parseFieldset(getResourceFieldset(fieldsets, /** @type {string} */ (resourceType)))
|
|
97
|
+
if (fields === null || !record || typeof record !== 'object') return
|
|
98
|
+
const allowed = new Set(['id', '_type', ...fields])
|
|
99
|
+
for (const name of Object.keys(record)) if (!allowed.has(name)) delete /** @type {Record<string, unknown>} */ (record)[name]
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const selections = new Map(Object.entries(fieldsets).map(([type, fields]) => [type, new Set(parseFieldset(fields))]))
|
|
103
|
+
// The overload ties the representation to the corresponding record shape.
|
|
104
|
+
const document = /** @type {JsonApiDocument | null | undefined} */ (record)
|
|
105
|
+
const primary = Array.isArray(document?.data) ? document.data : [document?.data]
|
|
106
|
+
for (const resource of [...primary, ...(document?.included || [])]) {
|
|
107
|
+
if (!resource) continue
|
|
108
|
+
const allowed = selections.get(resource.type)
|
|
109
|
+
if (!allowed) continue
|
|
110
|
+
// Selection inspects keys and passes member values through unchanged.
|
|
111
|
+
/** @type {Partial<Record<'attributes' | 'relationships', Record<string, unknown>>>} */
|
|
112
|
+
const members = resource
|
|
113
|
+
for (const member of /** @type {const} */ (['attributes', 'relationships'])) {
|
|
114
|
+
if (!members[member]) continue
|
|
115
|
+
members[member] = Object.fromEntries(Object.entries(members[member]).filter(([name]) => allowed.has(name)))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
69
120
|
/**
|
|
70
121
|
* Filters hidden fields from attributes based on schema rules.
|
|
71
122
|
*
|
|
@@ -73,48 +124,18 @@ export const getForeignKeyFields = (schema) => {
|
|
|
73
124
|
* based on the schema definition and requested fields. It ensures that
|
|
74
125
|
* sensitive data is never exposed in API responses.
|
|
75
126
|
*
|
|
76
|
-
* @param {
|
|
77
|
-
* @param {
|
|
78
|
-
* @param {
|
|
79
|
-
* @returns {
|
|
80
|
-
*
|
|
81
|
-
* @example <caption>Filtering hidden fields</caption>
|
|
82
|
-
* const attributes = {
|
|
83
|
-
* name: 'John',
|
|
84
|
-
* email: 'john@example.com',
|
|
85
|
-
* password_hash: 'xxx',
|
|
86
|
-
* internal_id: '123'
|
|
87
|
-
* };
|
|
88
|
-
* const schema = {
|
|
89
|
-
* structure: {
|
|
90
|
-
* name: { type: 'string' },
|
|
91
|
-
* email: { type: 'string' },
|
|
92
|
-
* password_hash: { type: 'string', hidden: true },
|
|
93
|
-
* internal_id: { type: 'string', normallyHidden: true }
|
|
94
|
-
* }
|
|
95
|
-
* };
|
|
96
|
-
* const filtered = filterHiddenFields(attributes, schema, null);
|
|
97
|
-
* // Returns: { name: 'John', email: 'john@example.com' }
|
|
98
|
-
* // password_hash (hidden) and internal_id (normallyHidden) are removed
|
|
99
|
-
*
|
|
100
|
-
* @example <caption>Including normallyHidden fields when requested</caption>
|
|
101
|
-
* const filtered = filterHiddenFields(attributes, schema, 'name,internal_id');
|
|
102
|
-
* // Returns: { name: 'John', internal_id: '123' }
|
|
103
|
-
* // internal_id is included because explicitly requested
|
|
104
|
-
* // password_hash is still filtered (always hidden)
|
|
127
|
+
* @param {Record<string, unknown>} attributes - The attributes object to filter
|
|
128
|
+
* @param {{ structure?: Record<string, StorageFieldDefinition> }} schema - The schema object with structure property
|
|
129
|
+
* @param {Fieldset} requestedFields - Fields explicitly requested (for normallyHidden)
|
|
130
|
+
* @returns {Record<string, unknown>} Filtered attributes object
|
|
105
131
|
*/
|
|
106
132
|
export const filterHiddenFields = (attributes, schema, requestedFields) => {
|
|
133
|
+
/** @type {Record<string, unknown>} */
|
|
107
134
|
const filtered = {}
|
|
108
135
|
|
|
109
136
|
// Parse requested fields if it's a string (from query params)
|
|
110
137
|
// Example: "name,price,cost" -> ['name', 'price', 'cost']
|
|
111
|
-
const requested = requestedFields
|
|
112
|
-
? (
|
|
113
|
-
typeof requestedFields === 'string'
|
|
114
|
-
? requestedFields.split(',').map(f => f.trim()).filter(f => f)
|
|
115
|
-
: requestedFields
|
|
116
|
-
)
|
|
117
|
-
: null
|
|
138
|
+
const requested = parseFieldset(requestedFields)
|
|
118
139
|
|
|
119
140
|
Object.entries(attributes).forEach(([field, value]) => {
|
|
120
141
|
const fieldDef = schema.structure?.[field]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__'
|
|
2
|
+
export declare const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__'
|
|
3
|
+
export declare const ROW_NUMBER_KEY = '__$jsonrestapi_rn$__'
|
|
4
|
+
export declare const COMPUTED_DEPENDENCIES_KEY = '__$jsonrestapi_computed_deps$__'
|
|
5
|
+
|
|
6
|
+
export declare const DEFAULT_QUERY_LIMIT = 20
|
|
7
|
+
export declare const DEFAULT_MAX_QUERY_LIMIT = 100
|
|
8
|
+
export declare const DEFAULT_MAX_INCLUDE_LIMIT = 1000
|
|
9
|
+
export declare const DEFAULT_INCLUDE_DEPTH_LIMIT = 3
|
|
10
|
+
export declare const RELATIONSHIP_WRITE_BATCH_SIZE = 100
|
|
11
|
+
export declare const RELATIONSHIP_READ_BATCH_SIZE = 100
|
|
12
|
+
|
|
13
|
+
export declare const ERROR_SUBTYPES: {
|
|
14
|
+
NOT_FOUND: string
|
|
15
|
+
VALIDATION: string
|
|
16
|
+
PERMISSION: string
|
|
17
|
+
CONFLICT: string
|
|
18
|
+
}
|
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @module knex-constants
|
|
3
|
-
* @description Shared constants for the REST API Knex plugin
|
|
4
|
-
*
|
|
5
|
-
* This module contains all the magic strings and shared constants used
|
|
6
|
-
* throughout the plugin to avoid repetition and improve maintainability.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
1
|
// Internal property names - using unique names to avoid collisions with user data
|
|
10
2
|
export const RELATIONSHIPS_KEY = '__$jsonrestapi_relationships$__'
|
|
11
3
|
export const RELATIONSHIP_METADATA_KEY = '__$jsonrestapi_metadata$__'
|
|
@@ -17,24 +9,8 @@ export const DEFAULT_QUERY_LIMIT = 20
|
|
|
17
9
|
export const DEFAULT_MAX_QUERY_LIMIT = 100
|
|
18
10
|
export const DEFAULT_MAX_INCLUDE_LIMIT = 1000
|
|
19
11
|
export const DEFAULT_INCLUDE_DEPTH_LIMIT = 3
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
export const getSchemaStructure = (schema) => schema?.structure || schema || {}
|
|
23
|
-
export const getIdProperty = (schemaInfo) => schemaInfo?.idProperty || 'id'
|
|
24
|
-
|
|
25
|
-
// Default values
|
|
26
|
-
export const DEFAULT_PAGE_SIZE = 20
|
|
27
|
-
export const DEFAULT_MAX_PAGE_SIZE = 100
|
|
28
|
-
export const DEFAULT_PAGE_NUMBER = 1
|
|
29
|
-
|
|
30
|
-
// Database clients
|
|
31
|
-
export const DB_CLIENTS = {
|
|
32
|
-
POSTGRES: 'pg',
|
|
33
|
-
MYSQL: 'mysql',
|
|
34
|
-
MYSQL2: 'mysql2',
|
|
35
|
-
SQLITE: 'sqlite3',
|
|
36
|
-
MSSQL: 'mssql'
|
|
37
|
-
}
|
|
12
|
+
export const RELATIONSHIP_WRITE_BATCH_SIZE = 100
|
|
13
|
+
export const RELATIONSHIP_READ_BATCH_SIZE = 100
|
|
38
14
|
|
|
39
15
|
// Error subtypes
|
|
40
16
|
export const ERROR_SUBTYPES = {
|