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
@@ -0,0 +1,75 @@
1
+ ---
2
+ title: "Architecture"
3
+ ---
4
+
5
+ # Architecture
6
+
7
+ The library exposes resource methods directly and uses connectors to translate
8
+ HTTP requests into those same calls. Storage and transaction rules are shared
9
+ between direct calls and transports.
10
+
11
+ ## Runtime
12
+
13
+ `lib/runtime/json-rest-api.js` contains the API object and its sequential hook list.
14
+ It registers ordinary resource methods, installs plugins and constructs the argument
15
+ object passed to existing operation handlers. There are no proxy-based resource
16
+ objects, generic scope aliases or automatic dispatcher logs.
17
+
18
+ A resource inherits shared methods; its vars and helpers inherit API defaults.
19
+ Local properties override those defaults. The internal operation argument names
20
+ `scope`, `scopes` and `scopeName` refer to resources. The public collection is
21
+ `api.resources`.
22
+
23
+ ## Resource operations and schemas
24
+
25
+ `plugins/core/rest-api-plugin.js` installs CRUD, relationship and transaction methods.
26
+ The operation handlers live in `plugins/core/rest-api-plugin-methods/`. They validate
27
+ requests, run permission/lifecycle hooks and call storage helpers.
28
+
29
+ Writes select plain input with `data` or JSON:API input with `document`.
30
+ `request-contracts.js` validates that selection; plain conversion produces the
31
+ normalized `context.inputRecord` used by the existing write lifecycle. `format`
32
+ controls output only. Both input forms run through the same resource operation,
33
+ including response preparation before transaction completion. Connectors submit
34
+ documents through this same boundary.
35
+
36
+ Hooks deliberately share mutable operation context for caches, added information
37
+ and phase-specific field changes. The caller observes those mutations. See the
38
+ [hook contract](GUIDE/13-hooks-and-lifecycle.md) for wrapper hooks, nested calls,
39
+ writable values and library-owned bookkeeping. Independent calls use separate
40
+ contexts; this is not an immutable-context API.
41
+
42
+ `plugins/core/lib/querying-writing/compile-schemas.js` compiles resource declarations.
43
+ Its neighboring modules own response options, field selection, ID normalization and
44
+ representation conversion. The JSON schema package remains a separate dependency;
45
+ it is unrelated to the removed hooked-api runtime.
46
+
47
+ ## Storage
48
+
49
+ `rest-api-knex-plugin.js` maps resources to ordinary tables.
50
+ `rest-api-anyapi-knex-plugin.js` maps them to canonical records and relationship links.
51
+ Both use native Knex queries and the common storage interfaces under
52
+ `plugins/core/lib/storage/`.
53
+
54
+ Canonical links and relationship reads have separate modules under `lib/anyapi/`
55
+ within the core plugin. Include traversal and to-one/to-many loaders live under
56
+ `plugins/core/lib/querying/`. Keep query ownership and visibility checks in those
57
+ modules rather than duplicating them in connectors.
58
+
59
+ ## Transactions and errors
60
+
61
+ `lib/error-context.js` owns managed transactions, participating operations,
62
+ completion hooks and write outcomes. `lib/knex-transaction.js` handles connection
63
+ completion. Resource methods delegate to these modules.
64
+
65
+ `lib/rest-api-errors.js` defines the local error hierarchy. The runtime preserves
66
+ thrown values; operation-level code attaches transaction outcomes and secondary
67
+ cleanup diagnostics. See the [transaction contract](GUIDE/19-managed-transactions.md)
68
+ and [error outcomes](GUIDE/20-transaction-outcomes.md).
69
+
70
+ ## Transports and files
71
+
72
+ Express and Fastify connectors share parsing, route and response helpers under
73
+ `plugins/core/connectors/lib/`. Socket.IO remains an optional integration.
74
+ File handling owns upload lifetime; storage plugins under `plugins/storage/` own
75
+ file bytes. Their effects participate in the documented completion-hook lifecycle.
@@ -0,0 +1,178 @@
1
+ ---
2
+ title: "Contributing"
3
+ ---
4
+
5
+ # Contributing
6
+
7
+ Use **Node 24** for development and verification. The library requires Node 24 or
8
+ newer; `.nvmrc` pins the version used by this repository and CI. Do not add a
9
+ second Node-version matrix without a concrete compatibility requirement.
10
+
11
+ ## Setup
12
+
13
+ ```sh
14
+ nvm install
15
+ nvm use
16
+ npm ci
17
+ ```
18
+
19
+ SQLite fixtures need no external database or credentials. If the native SQLite
20
+ driver compiles from source, install Python 3, make and a C/C++ compiler.
21
+ Documentation uses Ruby 3.2 and Bundler 2.6.9. Install Bundler and run `npm run docs`;
22
+ the script installs missing gems into `docs/vendor/bundle` before building.
23
+ `npm run docs:dev -- --no-open` serves the site without opening a browser.
24
+
25
+ ## Repository layout
26
+
27
+ | Path | Purpose |
28
+ | --- | --- |
29
+ | `index.js`, `index.d.ts` | Public exports |
30
+ | `lib/` | Runtime, errors, diagnostics and transaction ownership |
31
+ | `plugins/` | Resource operations, storage and optional integrations |
32
+ | `types/` | Public TypeScript contracts |
33
+ | `tests/fixtures/`, `tests/helpers/` | Resource factories and test infrastructure |
34
+ | `tests/*.test.js` | Library regression suites |
35
+ | `examples/` | Maintained runnable examples and data migration utilities |
36
+ | `scripts/` | Documentation, package and verification tools |
37
+ | `docs/GUIDE/` | User guides and the application migration guide |
38
+ | `old/` | Inactive history; excluded from packaging and automated discovery |
39
+
40
+ [Architecture](architecture.md) explains the main execution boundaries.
41
+ `tests/README.md` in a source checkout contains fixture conventions and the command matrix.
42
+
43
+ ## Verification
44
+
45
+ Run focused suites for changed behavior. Reserve the comprehensive gate for
46
+ changes that need it and release preparation:
47
+
48
+ ```sh
49
+ npm run verify
50
+ ```
51
+
52
+ This runs checked JavaScript/types, packed public declarations and imports,
53
+ query budgets, both complete SQLite storage suites, Express 4 checks, lint and
54
+ the documentation build. A storage-specific test may be skipped in one mode
55
+ and executed in the other; inspect both results before calling it untested.
56
+
57
+ `npm run test:clean-package` installs the actual tarball in a fresh temporary
58
+ consumer and checks optional dependencies plus ordinary/canonical CRUD.
59
+ The `test:*guide`, `test:quickstart`, `test:api-reference` and `test:server-examples`
60
+ commands execute maintained documentation examples. Run those when their code changes.
61
+ `npm run test:migration-guide:package` executes the migration guide's selected
62
+ examples against an extracted tarball and reports its hash. It reuses local test
63
+ dependencies; the separate clean-package check verifies fresh installation.
64
+
65
+ Tests and package tools use temporary directories and dispose of their databases,
66
+ servers and sockets. They do not operate on application databases. Consumer checks
67
+ require an explicit path and command; library verification does not establish an
68
+ application migration.
69
+
70
+ ## Resource stress checks
71
+
72
+ For smaller repeatable measurements, `node scripts/measure-write-lifecycles.js`
73
+ records SQL counts and checks child-hook order for bulk and reverse-relationship
74
+ writes. It supports both storage modes and the database runner.
75
+ `node scripts/measure-diagnostics.js` reports bounded logger formatting cost;
76
+ its local timings are observations, not pass/fail performance thresholds.
77
+
78
+ `npm run test:stress` runs a bounded workload in both SQLite storage modes,
79
+ separately from the full suite. It repeats authorized, paginated `tasks` and
80
+ `shared_tasks` relationship reads with polymorphic `subject` includes. The
81
+ fixture contains equal visible, policy-hidden and foreign-workspace groups;
82
+ every response must contain exactly the expected visible records and includes.
83
+
84
+ Four sequential write scenarios check owned and managed commit/rollback,
85
+ persisted values and exact completion-hook counts. SQL is attributed to each
86
+ operation with `AsyncLocalStorage`; fixed per-operation query ceilings and zero
87
+ runtime metadata lookups are enforced. SQL attribution failures fail the run.
88
+
89
+ To save one JSON report directly:
90
+
91
+ ```sh
92
+ node scripts/stress-resources.js > /tmp/json-rest-api-stress-knex.json
93
+ JSON_REST_API_STORAGE=anyapi node scripts/stress-resources.js > /tmp/json-rest-api-stress-anyapi.json
94
+ ```
95
+
96
+ Store reports outside the tracked source tree. The report includes configuration,
97
+ environment, per-scenario query counts, latency p50/p95/p99, throughput and memory
98
+ samples. Timing includes correctness checks; writes include a persisted-state
99
+ read. There are no machine-dependent latency or memory pass thresholds.
100
+
101
+ | Environment variable | Default | Allowed values |
102
+ | --- | ---: | --- |
103
+ | `JSON_REST_API_STRESS_ROWS` | 100 | 1–1000 per visibility group |
104
+ | `JSON_REST_API_STRESS_READS` | 200 | 2–100000 per batch |
105
+ | `JSON_REST_API_STRESS_WRITES` | 32 | 4–20000 per batch, alternating four scenarios |
106
+ | `JSON_REST_API_STRESS_CONCURRENCY` | 4 | 1–32 overlapping read operations |
107
+ | `JSON_REST_API_STRESS_BATCHES` | 1 | 1–100 repetitions against the same fixture |
108
+ | `JSON_REST_API_STRESS_PAGE_SIZE` | 20 | 1–100 |
109
+
110
+ Totals across batches are capped at 100000 reads and 20000 writes. Defaults seed
111
+ 300 linked tasks plus a project and a task used as a polymorphic subject.
112
+ Each independent operation receives fresh context. SQLite uses one in-memory
113
+ connection, so overlapping reads queue their SQL; this does not test concurrent
114
+ SQLite writers. Writes are sequential on every backend.
115
+
116
+ For a longer bounded run with explicit retained-heap sampling:
117
+
118
+ ```sh
119
+ JSON_REST_API_STRESS_BATCHES=10 node --expose-gc scripts/stress-resources.js > /tmp/json-rest-api-stress-soak.json
120
+ ```
121
+
122
+ Without `--expose-gc`, memory samples describe ordinary heap/RSS fluctuations.
123
+ With it, samples additionally record heap retained after requested garbage
124
+ collection. Both are observations; a bounded run cannot establish leak freedom.
125
+ The driver-aware fixture also supports native checks through the existing runner:
126
+
127
+ ```sh
128
+ node scripts/test-databases.js pg scripts/stress-resources.js
129
+ node scripts/test-databases.js mysql2 scripts/stress-resources.js
130
+ ```
131
+
132
+ ## Real databases and Redis
133
+
134
+ The database runner starts disposable servers and creates isolated test databases.
135
+ Supply PostgreSQL's `initdb`/`postgres` and MySQL's `mysqld`, then run:
136
+
137
+ ```sh
138
+ npm run test:databases:pg
139
+ npm run test:databases:mysql
140
+ npm run test:redis
141
+ ```
142
+
143
+ `JSON_REST_API_POSTGRES_BIN` selects the PostgreSQL executable directory; otherwise
144
+ the runner uses `pg_config --bindir`. `JSON_REST_API_MYSQL_BIN` selects the MySQL
145
+ executable; otherwise it uses `mysqld` on PATH. MariaDB does not replace this MySQL
146
+ check. `JSON_REST_API_REDIS_BIN` selects Redis; otherwise the runner uses PATH.
147
+
148
+ For an affected subset, pass test paths directly:
149
+
150
+ ```sh
151
+ node scripts/test-databases.js pg tests/conformance-managed-transactions.test.js
152
+ ```
153
+
154
+ The runner checks both storage modes by default. Select one explicitly when
155
+ rechecking a backend-specific failure:
156
+
157
+ ```sh
158
+ JSON_REST_API_RUNNER_STORAGE=anyapi npm run test:databases:pg
159
+ ```
160
+
161
+ A complete backend/storage selection has a 30-minute limit; explicitly selected
162
+ test files retain a 15-minute limit. Both limits terminate the owned process group
163
+ and dispose of the database. They do not retry tests or relax assertions.
164
+
165
+ See [backend capabilities](GUIDE/30-backend-capabilities.md) for supported semantics.
166
+ The CI workflow documents its disposable binary setup and database matrix.
167
+
168
+ ## Releases
169
+
170
+ 1. Review the public API and update the [migration guide](GUIDE/33-migrating-to-v2.md).
171
+ 2. Use a new major version for breaking changes. Keep the manifest and lock aligned.
172
+ 3. Run the agreed verification scope and inspect failures, cancellations and skips.
173
+ 4. Inspect the tarball contents and confirm that `old/`, tests, local configuration
174
+ and development dependencies are absent from the installed runtime package.
175
+ 5. Commit the release changes. Publishing and tagging are explicit maintainer actions.
176
+
177
+ Keep secrets in ignored local configuration. Record release evidence in the release
178
+ or pull request; do not add another execution ledger or TODO document to the active tree.
package/index.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ export * from './types/errors.js'
2
+ export * from './types/file-storage.js'
3
+ export type * from './types/representations.js'
4
+ export type * from './types/resource-methods.js'
5
+ export type * from './types/relationship-methods.js'
6
+ export type * from './types/bulk-methods.js'
7
+ export type { ManagedTransaction, ManagedTransactionParam, TransactionMethods } from './types/transactions.js'
8
+ export type * from './types/plugin-options.js'
9
+ export type * from './types/file-detectors.js'
10
+ export type * from './types/fastify-options.js'
11
+ export type * from './types/row-policy.js'
12
+ export type * from './types/autofilter.js'
13
+
14
+ export { JsonRestApi } from './types/runtime.js'
15
+ export type * from './types/runtime.js'
16
+ export type * from './types/resource-schema.js'
17
+ export type * from './types/hook-context.js'
18
+ import type { LibraryPlugin } from './types/runtime.js'
19
+
20
+ export const RestApiPlugin: LibraryPlugin<'rest-api'>
21
+ export const AutoFilterPlugin: LibraryPlugin<'autofilter'>
22
+ export const RowPolicyPlugin: LibraryPlugin<'row-policy'>
23
+ export const QueryProjectionsPlugin: LibraryPlugin<'query-projections'>
24
+ export const FileHandlingPlugin: LibraryPlugin<'file-handling'>
25
+ export const CorsPlugin: LibraryPlugin<'rest-api-cors'>
26
+ export const LabelPlugin: LibraryPlugin<'rest-api-label'>
27
+ export const SocketIOPlugin: LibraryPlugin<'socketio'>
28
+ export const RestApiKnexPlugin: LibraryPlugin<'rest-api-knex'>
29
+ export const RestApiAnyapiKnexPlugin: LibraryPlugin<'rest-api-anyapi-knex'>
30
+ export const ExpressPlugin: LibraryPlugin<'express'>
31
+ export const FastifyPlugin: LibraryPlugin<'fastify'>
32
+
33
+ export function getUrlPrefix(
34
+ context?: { urlPrefixOverride?: string; urlPrefix?: string } | null,
35
+ scope?: { vars?: { transport?: { publicBaseUrl?: string; mountPath?: string } } } | null
36
+ ): string
package/index.js CHANGED
@@ -1,12 +1,4 @@
1
- /**
2
- * JSON REST API - REST API plugin for hooked-api with JSON:API compliance
3
- *
4
- * This package provides:
5
- * - REST API plugin that adds query, get, post, put, patch, delete methods to scopes
6
- * - Connector plugins for various HTTP frameworks (Express, etc.)
7
- * - JSON:API compliant request/response handling
8
- * - Comprehensive error handling and validation
9
- */
1
+ export { JsonRestApi } from './lib/runtime/json-rest-api.js'
10
2
 
