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