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
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { getForeignKeyFields } from './lib/querying-writing/field-utils.js'
|
|
2
|
+
|
|
1
3
|
export const LabelPlugin = {
|
|
2
4
|
name: 'rest-api-label',
|
|
3
5
|
dependencies: ['rest-api'],
|
|
@@ -15,57 +17,42 @@ export const LabelPlugin = {
|
|
|
15
17
|
...pluginOptions
|
|
16
18
|
}
|
|
17
19
|
|
|
18
|
-
function
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
function labelFields (fields, searchSchema, idProperty, relationships) {
|
|
21
|
+
const relationshipFields = getForeignKeyFields(fields, relationships)
|
|
22
|
+
const publicField = name => {
|
|
23
|
+
const field = fields[name]
|
|
24
|
+
return Object.hasOwn(fields, name) && name !== idProperty && !relationshipFields.has(name) && !field.hidden && !field.normallyHidden && !field.virtual
|
|
23
25
|
}
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
|
|
26
|
+
const byGlobal = Object.entries(searchSchema || {})
|
|
27
|
+
.filter(([, field]) => field.globalSearch)
|
|
28
|
+
.map(([name, field]) => field.actualField || name)
|
|
29
|
+
.find(publicField)
|
|
30
|
+
const strings = Object.keys(fields).filter(name => publicField(name) && fields[name].type === 'string')
|
|
31
|
+
return [...new Set([byGlobal, ...opts.preferNameFields.filter(name => strings.includes(name)), strings[0]].filter(Boolean))]
|
|
27
32
|
}
|
|
28
33
|
|
|
29
|
-
// Inject as a computed field AFTER schemas are compiled,
|
|
30
|
-
// so it lives in schemaInfo.computed and not in schemaStructure
|
|
31
34
|
addHook(
|
|
32
|
-
'
|
|
35
|
+
'computedSchema:enrich',
|
|
33
36
|
'rest-api-label:inject-computed',
|
|
34
|
-
{
|
|
35
|
-
({ context
|
|
37
|
+
{},
|
|
38
|
+
({ context }) => {
|
|
36
39
|
if (opts.disable) return
|
|
37
|
-
const {
|
|
38
|
-
const scope = scopes?.[scopeName]
|
|
39
|
-
const schemaInfo = scope?.vars?.schemaInfo
|
|
40
|
-
if (!schemaInfo) return
|
|
40
|
+
const { fields, schemaStructure, searchSchemaStructure, schemaRelationships, idProperty } = context
|
|
41
41
|
|
|
42
42
|
// Respect explicit label already defined by resource
|
|
43
|
-
if (
|
|
44
|
-
// If label accidentally made it into schemaStructure, remove it
|
|
45
|
-
if (schemaInfo.schemaStructure?.label) delete schemaInfo.schemaStructure.label
|
|
46
|
-
return
|
|
47
|
-
}
|
|
43
|
+
if (Object.hasOwn(fields, 'label') || Object.hasOwn(schemaStructure, 'label')) return
|
|
48
44
|
|
|
49
|
-
const
|
|
50
|
-
const searchStruct = schemaInfo.searchSchemaStructure || {}
|
|
51
|
-
const idProp = schemaInfo.idProperty || 'id'
|
|
45
|
+
const candidates = labelFields(schemaStructure, searchSchemaStructure, idProperty, schemaRelationships)
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
schemaInfo.computed = schemaInfo.computed || {}
|
|
56
|
-
schemaInfo.computed.label = {
|
|
47
|
+
fields.label = {
|
|
57
48
|
type: 'string',
|
|
58
49
|
computed: true,
|
|
59
|
-
|
|
60
|
-
compute: ({ attributes }) => {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
if (v != null) return String(v)
|
|
64
|
-
}
|
|
65
|
-
for (const k of opts.preferNameFields) {
|
|
66
|
-
if (attributes?.[k] != null) return String(attributes[k])
|
|
50
|
+
dependencies: candidates,
|
|
51
|
+
compute: ({ attributes, id }) => {
|
|
52
|
+
for (const field of candidates) {
|
|
53
|
+
if (attributes?.[field] != null) return String(attributes[field])
|
|
67
54
|
}
|
|
68
|
-
return String(
|
|
55
|
+
return String(id ?? '')
|
|
69
56
|
}
|
|
70
57
|
}
|
|
71
58
|
}
|
|
@@ -2,18 +2,18 @@ import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-pars
|
|
|
2
2
|
|
|
3
3
|
export default async function registerRelationshipRoutes ({ context, api, log }) {
|
|
4
4
|
const { scopeName } = context
|
|
5
|
-
const basePath = api.
|
|
5
|
+
const basePath = api.resources[scopeName].vars.transport?.mountPath || ''
|
|
6
6
|
const scopePath = `${basePath}/${scopeName}`
|
|
7
7
|
|
|
8
8
|
// Helper to create route handlers
|
|
9
9
|
const createRouteHandler = (methodName) => {
|
|
10
|
-
return async ({ params, body, queryString }) => {
|
|
11
|
-
const scope = api.
|
|
10
|
+
return async ({ params, body, queryString, context }) => {
|
|
11
|
+
const scope = api.resources[scopeName]
|
|
12
12
|
|
|
13
13
|
const methodParams = {
|
|
14
14
|
id: params.id,
|
|
15
15
|
relationshipName: params.relationshipName,
|
|
16
|
-
|
|
16
|
+
format: 'jsonapi'
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
// Add query params for getRelated
|
|
@@ -26,7 +26,7 @@ export default async function registerRelationshipRoutes ({ context, api, log })
|
|
|
26
26
|
methodParams.relationshipData = body.data
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
return await scope[methodName](methodParams)
|
|
29
|
+
return await scope[methodName](methodParams, context)
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { RestApiResourceError } from '../../../lib/rest-api-errors.js'
|
|
2
2
|
import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js'
|
|
3
|
+
import { writePrecondition } from '../rest-api-plugin-methods/common.js'
|
|
3
4
|
|
|
4
5
|
export default async function registerScopeRoutes ({ context, api, vars, log }) {
|
|
5
6
|
const { scopeName } = context
|
|
@@ -7,8 +8,8 @@ export default async function registerScopeRoutes ({ context, api, vars, log })
|
|
|
7
8
|
|
|
8
9
|
// Helper to create route handlers
|
|
9
10
|
const createRouteHandler = (scopeName, methodName) => {
|
|
10
|
-
return async ({ queryString, headers, params, body, context }) => {
|
|
11
|
-
const scope = api.
|
|
11
|
+
return async ({ queryString, headers, params, body, context, transaction, precondition }) => {
|
|
12
|
+
const scope = api.resources[scopeName]
|
|
12
13
|
if (!scope) {
|
|
13
14
|
throw new RestApiResourceError(
|
|
14
15
|
`Scope '${scopeName}' not found`,
|
|
@@ -21,7 +22,10 @@ export default async function registerScopeRoutes ({ context, api, vars, log })
|
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
// Build parameters for the scope method
|
|
24
|
-
const methodParams = {}
|
|
25
|
+
const methodParams = { format: 'jsonapi' }
|
|
26
|
+
// Only the connector supplies this handle; HTTP input cannot select an owner.
|
|
27
|
+
if (transaction !== undefined) methodParams.transaction = transaction
|
|
28
|
+
if (precondition !== undefined) methodParams[writePrecondition] = precondition
|
|
25
29
|
|
|
26
30
|
// Add ID for single-resource operations
|
|
27
31
|
if (['get', 'put', 'patch', 'delete'].includes(methodName)) {
|
|
@@ -31,13 +35,12 @@ export default async function registerScopeRoutes ({ context, api, vars, log })
|
|
|
31
35
|
// Parse query parameters for read operations
|
|
32
36
|
if (['query', 'get'].includes(methodName)) {
|
|
33
37
|
methodParams.queryParams = parseJsonApiQuery(queryString)
|
|
34
|
-
methodParams.isTransport = true
|
|
35
38
|
}
|
|
36
39
|
|
|
37
40
|
// Add body for write operations
|
|
38
41
|
if (['post', 'put', 'patch'].includes(methodName)) {
|
|
39
|
-
methodParams.
|
|
40
|
-
methodParams.
|
|
42
|
+
methodParams.document = body
|
|
43
|
+
methodParams.returning = 'full'
|
|
41
44
|
|
|
42
45
|
// Add query params for includes/fields on write operations
|
|
43
46
|
if (queryString) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { snapshotResourceConfiguration } from '../lib/querying-writing/schema-helpers.js'
|
|
2
|
+
import { rejectRemovedOptions, resolveFormat, resolveReturning } from '../lib/querying-writing/response-options.js'
|
|
2
3
|
|
|
3
4
|
export default async function turnScopeInitIntoVars ({ context, scopes, vars: apiVars }) {
|
|
4
5
|
// Refer to the scope's vars
|
|
@@ -7,8 +8,8 @@ export default async function turnScopeInitIntoVars ({ context, scopes, vars: ap
|
|
|
7
8
|
const vars = scope?.vars || apiVars
|
|
8
9
|
|
|
9
10
|
// The scope-specific ones
|
|
10
|
-
vars.sortableFields = scopeOptions.sortableFields || []
|
|
11
|
-
vars.defaultSort = scopeOptions.defaultSort || null
|
|
11
|
+
vars.sortableFields = snapshotResourceConfiguration(scopeOptions.sortableFields || [])
|
|
12
|
+
vars.defaultSort = snapshotResourceConfiguration(scopeOptions.defaultSort || null)
|
|
12
13
|
|
|
13
14
|
// The general ones that are also set at api level, but overrideable
|
|
14
15
|
if (typeof scopeOptions.queryDefaultLimit !== 'undefined') vars.queryDefaultLimit = scopeOptions.queryDefaultLimit
|
|
@@ -16,17 +17,9 @@ export default async function turnScopeInitIntoVars ({ context, scopes, vars: ap
|
|
|
16
17
|
if (typeof scopeOptions.includeDepthLimit !== 'undefined') vars.includeDepthLimit = scopeOptions.includeDepthLimit
|
|
17
18
|
if (typeof scopeOptions.enablePaginationCounts !== 'undefined') vars.enablePaginationCounts = scopeOptions.enablePaginationCounts
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
if (
|
|
21
|
-
if (
|
|
22
|
-
|
|
23
|
-
// Set returnRecord settings as scope vars
|
|
24
|
-
if (typeof scopeOptions.returnRecordApi !== 'undefined') {
|
|
25
|
-
vars.returnRecordApi = normalizeReturnRecordSetting(scopeOptions.returnRecordApi, vars.returnRecordApi || 'full')
|
|
26
|
-
}
|
|
27
|
-
if (typeof scopeOptions.returnRecordTransport !== 'undefined') {
|
|
28
|
-
vars.returnRecordTransport = normalizeReturnRecordSetting(scopeOptions.returnRecordTransport, vars.returnRecordTransport || 'no')
|
|
29
|
-
}
|
|
20
|
+
rejectRemovedOptions(scopeOptions)
|
|
21
|
+
if (scopeOptions.format !== undefined) vars.format = resolveFormat(scopeOptions.format)
|
|
22
|
+
if (scopeOptions.returning !== undefined) vars.returning = resolveReturning(scopeOptions.returning)
|
|
30
23
|
|
|
31
24
|
// Set idProperty as scope var
|
|
32
25
|
if (typeof scopeOptions.idProperty !== 'undefined') vars.idProperty = scopeOptions.idProperty
|
|
@@ -41,16 +34,4 @@ export default async function turnScopeInitIntoVars ({ context, scopes, vars: ap
|
|
|
41
34
|
)
|
|
42
35
|
}
|
|
43
36
|
}
|
|
44
|
-
|
|
45
|
-
// Validate relationship include limits at scope creation time
|
|
46
|
-
Object.entries(scopeOptions.relationships || {}).forEach(([relName, relDef]) => {
|
|
47
|
-
if (relDef.include?.limit && vars.queryMaxLimit) {
|
|
48
|
-
if (relDef.include.limit > vars.queryMaxLimit) {
|
|
49
|
-
throw new Error(
|
|
50
|
-
`Invalid relationship '${context.scopeName}.${relName}' configuration: ` +
|
|
51
|
-
`include.limit (${relDef.include.limit}) cannot exceed queryMaxLimit (${vars.queryMaxLimit})`
|
|
52
|
-
)
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
})
|
|
56
37
|
}
|
|
@@ -1,39 +1,33 @@
|
|
|
1
|
-
|
|
2
|
-
const { scopeName } = context
|
|
3
|
-
const scope = scopes[scopeName]
|
|
4
|
-
const relationships = scope.vars.schemaInfo?.schemaRelationships
|
|
1
|
+
import { buildEffectiveSortList } from '../lib/querying/query-field-sort-helpers.js'
|
|
5
2
|
|
|
6
|
-
|
|
3
|
+
export function validateIncludeConfigurations ({ context, scopes, log }) {
|
|
4
|
+
const { scopeName, scopeOptions, schemaInfo } = context
|
|
5
|
+
const maxLimit = scopeOptions.queryMaxLimit !== undefined
|
|
6
|
+
? scopeOptions.queryMaxLimit
|
|
7
|
+
: scopes[scopeName]?.vars?.queryMaxLimit
|
|
7
8
|
|
|
8
|
-
|
|
9
|
-
for (const [relName, relDef] of Object.entries(relationships)) {
|
|
9
|
+
for (const [relName, relDef] of Object.entries(schemaInfo.outputRelationships)) {
|
|
10
10
|
if (relDef.include?.strategy === 'window') {
|
|
11
|
-
//
|
|
12
|
-
// We'll validate this at query time since the database might not be connected yet
|
|
11
|
+
// Database support is checked when the include query is prepared.
|
|
13
12
|
log.debug(`Relationship ${scopeName}.${relName} configured for window function includes`)
|
|
14
13
|
}
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
if (
|
|
18
|
-
if (
|
|
19
|
-
throw new Error(
|
|
20
|
-
`Invalid include limit for ${scopeName}.${relName}: limit must be a number`
|
|
21
|
-
)
|
|
15
|
+
const limit = relDef.include?.limit
|
|
16
|
+
if (limit !== undefined && limit !== null && limit !== false) {
|
|
17
|
+
if (!Number.isInteger(limit) || limit < 0) {
|
|
18
|
+
throw new Error(`Invalid include limit for ${scopeName}.${relName}: expected a non-negative integer, null or false`)
|
|
22
19
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
if (maxLimit && relDef.include.limit > maxLimit) {
|
|
26
|
-
throw new Error(
|
|
27
|
-
`Invalid include limit for ${scopeName}.${relName}: ` +
|
|
28
|
-
`limit (${relDef.include.limit}) exceeds queryMaxLimit (${maxLimit})`
|
|
29
|
-
)
|
|
20
|
+
if (maxLimit && limit > maxLimit) {
|
|
21
|
+
throw new Error(`Invalid include limit for ${scopeName}.${relName}: limit (${limit}) exceeds queryMaxLimit (${maxLimit})`)
|
|
30
22
|
}
|
|
31
23
|
}
|
|
32
24
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
25
|
+
const orderBy = relDef.include?.orderBy
|
|
26
|
+
if (orderBy !== undefined && orderBy !== null) {
|
|
27
|
+
if (!Array.isArray(orderBy)) {
|
|
28
|
+
throw new Error(`Invalid include orderBy for ${scopeName}.${relName}: orderBy must be an array`)
|
|
29
|
+
}
|
|
30
|
+
buildEffectiveSortList(orderBy)
|
|
37
31
|
}
|
|
38
32
|
}
|
|
39
33
|
}
|
|
@@ -1,22 +1,20 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { RestApiValidationError } from '../../../lib/rest-api-errors.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* addRoute
|
|
5
6
|
* Registers a new route with the transport layer
|
|
6
7
|
*/
|
|
8
|
+
/** @param {import('../../../types/runtime.js').RuntimeArguments} args */
|
|
7
9
|
export default async ({ params, context, runHooks }) => {
|
|
8
10
|
const { method, path, handler } = params
|
|
9
11
|
|
|
10
|
-
// Validate route configuration
|
|
11
12
|
if (!method || !path || !handler) {
|
|
12
|
-
throw new
|
|
13
|
+
throw new RestApiValidationError('Route requires method, path, and handler')
|
|
13
14
|
}
|
|
14
15
|
|
|
15
|
-
// debugger
|
|
16
|
-
// Create context for enrichAttributes hooks
|
|
17
16
|
Object.assign(context, params)
|
|
18
17
|
|
|
19
|
-
// Run the addRoute hook to notify transport plugins
|
|
20
18
|
await runHooks('addRoute')
|
|
21
19
|
|
|
22
20
|
return { registered: true, method, path }
|
|
@@ -1,14 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import { QueryFilteringState, StorageAdapter, StorageSchemaInfo } from '../lib/storage/storage-types.js' */
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {object} FilteringContext
|
|
6
|
+
* @property {QueryFilteringState | null} [knexQuery]
|
|
7
|
+
* @property {StorageAdapter} [storageAdapter]
|
|
8
|
+
* @property {Record<string, unknown>} [queryParams]
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { withQueryFilteringContext } from '../lib/querying/query-builder-utils.js'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {object} request
|
|
15
|
+
* @param {FilteringContext} request.context
|
|
16
|
+
* @param {QueryFilteringState} [request.params]
|
|
17
|
+
* @param {(name: 'knexQueryFiltering') => unknown | Promise<unknown>} request.runHooks
|
|
18
|
+
* @param {{ vars: { schemaInfo: StorageSchemaInfo }, checkPermissions: (request: {
|
|
19
|
+
* method: 'query', originalContext: FilteringContext & {
|
|
20
|
+
* method: 'query', scopeName: string, schemaInfo: StorageSchemaInfo, id: undefined
|
|
21
|
+
* }
|
|
22
|
+
* }) => unknown | Promise<unknown> }} request.scope
|
|
23
|
+
* @param {string} request.scopeName
|
|
24
|
+
*/
|
|
25
|
+
export default async function applyQueryFiltersMethod ({ context, params = {}, runHooks, scope, scopeName }) {
|
|
2
26
|
const { query, filters, storageAdapter, ...knexQuery } = params
|
|
3
27
|
|
|
4
28
|
if (!query) {
|
|
5
29
|
return query
|
|
6
30
|
}
|
|
7
31
|
|
|
8
|
-
const previousKnexQuery = context.knexQuery
|
|
9
32
|
const previousStorageAdapter = context.storageAdapter
|
|
10
33
|
|
|
11
|
-
|
|
34
|
+
const queryState = {
|
|
12
35
|
query,
|
|
13
36
|
filters,
|
|
14
37
|
storageAdapter,
|
|
@@ -21,17 +44,23 @@ export default async function applyQueryFiltersMethod ({ context, params = {}, r
|
|
|
21
44
|
}
|
|
22
45
|
|
|
23
46
|
try {
|
|
24
|
-
await
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
47
|
+
return await withQueryFilteringContext(context, queryState, async () => {
|
|
48
|
+
if (['include', 'relationship-identifiers', 'search-join'].includes(params.queryPurpose || '')) {
|
|
49
|
+
await scope.checkPermissions({
|
|
50
|
+
method: 'query',
|
|
51
|
+
originalContext: {
|
|
52
|
+
...context,
|
|
53
|
+
method: 'query',
|
|
54
|
+
scopeName,
|
|
55
|
+
schemaInfo: scope.vars.schemaInfo,
|
|
56
|
+
id: undefined,
|
|
57
|
+
queryParams: { ...context.queryParams, filters: filters || {} }
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
await runHooks('knexQueryFiltering')
|
|
62
|
+
})
|
|
28
63
|
} finally {
|
|
29
|
-
if (previousKnexQuery === undefined) {
|
|
30
|
-
delete context.knexQuery
|
|
31
|
-
} else {
|
|
32
|
-
context.knexQuery = previousKnexQuery
|
|
33
|
-
}
|
|
34
|
-
|
|
35
64
|
if (previousStorageAdapter === undefined) {
|
|
36
65
|
delete context.storageAdapter
|
|
37
66
|
} else {
|
|
@@ -1,13 +1,19 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
/** @import { HookContext, PermissionHookContext } from '../../../types/hook-context.js' */
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
+
* Run the resource permission hook with the originating operation context.
|
|
6
|
+
* @param {object} request
|
|
7
|
+
* @param {Record<string, unknown>} request.context
|
|
8
|
+
* @param {{ method: string, originalContext: HookContext }} request.params
|
|
9
|
+
* @param {(name: 'checkPermissions') => unknown} request.runHooks
|
|
5
10
|
*/
|
|
6
|
-
export default async function checkPermissionsMethod ({ context, params, runHooks
|
|
7
|
-
|
|
11
|
+
export default async function checkPermissionsMethod ({ context, params, runHooks }) {
|
|
12
|
+
const permissionContext = /** @satisfies {PermissionHookContext} */ ({
|
|
8
13
|
method: params.method,
|
|
9
14
|
originalContext: params.originalContext,
|
|
10
15
|
})
|
|
16
|
+
Object.assign(context, permissionContext)
|
|
11
17
|
|
|
12
18
|
await runHooks('checkPermissions')
|
|
13
19
|
}
|