11
3
  // Core plugins
12
4
  export { RestApiPlugin } from './plugins/core/rest-api-plugin.js'
@@ -25,23 +17,10 @@ export { RestApiAnyapiKnexPlugin } from './plugins/core/rest-api-anyapi-knex-plu
25
17
  // Connector plugins
26
18
  export { ExpressPlugin } from './plugins/core/connectors/express-plugin.js'
27
19
  export { FastifyPlugin } from './plugins/core/connectors/fastify-plugin.js'
28
- // Future: export { KoaPlugin } from './plugins/core/connectors/koa-plugin.js';
29
20
 
30
21
  // Storage plugins for file handling
31
22
  export { LocalStorage } from './plugins/storage/local-storage.js'
32
- export { S3Storage } from './plugins/storage/s3-storage.js'
33
23
 
34
- // Error classes for consumers who need them
35
- export {
36
- RestApiFieldsetError,
37
- RestApiTemporalDataError,
38
- RestApiValidationError,
39
- RestApiResourceError,
40
- RestApiPayloadError
41
- } from './lib/rest-api-errors.js'
42
-
43
- // Re-export everything for backward compatibility
44
24
  export * from './lib/rest-api-errors.js'
45
25
 
46
- // URL helper function for generating API URL prefix
47
26
  export { getUrlPrefix } from './plugins/core/lib/querying/url-helpers.js'
