json-rest-api 1.0.29 → 2.1.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 +744 -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 +733 -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 +103 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3838 -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 +37 -0
- package/index.js +3 -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 +70 -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 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
- 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 +120 -56
- package/plugins/core/lib/anyapi/stored-definition.js +109 -0
- 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 +48 -317
- 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 +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -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 +179 -194
- 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 +100 -290
- 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 +93 -38
- 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 +128 -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 +290 -2475
- 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 -1077
- 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 -395
- 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 -148
- 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 +36 -68
- 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 +37 -81
- 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/anyapi.d.ts +30 -0
- 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 +65 -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 -1247
- 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 -148
- 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 -155
- package/tests/fixtures/api-configs.js +0 -2566
- 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-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- 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/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,370 +0,0 @@
|
|
|
1
|
-
# Knex Schema and Migrations
|
|
2
|
-
|
|
3
|
-
`RestApiKnexPlugin` exposes a small schema-management surface for table-backed resources.
|
|
4
|
-
|
|
5
|
-
Use it when you want to:
|
|
6
|
-
|
|
7
|
-
- create tables directly from resource definitions
|
|
8
|
-
- add or alter plain columns on existing tables
|
|
9
|
-
- inspect a live table snapshot
|
|
10
|
-
- generate a create migration from a resource
|
|
11
|
-
- generate an additive diff migration from a live table
|
|
12
|
-
|
|
13
|
-
This is a Knex/table feature surface. It does not exist for non-table resources.
|
|
14
|
-
|
|
15
|
-
## Overview
|
|
16
|
-
|
|
17
|
-
Once a resource is backed by `RestApiKnexPlugin`, these scope methods are available:
|
|
18
|
-
|
|
19
|
-
- `createKnexTable()`
|
|
20
|
-
- `addKnexFields({ fields })`
|
|
21
|
-
- `alterKnexFields({ fields })`
|
|
22
|
-
- `introspectKnexTableSnapshot()`
|
|
23
|
-
- `generateKnexMigration()`
|
|
24
|
-
- `generateKnexMigrationDiff()`
|
|
25
|
-
|
|
26
|
-
Example:
|
|
27
|
-
|
|
28
|
-
```js
|
|
29
|
-
await api.addResource('memberships', {
|
|
30
|
-
schema: {
|
|
31
|
-
id: { type: 'id' },
|
|
32
|
-
workspaceId: { type: 'id', required: true },
|
|
33
|
-
userId: { type: 'id', required: true },
|
|
34
|
-
role: { type: 'string', enum: ['owner', 'member'], required: true, defaultTo: 'member' }
|
|
35
|
-
},
|
|
36
|
-
|
|
37
|
-
indexes: [
|
|
38
|
-
{
|
|
39
|
-
name: 'uq_memberships_workspace_user',
|
|
40
|
-
unique: true,
|
|
41
|
-
columns: ['workspaceId', 'userId']
|
|
42
|
-
}
|
|
43
|
-
],
|
|
44
|
-
|
|
45
|
-
foreignKeys: [
|
|
46
|
-
{
|
|
47
|
-
name: 'fk_memberships_workspace_user',
|
|
48
|
-
columns: ['workspaceId', 'userId'],
|
|
49
|
-
referencedTableName: 'workspace_users',
|
|
50
|
-
referencedColumns: ['workspace_id', 'user_id'],
|
|
51
|
-
deleteRule: 'CASCADE',
|
|
52
|
-
updateRule: 'RESTRICT'
|
|
53
|
-
}
|
|
54
|
-
],
|
|
55
|
-
|
|
56
|
-
checkConstraints: [
|
|
57
|
-
{
|
|
58
|
-
name: 'chk_memberships_workspace_positive',
|
|
59
|
-
clause: 'workspace_id > 0'
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
|
|
63
|
-
tableName: 'memberships'
|
|
64
|
-
})
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Default Column Naming
|
|
68
|
-
|
|
69
|
-
Table-backed resources keep logical field names in the resource schema and map them to physical columns for Knex operations.
|
|
70
|
-
|
|
71
|
-
By default, physical columns use snake_case:
|
|
72
|
-
|
|
73
|
-
- `workspaceId` -> `workspace_id`
|
|
74
|
-
- `userId` -> `user_id`
|
|
75
|
-
- `createdAt` -> `created_at`
|
|
76
|
-
|
|
77
|
-
This keeps the API surface expressive without repeating `storage.column` on every camelCase field.
|
|
78
|
-
|
|
79
|
-
You only need `storage.column` when a field should use a non-standard column name:
|
|
80
|
-
|
|
81
|
-
```js
|
|
82
|
-
await api.addResource('profiles', {
|
|
83
|
-
schema: {
|
|
84
|
-
id: { type: 'id' },
|
|
85
|
-
displayName: { type: 'string', required: true },
|
|
86
|
-
legacyRef: { type: 'string', storage: { column: 'legacy_profile_ref' } }
|
|
87
|
-
},
|
|
88
|
-
tableName: 'profiles'
|
|
89
|
-
})
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
If you need physical columns to match the logical field names exactly for a whole resource, opt out explicitly:
|
|
93
|
-
|
|
94
|
-
```js
|
|
95
|
-
await api.addResource('verbatim_profiles', {
|
|
96
|
-
storage: { naming: 'exact' },
|
|
97
|
-
schema: {
|
|
98
|
-
id: { type: 'id' },
|
|
99
|
-
displayName: { type: 'string', required: true }
|
|
100
|
-
},
|
|
101
|
-
tableName: 'verbatim_profiles'
|
|
102
|
-
})
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
## Logical IDs and `idProperty`
|
|
106
|
-
|
|
107
|
-
`idProperty` names the physical primary-key column for table-backed resources. The API contract still uses the logical resource id.
|
|
108
|
-
|
|
109
|
-
```js
|
|
110
|
-
await api.addResource('profiles', {
|
|
111
|
-
idProperty: 'user_id',
|
|
112
|
-
schema: {
|
|
113
|
-
id: { type: 'id', required: true, storage: { column: 'user_id' } },
|
|
114
|
-
displayName: { type: 'string', required: true },
|
|
115
|
-
loginCount: { type: 'number', defaultTo: 0 }
|
|
116
|
-
},
|
|
117
|
-
tableName: 'profiles'
|
|
118
|
-
})
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
With this definition:
|
|
122
|
-
|
|
123
|
-
- writes send the resource id as `id` or `data.id`
|
|
124
|
-
- reads return the resource id as `id` or `data.id`
|
|
125
|
-
- the primary key is stored in `user_id`
|
|
126
|
-
- `displayName` and `loginCount` are stored in `display_name` and `login_count`
|
|
127
|
-
|
|
128
|
-
The resource id is not part of `attributes`:
|
|
129
|
-
|
|
130
|
-
```js
|
|
131
|
-
await api.resources.profiles.post({
|
|
132
|
-
inputRecord: {
|
|
133
|
-
data: {
|
|
134
|
-
type: 'profiles',
|
|
135
|
-
id: '42',
|
|
136
|
-
attributes: {
|
|
137
|
-
displayName: 'Mercury'
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
})
|
|
142
|
-
```
|
|
143
|
-
|
|
144
|
-
### ID normalization
|
|
145
|
-
|
|
146
|
-
If your primary keys need canonicalization beyond the default trim-and-stringify behavior, configure `normalizeId`.
|
|
147
|
-
|
|
148
|
-
```js
|
|
149
|
-
await api.use(RestApiPlugin, {
|
|
150
|
-
normalizeId: (value) => {
|
|
151
|
-
if (value === null || value === undefined) return null
|
|
152
|
-
|
|
153
|
-
const normalized = String(value).trim()
|
|
154
|
-
return normalized ? normalized.toUpperCase() : null
|
|
155
|
-
}
|
|
156
|
-
})
|
|
157
|
-
```
|
|
158
|
-
|
|
159
|
-
You can override that per resource:
|
|
160
|
-
|
|
161
|
-
```js
|
|
162
|
-
await api.addResource('profiles', {
|
|
163
|
-
idProperty: 'user_id',
|
|
164
|
-
normalizeId: (value) => {
|
|
165
|
-
if (value === null || value === undefined) return null
|
|
166
|
-
|
|
167
|
-
const normalized = String(value).trim()
|
|
168
|
-
return normalized ? normalized.toLowerCase() : null
|
|
169
|
-
},
|
|
170
|
-
schema: {
|
|
171
|
-
id: { type: 'id', required: true, storage: { column: 'user_id' } },
|
|
172
|
-
displayName: { type: 'string', required: true }
|
|
173
|
-
},
|
|
174
|
-
tableName: 'profiles'
|
|
175
|
-
})
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
For table-backed resources, `normalizeId` is applied before:
|
|
179
|
-
|
|
180
|
-
- reading a record by id
|
|
181
|
-
- replacing, patching, or deleting a record
|
|
182
|
-
- persisting an explicit `POST` resource id
|
|
183
|
-
- validating or writing relationship identifiers that point at the resource
|
|
184
|
-
|
|
185
|
-
If the normalizer returns an empty value, existing-resource operations fail as `not_found`, while explicit `POST` document ids fail validation on `data.id`.
|
|
186
|
-
|
|
187
|
-
## Create Tables
|
|
188
|
-
|
|
189
|
-
Create the table directly from the resource definition:
|
|
190
|
-
|
|
191
|
-
```js
|
|
192
|
-
await api.resources.memberships.createKnexTable()
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
`createKnexTable()` understands:
|
|
196
|
-
|
|
197
|
-
- storage-mapped column names
|
|
198
|
-
- mapped logical id columns
|
|
199
|
-
- top-level `indexes`
|
|
200
|
-
- top-level `foreignKeys`
|
|
201
|
-
- top-level `checkConstraints`
|
|
202
|
-
- enum columns
|
|
203
|
-
- MySQL-style `setValues`
|
|
204
|
-
|
|
205
|
-
This is the same table metadata used by migration generation and diffing.
|
|
206
|
-
|
|
207
|
-
## Field-Only Helpers
|
|
208
|
-
|
|
209
|
-
Two helpers only operate on columns:
|
|
210
|
-
|
|
211
|
-
```js
|
|
212
|
-
await api.resources.memberships.addKnexFields({
|
|
213
|
-
fields: {
|
|
214
|
-
noteCount: { type: 'number', defaultTo: 0, storage: { column: 'note_count' } }
|
|
215
|
-
}
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
await api.resources.memberships.alterKnexFields({
|
|
219
|
-
fields: {
|
|
220
|
-
role: { type: 'string', enum: ['owner', 'member', 'viewer'] }
|
|
221
|
-
}
|
|
222
|
-
})
|
|
223
|
-
```
|
|
224
|
-
|
|
225
|
-
Important:
|
|
226
|
-
|
|
227
|
-
- these helpers are field-only
|
|
228
|
-
- they reject top-level `indexes`, `foreignKeys`, and `checkConstraints`
|
|
229
|
-
- use `createKnexTable()`, `generateKnexMigration()`, or `generateKnexMigrationDiff()` for table metadata
|
|
230
|
-
|
|
231
|
-
## Live Table Snapshots
|
|
232
|
-
|
|
233
|
-
Use `introspectKnexTableSnapshot()` to inspect the physical table shape:
|
|
234
|
-
|
|
235
|
-
```js
|
|
236
|
-
const snapshot = await api.resources.memberships.introspectKnexTableSnapshot()
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
Returned shape:
|
|
240
|
-
|
|
241
|
-
```js
|
|
242
|
-
{
|
|
243
|
-
dialect: 'sqlite',
|
|
244
|
-
schemaName: 'main',
|
|
245
|
-
tableName: 'memberships',
|
|
246
|
-
tableCollation: '',
|
|
247
|
-
idColumn: 'id',
|
|
248
|
-
primaryKeyColumns: ['id'],
|
|
249
|
-
hasWorkspaceIdColumn: true,
|
|
250
|
-
hasUserIdColumn: true,
|
|
251
|
-
columns: [...],
|
|
252
|
-
indexes: [...],
|
|
253
|
-
foreignKeys: [...],
|
|
254
|
-
checkConstraints: [...]
|
|
255
|
-
}
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Column entries are normalized and include information such as:
|
|
259
|
-
|
|
260
|
-
- physical column name
|
|
261
|
-
- logical key
|
|
262
|
-
- type kind
|
|
263
|
-
- nullability
|
|
264
|
-
- default value
|
|
265
|
-
- precision / scale / datetime precision
|
|
266
|
-
- enum values
|
|
267
|
-
- set values
|
|
268
|
-
|
|
269
|
-
Current live introspection support:
|
|
270
|
-
|
|
271
|
-
- SQLite
|
|
272
|
-
- MySQL / MariaDB via `mysql2`
|
|
273
|
-
|
|
274
|
-
## Create Migrations
|
|
275
|
-
|
|
276
|
-
Use `generateKnexMigration()` to emit a Knex migration string from the resource definition:
|
|
277
|
-
|
|
278
|
-
```js
|
|
279
|
-
const migration = await api.resources.memberships.generateKnexMigration()
|
|
280
|
-
console.log(migration)
|
|
281
|
-
```
|
|
282
|
-
|
|
283
|
-
This generates `exports.up` and `exports.down` for full table creation/drop.
|
|
284
|
-
|
|
285
|
-
It includes the same table metadata as `createKnexTable()`:
|
|
286
|
-
|
|
287
|
-
- mapped columns
|
|
288
|
-
- mapped id column
|
|
289
|
-
- indexes
|
|
290
|
-
- foreign keys
|
|
291
|
-
- check constraints
|
|
292
|
-
- enum columns
|
|
293
|
-
- supported MySQL-specific set columns
|
|
294
|
-
|
|
295
|
-
## Diff Migrations
|
|
296
|
-
|
|
297
|
-
Use `generateKnexMigrationDiff()` to compare the desired schema against the live table snapshot:
|
|
298
|
-
|
|
299
|
-
```js
|
|
300
|
-
const diff = await api.resources.memberships.generateKnexMigrationDiff()
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
Returned shape:
|
|
304
|
-
|
|
305
|
-
```js
|
|
306
|
-
{
|
|
307
|
-
migration,
|
|
308
|
-
warnings,
|
|
309
|
-
plan
|
|
310
|
-
}
|
|
311
|
-
```
|
|
312
|
-
|
|
313
|
-
`plan` is normalized into:
|
|
314
|
-
|
|
315
|
-
- `addColumns`
|
|
316
|
-
- `alterColumns`
|
|
317
|
-
- `dropColumns`
|
|
318
|
-
- `addIndexes`
|
|
319
|
-
- `dropIndexes`
|
|
320
|
-
- `addForeignKeys`
|
|
321
|
-
- `dropForeignKeys`
|
|
322
|
-
- `addCheckConstraints`
|
|
323
|
-
|
|
324
|
-
The generated migration is intentionally additive-first:
|
|
325
|
-
|
|
326
|
-
- create missing columns
|
|
327
|
-
- alter columns when supported
|
|
328
|
-
- recreate changed indexes when needed
|
|
329
|
-
- add missing foreign keys
|
|
330
|
-
- add supported check constraints
|
|
331
|
-
|
|
332
|
-
## Important Limits
|
|
333
|
-
|
|
334
|
-
This is a schema-vs-live-table diff, not a migration-history engine.
|
|
335
|
-
|
|
336
|
-
It answers:
|
|
337
|
-
|
|
338
|
-
- what the table looks like now
|
|
339
|
-
- what the resource says it should look like
|
|
340
|
-
- what Knex migration can safely move the table toward that shape
|
|
341
|
-
|
|
342
|
-
It does not answer:
|
|
343
|
-
|
|
344
|
-
- what changed since a previous migration file
|
|
345
|
-
- how to reconstruct migration history
|
|
346
|
-
|
|
347
|
-
Other important limits:
|
|
348
|
-
|
|
349
|
-
- destructive changes are surfaced as warnings
|
|
350
|
-
- dropped columns are skipped unless explicitly allowed by diff options
|
|
351
|
-
- SQLite check-constraint add/drop/alter support is warning-only
|
|
352
|
-
- `setValues` diffing is only emitted for MySQL-compatible targets
|
|
353
|
-
- diff migrations do not auto-generate a destructive `down`
|
|
354
|
-
|
|
355
|
-
## When to Use Which Helper
|
|
356
|
-
|
|
357
|
-
- `createKnexTable()`
|
|
358
|
-
- bootstrap a table directly from the resource
|
|
359
|
-
|
|
360
|
-
- `addKnexFields()` / `alterKnexFields()`
|
|
361
|
-
- small column-only local changes
|
|
362
|
-
|
|
363
|
-
- `introspectKnexTableSnapshot()`
|
|
364
|
-
- inspect the real physical table
|
|
365
|
-
|
|
366
|
-
- `generateKnexMigration()`
|
|
367
|
-
- emit a full create migration from the current resource definition
|
|
368
|
-
|
|
369
|
-
- `generateKnexMigrationDiff()`
|
|
370
|
-
- emit an additive migration against the live table
|
|
@@ -1,159 +0,0 @@
|
|
|
1
|
-
# Query Projections
|
|
2
|
-
|
|
3
|
-
Query projections are SQL-backed, output-only query fields provided by the optional `QueryProjectionsPlugin`. They are different from normal computed fields.
|
|
4
|
-
|
|
5
|
-
If you want to build your own plugin on the same query-field seam, see [Writing Plugins](GUIDE_X_Writing_Plugins.md).
|
|
6
|
-
|
|
7
|
-
- **Computed fields** run after the row is fetched, in JavaScript.
|
|
8
|
-
- **Projected fields** are selected inside the query, so they can participate in `SELECT`, `ORDER BY`, and cursor pagination.
|
|
9
|
-
|
|
10
|
-
Use query projection fields when a derived value must behave like a real list field.
|
|
11
|
-
|
|
12
|
-
Typical cases:
|
|
13
|
-
- sorting by a derived label such as `full_name`
|
|
14
|
-
- cursor pagination over a derived value
|
|
15
|
-
- returning a derived SQL value without storing it
|
|
16
|
-
|
|
17
|
-
Do **not** use query projection fields for normal response decoration. Regular computed fields are simpler and remain the right default for that.
|
|
18
|
-
|
|
19
|
-
## Setup
|
|
20
|
-
|
|
21
|
-
```javascript
|
|
22
|
-
import { RestApiPlugin, QueryProjectionsPlugin } from 'json-rest-api'
|
|
23
|
-
|
|
24
|
-
await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
|
|
25
|
-
await api.use(QueryProjectionsPlugin)
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
## Defining a query field
|
|
29
|
-
|
|
30
|
-
```javascript
|
|
31
|
-
await api.addResource('authors', {
|
|
32
|
-
schema: {
|
|
33
|
-
id: { type: 'id' },
|
|
34
|
-
first_name: { type: 'string', required: true },
|
|
35
|
-
last_name: { type: 'string', required: true }
|
|
36
|
-
},
|
|
37
|
-
|
|
38
|
-
queryFields: {
|
|
39
|
-
full_name: {
|
|
40
|
-
type: 'string',
|
|
41
|
-
sortable: true,
|
|
42
|
-
select: ({ knex, column }) => knex.raw(
|
|
43
|
-
"trim(coalesce(??, '') || ' ' || coalesce(??, ''))",
|
|
44
|
-
[column('first_name'), column('last_name')]
|
|
45
|
-
)
|
|
46
|
-
}
|
|
47
|
-
},
|
|
48
|
-
|
|
49
|
-
sortableFields: ['id', 'first_name', 'last_name'],
|
|
50
|
-
defaultSort: ['full_name']
|
|
51
|
-
})
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
`select()` is called at query time. It can return:
|
|
55
|
-
|
|
56
|
-
- a `knex.raw(...)` expression
|
|
57
|
-
- a Knex reference
|
|
58
|
-
- a Knex query/subquery expression
|
|
59
|
-
|
|
60
|
-
The callback receives:
|
|
61
|
-
|
|
62
|
-
```javascript
|
|
63
|
-
{
|
|
64
|
-
knex, // current knex/db handle
|
|
65
|
-
db, // same as knex
|
|
66
|
-
context, // request context
|
|
67
|
-
scopeName,
|
|
68
|
-
tableName,
|
|
69
|
-
fieldName,
|
|
70
|
-
schemaInfo,
|
|
71
|
-
adapter, // storage adapter
|
|
72
|
-
column, // helper: logical field -> qualified storage column
|
|
73
|
-
ref // helper: logical field -> knex ref
|
|
74
|
-
}
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Behavior
|
|
78
|
-
|
|
79
|
-
Query projection fields are:
|
|
80
|
-
|
|
81
|
-
- returned by default in `get()` and `query()`
|
|
82
|
-
- available in sparse fieldsets
|
|
83
|
-
- usable in sorting and cursor pagination
|
|
84
|
-
- available on included resources
|
|
85
|
-
- ignored on writes
|
|
86
|
-
|
|
87
|
-
They are **not** stored columns, and they are **not** part of write validation.
|
|
88
|
-
|
|
89
|
-
## Sparse fieldsets
|
|
90
|
-
|
|
91
|
-
Query projection fields can be requested explicitly:
|
|
92
|
-
|
|
93
|
-
```javascript
|
|
94
|
-
const result = await api.resources.authors.get({
|
|
95
|
-
id: '1',
|
|
96
|
-
queryParams: {
|
|
97
|
-
fields: {
|
|
98
|
-
authors: 'first_name,full_name'
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
```
|
|
103
|
-
|
|
104
|
-
If a query field is needed only for sorting, it is still selected internally, but it is removed from the response unless the sparse fieldset asked for it.
|
|
105
|
-
|
|
106
|
-
## Sorting and cursor pagination
|
|
107
|
-
|
|
108
|
-
Query projection fields are useful when a derived value must drive list behavior:
|
|
109
|
-
|
|
110
|
-
```javascript
|
|
111
|
-
const result = await api.resources.authors.query({
|
|
112
|
-
queryParams: {
|
|
113
|
-
sort: ['full_name'],
|
|
114
|
-
page: { size: 20 }
|
|
115
|
-
}
|
|
116
|
-
})
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
`json-rest-api` automatically appends `id` as a stable tie-breaker for cursor ordering. That prevents duplicate or skipped rows when multiple records share the same projected value.
|
|
120
|
-
|
|
121
|
-
## Included resources
|
|
122
|
-
|
|
123
|
-
Query projection fields can be selected for included resources too:
|
|
124
|
-
|
|
125
|
-
```javascript
|
|
126
|
-
const result = await api.resources.books.get({
|
|
127
|
-
id: '1',
|
|
128
|
-
queryParams: {
|
|
129
|
-
include: ['author'],
|
|
130
|
-
fields: {
|
|
131
|
-
books: 'title',
|
|
132
|
-
authors: 'full_name'
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
})
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
## Write behavior
|
|
139
|
-
|
|
140
|
-
Query projection fields are output-only. If a client sends them in `POST`, `PUT`, or `PATCH`, they are ignored.
|
|
141
|
-
|
|
142
|
-
That keeps the boundary clean:
|
|
143
|
-
|
|
144
|
-
- persisted fields own storage
|
|
145
|
-
- computed fields own response-layer enrichment
|
|
146
|
-
- query projection fields own query-layer derivation
|
|
147
|
-
|
|
148
|
-
## Limits
|
|
149
|
-
|
|
150
|
-
Projected fields are an advanced query feature. They are intentionally **backend-sensitive**.
|
|
151
|
-
|
|
152
|
-
Important limits:
|
|
153
|
-
|
|
154
|
-
- projection SQL can be dialect-specific
|
|
155
|
-
- this is not a portability layer across every SQL engine
|
|
156
|
-
- query projection fields are not auto-searchable
|
|
157
|
-
- if you want to sort by one, mark it `sortable: true`
|
|
158
|
-
|
|
159
|
-
For display-only derived values, prefer normal computed fields from [Field Transformations](GUIDE_3_Field_Transformations.md).
|