json-rest-api 1.0.21 → 1.0.23

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.
Files changed (88) hide show
  1. package/CLAUDE.md +1 -2
  2. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +180 -0
  3. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +172 -0
  4. package/docs/API.md +51 -14
  5. package/docs/COMPARISON.md +2 -2
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +28 -1
  7. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +5 -1
  8. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +3 -1
  9. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +12 -60
  10. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +2 -2
  11. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -1
  12. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +2 -0
  13. package/docs/GUIDE/GUIDE_X_Autofiltering.md +355 -0
  14. package/docs/GUIDE/GUIDE_X_Fastify.md +78 -0
  15. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +250 -0
  16. package/docs/GUIDE/GUIDE_X_Query_Projections.md +159 -0
  17. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +199 -0
  18. package/docs/GUIDE/index.md +18 -9
  19. package/docs/QUICKSTART.md +5 -1
  20. package/docs/index.md +1 -1
  21. package/index.js +3 -2
  22. package/package.json +4 -4
  23. package/plugins/core/connectors/express-plugin.js +97 -184
  24. package/plugins/core/connectors/fastify-plugin.js +206 -0
  25. package/plugins/core/connectors/lib/connector-core.js +195 -0
  26. package/plugins/core/connectors/lib/transport-http-helpers.js +122 -0
  27. package/plugins/core/connectors/lib/transport-route-schemas.js +92 -0
  28. package/plugins/core/lib/dbIntrospection.js +1078 -0
  29. package/plugins/core/lib/dbTablesOperations.js +1104 -160
  30. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +5 -2
  31. package/plugins/core/lib/querying/knex-process-includes.js +3 -4
  32. package/plugins/core/lib/querying/knex-query-helpers.js +17 -71
  33. package/plugins/core/lib/querying/knex-relationship-includes.js +466 -206
  34. package/plugins/core/lib/querying/query-builder-utils.js +19 -0
  35. package/plugins/core/lib/querying/query-field-sort-helpers.js +135 -0
  36. package/plugins/core/lib/querying/sort-helpers.js +33 -0
  37. package/plugins/core/lib/querying/storage-adapter-utils.js +51 -0
  38. package/plugins/core/lib/querying/url-helpers.js +8 -2
  39. package/plugins/core/lib/querying-writing/compile-schemas.js +12 -1
  40. package/plugins/core/lib/querying-writing/knex-field-helpers.js +83 -13
  41. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +13 -12
  42. package/plugins/core/lib/querying-writing/query-field-helpers.js +88 -0
  43. package/plugins/core/lib/querying-writing/request-contracts.js +590 -0
  44. package/plugins/core/lib/storage/canonical-storage-mapping.js +113 -0
  45. package/plugins/core/lib/storage/storage-adapter.js +79 -2
  46. package/plugins/core/lib/storage/storage-mapping.js +204 -0
  47. package/plugins/core/rest-api-anyapi-knex-plugin.js +355 -228
  48. package/plugins/core/rest-api-autofilter-plugin.js +414 -0
  49. package/plugins/core/rest-api-knex-plugin.js +249 -211
  50. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +10 -5
  51. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +12 -6
  52. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +41 -0
  53. package/plugins/core/rest-api-plugin-methods/common.js +121 -83
  54. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +6 -1
  55. package/plugins/core/rest-api-plugin-methods/get.js +23 -14
  56. package/plugins/core/rest-api-plugin-methods/patch.js +28 -12
  57. package/plugins/core/rest-api-plugin-methods/post.js +13 -8
  58. package/plugins/core/rest-api-plugin-methods/put.js +45 -21
  59. package/plugins/core/rest-api-plugin-methods/query.js +15 -53
  60. package/plugins/core/rest-api-plugin.js +2 -0
  61. package/plugins/core/rest-api-positioning-plugin.js +1 -1
  62. package/plugins/core/rest-api-query-projections-plugin.js +130 -0
  63. package/plugins/core/socketio-plugin.js +1 -3
  64. package/tests/autofilter.test.js +463 -0
  65. package/tests/custom-id-simplified-post.test.js +75 -0
  66. package/tests/custom-idproperty-relationships.test.js +33 -0
  67. package/tests/db-introspection.test.js +518 -0
  68. package/tests/db-table-operations.test.js +781 -0
  69. package/tests/fastify-plugin.test.js +141 -0
  70. package/tests/field-getters.test.js +1 -2
  71. package/tests/field-setters.test.js +0 -4
  72. package/tests/fixtures/api-configs.js +188 -219
  73. package/tests/helpers/fake-fastify.js +99 -0
  74. package/tests/http-connectors-parity.test.js +235 -0
  75. package/tests/projected-fields.test.js +319 -0
  76. package/tests/relationships.test.js +3 -3
  77. package/tests/searchschema-knex-behavior.test.js +187 -0
  78. package/tests/searchschema-merge.test.js +21 -69
  79. package/tests/storage-mapping.test.js +210 -0
  80. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +0 -707
  81. package/docs/GUIDE/GUIDE_X_Multihome.md +0 -666
  82. package/plugins/core/lib/querying-writing/payload-validators.js +0 -1314
  83. package/plugins/core/rest-api-access.js +0 -459
  84. package/plugins/core/rest-api-multihome-plugin.js +0 -314
  85. package/tests/multihome.test.js +0 -553
  86. package/tests/multihome.test.js.bak +0 -550
  87. package/tests/ownership-optout.test.js +0 -76
  88. package/tests/rest-api-access.test.js +0 -206