@@ -1,124 +1,130 @@
1
- /**
2
- * Enhanced logger wrapper that automatically serializes errors with full details
3
- * Wraps any logger to provide comprehensive error logging
4
- */
1
+ // @ts-check
2
+ /** @import { DiagnosticLogger, DiagnosticOptions, EnhancedDiagnosticLogger, DiagnosticWriter } from './logger-types.js' */
5
3
 
6
- import { formatError, formatErrorSummary } from './error-formatter.js'
4
+ import { isErrorObject } from './error-context.js'
5
+ import { formatDiagnosticValue, formatError, formatErrorSummary, readDiagnosticProperty } from './error-formatter.js'
7
6
 
8
7
  /**
9
- * Creates an enhanced logger that automatically formats errors
10
- * @param {Object} baseLogger - The base logger to wrap (must have debug, info, warn, error methods)
11
- * @param {Object} options - Configuration options
12
- * @param {boolean} options.logFullErrors - Whether to log full error details (default: true)
13
- * @param {boolean} options.includeStack - Whether to include stack traces (default: true)
14
- * @returns {Object} Enhanced logger with automatic error formatting
8
+ * @template {DiagnosticLogger} Logger
9
+ * @overload
10
+ * @param {Logger & { error: DiagnosticWriter }} baseLogger
11
+ * @param {DiagnosticOptions} [options]
12
+ * @returns {EnhancedDiagnosticLogger & { error: DiagnosticWriter }}
13
+ */
14
+ /**
15
+ * @template {DiagnosticLogger} Logger
16
+ * @overload
17
+ * @param {Logger} baseLogger
18
+ * @param {DiagnosticOptions} [options]
19
+ * @returns {EnhancedDiagnosticLogger}
20
+ */
21
+ /**
22
+ * Bound diagnostic previews and apply compiled field visibility to supplied levels.
23
+ * @param {DiagnosticLogger} baseLogger
24
+ * @param {DiagnosticOptions} [options]
25
+ * @returns {EnhancedDiagnosticLogger}
15
26
  */
