json-rest-api 1.0.29 → 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.
Files changed (292) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +744 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +634 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +100 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +36 -0
  44. package/index.js +1 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +64 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -148
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -155
  251. package/tests/fixtures/api-configs.js +0 -2566
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-boundaries.test.js +0 -154
  264. package/tests/pagination-contract.test.js +0 -122
  265. package/tests/pagination-cursor-multifield.test.js +0 -641
  266. package/tests/pagination-enhanced.test.js +0 -563
  267. package/tests/polymorphic-relationships.test.js +0 -347
  268. package/tests/polymorphic-simple.test.js +0 -148
  269. package/tests/positioning-advanced.test.js +0 -692
  270. package/tests/positioning-extended.test.js +0 -915
  271. package/tests/positioning-multifilter.test.js +0 -699
  272. package/tests/positioning.test.js +0 -560
  273. package/tests/post-commit-hooks.test.js +0 -120
  274. package/tests/projected-fields.test.js +0 -319
  275. package/tests/public-exports.test.js +0 -15
  276. package/tests/put-create-parity.test.js +0 -216
  277. package/tests/queries.test.js +0 -521
  278. package/tests/query-limits.test.js +0 -584
  279. package/tests/relationship-endpoints.test.js +0 -620
  280. package/tests/relationship-include-storage-adapter.test.js +0 -56
  281. package/tests/relationships.test.js +0 -681
  282. package/tests/rest-api.test.js +0 -337
  283. package/tests/return-record-settings.test.js +0 -184
  284. package/tests/row-policy.test.js +0 -489
  285. package/tests/s3-storage.test.js +0 -38
  286. package/tests/searchschema-knex-behavior.test.js +0 -187
  287. package/tests/searchschema-merge.test.js +0 -151
  288. package/tests/socketio.test.js +0 -860
  289. package/tests/storage-mapping.test.js +0 -362
  290. package/tests/temporal-boundaries.test.js +0 -208
  291. package/tests/test-development-guide.md +0 -370
  292. 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).