json-rest-api 1.0.28 → 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 (289) 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 +746 -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} +90 -31
  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 -174
  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 +67 -330
  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 +205 -30
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +20 -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 +238 -187
  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 +111 -287
  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 +109 -44
  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 +142 -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 -2509
  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 -1069
  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 -385
  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 -145
  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 +38 -69
  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 +39 -82
  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 -1238
  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 -150
  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 -118
  251. package/tests/fixtures/api-configs.js +0 -2509
  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-cursor-multifield.test.js +0 -643
  264. package/tests/pagination-enhanced.test.js +0 -563
  265. package/tests/polymorphic-relationships.test.js +0 -347
  266. package/tests/polymorphic-simple.test.js +0 -148
  267. package/tests/positioning-advanced.test.js +0 -692
  268. package/tests/positioning-extended.test.js +0 -915
  269. package/tests/positioning-multifilter.test.js +0 -699
  270. package/tests/positioning.test.js +0 -560
  271. package/tests/post-commit-hooks.test.js +0 -120
  272. package/tests/projected-fields.test.js +0 -319
  273. package/tests/public-exports.test.js +0 -15
  274. package/tests/put-create-parity.test.js +0 -216
  275. package/tests/queries.test.js +0 -521
  276. package/tests/query-limits.test.js +0 -584
  277. package/tests/relationship-endpoints.test.js +0 -620
  278. package/tests/relationship-include-storage-adapter.test.js +0 -56
  279. package/tests/relationships.test.js +0 -681
  280. package/tests/rest-api.test.js +0 -337
  281. package/tests/return-record-settings.test.js +0 -184
  282. package/tests/row-policy.test.js +0 -489
  283. package/tests/s3-storage.test.js +0 -38
  284. package/tests/searchschema-knex-behavior.test.js +0 -187
  285. package/tests/searchschema-merge.test.js +0 -151
  286. package/tests/socketio.test.js +0 -860
  287. package/tests/storage-mapping.test.js +0 -362
  288. package/tests/test-development-guide.md +0 -370
  289. package/tests/virtual-fields.test.js +0 -404