16
27
  export function createEnhancedLogger (baseLogger, options = {}) {
17
28
  const {
18
29
  logFullErrors = true,
19
- includeStack = true
30
+ includeStack = true,
31
+ redactFields = [],
32
+ schemaInfo
20
33
  } = options
21
-
22
- // Helper to process arguments and format errors
23
- function processArgs (args) {
24
- return args.map(arg => {
25
- // If it's an error object, format it
26
- if (arg instanceof Error) {
27
- if (logFullErrors) {
28
- return formatError(arg, { includeStack })
29
- } else {
30
- return formatErrorSummary(arg)
31
- }
32
- }
33
-
34
- // If it's an object with an error property, format the error
35
- if (arg && typeof arg === 'object' && arg.error instanceof Error) {
36
- return {
37
- ...arg,
38
- error: logFullErrors
39
- ? formatError(arg.error, { includeStack })
40
- : formatErrorSummary(arg.error)
41
- }
34
+ const hiddenFields = Object.entries(schemaInfo?.outputFields || {})
35
+ .filter(([, definition]) => definition.hidden === true || definition.normallyHidden === true)
36
+ .map(([field]) => field)
37
+ const diagnosticOptions = { includeStack, redactFields: [...redactFields, ...hiddenFields] }
38
+ const formatLoggedError = logFullErrors ? formatError : formatErrorSummary
39
+
40
+ /** @param {unknown} arg */
41
+ function formatArgument (arg) {
42
+ try {
43
+ if (isErrorObject(arg)) return formatLoggedError(arg, diagnosticOptions)
44
+ if (!arg || typeof arg !== 'object' || diagnosticOptions.redactFields.includes('error')) return arg
45
+
46
+ /** @type {unknown} */
47
+ const nestedError = Object.getOwnPropertyDescriptor(arg, 'error')?.value
48
+ if (!isErrorObject(nestedError)) return arg
49
+ /** @type {object} */
50
+ const metadata = Object(formatDiagnosticValue(arg, diagnosticOptions))
51
+ if (!Object.hasOwn(metadata, 'error')) return metadata
52
+ return {
53
+ ...metadata,
54
+ error: formatLoggedError(nestedError, diagnosticOptions)
42
55
  }
43
-
56
+ } catch {
57
+ // The final shared formatter contains values that cannot even be classified.
44
58
  return arg
45
- })
59
+ }
46
60
  }
47
61
 
48
62
  // Create wrapper methods for each log level
63
+ /** @type {DiagnosticLogger & Record<string, unknown>} */
49
64
  const enhancedLogger = {}
50
65
 
51
- // Common log levels
52
- const logLevels = ['debug', 'info', 'warn', 'error', 'trace', 'fatal', 'log']
66
+ const logLevels = /** @type {const} */ (['debug', 'info', 'warn', 'error', 'trace', 'fatal', 'log'])
53
67
 
54
68
  for (const level of logLevels) {
55
- if (typeof baseLogger[level] === 'function') {
56
- enhancedLogger[level] = function (...args) {
57
- const processedArgs = processArgs(args)
58
-
59
- // Special handling for error level - always log full details
60
- if (level === 'error' && args.some(arg => arg instanceof Error || (arg?.error instanceof Error))) {
61
- // Log a formatted version as well for better readability
62
- const errors = args.filter(arg => arg instanceof Error || arg?.error instanceof Error)
63
- for (const errorArg of errors) {
64
- const error = errorArg instanceof Error ? errorArg : errorArg.error
65
- if (error.details?.violations && Array.isArray(error.details.violations) && error.details.violations.length > 0) {
66
- // Log violations in a readable format
67
- baseLogger[level]('Validation error details:', {
68
- message: error.message,
69
- violations: error.details.violations.map(v => ({
70
- field: v.field,
71
- rule: v.rule,
72
- message: v.message
73
- }))
74
- })
75
- }
76
- }
77
- }
78
-
79
- return baseLogger[level](...processedArgs)
80
- }
69
+ if (typeof baseLogger[level] !== 'function') continue
70
+ // Capture before enhanceLogger replaces methods on the same object.
71
+ const write = baseLogger[level].bind(baseLogger)
72
+ enhancedLogger[level] = function (...args) {
73
+ const processedArgs = args.slice(0, 50).map(formatArgument)
74
+ if (args.length > 50) processedArgs.push('[Truncated]')
75
+ const formatted = formatDiagnosticValue(processedArgs, diagnosticOptions)
76
+ return write(...(Array.isArray(formatted) ? formatted : [formatted]))
81
77
  }
82
78
  }
83
79
 
84
80
  // Preserve any other properties/methods from the base logger
85
81
  for (const key in baseLogger) {
86
82
  if (!(key in enhancedLogger)) {
87
- enhancedLogger[key] = baseLogger[key]
83
+ enhancedLogger[key] = Reflect.get(baseLogger, key)
88
84
  }
89
85
  }
90
86
 
91
- // Add utility methods
92
- enhancedLogger.logError = function (message, error, additionalData = {}) {
93
- const errorData = formatError(error, { includeStack })
94
- this.error(message, {
95
- ...additionalData,
87
+ /** @type {EnhancedDiagnosticLogger['logError']} */
88
+ const logError = function (message, error, additionalData = {}) {
89
+ const errorData = formatError(error, diagnosticOptions)
90
+ /** @type {object} */
91
+ const metadata = Object(formatDiagnosticValue(additionalData, diagnosticOptions))
92
+ return this.error(message, {
93
+ ...metadata,
96
94
  error: errorData,
97
- errorSummary: formatErrorSummary(error)
95
+ errorSummary: formatErrorSummary(error, diagnosticOptions)
98
96
  })
99
97
  }
100
98
 
101
- enhancedLogger.logValidationError = function (message, error, additionalData = {}) {
102
- if (error.details?.violations && Array.isArray(error.details.violations)) {
103
- this.error(message, {
104
- ...additionalData,
105
- message: error.message,
106
- code: error.code,
107
- violations: error.details.violations,
108
- fields: error.details?.fields || [],
109
- fullError: formatError(error, { includeStack: false })
110
- })
111
- } else {
112
- this.logError(message, error, additionalData)
113
- }
99
+ /** @type {EnhancedDiagnosticLogger['logValidationError']} */
100
+ const logValidationError = function (message, error, additionalData = {}) {
101
+ const details = diagnosticOptions.redactFields.includes('details') ? undefined : readDiagnosticProperty(error, 'details')
102
+ const violations = diagnosticOptions.redactFields.includes('violations') ? undefined : readDiagnosticProperty(details, 'violations')
103
+ if (!Array.isArray(violations)) return this.logError(message, error, additionalData)
104
+ const formatted = formatError(error, { ...diagnosticOptions, includeStack: false })
105
+ const formattedDetails = readDiagnosticProperty(formatted, 'details')
106
+ /** @type {object} */
107
+ const metadata = Object(formatDiagnosticValue(additionalData, diagnosticOptions))
108
+ return this.error(message, {
109
+ ...metadata,
110
+ message: readDiagnosticProperty(formatted, 'message'),
111
+ code: readDiagnosticProperty(formatted, 'code'),
112
+ violations: readDiagnosticProperty(formattedDetails, 'violations'),
113
+ fields: readDiagnosticProperty(formattedDetails, 'fields') || [],
114
+ fullError: formatted
115
+ })
114
116
  }
115
117
 
116
- return enhancedLogger
118
+ return Object.assign(enhancedLogger, { logError, logValidationError })
117
119
  }
118
120
 
119
121
  /**
120
122
  * Middleware to enhance an existing logger instance
121
123
  * Can be used to patch global loggers
124
+ * @template {DiagnosticLogger} Logger
125
+ * @param {Logger} logger
126
+ * @param {DiagnosticOptions} [options]
127
+ * @returns {Logger}
122
128
  */
123
129
  export function enhanceLogger (logger, options = {}) {
124
130
  const enhanced = createEnhancedLogger(logger, options)
@@ -126,7 +132,7 @@ export function enhanceLogger (logger, options = {}) {
126
132
  // Replace methods on the original logger
127
133
  for (const key in enhanced) {
128
134
  if (typeof enhanced[key] === 'function') {
129
- logger[key] = enhanced[key]
135
+ Object.assign(logger, { [key]: enhanced[key] })
130
136
  }
131
137
  }
132
138