package/CLAUDE.md CHANGED
@@ -71,7 +71,7 @@ This separation ensures backend developers have full control and visibility into
71
71
  - `url-helpers.js` - Centralized URL generation functions
72
72
  - `knex-json-api-transformers-querying.js` - JSON:API transformations
73
73
  - `relationship-processor.js` - Handles relationship updates
74
- - `payload-validators.js` - Request validation
74
+ - `request-contracts.js` - Compiled request validation contracts
75
75
  - `knex-query-helpers.js` - Database query building
76
76
  - `simplified-helpers.js` - Simplified API mode transformations
77
77
 
@@ -158,4 +158,3 @@ const book = await api.resources.books.post({
158
158
  3. **ID Type Confusion**: SQLite returns numeric IDs, but JSON:API expects strings
159
159
  4. **Context Placement**: Context is a separate parameter, not inside the first parameter
160
160
  5. **Cleanup**: Always destroy knex connection in `after()` hook
161
-
@@ -0,0 +1,180 @@
1
+ JSKIT Backend Migration Tracker
2
+
3
+ Scope
4
+ - Move DB, schema, casting, and normalization ownership out of jskit-ai.
5
+ - Enrich json-rest-schema and json-rest-api so they can natively cover the required capability set.
6
+ - Do not add compatibility runtimes.
7
+ - Do not bend json-rest-api or json-rest-schema around jskit-ai shapes unless the capability is a clean native addition.
8
+
9
+ Status Legend
10
+ - done
11
+ - in_progress
12
+ - pending
13
+ - blocked
14
+
15
+ Execution Rule
16
+ - Complete one numbered point at a time.
17
+ - After each point, write a full report:
18
+ - what changed
19
+ - how it worked before
20
+ - how it works now
21
+ - relevant code snippets
22
+ - Stop and wait for the next user prompt before starting the next numbered point.
23
+
24
+ Work List
25
+
26
+ 0. [done] Create and maintain this tracker as the canonical work ledger.
27
+ Notes:
28
+ - This file is the progress source of truth for the migration work.
29
+ - Status changes must be recorded here before moving to the next point.
30
+
31
+ 1. [done] Tighten json-rest-schema scalar semantics.
32
+ Deliverables:
33
+ - make `id` represent the real external/resource ID contract
34
+ - tighten boolean parsing
35
+ - tighten number/integer parsing where schema should reject invalid inputs instead of silently coercing
36
+
37
+ 2. [done] Define a native json-rest-schema create/replace/patch validation contract.
38
+ Deliverables:
39
+ - explicit operation mode semantics
40
+ - presence-aware field handling
41
+ - patch behavior that preserves omission and only normalizes touched fields
42
+ - switch json-rest-api call sites to the explicit schema contract
43
+ Notes:
44
+ - verification blocker removed by deleting stale DB-helper tests from `json-rest-schema`
45
+ - DB-helper coverage now lives in `json-rest-api`, which owns `createKnexTable()` and `generateKnexMigration()`
46
+ - verified end-to-end against a local symlinked `json-rest-schema@1.0.13`; `json-rest-api` test suite passes with the new schema contract
47
+
48
+ 3. [done] Add transport-schema export from json-rest-schema.
49
+ Deliverables:
50
+ - export usable by JSON Schema / Ajv style consumers
51
+ - enough structure for pre-handler validation in connectors such as Fastify
52
+ Notes:
53
+ - added `schema.toJsonSchema({ mode, additionalProperties })` in `json-rest-schema`
54
+ - export is draft-07, mode-aware, and transport-facing rather than a clone of every in-process coercion path
55
+ - custom transport export hooks are supported through `handler.toJsonSchema`
56
+ - unknown external metadata keys are ignored; custom registered validators without a transport hook fail loudly
57
+
58
+ 4. [done] Extend json-rest-schema field metadata only where it remains schema-level meaning.
59
+ Deliverables:
60
+ - preserve useful passive metadata such as nullability, defaults, precision, scale, unsigned, enum values, and temporal precision
61
+ Notes:
62
+ - made `enum` a first-class built-in rule in `json-rest-schema`
63
+ - added canonical passive metadata support for `temporalPrecision`
64
+ - transport export now preserves passive schema metadata under `x-json-rest-schema.metadata`
65
+ - passive metadata remains schema-owned metadata; no DB-specific runtime logic was added to `json-rest-schema`
66
+
67
+ 5. [done] Add repository/storage mapping in json-rest-api.
68
+ Deliverables:
69
+ - API field to DB column mapping
70
+ - virtual/non-column fields
71
+ - write serializers where persistence formatting is required
72
+ Notes:
73
+ - added `field.storage = { column, serialize }` as the native repository/storage surface in `json-rest-api`
74
+ - storage metadata is compiled once into `schemaInfo.storageInfo`
75
+ - Knex writes now translate logical attributes to storage columns through the compiled mapping
76
+ - table creation and migration generation now respect storage column overrides
77
+ - JSON:API transformers translate storage rows back to logical field names before response shaping
78
+ - storage adapters now own row/id/select translation used by Knex callers, reducing repeated mapping helpers
79
+ - runtime row access now uses the same logical field path for `id` as for other fields: `getFieldValue(record, 'id')`
80
+ - include handling and relationship links now use logical record IDs consistently, including custom-id resources
81
+ - direct DB helper surfaces now support logical `id` fields mapped to custom storage columns without duplicate-column drift
82
+ - verified with dedicated storage-mapping tests and the full `json-rest-api` suite
83
+
84
+ 6. [done] Add DB introspection snapshot support in json-rest-api.
85
+ Deliverables:
86
+ - column type and shape introspection
87
+ - enum/set values
88
+ - indexes
89
+ - foreign keys
90
+ - check constraints
91
+ - precision/scale/temporal precision
92
+ Notes:
93
+ - added `introspectKnexTableSnapshot(knex, { tableName, idColumn })` as the native normalized snapshot helper
94
+ - snapshot support is dialect-aware for `better-sqlite3`/SQLite and `mysql2`
95
+ - SQLite captures named check constraints and foreign keys from table SQL, while indexes/columns/foreign-key actions come from PRAGMA metadata
96
+ - `RestApiKnexPlugin` now exposes `api.resources.{resource}.introspectKnexTableSnapshot()` for table-backed resources
97
+ - verified with real SQLite snapshot tests, MySQL raw-double snapshot tests, and a public scope-method test
98
+
99
+ 7. [done] Add deterministic migration generation and diffing in json-rest-api.
100
+ Deliverables:
101
+ - create and alter support
102
+ - named/composite indexes
103
+ - FK rules
104
+ - enum/set handling
105
+ - defaults and safety warnings for destructive changes
106
+ Notes:
107
+ - `dbTablesOperations` now normalizes table-level metadata (`indexes`, `foreignKeys`, `checkConstraints`) and uses one contract for live table creation, create-migration generation, and snapshot diffing
108
+ - field-level `unique` / `index` / `references` are compiled into deterministic named indexes and foreign keys instead of relying on ad hoc column builder side effects
109
+ - added `generateKnexMigrationDiff(tableName, currentSnapshot, schema, options)` returning `{ migration, warnings, plan }`
110
+ - additive diff generation supports columns, indexes, and foreign keys; destructive or unsupported changes stay explicit as warnings
111
+ - `RestApiKnexPlugin` now exposes `generateKnexMigration()` and `generateKnexMigrationDiff()` on table-backed scopes and threads table-level metadata through `schemaInfo`
112
+
113
+ 8. [done] Preserve and clarify the pass-through searchSchema contract in json-rest-api.
114
+ Deliverables:
115
+ - searchSchema validates allowed filter keys and normalizes values only
116
+ - filter meaning remains backend-owned and server-defined
117
+ - arbitrary public filter keys are supported when the backend maps them intentionally
118
+ - regression coverage for shared merge/validation behavior and invalid filter keys
119
+ Notes:
120
+ - did not introduce a generic filter DSL
121
+ - documented that searchSchema keys are public filter names, not a portable operator language
122
+ - verified that alias filters (`oneOf`) and custom backend filters (`applyFilter`) work as intended in the Knex path, where their execution semantics are owned by the backend
123
+
124
+ 9. [done] Add declarative autofiltering/scoping in json-rest-api.
125
+ Deliverables:
126
+ - public
127
+ - workspace
128
+ - user
129
+ - workspace_user
130
+ Notes:
131
+ - replaced the purpose-specific multihome surface with a generic `AutoFilterPlugin`
132
+ - resources now declare `autofilter` presets or inline filters, while the plugin resolves scope values from configured resolver functions
133
+ - `workspace`, `user`, `workspace_user`, and `public` are example presets, not built-in auth meanings
134
+ - the plugin scopes collection queries and single-record lookups, stamps scoped fields on `POST`/`PUT`, and rejects inconsistent scoped updates
135
+ - scoped `belongsTo` relationships are supported by injecting or validating JSON:API relationship identifiers before write validation
136
+ - auth/admission semantics remain out of scope for `json-rest-api`; core keeps `checkPermissions` hooks as a neutral extension point only
137
+ - shipped `AccessPlugin` was removed before this point so scoping does not inherit built-in auth assumptions
138
+
139
+ 10. [done] Add SQL-backed query projections and stable list behavior in json-rest-api.
140
+ Deliverables:
141
+ - SQL-backed output-only query fields via an optional plugin
142
+ - sparse-field and include support for declared query fields
143
+ - deterministic ordered-list / cursor behavior with stable tie-breakers
144
+ Notes:
145
+ - added an optional `QueryProjectionsPlugin` that compiles resource-level `queryFields`, separate from normal schema fields and response-layer computed fields
146
+ - query fields are selected by default, can be requested in sparse fieldsets, and are stripped from write input
147
+ - query fields can drive sorting and cursor pagination; `id` is appended as a deterministic tie-breaker
148
+ - query-field selection is supported on the Knex path and the AnyAPI Knex path, including included resources
149
+ - documented as a separate advanced guide so it does not blur with response-layer computed fields
150
+
151
+ 11. [done] Add a Fastify-oriented connector layer in json-rest-api.
152
+ Deliverables:
153
+ - consume exported schema before request handling
154
+ - no jskit-ai compatibility shim
155
+ Notes:
156
+ - added `FastifyPlugin` as a transport connector that registers REST routes on a supplied Fastify instance
157
+ - resource `POST`/`PUT`/`PATCH` routes now attach transport-facing JSON Schemas built from `schema.toJsonSchema({ mode })`, wrapped in the JSON:API document envelope
158
+ - connector-level transport schemas strip output-only/computed fields and direct `belongsTo` foreign keys from `data.attributes`, matching the existing transport write contract
159
+ - connector registers an `application/vnd.api+json` parser and rejects unsupported write content types before route handlers run
160
+ - no jskit-ai compatibility shim or duplicate hand-written field validation layer was added
161
+
162
+ 12. [pending] Remove the corresponding DB/schema/normalization ownership from jskit-ai and verify parity.
163
+ Deliverables:
164
+ - no jskit-ai-owned DB/schema normalization path remains for the migrated surface
165
+ - verification notes for anything still intentionally left outside scope
166
+
167
+ ADDENDUM ITEMS TO POINT 12:
168
+
169
+ * This is the reason we did points 1 to 11 (!).
170
+ * Needs to be done with a lot of care, since it's harder than it looks
171
+ * You MUST read AGENTS.md in jskit-ai VERY carefully, follow it VERY carefully. One of the previous attempts (4 failed in total) was unsuccessful because this step was missed. That file has important instructions on how to avoid repeated helpers and a pointer to important function maps
172
+ * There must be NOTHING left of the in house DB stuff. jskit is only a user of what json-rest-api offers.
173
+ * NO SHIMS. NO COMPATIBILITY LAYERS. NO LAYERS AT ALL. NO RUNTIMES.
174
+ * In an earlier attempt, you made a runtime that mimiked the repository's functions. NO. Repository functions will use json-rest-api's functions to do what they need to do
175
+ * A repository should be simple. It can have helper functions, but if it has a LOT of helper functions, there is a problem -- interrupt and let's talk. This happened in the workspace repository
176
+ * Multi homing is to be done with the generic filtering option of json-rest-api
177
+ * Take a LOT of care with the definiton of the resource file. Do NOT keep the old Type + Normalisation flow. That is GONE. We now have json-rest-schema, and that's what you need to use. One of the 4 failed attempts failed because you were stubborn about reusing that paradigm
178
+ * The resource file (which is what is used by the server, AND by the client) needs to be clean, and contain the right information (schemas, relationships, etc). It's a DATA ONLY file.
179
+ * The goal is to devoid jskit of any db logic/layering/
180
+
@@ -0,0 +1,172 @@
1
+ Single Validation Contract TODO
2
+
3
+ Goal
4
+ - Make `json-rest-schema` the single authored validation contract in `json-rest-api`.
5
+ - Keep plain JSON Schema only as a derived transport artifact for connectors.
6
+ - Remove the split between:
7
+ - resource/search validation via `json-rest-schema`
8
+ - hand-written JSON:API/query validators in `plugins/core/lib/querying-writing/payload-validators.js`
9
+
10
+ Current audit status
11
+ - [done] Audit the live validation paths and identify the actual indirection.
12
+
13
+ Root cause
14
+ - Validation is split across three places:
15
+ 1. `plugins/core/lib/querying-writing/compile-schemas.js`
16
+ - compiles resource and search schemas with `createSchema(...)`
17
+ 2. `plugins/core/lib/querying-writing/payload-validators.js`
18
+ - manually validates JSON:API request envelopes and query parameter objects
19
+ 3. `plugins/core/connectors/lib/transport-route-schemas.js`
20
+ - separately builds connector-facing JSON Schemas from `schemaInfo`
21
+ - This duplicates contract meaning across runtime and transport.
22
+ - Route methods still call imperative payload validators before calling
23
+ `schemaInstance.create()`, `schemaInstance.replace()`, or `schemaInstance.patch()`.
24
+ - `setupCommonRequest()` in `plugins/core/rest-api-plugin-methods/common.js`
25
+ also repeats some checks that are already transport-contract concerns.
26
+
27
+ What the neat end state looks like
28
+ - Authored contract:
29
+ - `json-rest-schema` only
30
+ - Derived transport schema:
31
+ - plain JSON Schema, generated from the same compiled contract
32
+ - Runtime write validation:
33
+ - uses the same compiled contract as Fastify pre-validation
34
+ - Remaining imperative checks:
35
+ - only stateful/business checks that are not schema concerns
36
+ - examples: permission checks, record existence, relationship access, complete-replace semantics
37
+
38
+ What should stay imperative
39
+ - `validateRelationshipAccess()` in `plugins/core/rest-api-plugin-methods/common.js`
40
+ - `validateCompleteReplacePayload()` in `plugins/core/rest-api-plugin-methods/common.js`
41
+ - path/body cross-source checks such as URL id vs body id consistency
42
+ - permission and data existence checks
43
+
44
+ What should stop existing
45
+ - `validateGetPayload()`
46
+ - `validateQueryPayload()`
47
+ - `validatePostPayload()`
48
+ - `validatePutPayload()`
49
+ - `validatePatchPayload()`
50
+ - duplicate route/body/query syntax checks spread across route methods and `setupCommonRequest()`
51
+
52
+ Work list
53
+
54
+ 1. [done] Define one compiled request-contract surface for resource `get/query/post/put/patch`.
55
+ Target files:
56
+ - `plugins/core/lib/querying-writing/compile-schemas.js`
57
+ - `plugins/core/connectors/lib/transport-route-schemas.js`
58
+ Deliverables:
59
+ - compile request contracts once per scope
60
+ - store them on `scope.vars.schemaInfo` or an adjacent compiled-contract object
61
+ - separate:
62
+ - write document contracts (`post`, `put`, `patch`)
63
+ - query/get contracts (`get`, `query`)
64
+ - relationship route contracts
65
+ Status:
66
+ - resource route contracts now compile once in `request-contracts.js`
67
+ - relationship route contracts are still separate and remain pending work
68
+
69
+ 2. [done] Replace manual write-document validation with compiled schema contracts.
70
+ Target files:
71
+ - `plugins/core/lib/querying-writing/payload-validators.js`
72
+ - `plugins/core/rest-api-plugin-methods/post.js`
73
+ - `plugins/core/rest-api-plugin-methods/put.js`
74
+ - `plugins/core/rest-api-plugin-methods/patch.js`
75
+ Deliverables:
76
+ - one compiled JSON:API document contract per write mode
77
+ - `data.type` constrained to the scope name inside the contract
78
+ - `data.id` required/optional according to mode
79
+ - `attributes` contract derived from `schemaInstance.toJsonSchema({ mode })`
80
+ - relationship identifier contracts derived from `schemaRelationships` and `belongsTo` metadata
81
+ - `included` rules expressed once in the contract rather than repeated manually
82
+
83
+ 3. [done] Replace manual GET/query parameter validation with compiled schema contracts.
84
+ Target files:
85
+ - `plugins/core/lib/querying-writing/payload-validators.js`
86
+ - `plugins/core/rest-api-plugin-methods/get.js`
87
+ - `plugins/core/rest-api-plugin-methods/query.js`
88
+ Deliverables:
89
+ - a compiled `get` contract for:
90
+ - `id`
91
+ - `queryParams.include`
92
+ - `queryParams.fields`
93
+ - a compiled `query` contract for:
94
+ - `include`
95
+ - `fields`
96
+ - `filters`
97
+ - `sort`
98
+ - `page`
99
+ - include-depth validation and sortable-field restrictions represented in the compiled contract, not duplicated in ad hoc code
100
+ Notes:
101
+ - `searchSchemaInstance` should remain the value contract for `filters`
102
+ - the query contract should validate the envelope/shape, not duplicate filter semantics
103
+
104
+ 4. [done] Collapse Fastify pre-validation and in-process validation onto the same source for resource write routes.
105
+ Target files:
106
+ - `plugins/core/connectors/lib/transport-route-schemas.js`
107
+ - `plugins/core/connectors/fastify-plugin.js`
108
+ - route methods in `plugins/core/rest-api-plugin-methods/`
109
+ Deliverables:
110
+ - Fastify consumes transport JSON Schema exported from the compiled request contract
111
+ - route methods consume the same compiled contract directly for runtime validation
112
+ - no second hand-written envelope contract path
113
+
114
+ 5. [done] Shrink `setupCommonRequest()` to non-schema responsibilities.
115
+ Target file:
116
+ - `plugins/core/rest-api-plugin-methods/common.js`
117
+ Deliverables:
118
+ - keep simplified-mode conversion, transaction setup, context wiring
119
+ - keep stateful/business checks only
120
+ - remove syntax/envelope checks that belong in the compiled contract
121
+ - keep only cross-input checks that a single schema cannot express cleanly
122
+
123
+ 6. [done] Delete `payload-validators.js`.
124
+ Target file:
125
+ - `plugins/core/lib/querying-writing/payload-validators.js`
126
+ Deliverables:
127
+ - no route method imports it anymore
128
+ - helper logic worth keeping is either:
129
+ - absorbed into compiled contracts
130
+ - or moved into narrowly named stateful helpers
131
+
132
+ 7. [done] Tighten tests around the single-contract flow.
133
+ Target areas:
134
+ - `tests/fastify-plugin.test.js`
135
+ - route method coverage exercised by the existing REST suites
136
+ - add one focused contract test suite if needed
137
+ Deliverables:
138
+ - prove Fastify pre-validation and runtime validation reject the same bad inputs
139
+ - cover:
140
+ - wrong `data.type`
141
+ - missing/invalid `data.id`
142
+ - invalid relationships
143
+ - invalid query/include/page/sort shapes
144
+ - verify there is no drift between transport schema and runtime schema behavior
145
+
146
+ 8. [done] Proof cleanup.
147
+ Deliverables:
148
+ - `rg` proof that route methods no longer import `payload-validators.js`
149
+ - `rg` proof that `validateGetPayload`, `validateQueryPayload`, `validatePostPayload`,
150
+ `validatePutPayload`, and `validatePatchPayload` are gone
151
+ - `npm test`
152
+
153
+ Current state
154
+ - Resource `get/query/post/put/patch` now use one compiled contract source.
155
+ - Fastify transport schemas for those resource routes are derived from the same compiled contract.
156
+ - `payload-validators.js` is deleted.
157
+ - `npm test` passes.
158
+ - The main remaining simplification is the separate relationship-route contract path.
159
+
160
+ File map for the next implementation pass
161
+ - Remaining relationship-route cleanup:
162
+ - `plugins/core/rest-api-plugin-methods/post-relationship.js`
163
+ - `plugins/core/rest-api-plugin-methods/patch-relationship.js`
164
+ - `plugins/core/rest-api-plugin-methods/delete-relationship.js`
165
+ - Transport schema generator to reuse, not duplicate:
166
+ - `plugins/core/connectors/lib/transport-route-schemas.js`
167
+
168
+ Explicit non-goals
169
+ - Do not move permission checks into schemas.
170
+ - Do not move DB existence checks into schemas.
171
+ - Do not turn `searchSchema` into a generic filter DSL.
172
+ - Do not add a compatibility layer that preserves both manual validators and compiled contracts.
package/docs/API.md CHANGED
@@ -4,13 +4,15 @@ This reference provides comprehensive documentation for all methods, parameters,
4
4
 
5
5
  ## Important Notes
6
6
 
7
- 1. **Context Parameter**: The `context` parameter shown in method signatures is typically managed internally by the framework. When using the API programmatically, you usually don't need to provide it unless you're implementing custom authentication or request-specific data.
7
+ 1. **Context Parameter**: The `context` parameter shown in method signatures is typically managed internally by the framework. When using the API programmatically, you usually don't need to provide it unless you're implementing custom request identity/auth context or other request-specific data.
8
8
 
9
- 2. **Simplified Mode Defaults**:
9
+ 2. **App-Owned Auth Context**: `json-rest-api` does not define authentication semantics. If you use `context.auth` in hooks, treat it as application-owned data supplied by your transport or surrounding stack.
10
+
11
+ 3. **Simplified Mode Defaults**:
10
12
  - `simplifiedApi`: `true` (default for programmatic API calls)
11
13
  - `simplifiedTransport`: `false` (default for HTTP transport)
12
14
 
13
- 3. **All parameters must be passed within a single params object** as the first argument to each method.
15
+ 4. **All parameters must be passed within a single params object** as the first argument to each method.
14
16
 
15
17
  ## Table of Contents
16
18
 
@@ -1400,7 +1402,7 @@ Each API method has its own specific hook execution order. Here's the exact sequ
1400
1402
 
1401
1403
  1. **Processing Hooks**: Only POST, PUT, and PATCH have `beforeProcessing` hooks
1402
1404
  2. **Schema Validation**: Only POST, PUT, and PATCH have schema validation hooks
1403
- 3. **Permission Checks**: GET uses `checkDataPermissions`, while relationship methods use `checkPermissions`
1405
+ 3. **Permission Hooks**: GET uses `checkDataPermissions`, while relationship methods use `checkPermissions`
1404
1406
  4. **Query Filtering**: Only QUERY method triggers `knexQueryFiltering` hooks
1405
1407
  5. **Enrichment**: Only GET and QUERY have `enrichRecord` hooks
1406
1408
  6. **Relationships**: Only GET has `enrichRecordWithRelationships`
@@ -1421,7 +1423,7 @@ Each hook receives a context object with different properties based on the hook
1421
1423
  queryParams: {...},
1422
1424
  simplified: true
1423
1425
  },
1424
- auth: {...}, // Authentication info
1426
+ auth: {...}, // App-provided auth/identity context if present
1425
1427
  transaction: {...}, // Database transaction
1426
1428
  schemaInfo: {...}, // Resource schema
1427
1429
  db: {...} // Database connection
@@ -1490,7 +1492,7 @@ api.resource('articles').hook('afterSchemaValidate', async (context) => {
1490
1492
  }
1491
1493
  ```
1492
1494
 
1493
- **Purpose:** Authorization checks - throw error to deny access
1495
+ **Purpose:** Permission or visibility checks implemented by your app hooks. Throw an error to deny the operation.
1494
1496
 
1495
1497
  **Example:**
1496
1498
  ```javascript
@@ -2243,13 +2245,9 @@ api.addResource({
2243
2245
  }
2244
2246
  },
2245
2247
 
2246
- // Permissions
2247
- permissions: {
2248
- create: ['author', 'admin'],
2249
- read: ['*'],
2250
- update: ['author', 'editor', 'admin'],
2251
- delete: ['admin']
2252
- },
2248
+ // No built-in permissions option exists here.
2249
+ // Use permission hooks for access decisions, and optional plugins
2250
+ // such as AutoFilterPlugin for dataset scoping.
2253
2251
 
2254
2252
  // Soft delete configuration
2255
2253
  softDelete: {
@@ -2499,6 +2497,45 @@ try {
2499
2497
 
2500
2498
  **Important:** When you provide a transaction, you're responsible for committing/rolling back. When you don't provide one, the library auto-commits.
2501
2499
 
2500
+ ### Knex Schema Helpers
2501
+
2502
+ When a resource is backed by `RestApiKnexPlugin`, its scope also exposes table-management helpers:
2503
+
2504
+ ```javascript
2505
+ await api.resources.articles.createKnexTable()
2506
+
2507
+ const snapshot = await api.resources.articles.introspectKnexTableSnapshot()
2508
+ const createMigration = await api.resources.articles.generateKnexMigration()
2509
+ const diff = await api.resources.articles.generateKnexMigrationDiff()
2510
+ ```
2511
+
2512
+ Available helpers:
2513
+
2514
+ - `createKnexTable()`
2515
+ - `addKnexFields({ fields })`
2516
+ - `alterKnexFields({ fields })`
2517
+ - `introspectKnexTableSnapshot()`
2518
+ - `generateKnexMigration()`
2519
+ - `generateKnexMigrationDiff()`
2520
+
2521
+ `generateKnexMigrationDiff()` returns:
2522
+
2523
+ ```javascript
2524
+ {
2525
+ migration,
2526
+ warnings,
2527
+ plan
2528
+ }
2529
+ ```
2530
+
2531
+ Important boundaries:
2532
+
2533
+ - this is a live-table snapshot diff, not migration-history reconstruction
2534
+ - `addKnexFields()` and `alterKnexFields()` are field-only helpers
2535
+ - indexes, foreign keys, and check constraints belong on the full table schema surface
2536
+
2537
+ For full examples and dialect notes, see [Knex Schema and Migrations](GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md).
2538
+
2502
2539
  ### Batch Operations
2503
2540
 
2504
2541
  Process multiple operations efficiently:
@@ -2746,4 +2783,4 @@ api.resource('articles').hook('knexQueryFiltering', async (context) => {
2746
2783
  query.whereRaw("metadata->>'key' = ?", [filters.metadata_key]);
2747
2784
  }
2748
2785
  });
2749
- ```
2786
+ ```
@@ -30,7 +30,7 @@ json-rest-api is a full-featured, plugin-based JSON:API implementation built on
30
30
  | WebSocket support | ✅ | ❌ | ❌ | ❌ | ❌ |
31
31
  | Bulk operations | ✅ | ❌ | ❌ | ❌ | ❌ |
32
32
  | Positioning/sorting | ✅ | ❌ | ❌ | ❌ | ❌ |
33
- | Authentication | ✅ | ❌ | ⚠️ | ❌ | ❌ |
33
+ | Permission hooks / app-owned auth integration | ✅ | ❌ | ⚠️ | ❌ | ❌ |
34
34
  | **Developer Experience** |
35
35
  | TypeScript support | ✅ | ✅ | ✅ | ❌ | ❌ |
36
36
  | Active maintenance | ✅ (2025) | ✅ (2024) | ✅ (2024) | 🟡 (2022) | ❌ (2022) |
@@ -430,4 +430,4 @@ json-rest-api stands out as the most comprehensive JSON:API solution for Node.js
430
430
  4. **Active Development**: Regular updates and community support
431
431
  5. **Production Ready**: Battle-tested with extensive test coverage
432
432
 
433
- For teams building JSON:API services in 2025, json-rest-api offers the best combination of compliance, features, and developer experience.
433
+ For teams building JSON:API services in 2025, json-rest-api offers the best combination of compliance, features, and developer experience.
@@ -928,6 +928,32 @@ Each resource (added via `api.addResource()`) gets these methods automatically:
928
928
  await api.resources.countries.createKnexTable();
929
929
  ```
930
930
 
931
+ - **`api.resources.{resourceName}.introspectKnexTableSnapshot()`** - Returns a normalized snapshot of the live physical table
932
+ ```javascript
933
+ const snapshot = await api.resources.countries.introspectKnexTableSnapshot();
934
+ ```
935
+
936
+ - **`api.resources.{resourceName}.generateKnexMigration()`** - Generates a Knex create migration string for the resource
937
+ ```javascript
938
+ const migration = await api.resources.countries.generateKnexMigration();
939
+ ```
940
+
941
+ - **`api.resources.{resourceName}.generateKnexMigrationDiff()`** - Generates an additive diff migration from the live table snapshot
942
+ ```javascript
943
+ const diff = await api.resources.countries.generateKnexMigrationDiff();
944
+ ```
945
+
946
+ - **`api.resources.{resourceName}.addKnexFields({ fields })`** / **`alterKnexFields({ fields })`** - Column-only helpers for existing tables
947
+ ```javascript
948
+ await api.resources.countries.addKnexFields({
949
+ fields: {
950
+ iso3: { type: 'string', maxLength: 3 }
951
+ }
952
+ });
953
+ ```
954
+
955
+ For full examples of snapshots, migration generation, diffing, and table metadata such as indexes / foreign keys / checks, see [Knex Schema and Migrations](GUIDE_X_Knex_Schema_And_Migrations.md).
956
+
931
957
 
932
958
  ## API Namespaces (internal, for plugin developers)
933
959
 
@@ -943,6 +969,7 @@ These helpers and methods provide a complete toolkit for:
943
969
  - Managing database transactions
944
970
  - Generating proper URLs and links
945
971
  - Accessing the underlying database when needed
972
+ - Inspecting live tables and generating Knex migrations when using table-backed resources
946
973
  - Integrating with web frameworks like Express
947
974
 
948
- The architecture ensures clean separation between HTTP transport, business logic, and data persistence layers.
975
+ The architecture ensures clean separation between HTTP transport, business logic, and data persistence layers.
@@ -306,6 +306,10 @@ The `searchSchema` gives you:
306
306
  * Flexibility to extend: Add virtual fields or complex filters via searchSchema while basic fields use `search: true`
307
307
  * Best of both worlds: Explicit control for complex cases, automatic setup for simple ones
308
308
 
309
+ `searchSchema` also defines the public filter names your API accepts. Those keys do not need to match database columns or even resource field names. Treat `searchSchema` as an allowlist plus normalization layer for filter input: the actual meaning of each key is still implemented by the backend through mappings such as `actualField`, multi-field rules such as `oneOf`, standard operators, or custom `applyFilter` logic. It is not a portable filter DSL.
310
+
311
+ The merge rules and validation surface are shared across storage backends. Advanced execution semantics are still backend-owned. In practice, that means alias filters such as `oneOf` and custom `applyFilter` functions should be validated against the backend you actually run, and the SQL-heavy examples in this chapter assume the Knex-backed query layer.
312
+
309
313
  `searchSchema` also gives you the ability to define a search field that will search in multiple fields. For example:
310
314
 
311
315
  ```javascript
@@ -801,4 +805,4 @@ GET /api/countries?filter[search]=united+states&fields[countries]=code
801
805
 
802
806
  ---
803
807
 
804
- [Previous: 2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md) | [Back to Guide](./README.md) | [Next: 2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)
808
+ [Previous: 2.1 The starting point](./GUIDE_2_1_The_Starting_Point.md) | [Back to Guide](./README.md) | [Next: 2.3 `belongsTo` Relationships](./GUIDE_2_3_BelongsTo_Relationships.md)
@@ -2,6 +2,8 @@
2
2
 
3
3
  Pagination and ordering are essential features for working with large datasets. The json-rest-api library provides powerful pagination that applies not just to main resources, but also to included relationships. This means you can limit and order both parent records AND their children independently.
4
4
 
5
+ For derived fields that must participate in `ORDER BY` and cursor pagination, see [Query Projections](GUIDE_X_Query_Projections.md). Regular computed fields are response-layer only and are not the right tool for stable SQL ordering.
6
+
5
7
  Let's create a comprehensive example with posts, comments, and tags to demonstrate all pagination and ordering features:
6
8
 
7
9
  ```javascript
@@ -1233,4 +1235,4 @@ The pagination and ordering system is designed to be efficient and predictable:
1233
1235
 
1234
1236
  ---
1235
1237
 
1236
- [Previous: 2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md) | [Back to Guide](./README.md)
1238
+ [Previous: 2.6 Many to Many Relationships](./GUIDE_2_6_Many_To_Many.md) | [Back to Guide](./README.md)