@@ -1,1491 +0,0 @@
1
- # Hooks and Data Management
2
-
3
- This guide provides a comprehensive reference for all hooks available in the json-rest-api system, including system-wide hooks from hooked-api and method-specific hooks from the REST API plugin.
4
-
5
- If you are building a reusable plugin rather than just attaching hooks to one API, see [Writing Plugins](GUIDE_X_Writing_Plugins.md). This guide focuses on hook behavior; the plugin guide focuses on the supported extension surface.
6
-
7
- An important concept when working with hooks is understanding how simplified mode affects data flow.
8
-
9
- Inside hooks, JSON:API format is king _regardless of simplified mode_.
10
- Simplified mode only ever affects:
11
- - **Input**: How parameters are passed to the API (simplified data is converted to JSON:API before entering the lifecycle)
12
- - **Output**: How the record is returned to the client (JSON:API data is converted to simplified format before returning)
13
- Hook context _always_ contains: Full JSON:API formatted records with `data`, `type`, `attributes`, and `relationships`. So, as far as the hooks are concerned, `context.inputRecord` is _always_ a full JSON:API object.
14
-
15
- This means when writing hooks, you always work with the standard JSON:API structure:
16
- ```javascript
17
- // In a hook, the record is ALWAYS JSON:API format:
18
- hooks: {
19
- beforeData: async ({ context }) => {
20
- // Even in simplified mode, inputRecord has JSON:API structure
21
- if (context.method === 'post' && context.inputRecord) {
22
- // Always access via data.attributes
23
- context.inputRecord.data.attributes.created_at = new Date().toISOString();
24
- }
25
- },
26
-
27
- // IMPORTANT: Use enrichAttributes to modify attributes, NOT enrichRecord
28
- enrichAttributes: async ({ context }) => {
29
- // This is called for ALL records (main and included/child records)
30
- // Add computed fields directly to context.attributes
31
- context.attributes.computed_field = 'value';
32
- context.attributes.word_count = context.attributes.content?.split(' ').length || 0;
33
- }
34
- }
35
- ```
36
-
37
- One of the main practical use of hooks is to manupulate data before it's committed to the database.
38
-
39
- ## Customizing the API as a whole with customize()
40
-
41
- The `customize()` method is the primary way to extend your API with hooks, variables, and helper functions. This method is available on the API instance and provides a cleaner alternative to calling individual methods like `addHook()`.
42
-
43
- The `customize()` method accepts an object with the following properties:
44
- - `hooks` - Hook handlers for various lifecycle events
45
- - `vars` - Variables accessible throughout the API
46
- - `helpers` - Reusable functions
47
- - `apiMethods` - Methods added to the API instance
48
- - `scopeMethods` - Methods added to _all_ scopes/resources
49
-
50
- ### Basic Example
51
-
52
- The `customize()` method accepts an object with hooks, vars (shared state), helpers (reusable functions), apiMethods (global methods), and scopeMethods (methods for all scopes):
53
-
54
- ```javascript
55
- api.customize({
56
- // Shared variables accessible throughout the API
57
- vars: {
58
- appName: 'My Application',
59
- userRoles: ['admin', 'editor', 'viewer'],
60
- environment: process.env.NODE_ENV
61
- },
62
-
63
- // Reusable helper functions
64
- helpers: {
65
- formatDate: (date) => new Date(date).toLocaleDateString(),
66
- validateEmail: (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email),
67
- hashPassword: async (password) => {
68
- const salt = await bcrypt.genSalt(10);
69
- return bcrypt.hash(password, salt);
70
- }
71
- },
72
-
73
- // Hooks for customizing behavior
74
- hooks: {
75
- beforeData: async ({ context, vars, helpers, log }) => {
76
- log.info(`${context.method} operation on ${context.scopeName}`);
77
-
78
- // Use vars for configuration
79
- if (vars.environment === 'production') {
80
- // Production-specific logic
81
- }
82
-
83
- // Modify data for POST requests
84
- if (context.method === 'post' && context.inputRecord) {
85
- // Set timestamps
86
- context.inputRecord.data.attributes.created_at = new Date().toISOString();
87
-
88
- // Validate and transform data using helpers
89
- if (context.scopeName === 'users') {
90
- // Hash password
91
- if (context.inputRecord.data.attributes.password) {
92
- context.inputRecord.data.attributes.password = await helpers.hashPassword(
93
- context.inputRecord.data.attributes.password
94
- );
95
- }
96
-
97
- }
98
- }
99
- },
100
-
101
- enrichAttributes: async ({ context }) => {
102
- // Add computed fields to posts
103
- if (context.scopeName === 'posts') {
104
- // NOTE that 'preview' MUST be an existing record on the database
105
- context.attributes.preview = context.attributes.content?.substring(0, 200) + '...';
106
- }
107
- }
108
- },
109
-
110
- // API-level methods: api.methodName()
111
- apiMethods: {
112
- healthCheck: async ({ vars, scopes }) => ({
113
- status: 'ok',
114
- environment: vars.environment,
115
- timestamp: new Date().toISOString(),
116
- resources: Object.keys(scopes)
117
- })
118
- },
119
-
120
- // Scope methods: api.scopes.resourceName.methodName()
121
- scopeMethods: {
122
- count: async ({ scopeName, db }) => {
123
- const result = await db(scopeName).count('* as total').first();
124
- return result.total;
125
- }
126
- }
127
- });
128
-
129
- // Usage examples:
130
- const health = await api.healthCheck();
131
- const userCount = await api.scopes.users.count();
132
- ```
133
-
134
- **Important**: Hooks added via `customize()` are registered with the plugin name `api-custom:{apiName}` for tracking purposes.
135
-
136
- ## Customizing Resources with extras (per resource)
137
-
138
- While `customize()` applies globally to all resources, you can customize individual resources using the `extras` parameter in `addScope()`. This provides resource-specific hooks, methods, variables, and helpers that apply **only to that particular resource**.
139
-
140
- ### Using extras in addScope
141
-
142
- ```javascript
143
- api.addScope('posts', {
144
- // Schema and options
145
- }, {
146
- // extras parameter - everything here applies ONLY to posts
147
- hooks: {
148
-
149
- // This will change data before getting posted
150
- beforeDataPost: async ({ context }) => {
151
- const attributes = context.inputRecord.data.attributes
152
- // This hook only runs for POST operations on posts
153
- attributes.created_at = new Date().toISOString();
154
- },
155
-
156
- // This will enrich all attributes (main and included) when fetched
157
- // It's for adding computed fields to the RESPONSE, NOT for saving to database
158
- // These computed fields are sent to the client but never stored
159
- enrichAttributes: async ({ context }) => {
160
- // This wordCount is calculated on-the-fly and included in the response
161
- // It is NOT saved to the database
162
- context.attributes.wordCount = context.attributes.content?.split(' ').length || 0;
163
-
164
- // Example: Add a reading time estimate (also not stored)
165
- const wordsPerMinute = 200;
166
- context.attributes.readingTimeMinutes = Math.ceil(context.attributes.wordCount / wordsPerMinute);
167
- }
168
- },
169
-
170
- // CLAUDE: NO. This should use patch, since direct database operations
171
- // will skip the lifecycle (e.g. Sockets notifications etc.)
172
- scopeMethods: {
173
- // This method is ONLY available on posts: api.scopes.posts.publish()
174
- publish: async ({ params, context, db, runHooks }) => {
175
- const id = params.id;
176
- const result = await db('posts')
177
- .where('id', id)
178
- .update({
179
- status: 'published',
180
- published_at: new Date().toISOString()
181
- });
182
- return { success: true, published: result };
183
- },
184
-
185
- // Only on posts: api.scopes.posts.findByAuthor()
186
- findByAuthor: async ({ params, scope }) => {
187
- return await scope.query({
188
- filters: { author_id: params.authorId }
189
- });
190
- }
191
- },
192
-
193
- vars: {
194
- // Resource-specific configuration
195
- maxTitleLength: 200,
196
- allowedStatuses: ['draft', 'published', 'archived'],
197
- defaultStatus: 'draft'
198
- },
199
-
200
- helpers: {
201
- // Resource-specific helper functions
202
- generateSlug: (title) => {
203
- return title.toLowerCase()
204
- .replace(/[^a-z0-9]+/g, '-')
205
- .replace(/^-|-$/g, '');
206
- },
207
-
208
- validateStatus: (status, vars) => {
209
- // Note: can access vars through second parameter
210
- return vars.allowedStatuses.includes(status);
211
- }
212
- }
213
- });
214
- ```
215
-
216
- ### Variable and Helper Fallback System
217
-
218
- An important feature of resource-specific vars and helpers is the **fallback system**:
219
-
220
- 1. **Variables (vars)**: When you access a variable in a resource context, it first checks the resource's vars. If not found, it falls back to the global API vars.
221
-
222
- ```javascript
223
- // Global vars
224
- api.customize({
225
- vars: {
226
- appName: 'My Blog',
227
- defaultPageSize: 20,
228
- maxUploadSize: 5242880 // 5MB
229
- }
230
- });
231
-
232
- // Resource-specific vars
233
- api.addScope('posts', {}, {
234
- vars: {
235
- defaultPageSize: 10, // Override for posts only
236
- maxTitleLength: 200 // Posts-specific var
237
- }
238
- });
239
-
240
- // In a posts hook or method:
241
- // vars.defaultPageSize → 10 (from posts vars)
242
- // vars.maxUploadSize → 5242880 (fallback to global)
243
- // vars.maxTitleLength → 200 (posts-specific)
244
- // vars.appName → 'My Blog' (fallback to global)
245
- ```
246
-
247
- 2. **Helpers**: Same fallback behavior - resource helpers are checked first, then global helpers.
248
-
249
- ```javascript
250
- // Global helpers
251
- api.customize({
252
- helpers: {
253
- formatDate: (date) => new Date(date).toLocaleDateString(),
254
- sanitizeHtml: (html) => { /* ... */ }
255
- }
256
- });
257
-
258
- // Resource-specific helpers
259
- api.addScope('posts', {}, {
260
- helpers: {
261
- formatDate: (date) => new Date(date).toISOString(), // Override for posts
262
- generateExcerpt: (content) => content.substring(0, 150) + '...'
263
- }
264
- });
265
-
266
- // In posts context:
267
- // helpers.formatDate() → uses posts version (ISO format)
268
- // helpers.sanitizeHtml() → uses global version (fallback)
269
- // helpers.generateExcerpt() → posts-specific helper
270
- ```
271
-
272
- This means that you are able to specify api-wide variables and helpers, but can then override them by resource.
273
-
274
- ### Resource-Specific vs Global Customization
275
-
276
- | Feature | Global (`customize()`) | Resource-Specific (`extras`) |
277
- |---------|----------------------|----------------------------|
278
- | **Scope** | Applies to all resources | Applies to one resource only |
279
- | **Hooks** | Must check `context.scopeName` | Automatically scoped |
280
- | **Methods** | `apiMethods` → `api.methodName()`<br>`scopeMethods` → all scopes | `scopeMethods` → only this scope |
281
- | **Vars** | Global defaults | Resource-specific with fallback |
282
- | **Helpers** | Global utilities | Resource-specific with fallback |
283
-
284
-
285
- ### Best Practices for Resource Customization
286
-
287
- 1. **Use extras for resource-specific logic** - Don't clutter global hooks with scopeName checks
288
- 2. **Leverage the fallback system** - Define common utilities globally, override when needed
289
- 3. **Keep resource methods focused** - Methods should relate to that specific resource
290
- 4. **Document resource-specific vars** - Make it clear what configuration is available
291
- 5. **Avoid naming conflicts** - Be aware that resource vars/helpers can shadow global ones
292
-
293
- ### Best Practices for Global Customization
294
-
295
- 1. **Check scopeName in Hooks** - Since customize() is API-level only, use `context.scopeName` to implement resource-specific logic
296
- 2. **Keep Helpers Pure** - Make helpers independent functions that are easier to test and reuse
297
- 3. **Use Vars for Configuration** - Store configuration values in vars instead of hardcoding them
298
- 4. **Avoid Mutable Shared State** - Be careful with objects/arrays in vars as they're shared across all requests
299
- 5. **Handle Errors Gracefully** - Thrown errors in hooks will stop the operation and return to the client
300
- 6. **Use Method-Specific Hooks** - Use `beforeDataPost`, `afterDataPatch`, etc. for operation-specific logic
301
-
302
-
303
- ## REST API Method Hooks
304
-
305
- These hooks are triggered by the REST API plugin during CRUD operations. Each method follows a consistent pattern but with method-specific variations.
306
-
307
- ### Important Context Properties
308
-
309
- The context object contains different properties depending on the method and stage of execution:
310
-
311
- **Common Properties**:
312
- - `method` (string) - The HTTP method: 'query', 'get', 'post', 'put', 'patch', or 'delete'
313
- - `simplified` (boolean) - Whether simplified mode is active (affects input/output format, not hook data)
314
- - `schemaInfo` (object) - Compiled schema information containing:
315
- - `schema` - The full schema object
316
- - `searchSchema` - Schema for filtering (query method)
317
- - `tableName` - Database table name
318
- - `idProperty` - Primary key field name
319
- - `schemaRelationships` - Relationship definitions
320
- - `scopeName` (string) - Name of the current resource scope
321
- - `transaction` (object/null) - Database transaction if provided
322
- - `db` (object) - Database connection (knex instance or transaction)
323
- - `auth` (object) - Authentication context if provided
324
-
325
- **Write Operation Properties** (POST/PUT/PATCH):
326
- - `inputRecord` (object) - The JSON:API formatted input containing:
327
- - `data.type` - Resource type
328
- - `data.attributes` - Resource attributes (this is where you modify input data)
329
- - `data.relationships` - Resource relationships
330
- - `belongsToUpdates` (object) - Foreign key updates extracted from relationships
331
- - `returnRecordSetting` (object) - Configuration for what to return
332
-
333
- **Query/Read Properties**:
334
- - `queryParams` (object) - Query parameters containing:
335
- - `fields` - Sparse fieldset selections
336
- - `include` - Relationships to include
337
- - `sort` - Sort fields array (query only)
338
- - `page` - Pagination settings (query only)
339
- - `filters` - Filter conditions (query only)
340
- - `record` (object) - The fetched/created record in JSON:API format
341
- - `originalRecord` (object) - Backup of the record before modifications
342
-
343
- ### Hook Execution Pattern
344
-
345
- All REST API methods follow this general pattern:
346
-
347
- 1. **Before hooks** - Run before the main operation
348
- 2. **Permission checks** - For GET method
349
- 3. **Main operation** - The actual database operation
350
- 4. **After hooks** - Run after the operation
351
- 5. **Enrichment hooks** - To enhance the record
352
- 6. **Transaction hooks** - Commit/rollback for write operations
353
- 7. **Finish hooks** - Final cleanup/processing
354
-
355
- ## QUERY Method Hooks
356
-
357
- Used for retrieving collections of resources with filtering, sorting, and pagination.
358
-
359
- ### beforeData
360
-
361
- **When**: Before executing the database query
362
- **Purpose**: Modify query parameters, add custom filters, set defaults
363
-
364
- **Context contains**:
365
- - `method` (string) - "query"
366
- - `simplified` (boolean) - Whether simplified mode is active (affects input/output format, not hook data)
367
- - `schemaInfo` (object) - Compiled schema information
368
- - `schema` - Full schema object
369
- - `searchSchema` - Schema for filtering
370
- - `tableName` - Database table name
371
- - `idProperty` - Primary key field name
372
- - `queryParams` (object) - Query parameters
373
- - `fields` - Sparse fieldset selections
374
- - `include` - Relationships to include
375
- - `sort` - Sort fields array
376
- - `page` - Pagination settings
377
- - `filters` - Filter conditions
378
- - `transaction` (object/null) - Database transaction if provided
379
- - `db` (object) - Database connection (knex instance or transaction)
380
- - `scopeName` (string) - Name of the current scope
381
- - `sortableFields` (array) - Fields allowed for sorting
382
- - `auth` (object) - Authentication context if provided
383
-
384
- **What can be changed**:
385
- - `queryParams.filters` - Modify filter conditions
386
- - `queryParams.sort` - Modify sort order
387
- - `queryParams.page` - Modify pagination
388
- - `queryParams.fields` - Modify field selection
389
- - `queryParams.include` - Modify includes
390
- - Any custom properties added to context
391
-
392
- **Example**:
393
- ```javascript
394
- // In api.addScope('posts', {}, extras):
395
- hooks: {
396
- beforeData: async ({ context }) => {
397
- if (context.method === 'query' && context.auth?.userId) {
398
- // Only show posts by the current user
399
- context.queryParams.filters = {
400
- ...context.queryParams.filters,
401
- author_id: context.auth.userId
402
- };
403
- }
404
- }
405
- }
406
- ```
407
-
408
- ### beforeDataQuery
409
-
410
- **When**: Immediately after `beforeData`, query-specific
411
- **Purpose**: Query-specific modifications
412
-
413
- **Context**: Same as `beforeData`
414
- **What can be changed**: Same as `beforeData`
415
-
416
- ### enrichRecord
417
-
418
- **When**: After data is fetched from database and normalized
419
- **Purpose**: Modify the response structure, add metadata, or handle response-level concerns
420
-
421
- **IMPORTANT**: Do NOT use this hook to add/modify attributes. Use `enrichAttributes` instead.
422
-
423
- **Context contains**:
424
- - All previous context properties
425
- - `record` (object) - The JSON:API formatted response
426
- - `data` - Array of resource objects
427
- - `included` - Array of included resources
428
- - `meta` - Metadata (pagination, etc.)
429
- - `links` - Pagination links
430
- - `originalRecord` (object) - Backup of the record before enrichment
431
-
432
- **What can be changed**:
433
- - `record.meta` - Add or modify metadata
434
- - `record.links` - Modify links
435
- - Response structure modifications (but NOT attributes)
436
- - Should NOT modify attributes - use `enrichAttributes` hook instead
437
-
438
- **Example**:
439
- ```javascript
440
- hooks: {
441
- enrichAttributes: async ({ context }) => {
442
- if (context.parentContext?.method === 'query') {
443
- // Add computed fields that are NOT stored in database
444
- // These are calculated fresh for each response
445
- context.attributes.wordCount = context.attributes.content?.split(' ').length || 0;
446
- context.attributes.excerpt = context.attributes.content?.substring(0, 150) + '...';
447
-
448
- // Transform display values (original remains in database)
449
- // Database still has lowercase title, this is just for this response
450
- context.attributes.displayTitle = context.attributes.title?.toUpperCase();
451
- }
452
- }
453
- }
454
- ```
455
-
456
- ### finish
457
-
458
- **When**: Before returning the response
459
- **Purpose**: Final logging, metrics collection
460
-
461
- **Context contains**: All previous context properties
462
- **What can be changed**: Nothing - hooks should NOT change `context.record` at this stage
463
-
464
- ### finishQuery
465
-
466
- **When**: Immediately after `finish`, query-specific
467
- **Purpose**: Query-specific final processing
468
-
469
- **Context**: Same as `finish`
470
- **What can be changed**: Nothing - informational only
471
-
472
- ## GET Method Hooks
473
-
474
- Used for retrieving a single resource by ID.
475
-
476
- ### beforeData
477
-
478
- **When**: Before fetching the single resource
479
- **Purpose**: Modify query parameters, prepare for fetch
480
-
481
- **Context contains**:
482
- - `method` (string) - "get"
483
- - `simplified` (boolean) - Whether simplified mode is active (affects input/output format, not hook data)
484
- - `schemaInfo` (object) - Compiled schema information
485
- - `queryParams` (object) - Query parameters
486
- - `fields` - Sparse fieldset selections
487
- - `include` - Relationships to include
488
- - `transaction` (object/null) - Database transaction if provided
489
- - `db` (object) - Database connection
490
- - `scopeName` (string) - Name of the current scope
491
- - `id` (string/number) - The ID of the resource to fetch
492
- - `minimalRecord` (object) - Minimal record fetched for authorization
493
- - `auth` (object) - Authentication context if provided
494
-
495
- **What can be changed**:
496
- - `queryParams.fields` - Modify field selection
497
- - `queryParams.include` - Modify includes
498
- - Custom context properties
499
-
500
- ### beforeDataGet
501
-
502
- **When**: Immediately after `beforeData`, get-specific
503
- **Context**: Same as `beforeData`
504
- **What can be changed**: Same as `beforeData`
505
-
506
- ### checkDataPermissions
507
-
508
- **When**: After the record is fetched, before enrichment
509
- **Purpose**: Implement row-level security, check access permissions
510
-
511
- **Context contains**:
512
- - All previous context properties
513
- - `record` (object) - The fetched JSON:API record
514
-
515
- **What can be changed**:
516
- - Can throw errors to deny access
517
- - Should NOT modify the record
518
-
519
- **Example**:
520
- ```javascript
521
- hooks: {
522
- checkDataPermissions: async ({ context }) => {
523
- if (context.method === 'get') {
524
- const post = context.record.data;
525
- if (post.attributes.status === 'draft' && post.attributes.author_id !== context.auth?.userId) {
526
- throw new Error('Access denied: Cannot view draft posts by other authors');
527
- }
528
- }
529
- }
530
- }
531
- ```
532
-
533
- ### checkDataPermissionsGet
534
-
535
- **When**: Immediately after `checkDataPermissions`, get-specific
536
- **Context**: Same as `checkDataPermissions`
537
- **What can be changed**: Same as `checkDataPermissions`
538
-
539
- ### enrichRecord
540
-
541
- **When**: After permission checks
542
- **Purpose**: Modify the response structure or add metadata
543
-
544
- **IMPORTANT**: Do NOT use this hook to add/modify attributes. Use `enrichAttributes` instead.
545
-
546
- **Context contains**:
547
- - All previous context properties
548
- - `record` (object) - The JSON:API formatted response
549
- - `originalRecord` (object) - Backup of the record
550
- - `computedDependencies` (object) - Fields needed for computed fields
551
-
552
- **What can be changed**:
553
- - Response structure (but NOT attributes)
554
- - `record.meta` - Add metadata
555
- - Should NOT modify attributes - use `enrichAttributes` hook instead
556
-
557
- ### enrichRecordWithRelationships
558
-
559
- **When**: After basic enrichment
560
- **Purpose**: Add relationship metadata, enhance relationship data
561
-
562
- **Context**: Same as `enrichRecord`
563
- **What can be changed**:
564
- - Can modify relationship data
565
- - Can add relationship metadata
566
-
567
- ### finish
568
-
569
- **When**: Before returning the response
570
- **Context**: All accumulated context
571
- **What can be changed**: Nothing - informational only
572
-
573
- ### finishGet
574
-
575
- **When**: Immediately after `finish`, get-specific
576
- **Context**: Same as `finish`
577
- **What can be changed**: Nothing - informational only
578
-
579
- ## POST Method Hooks
580
-
581
- Used for creating new resources.
582
-
583
- ### beforeData
584
-
585
- **When**: Before creating the new resource
586
- **Purpose**: Validate data, set defaults, compute values
587
-
588
- **Context contains**:
589
- - `method` (string) - "post"
590
- - `simplified` (boolean) - Whether simplified mode is active (affects input/output format, not hook data)
591
- - `schemaInfo` (object) - Compiled schema information
592
- - `inputRecord` (object) - The JSON:API formatted input data
593
- - `data.type` - Resource type
594
- - `data.attributes` - Resource attributes
595
- - `data.relationships` - Resource relationships
596
- - `params` (object) - Original parameters (may contain `returnFullRecord`)
597
- - `queryParams` (object) - Contains `fields`, `include` for response
598
- - `transaction` (object) - Database transaction (created if not provided)
599
- - `shouldCommit` (boolean) - Whether to commit the transaction
600
- - `db` (object) - Database connection
601
- - `scopeName` (string) - Name of the current scope
602
- - `returnRecordSetting` (object) - Settings for what to return
603
- - `post` - 'no', 'minimal', or 'full'
604
- - `put` - 'no', 'minimal', or 'full'
605
- - `patch` - 'no', 'minimal', or 'full'
606
- - `auth` (object) - Authentication context if provided
607
-
608
- **What can be changed**:
609
- - `inputRecord.data.attributes` - Modify validated attributes before insert
610
- - `belongsToUpdates` - Modify foreign key values (but these are usually already merged into attributes)
611
- - Custom context properties
612
- - Can set defaults or compute values
613
-
614
- **Note**: After validation, attributes are stored in `context.inputRecord.data.attributes`, not directly in `context.attributes`.
615
- **Note**: `context.minimalRecord` is always a JSON:API resource object; for POST requests it is a snapshot of the incoming payload (`{ type, id?, attributes, relationships }`).
616
-
617
- **Example**:
618
- ```javascript
619
- hooks: {
620
- beforeData: async ({ context }) => {
621
- if (context.method === 'post' && context.inputRecord) {
622
- // Set default status
623
- if (!context.inputRecord.data.attributes.status) {
624
- context.inputRecord.data.attributes.status = 'draft';
625
- }
626
-
627
- // Set author from auth context (this would typically be in belongsToUpdates)
628
- if (context.auth?.userId && context.belongsToUpdates) {
629
- context.belongsToUpdates.author_id = context.auth.userId;
630
- }
631
-
632
- // Add creation timestamp
633
- context.inputRecord.data.attributes.created_at = new Date().toISOString();
634
- }
635
- }
636
- }
637
- ```
638
-
639
- ### beforeDataPost
640
-
641
- **When**: Immediately after `beforeData`, post-specific
642
- **Context**: Same as `beforeData`
643
- **What can be changed**: Same as `beforeData`
644
-
645
- ### afterData
646
-
647
- **When**: After the resource is created in the database
648
- **Purpose**: Trigger side effects, create related records
649
-
650
- **Context contains**:
651
- - All previous context properties
652
- - `id` (string/number) - The ID of the created resource
653
- - `newRecord` (object) - The raw database record
654
-
655
- **What can be changed**:
656
- - Can perform side effects (create related records, etc.)
657
- - Can add properties to context for later hooks
658
- - Should NOT modify `newRecord` directly
659
-
660
- **Example**:
661
- ```javascript
662
- hooks: {
663
- afterData: async ({ context, scopes }) => {
664
- if (context.method === 'post') {
665
- // Create a notification for new post
666
- await scopes.notifications.create({
667
- type: 'new_post',
668
- post_id: context.id,
669
- user_id: context.belongsToUpdates.author_id,
670
- created_at: new Date().toISOString()
671
- });
672
- }
673
- }
674
- }
675
- ```
676
-
677
- ### afterDataPost
678
-
679
- **When**: Immediately after `afterData`, post-specific
680
- **Context**: Same as `afterData`
681
- **What can be changed**: Same as `afterData`
682
-
683
- ### enrichRecord
684
-
685
- **When**: After fetching the created record (if `returnFullRecord` is not 'no')
686
- **Purpose**: Modify response structure or add metadata
687
-
688
- **IMPORTANT**: Do NOT use this hook to add/modify attributes. Use `enrichAttributes` instead.
689
-
690
- **Context contains**:
691
- - All previous context properties
692
- - `record` (object) - The JSON:API formatted response
693
- - `originalRecord` (object) - Backup of the record
694
-
695
- **What can be changed**:
696
- - Response structure (but NOT attributes)
697
- - `record.meta` - Add metadata
698
- - Should NOT modify attributes - use `enrichAttributes` hook instead
699
-
700
- ### afterCommit
701
-
702
- **When**: After the transaction is committed (only if `shouldCommit` is true)
703
- **Purpose**: Trigger post-commit side effects like sending emails, webhooks
704
-
705
- **Context**: All accumulated context
706
- **What can be changed**: Nothing - for side effects only
707
-
708
- **Example**:
709
- ```javascript
710
- hooks: {
711
- afterCommit: async ({ context, helpers }) => {
712
- if (context.method === 'post') {
713
- // Send email notification (safe to do after commit)
714
- await helpers.emailService.send({
715
- template: 'new_post',
716
- data: {
717
- postId: context.id,
718
- title: context.inputRecord.data.attributes.title
719
- }
720
- });
721
- }
722
- }
723
- }
724
- ```
725
-
726
- ### afterRollback
727
-
728
- **When**: If an error occurs and transaction is rolled back
729
- **Purpose**: Clean up any external resources, log failures
730
-
731
- **Context**: All accumulated context plus error information
732
- **What can be changed**: Nothing - for cleanup/logging only
733
-
734
- ### finish
735
-
736
- **When**: Before returning the response
737
- **Context**: All accumulated context
738
- **What can be changed**: Nothing - informational only
739
-
740
- ### finishPost
741
-
742
- **When**: Immediately after `finish`, post-specific
743
- **Context**: Same as `finish`
744
- **What can be changed**: Nothing - informational only
745
-
746
- ## PUT Method Hooks
747
-
748
- Used for completely replacing a resource.
749
-
750
- ### beforeData
751
-
752
- **When**: Before replacing the resource
753
- **Purpose**: Validate replacement data, check permissions
754
-
755
- **Context contains**:
756
- - `method` (string) - "put"
757
- - `simplified` (boolean) - Whether simplified mode is active (affects input/output format, not hook data)
758
- - `schemaInfo` (object) - Compiled schema information
759
- - `inputRecord` (object) - The JSON:API formatted input data
760
- - `id` (string/number) - The ID from URL or input record
761
- - `params` (object) - Original parameters
762
- - `queryParams` (object) - Contains `fields`, `include` for response
763
- - `transaction` (object) - Database transaction
764
- - `shouldCommit` (boolean) - Whether to commit the transaction
765
- - `db` (object) - Database connection
766
- - `scopeName` (string) - Name of the current scope
767
- - `returnRecordSetting` (object) - Settings for what to return
768
- - `minimalRecord` (object) - Existing record for authorization
769
- - `existingRelationships` (object) - Current hasMany/manyToMany relationships
770
- - `auth` (object) - Authentication context if provided
771
-
772
- **What can be changed**:
773
- - `inputRecord.data.attributes` - Modify validated attributes before update
774
- - `belongsToUpdates` - Modify foreign key values (but these are usually already merged into attributes)
775
- - Can prevent certain field updates
776
-
777
- **Note**: After validation, attributes are stored in `context.inputRecord.data.attributes`.
778
-
779
- **Example**:
780
- ```javascript
781
- hooks: {
782
- beforeData: async ({ context }) => {
783
- if (context.method === 'put' && context.inputRecord) {
784
- // Prevent changing the author (check if belongsTo relationship changed)
785
- const newAuthorId = context.belongsToUpdates?.author_id;
786
- const currentAuthorId = context.minimalRecord?.relationships?.author?.data?.id;
787
- if (newAuthorId && newAuthorId !== currentAuthorId) {
788
- throw new Error('Cannot change post author');
789
- }
790
-
791
- // Add update timestamp
792
- context.inputRecord.data.attributes.updated_at = new Date().toISOString();
793
- }
794
- }
795
- }
796
- ```
797
-
798
- ### beforeDataPut
799
-
800
- **When**: Immediately after `beforeData`, put-specific
801
- **Context**: Same as `beforeData`
802
- **What can be changed**: Same as `beforeData`
803
-
804
- ### afterData
805
-
806
- **When**: After the resource is updated and relationships are replaced
807
- **Purpose**: Handle relationship changes, trigger updates
808
-
809
- **Context contains**:
810
- - All previous context properties
811
- - `updatedRecord` (object) - The updated database record
812
- - `relationshipChanges` (object) - Details of relationship modifications
813
-
814
- **What can be changed**:
815
- - Can perform side effects
816
- - Can clean up orphaned relationships
817
- - Should NOT modify the database record
818
-
819
- ### afterDataPut
820
-
821
- **When**: Immediately after `afterData`, put-specific
822
- **Context**: Same as `afterData`
823
- **What can be changed**: Same as `afterData`
824
-
825
- ### enrichRecord
826
-
827
- **When**: After fetching the updated record (if `returnFullRecord` is not 'no')
828
- **Purpose**: Modify response structure or add metadata
829
-
830
- **IMPORTANT**: Do NOT use this hook to add/modify attributes. Use `enrichAttributes` instead.
831
-
832
- **Context contains**:
833
- - All previous context properties
834
- - `record` (object) - The JSON:API formatted response
835
- - `originalRecord` (object) - Backup of the record
836
-
837
- **What can be changed**:
838
- - Response structure (but NOT attributes)
839
- - `record.meta` - Add metadata
840
- - Should NOT modify attributes - use `enrichAttributes` hook instead
841
-
842
- ### enrichRecordWithRelationships
843
-
844
- **When**: After basic enrichment
845
- **Context**: Same as `enrichRecord`
846
- **What can be changed**:
847
- - Can modify relationship data
848
- - Can add relationship metadata
849
-
850
- ### afterCommit
851
-
852
- **When**: After the transaction is committed
853
- **Context**: All accumulated context
854
- **What can be changed**: Nothing - for side effects only
855
-
856
- ### afterRollback
857
-
858
- **When**: If an error occurs and transaction is rolled back
859
- **Context**: All accumulated context plus error information
860
- **What can be changed**: Nothing - for cleanup only
861
-
862
- ### finish
863
-
864
- **When**: Before returning the response
865
- **Context**: All accumulated context
866
- **What can be changed**: Nothing - informational only
867
-
868
- ### finishPut
869
-
870
- **When**: Immediately after `finish`, put-specific
871
- **Context**: Same as `finish`
872
- **What can be changed**: Nothing - informational only
873
-
874
- ## PATCH Method Hooks
875
-
876
- Used for partially updating a resource.
877
-
878
- ### beforeData
879
-
880
- **When**: Before partially updating the resource
881
- **Purpose**: Validate partial updates, compute derived values
882
-
883
- **Context contains**:
884
- - `method` (string) - "patch"
885
- - `simplified` (boolean) - Whether simplified mode is active (affects input/output format, not hook data)
886
- - `schemaInfo` (object) - Compiled schema information
887
- - `inputRecord` (object) - The JSON:API formatted input data (partial)
888
- - `id` (string/number) - The ID from URL or input record
889
- - `params` (object) - Original parameters
890
- - `queryParams` (object) - Contains `fields`, `include` for response
891
- - `transaction` (object) - Database transaction
892
- - `shouldCommit` (boolean) - Whether to commit the transaction
893
- - `db` (object) - Database connection
894
- - `scopeName` (string) - Name of the current scope
895
- - `returnRecordSetting` (object) - Settings for what to return
896
- - `minimalRecord` (object) - Existing record for authorization
897
- - `auth` (object) - Authentication context if provided
898
-
899
- **What can be changed**:
900
- - `inputRecord.data.attributes` - Modify validated attributes before update
901
- - `belongsToUpdates` - Modify foreign key values (if any)
902
- - Can add computed values or prevent updates
903
-
904
- **Note**: For PATCH, `context.inputRecord.data.attributes` contains only the fields being updated. Use `context.minimalRecord.attributes` to access the complete current record.
905
-
906
- **Example**:
907
- ```javascript
908
- hooks: {
909
- beforeData: async ({ context }) => {
910
- if (context.method === 'patch' && context.inputRecord) {
911
- // If status is being changed to published, set publish date
912
- if (context.inputRecord.data.attributes.status === 'published' &&
913
- context.minimalRecord?.attributes?.status !== 'published') {
914
- context.inputRecord.data.attributes.published_at = new Date().toISOString();
915
- }
916
-
917
- // Always update the modified timestamp
918
- context.inputRecord.data.attributes.updated_at = new Date().toISOString();
919
- }
920
- }
921
- }
922
- ```
923
-
924
- ### beforeDataPatch
925
-
926
- **When**: Immediately after `beforeData`, patch-specific
927
- **Context**: Same as `beforeData`
928
- **What can be changed**: Same as `beforeData`
929
-
930
- ### afterData
931
-
932
- **When**: After the partial update is applied
933
- **Purpose**: React to specific changes, trigger conditional side effects
934
-
935
- **Context contains**:
936
- - All previous context properties
937
- - `updatedRecord` (object) - The updated database record
938
- - `relationshipChanges` (object) - Details of any relationship modifications
939
-
940
- **What can be changed**:
941
- - Can perform side effects based on what changed
942
- - Should NOT modify the database record
943
-
944
- ### afterDataPatch
945
-
946
- **When**: Immediately after `afterData`, patch-specific
947
- **Context**: Same as `afterData`
948
- **What can be changed**: Same as `afterData`
949
-
950
- ### enrichRecord
951
-
952
- **When**: After fetching the updated record (if `returnFullRecord` is not 'no')
953
- **Purpose**: Modify response structure or add metadata
954
-
955
- **IMPORTANT**: Do NOT use this hook to add/modify attributes. Use `enrichAttributes` instead.
956
-
957
- **Context contains**:
958
- - All previous context properties
959
- - `record` (object) - The JSON:API formatted response
960
- - `originalRecord` (object) - Backup of the record
961
-
962
- **What can be changed**:
963
- - Response structure (but NOT attributes)
964
- - `record.meta` - Add metadata
965
- - Should NOT modify attributes - use `enrichAttributes` hook instead
966
-
967
- ### enrichRecordWithRelationships
968
-
969
- **When**: After basic enrichment
970
- **Context**: Same as `enrichRecord`
971
- **What can be changed**:
972
- - Can modify relationship data
973
- - Can add relationship metadata
974
-
975
- ### afterCommit
976
-
977
- **When**: After the transaction is committed
978
- **Context**: All accumulated context
979
- **What can be changed**: Nothing - for side effects only
980
-
981
- ### afterRollback
982
-
983
- **When**: If an error occurs and transaction is rolled back
984
- **Context**: All accumulated context plus error information
985
- **What can be changed**: Nothing - for cleanup only
986
-
987
- ### finish
988
-
989
- **When**: Before returning the response
990
- **Context**: All accumulated context
991
- **What can be changed**: Nothing - informational only
992
-
993
- ### finishPatch
994
-
995
- **When**: Immediately after `finish`, patch-specific
996
- **Context**: Same as `finish`
997
- **What can be changed**: Nothing - informational only
998
-
999
- ## DELETE Method Hooks
1000
-
1001
- Used for removing resources.
1002
-
1003
- ### beforeData
1004
-
1005
- **When**: Before deleting the resource
1006
- **Purpose**: Validate deletion, check for dependencies
1007
-
1008
- **Context contains**:
1009
- - `method` (string) - "delete"
1010
- - `schemaInfo` (object) - Compiled schema information
1011
- - `id` (string/number) - The ID of the resource to delete
1012
- - `transaction` (object) - Database transaction
1013
- - `shouldCommit` (boolean) - Whether to commit the transaction
1014
- - `db` (object) - Database connection
1015
- - `scopeName` (string) - Name of the current scope
1016
- - `minimalRecord` (object) - Record fetched for authorization checks
1017
- - `auth` (object) - Authentication context if provided
1018
-
1019
- **What can be changed**:
1020
- - Can throw errors to prevent deletion
1021
- - Can add properties to context for later hooks
1022
- - Cannot modify the deletion itself
1023
-
1024
- **Example**:
1025
- ```javascript
1026
- hooks: {
1027
- beforeData: async ({ context }) => {
1028
- if (context.method === 'delete') {
1029
- // Check if post has comments
1030
- const commentCount = await context.db('comments')
1031
- .where('post_id', context.id)
1032
- .count('* as count')
1033
- .first();
1034
-
1035
- if (commentCount.count > 0) {
1036
- throw new Error('Cannot delete post with comments');
1037
- }
1038
- }
1039
- }
1040
- }
1041
- ```
1042
-
1043
- ### beforeDataDelete
1044
-
1045
- **When**: Immediately after `beforeData`, delete-specific
1046
- **Context**: Same as `beforeData`
1047
- **What can be changed**: Same as `beforeData`
1048
-
1049
- ### afterData
1050
-
1051
- **When**: After the resource is deleted from the database
1052
- **Purpose**: Clean up related data, log deletions
1053
-
1054
- **Context contains**:
1055
- - All previous context properties
1056
- - `deletedCount` (number) - Number of records deleted (should be 1)
1057
- - `deletedRecord` (object) - The record that was deleted
1058
-
1059
- **What can be changed**:
1060
- - Can perform cascading deletes or cleanup
1061
- - Can log the deletion
1062
- - Cannot undo the deletion
1063
-
1064
- **Example**:
1065
- ```javascript
1066
- hooks: {
1067
- afterData: async ({ context, scopes }) => {
1068
- if (context.method === 'delete') {
1069
- // Log the deletion
1070
- await scopes.audit_logs.create({
1071
- action: 'delete',
1072
- resource_type: 'posts',
1073
- resource_id: context.id,
1074
- user_id: context.auth?.userId,
1075
- timestamp: new Date().toISOString()
1076
- });
1077
-
1078
- // Clean up orphaned images
1079
- await context.db('post_images')
1080
- .where('post_id', context.id)
1081
- .delete();
1082
- }
1083
- }
1084
- }
1085
- ```
1086
-
1087
- ### afterDataDelete
1088
-
1089
- **When**: Immediately after `afterData`, delete-specific
1090
- **Context**: Same as `afterData`
1091
- **What can be changed**: Same as `afterData`
1092
-
1093
- ### afterCommit
1094
-
1095
- **When**: After the transaction is committed
1096
- **Purpose**: Trigger post-deletion side effects
1097
-
1098
- **Context**: All accumulated context
1099
- **What can be changed**: Nothing - for side effects only
1100
-
1101
- ### afterRollback
1102
-
1103
- **When**: If an error occurs and transaction is rolled back
1104
- **Context**: All accumulated context plus error information
1105
- **What can be changed**: Nothing - for cleanup only
1106
-
1107
- ### finish
1108
-
1109
- **When**: Before returning the response (typically empty for DELETE)
1110
- **Context**: All accumulated context
1111
- **What can be changed**: Nothing - informational only
1112
-
1113
- ### finishDelete
1114
-
1115
- **When**: Immediately after `finish`, delete-specific
1116
- **Context**: Same as `finish`
1117
- **What can be changed**: Nothing - informational only
1118
-
1119
- ## Special Hooks
1120
-
1121
- ### enrichAttributes
1122
-
1123
- The `enrichAttributes` hook is the correct way to add or modify attributes on records. This hook is called for ALL records - both main records and included/related records.
1124
-
1125
- **When**: After records are fetched and before they are returned
1126
- **Purpose**: Add computed fields, transform attribute values, enhance record data
1127
-
1128
- **Context contains**:
1129
- - `attributes` (object) - The record's attributes that should be modified
1130
- - `parentContext` (object) - The parent context from the calling method (contains method, queryParams, etc.)
1131
- - `computedFields` (object) - Computed field definitions from schema
1132
- - `requestedComputedFields` (array) - Which computed fields were requested
1133
- - `scopeName` (string) - Name of the current scope
1134
- - `helpers` (object) - Helper functions
1135
- - `api` (object) - API instance
1136
-
1137
- **What can be changed**:
1138
- - Modify `context.attributes` to add new properties
1139
- - Transform existing attribute values
1140
- - Remove sensitive attributes
1141
-
1142
- **Important**:
1143
- - This hook is called for EVERY record (main and included)
1144
- - Works with both single records and collections
1145
- - Modify `context.attributes` directly
1146
-
1147
- **Example**:
1148
- ```javascript
1149
- // In global customize()
1150
- api.customize({
1151
- hooks: {
1152
- enrichAttributes: async ({ context }) => {
1153
- // Add computed fields based on scope
1154
- if (context.scopeName === 'posts') {
1155
- context.attributes.wordCount = context.attributes.content?.split(' ').length || 0;
1156
- context.attributes.readingTime = Math.ceil(context.attributes.wordCount / 200) + ' min';
1157
- context.attributes.preview = context.attributes.content?.substring(0, 150) + '...';
1158
- }
1159
-
1160
- if (context.scopeName === 'users') {
1161
- // Hide sensitive data
1162
- delete context.attributes.password;
1163
- delete context.attributes.resetToken;
1164
-
1165
- // Add display name
1166
- context.attributes.displayName = `${context.attributes.firstName} ${context.attributes.lastName}`;
1167
- }
1168
- }
1169
- }
1170
- });
1171
-
1172
- // In resource-specific extras
1173
- api.addScope('articles', {}, {
1174
- hooks: {
1175
- enrichAttributes: async ({ context }) => {
1176
- // This only runs for articles
1177
- context.attributes.isPublished = context.attributes.status === 'published';
1178
- context.attributes.isNew = new Date() - new Date(context.attributes.created_at) < 7 * 24 * 60 * 60 * 1000;
1179
-
1180
- // Format dates for display
1181
- context.attributes.formattedDate = new Date(context.attributes.created_at).toLocaleDateString();
1182
- }
1183
- }
1184
- });
1185
- ```
1186
-
1187
- ### knexQueryFiltering
1188
-
1189
- The `knexQueryFiltering` hook is called while storage-backed resource selections are being built. Collection queries run it before sorting and pagination; scoped lookup and relationship paths can also run it. This is a special hook that allows complex query modifications. Use the supported [Row Policies](GUIDE_X_Row_Policies.md) contract for mandatory resource visibility.
1190
-
1191
- **When**: During `dataQuery` execution, before sorting and pagination
1192
- **Purpose**: Apply filters, add JOINs, modify query conditions
1193
-
1194
- **Context contains**:
1195
- - `knexQuery` (object) - Temporary object with:
1196
- - `query` (knex query builder) - The active query being built
1197
- - `filters` (object) - Filter parameters from request
1198
- - `searchSchema` (object) - Schema defining searchable fields
1199
- - `scopeName` (string) - Current resource scope
1200
- - `tableName` (string) - Database table name
1201
- - `db` (object) - Database connection
1202
- - All other standard query context properties
1203
-
1204
- The REST API Knex Plugin registers three sub-hooks that run in sequence:
1205
-
1206
- #### 1. polymorphicFiltersHook
1207
-
1208
- **Purpose**: Handles filtering on polymorphic relationships
1209
- **What it does**:
1210
- - Detects polymorphic filter fields (e.g., `commentable.title`)
1211
- - Adds appropriate JOINs for each polymorphic type
1212
- - Builds WHERE conditions with proper type checking
1213
-
1214
- **Example**:
1215
- ```javascript
1216
- // This is handled automatically by the plugin
1217
- // When filtering: ?filters[commentable.title]=Hello
1218
- // It generates SQL like:
1219
- // LEFT JOIN posts ON (comments.commentable_type = 'posts' AND comments.commentable_id = posts.id)
1220
- // WHERE posts.title = 'Hello'
1221
- ```
1222
-
1223
- #### 2. crossTableFiltersHook
1224
-
1225
- **Purpose**: Handles filtering on cross-table fields
1226
- **What it does**:
1227
- - Detects cross-table filter fields (e.g., `author.name`)
1228
- - Adds JOINs to related tables
1229
- - Qualifies field names to avoid ambiguity
1230
-
1231
- **Example**:
1232
- ```javascript
1233
- // This is handled automatically by the plugin
1234
- // When filtering: ?filters[author.name]=John
1235
- // It generates SQL like:
1236
- // INNER JOIN users ON posts.author_id = users.id
1237
- // WHERE users.name = 'John'
1238
- ```
1239
-
1240
- #### 3. basicFiltersHook
1241
-
1242
- **Purpose**: Handles simple filters on the main table
1243
- **What it does**:
1244
- - Processes standard field filters
1245
- - Handles special operators (contains, starts_with, etc.)
1246
- - Applies filters to non-joined fields
1247
-
1248
- **Custom Filter Hook Example**:
1249
- ```javascript
1250
- hooks: {
1251
- knexQueryFiltering: async ({ context }) => {
1252
- if (context.knexQuery && context.knexQuery.filters) {
1253
- const { query, filters, tableName } = context.knexQuery;
1254
-
1255
- // Add custom filter logic
1256
- if (filters.special_filter) {
1257
- query.where(function() {
1258
- this.where(`${tableName}.status`, 'active')
1259
- .orWhere(`${tableName}.featured`, true);
1260
- });
1261
- }
1262
- }
1263
- }
1264
- }
1265
- ```
1266
-
1267
- ## Hook Best Practices
1268
-
1269
- ### 1. Hook Order Matters
1270
-
1271
- Hooks run in registration order. Consider dependencies between hooks:
1272
-
1273
- ```javascript
1274
- hooks: {
1275
- beforeData: [
1276
- async ({ context }) => {
1277
- // Validation runs first
1278
- if (!context.inputRecord?.data?.attributes?.title) {
1279
- throw new Error('Title is required');
1280
- }
1281
- },
1282
- async ({ context }) => {
1283
- // Enrichment runs second, after validation
1284
- context.inputRecord.data.attributes.slug = context.inputRecord.data.attributes.title
1285
- .toLowerCase()
1286
- .replace(/\s+/g, '-');
1287
- }
1288
- ]
1289
- }
1290
- ```
1291
-
1292
- ### 2. Use Proper Hook Placement
1293
-
1294
- If using addHook directly (less common), you can control placement:
1295
-
1296
- ```javascript
1297
- // Use afterPlugin to ensure your hook runs after the plugin's hooks
1298
- api.addHook('beforeData', 'myHook', { afterPlugin: 'rest-api-knex' }, handler);
1299
- ```
1300
-
1301
- ### 3. Context Mutation Guidelines
1302
-
1303
- - **DO**: Modify allowed properties as documented
1304
- - **DON'T**: Change properties marked as read-only
1305
- - **DO**: Add custom properties for communication between hooks
1306
- - **DON'T**: Remove required properties
1307
-
1308
- ### 4. Error Handling
1309
-
1310
- Throwing an error in any hook will:
1311
- - Stop the operation
1312
- - Trigger rollback for write operations
1313
- - Return the error to the client
1314
-
1315
- ```javascript
1316
- hooks: {
1317
- beforeData: async ({ context }) => {
1318
- if (context.inputRecord?.data?.attributes?.price < 0) {
1319
- throw new RestApiValidationError('Price cannot be negative', {
1320
- fields: ['data.attributes.price']
1321
- });
1322
- }
1323
- }
1324
- }
1325
- ```
1326
-
1327
- ### 5. Performance Considerations
1328
-
1329
- - Keep `enrichRecord` hooks lightweight for queries with many results
1330
- - Use database transactions appropriately
1331
- - Batch operations when possible
1332
- - Avoid N+1 queries in hooks
1333
-
1334
- ### 6. Transaction Safety
1335
-
1336
- For write operations:
1337
- - Use `afterCommit` for external side effects (emails, webhooks)
1338
- - Use `afterData` for database-related side effects
1339
- - Always handle `afterRollback` for cleanup
1340
-
1341
- ### 7. Scope-Specific Hooks
1342
-
1343
- Add hooks to specific scopes to avoid checking in every hook:
1344
-
1345
- ```javascript
1346
- // Better: Add hooks in the scope's extras parameter
1347
- api.addScope('posts', {}, {
1348
- hooks: {
1349
- beforeData: async ({ context }) => {
1350
- // This only runs for posts
1351
- }
1352
- }
1353
- });
1354
-
1355
- // Less ideal: Check scopeName in global hooks
1356
- hooks: {
1357
- beforeData: async ({ context }) => {
1358
- if (context.scopeName === 'posts') {
1359
- // ...
1360
- }
1361
- }
1362
- }
1363
- ```
1364
-
1365
- ### 8. Hook Communication
1366
-
1367
- Use context properties to communicate between hooks:
1368
-
1369
- ```javascript
1370
- hooks: {
1371
- beforeData: async ({ context }) => {
1372
- context.customData = { processed: true };
1373
- },
1374
-
1375
- afterData: async ({ context }) => {
1376
- if (context.customData?.processed) {
1377
- // React to first hook
1378
- }
1379
- }
1380
- }
1381
-
1382
- ## System-Wide Hooks
1383
-
1384
- These hooks are managed by the hooked-api framework and are triggered during core API operations.
1385
-
1386
- ### plugin:installed
1387
-
1388
- **When**: After a plugin is successfully installed
1389
- **Purpose**: React to plugin installations, set up inter-plugin communication
1390
-
1391
- **Context contains**:
1392
- - `pluginName` (string) - Name of the installed plugin
1393
- - `pluginOptions` (object) - Options passed to the plugin
1394
- - `plugin` (object) - The plugin object itself (informational only)
1395
-
1396
- **What can be changed**: Nothing - this is an informational hook
1397
-
1398
- **Example**:
1399
- ```javascript
1400
- hooks: {
1401
- 'plugin:installed': async ({ context }) => {
1402
- console.log(`Plugin ${context.pluginName} installed with options:`, context.pluginOptions);
1403
- }
1404
- }
1405
- ```
1406
-
1407
- ### scope:added
1408
-
1409
- **When**: After a scope is added to the API
1410
- **Purpose**: Initialize scope-specific settings, validate configurations, compile schemas
1411
-
1412
- **Context contains**:
1413
- - `scopeName` (string) - Name of the added scope
1414
- - `scopeOptions` (object) - Immutable copy of initial options
1415
- - `scopeExtras` (object) - Immutable copy of initial extras
1416
- - `vars` (proxy) - Proxy for current scope vars (can be mutated)
1417
- - `helpers` (proxy) - Proxy for current scope helpers (can be mutated)
1418
-
1419
- **What can be changed**:
1420
- - Can add/modify scope vars through the proxy
1421
- - Can add/modify scope helpers through the proxy
1422
- - Cannot modify scopeOptions (frozen after hook runs)
1423
-
1424
- **Example**:
1425
- ```javascript
1426
- hooks: {
1427
- 'scope:added': async ({ context }) => {
1428
- // Add a default value to scope vars
1429
- context.vars.defaultPageSize = 20;
1430
-
1431
- // Add a helper function
1432
- context.helpers.formatDate = (date) => new Date(date).toISOString();
1433
- }
1434
- }
1435
- ```
1436
-
1437
- ### method:api:added
1438
-
1439
- **When**: After an API method is added
1440
- **Purpose**: Wrap or modify API method handlers
1441
-
1442
- **Context contains**:
1443
- - `methodName` (string) - Name of the added method
1444
- - `handler` (function) - The method handler function
1445
-
1446
- **What can be changed**:
1447
- - `handler` - Can wrap or replace the handler function
1448
-
1449
- **Example**:
1450
- ```javascript
1451
- hooks: {
1452
- 'method:api:added': async ({ context }) => {
1453
- const originalHandler = context.handler;
1454
- context.handler = async (params) => {
1455
- console.log(`Calling ${context.methodName}`);
1456
- const result = await originalHandler(params);
1457
- console.log(`${context.methodName} completed`);
1458
- return result;
1459
- };
1460
- }
1461
- }
1462
- ```
1463
-
1464
- ### method:scope:adding
1465
-
1466
- **When**: Before adding a scope method
1467
- **Purpose**: Validate or modify scope methods before they're registered
1468
-
1469
- **Context contains**:
1470
- - `methodName` (string) - Name of the method being added
1471
- - `handler` (function) - The method handler function
1472
-
1473
- **What can be changed**:
1474
- - `handler` - Can wrap or replace the handler function before it's added
1475
-
1476
- ### method:scope:added
1477
-
1478
- **When**: After a scope method is added
1479
- **Purpose**: React to scope method additions
1480
-
1481
- **Context contains**:
1482
- - `methodName` (string) - Name of the added method
1483
- - `handler` (function) - The method handler function
1484
-
1485
- **What can be changed**: Nothing - this is an informational hook
1486
-
1487
- ```
1488
-
1489
- ---
1490
-
1491
- [Back to Guide](./README.md)