json-rest-api 1.0.7 → 1.0.8

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 (146) hide show
  1. package/.claude/agents/library-guide-expert.md +29 -0
  2. package/.claude/agents/rest-api-internals-expert.md +53 -0
  3. package/CLAUDE.md +139 -1
  4. package/README.md +2 -5
  5. package/docTest.js +238 -0
  6. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
  7. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
  8. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
  9. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
  10. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
  11. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
  12. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
  13. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
  14. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
  15. package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
  16. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
  17. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
  18. package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
  19. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
  20. package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
  21. package/docs/GUIDE/index.md +58 -0
  22. package/docs/GUIDE.md +61 -2238
  23. package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
  24. package/docs/README.md +57 -2239
  25. package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
  26. package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
  27. package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
  28. package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
  29. package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
  30. package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
  31. package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
  32. package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
  33. package/docs/assets/css/style.scss +78 -5
  34. package/docs/index.md +2 -12
  35. package/helper-reorganization-plan.md +85 -0
  36. package/helper-usage-analysis.md +94 -0
  37. package/package.json +5 -4
  38. package/plugins/core/bulk-operations-plugin.js +2 -2
  39. package/plugins/core/connectors/express-plugin.js +90 -32
  40. package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
  41. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
  42. package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
  43. package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
  44. package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
  45. package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
  46. package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
  47. package/plugins/core/lib/querying/knex-window-queries.js +328 -0
  48. package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
  49. package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
  50. package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
  51. package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
  52. package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
  53. package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
  54. package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
  55. package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
  56. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
  57. package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
  58. package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
  59. package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
  60. package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
  61. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
  62. package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
  63. package/plugins/core/lib/writing/relationship-processor.js +252 -0
  64. package/plugins/core/rest-api-knex-plugin.js +183 -62
  65. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
  66. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
  67. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
  68. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
  69. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
  70. package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
  71. package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
  72. package/plugins/core/rest-api-plugin-methods/common.js +605 -0
  73. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
  74. package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
  75. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
  76. package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
  77. package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
  78. package/plugins/core/rest-api-plugin-methods/get.js +187 -0
  79. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
  80. package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
  81. package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
  82. package/plugins/core/rest-api-plugin-methods/post.js +157 -0
  83. package/plugins/core/rest-api-plugin-methods/put.js +257 -0
  84. package/plugins/core/rest-api-plugin-methods/query.js +208 -0
  85. package/plugins/core/rest-api-plugin.js +108 -2498
  86. package/plugins/core/socketio-plugin.js +28 -5
  87. package/plugins/storage/local-storage.js +2 -2
  88. package/tests/computed-fields.test.js +483 -0
  89. package/tests/cors.test.js +4 -4
  90. package/tests/curl.test.js +5 -4
  91. package/tests/custom-idproperty-relationships.test.js +891 -0
  92. package/tests/field-getters.test.js +353 -0
  93. package/tests/field-setters.test.js +310 -0
  94. package/tests/fixtures/api-configs.js +800 -28
  95. package/tests/jwt-auth.test.js +0 -2
  96. package/tests/nested-includes.test.js +1 -1
  97. package/tests/pagination-cursor-multifield.test.js +514 -0
  98. package/tests/pagination-enhanced.test.js +12 -9
  99. package/tests/polymorphic-relationships.test.js +10 -8
  100. package/tests/polymorphic-simple.test.js +6 -4
  101. package/tests/positioning-advanced.test.js +12 -12
  102. package/tests/positioning-extended.test.js +25 -25
  103. package/tests/positioning-multifilter.test.js +24 -24
  104. package/tests/positioning.test.js +3 -3
  105. package/tests/query-limits.test.js +24 -8
  106. package/tests/relationship-endpoints.test.js +4 -5
  107. package/tests/relationships.test.js +0 -1
  108. package/tests/socketio.test.js +12 -1
  109. package/tests/virtual-fields.test.js +13 -13
  110. package/PLAN_FOR_WEBSOCKETS.md +0 -398
  111. package/README_1_Initial_Setup.md +0 -393
  112. package/README_X_Hooks.md +0 -647
  113. package/TODO_rest-api-plugin-extras.todo +0 -23
  114. package/connectors_ideas/express-plugin.js +0 -120
  115. package/connectors_ideas/http-plugin.js +0 -138
  116. package/docs/examples/express-plugin.js +0 -366
  117. package/example.js +0 -301
  118. package/plugins/core/HELPER_MODULES.md +0 -285
  119. package/plugins/core/lib/compile-schemas.js +0 -245
  120. package/plugins/core/lib/database-value-normalizers.js +0 -192
  121. package/plugins/core/lib/default-data-helpers.js +0 -100
  122. package/plugins/core/lib/knex-cross-table-search.js +0 -442
  123. package/plugins/core/lib/knex-json-api-helpers.js +0 -84
  124. package/plugins/core/lib/knex-json-api-transformers.js +0 -449
  125. package/plugins/core/lib/knex-pagination-helpers.js +0 -193
  126. package/plugins/core/lib/knex-window-queries.js +0 -142
  127. package/plugins/core/lib/many-to-many-manipulations.js +0 -343
  128. package/plugins/core/lib/relationship-processor.js +0 -278
  129. package/plugins/core/lib/schema-helpers.js +0 -343
  130. package/plugins/core/lib/scope-validations.js +0 -252
  131. package/plugins/core/rest-api-relationships-plugin.js +0 -700
  132. package/plugins/core/utils/knex-query-helpers-base.js +0 -56
  133. /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
  134. /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
  135. /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
  136. /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
  137. /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
  138. /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
  139. /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
  140. /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
  141. /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
  142. /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
  143. /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
  144. /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
  145. /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
  146. /package/{readmeSnippets → docs/README_VARIOUS}/test_put_fields_without_defaults.js +0 -0
package/README_X_Hooks.md DELETED
@@ -1,647 +0,0 @@
1
- # Hook System Guide
2
-
3
- The hook system in JSON REST API provides a powerful way to extend and customize API behavior at various points in the request lifecycle. This guide explains how to use hooks effectively.
4
-
5
- ## Table of Contents
6
- - [Overview](#overview)
7
- - [Hook Basics](#hook-basics)
8
- - [Available Hooks](#available-hooks)
9
- - [Hook Context](#hook-context)
10
- - [Creating Hook Handlers](#creating-hook-handlers)
11
- - [Hook Execution Order](#hook-execution-order)
12
- - [Common Patterns](#common-patterns)
13
- - [Best Practices](#best-practices)
14
-
15
- ## Overview
16
-
17
- Hooks allow you to:
18
- - Intercept and modify requests/responses
19
- - Add custom validation or authorization
20
- - Transform data before/after operations
21
- - React to events (like record creation or deletion)
22
- - Extend core functionality without modifying plugins
23
-
24
- ## Hook Basics
25
-
26
- ### Hook Signatures
27
-
28
- The hook system uses a simple, consistent signature where all data is passed through a single `context` object.
29
-
30
- ```javascript
31
- // Running hooks
32
- await runHooks('hookName', context);
33
-
34
- // Hook handler
35
- addHook('hookName', 'handlerName', {}, async ({ context }) => {
36
- const { someData } = context;
37
- });
38
- ```
39
-
40
- ### Adding Hook Handlers
41
-
42
- Hook handlers are added using the `addHook` function:
43
-
44
- ```javascript
45
- api.addHook(hookName, handlerName, options, handlerFunction);
46
- ```
47
-
48
- Parameters:
49
- - `hookName`: The name of the hook to listen to
50
- - `handlerName`: A unique name for your handler
51
- - `options`: Configuration object (optional)
52
- - `order`: Numeric execution order (lower runs first)
53
- - `sequence`: Alternative to order
54
- - `handlerFunction`: Async function that receives hook context
55
-
56
- ## Available Hooks
57
-
58
- ### Transport Hooks
59
-
60
- #### `transport:request`
61
- Runs when a request is received by the transport layer (e.g., Express).
62
-
63
- ```javascript
64
- api.addHook('transport:request', 'auth-check', {}, async ({ context }) => {
65
- // context includes transport-specific data
66
- const { request, response } = context.transport;
67
-
68
- // You can:
69
- // - Extract auth tokens
70
- // - Set security headers
71
- // - Log requests
72
- // - Block requests by setting context.handled = true
73
- });
74
- ```
75
-
76
- #### `transport:response`
77
- Runs before sending the response back to the client.
78
-
79
- ```javascript
80
- api.addHook('transport:response', 'add-headers', {}, async ({ context }) => {
81
- const { request, response } = context.transport;
82
-
83
- // Add custom headers
84
- response.headers['X-Custom-Header'] = 'value';
85
-
86
- // Modify response based on request
87
- if (request.path.startsWith('/api/public')) {
88
- response.headers['Cache-Control'] = 'public, max-age=3600';
89
- }
90
- });
91
- ```
92
-
93
- ### REST API Operation Hooks
94
-
95
- #### `beforeProcessing`
96
- Runs before processing any REST API operation (POST, PUT, PATCH).
97
-
98
- ```javascript
99
- api.addHook('beforeProcessing', 'validate-data', {}, async ({ context }) => {
100
- const { method, scopeName, params } = context;
101
-
102
- // Perform validation
103
- if (method === 'post' && scopeName === 'articles') {
104
- // Custom validation logic
105
- }
106
- });
107
- ```
108
-
109
- #### `checkPermissions`
110
- Runs during permission checking phase.
111
-
112
- ```javascript
113
- api.addHook('checkPermissions', 'custom-permissions', {}, async ({ context }) => {
114
- const { method, scopeName, auth, id } = context;
115
-
116
- // Implement custom permission logic
117
- if (scopeName === 'admin-resources' && !auth?.roles?.includes('admin')) {
118
- throw new Error('Admin access required');
119
- }
120
- });
121
- ```
122
-
123
- #### `beforeData` / `afterData`
124
- Run before/after data operations.
125
-
126
- ```javascript
127
- // Before data operation
128
- api.addHook('beforeData', 'prepare-data', {}, async ({ context }) => {
129
- // Modify query parameters, add filters, etc.
130
- });
131
-
132
- // After data operation
133
- api.addHook('afterData', 'transform-data', {}, async ({ context }) => {
134
- // Transform the returned data
135
- if (context.record) {
136
- context.record.attributes.processedAt = new Date();
137
- }
138
- });
139
- ```
140
-
141
- #### `finish`
142
- Runs after successful operation completion.
143
-
144
- ```javascript
145
- api.addHook('finish', 'log-success', {}, async ({ context }) => {
146
- const { method, scopeName, id } = context;
147
- console.log(`Operation ${method} on ${scopeName}/${id} completed`);
148
- });
149
- ```
150
-
151
- ### Schema Hooks
152
-
153
- #### `schema:enrich`
154
- Allows modification of resource schemas.
155
-
156
- ```javascript
157
- api.addHook('schema:enrich', 'add-timestamps', {}, async ({ context }) => {
158
- const { schema, scopeName } = context;
159
-
160
- // Add timestamp fields to all resources
161
- schema.attributes.createdAt = {
162
- type: 'string',
163
- format: 'date-time',
164
- computed: true
165
- };
166
- schema.attributes.updatedAt = {
167
- type: 'string',
168
- format: 'date-time',
169
- computed: true
170
- };
171
- });
172
- ```
173
-
174
- ### Authentication Hooks
175
-
176
- #### `afterAuthentication`
177
- Runs after successful JWT authentication.
178
-
179
- ```javascript
180
- api.addHook('afterAuthentication', 'enrich-auth', {}, async ({ context }) => {
181
- const { auth, authPayload } = context;
182
-
183
- // Enrich auth context with additional data
184
- const user = await db('users').where({ id: auth.userId }).first();
185
- context.auth.profile = user;
186
- });
187
- ```
188
-
189
- #### `afterLogout`
190
- Runs after user logout.
191
-
192
- ```javascript
193
- api.addHook('afterLogout', 'cleanup', {}, async ({ context }) => {
194
- const { logoutUserId } = context;
195
-
196
- // Clean up user sessions, caches, etc.
197
- await cache.delete(`user:${logoutUserId}`);
198
- });
199
- ```
200
-
201
- ### Database Query Hooks (Knex Plugin)
202
-
203
- #### `knexQueryFiltering`
204
- Allows modification of database queries.
205
-
206
- ```javascript
207
- api.addHook('knexQueryFiltering', 'tenant-filter', { order: -100 },
208
- async ({ context }) => {
209
- const { query, auth } = context;
210
-
211
- // Add tenant isolation
212
- if (auth?.tenantId) {
213
- query.where('tenant_id', auth.tenantId);
214
- }
215
- }
216
- );
217
- ```
218
-
219
- ## Hook Context
220
-
221
- The context object passed to hooks contains all relevant data for the operation. Common properties include:
222
-
223
- ```javascript
224
- {
225
- // Operation details
226
- method: 'post', // HTTP method
227
- scopeName: 'articles', // Resource name
228
- id: '123', // Resource ID (for single-resource ops)
229
-
230
- // Authentication
231
- auth: {
232
- userId: 'user-123',
233
- email: 'user@example.com',
234
- roles: ['admin'],
235
- permissions: ['articles:write']
236
- },
237
-
238
- // Request data
239
- params: {}, // Request parameters
240
- inputRecord: {}, // Input data (for mutations)
241
- queryParams: {}, // Query parameters
242
-
243
- // Response data
244
- record: {}, // Single record
245
- records: [], // Multiple records
246
-
247
- // Transport-specific (in transport hooks)
248
- transport: {
249
- request: {
250
- method: 'POST',
251
- path: '/api/articles',
252
- headers: {},
253
- body: {}
254
- },
255
- response: {
256
- status: 200,
257
- headers: {},
258
- body: {}
259
- }
260
- },
261
-
262
- // Database transaction
263
- transaction: knexTransaction,
264
-
265
- // Helpers and utilities
266
- db: knexInstance,
267
- log: logger
268
- }
269
- ```
270
-
271
- ## Creating Hook Handlers
272
-
273
- ### Basic Handler
274
-
275
- ```javascript
276
- api.addHook('beforeProcessing', 'my-handler', {}, async ({ context }) => {
277
- // Access context properties
278
- const { method, scopeName, auth } = context;
279
-
280
- // Perform operations
281
- if (scopeName === 'sensitive-data' && !auth) {
282
- throw new Error('Authentication required');
283
- }
284
-
285
- // Modify context (changes are passed to next handlers)
286
- context.customFlag = true;
287
- });
288
- ```
289
-
290
- ### Handler with Dependencies
291
-
292
- ```javascript
293
- // In a plugin
294
- const MyPlugin = {
295
- name: 'my-plugin',
296
-
297
- async install({ api, addHook, vars, helpers }) {
298
- // Use plugin context
299
- addHook('beforeData', 'my-handler', {}, async ({ context }) => {
300
- // Access plugin vars
301
- const config = vars.myPluginConfig;
302
-
303
- // Use helpers
304
- const result = await helpers.validateData(context.inputRecord);
305
-
306
- // Modify operation
307
- if (!result.valid) {
308
- throw new ValidationError(result.errors);
309
- }
310
- });
311
- }
312
- };
313
- ```
314
-
315
- ### Conditional Handler
316
-
317
- ```javascript
318
- api.addHook('finish', 'conditional-handler', {}, async ({ context }) => {
319
- // Only run for specific operations
320
- if (context.method !== 'post' || context.scopeName !== 'orders') {
321
- return; // Skip this handler
322
- }
323
-
324
- // Send notification for new orders
325
- await sendNotification({
326
- type: 'new-order',
327
- orderId: context.record.id
328
- });
329
- });
330
- ```
331
-
332
- ## Hook Execution Order
333
-
334
- Hooks execute in a specific order determined by:
335
-
336
- 1. **Hook type order** - Different hook types run at different lifecycle points
337
- 2. **Handler order** - Within a hook type, handlers run by their `order` value
338
- 3. **Registration order** - Handlers with same order run in registration sequence
339
-
340
- ```javascript
341
- // These will execute in order: -1000, -100, 0, 100
342
- api.addHook('checkPermissions', 'first', { order: -1000 }, handler1);
343
- api.addHook('checkPermissions', 'second', { order: -100 }, handler2);
344
- api.addHook('checkPermissions', 'third', {}, handler3); // default order: 0
345
- api.addHook('checkPermissions', 'fourth', { order: 100 }, handler4);
346
- ```
347
-
348
- ### Stopping Hook Execution
349
-
350
- Return `false` from a handler to stop the hook chain:
351
-
352
- ```javascript
353
- api.addHook('transport:request', 'rate-limiter', { order: -1000 },
354
- async ({ context }) => {
355
- const { request } = context.transport;
356
-
357
- if (await isRateLimited(request.ip)) {
358
- context.rejection = {
359
- status: 429,
360
- message: 'Too many requests'
361
- };
362
- return false; // Stop processing
363
- }
364
- }
365
- );
366
- ```
367
-
368
- ## Common Patterns
369
-
370
- ### Data Validation
371
-
372
- ```javascript
373
- api.addHook('beforeProcessing', 'validate-articles', {}, async ({ context }) => {
374
- if (context.scopeName !== 'articles') return;
375
-
376
- const { inputRecord, method } = context;
377
-
378
- if (['post', 'put', 'patch'].includes(method)) {
379
- const { title, content } = inputRecord.data.attributes;
380
-
381
- if (title && title.length > 200) {
382
- throw new ValidationError('Title too long');
383
- }
384
-
385
- if (content && content.length < 10) {
386
- throw new ValidationError('Content too short');
387
- }
388
- }
389
- });
390
- ```
391
-
392
- ### Automatic Field Population
393
-
394
- ```javascript
395
- api.addHook('beforeProcessing', 'auto-fields', {}, async ({ context }) => {
396
- const { method, auth, inputRecord } = context;
397
-
398
- if (method === 'post' && auth) {
399
- // Set creator
400
- inputRecord.data.attributes.createdBy = auth.userId;
401
- inputRecord.data.attributes.createdAt = new Date().toISOString();
402
- }
403
-
404
- if (['put', 'patch'].includes(method) && auth) {
405
- // Set updater
406
- inputRecord.data.attributes.updatedBy = auth.userId;
407
- inputRecord.data.attributes.updatedAt = new Date().toISOString();
408
- }
409
- });
410
- ```
411
-
412
- ### Filtering Based on User
413
-
414
- ```javascript
415
- api.addHook('knexQueryFiltering', 'user-filter', {}, async ({ context }) => {
416
- const { query, auth, scopeName } = context;
417
-
418
- if (scopeName === 'user-data' && auth) {
419
- // Users only see their own data
420
- query.where('user_id', auth.userId);
421
- }
422
- });
423
- ```
424
-
425
- ### Audit Logging
426
-
427
- ```javascript
428
- api.addHook('finish', 'audit-log', { order: 1000 }, async ({ context }) => {
429
- const { method, scopeName, id, auth, record } = context;
430
-
431
- // Skip read operations
432
- if (method === 'get' || method === 'query') return;
433
-
434
- await db('audit_logs').insert({
435
- user_id: auth?.userId || 'anonymous',
436
- action: method,
437
- resource_type: scopeName,
438
- resource_id: id || record?.id,
439
- changes: JSON.stringify(record),
440
- timestamp: new Date()
441
- });
442
- });
443
- ```
444
-
445
- ### Caching
446
-
447
- ```javascript
448
- const cache = new Map();
449
-
450
- // Cache reads
451
- api.addHook('beforeData', 'cache-check', { order: -1000 },
452
- async ({ context }) => {
453
- if (context.method !== 'get') return;
454
-
455
- const cacheKey = `${context.scopeName}:${context.id}`;
456
- const cached = cache.get(cacheKey);
457
-
458
- if (cached) {
459
- context.record = cached;
460
- context.skipDataOperation = true; // Custom flag
461
- }
462
- }
463
- );
464
-
465
- // Update cache on writes
466
- api.addHook('finish', 'cache-update', { order: 1000 },
467
- async ({ context }) => {
468
- const { method, scopeName, id, record } = context;
469
-
470
- if (method === 'get' && record) {
471
- cache.set(`${scopeName}:${id}`, record);
472
- }
473
-
474
- if (['patch', 'put', 'delete'].includes(method)) {
475
- cache.delete(`${scopeName}:${id}`);
476
- }
477
- }
478
- );
479
- ```
480
-
481
- ## Best Practices
482
-
483
- ### 1. Use Descriptive Handler Names
484
-
485
- ```javascript
486
- // ❌ Bad
487
- api.addHook('checkPermissions', 'handler1', {}, async ({ context }) => {});
488
-
489
- // ✅ Good
490
- api.addHook('checkPermissions', 'require-admin-for-settings', {},
491
- async ({ context }) => {});
492
- ```
493
-
494
- ### 2. Handle Errors Gracefully
495
-
496
- ```javascript
497
- api.addHook('beforeProcessing', 'validate-safely', {}, async ({ context }) => {
498
- try {
499
- await validateData(context.inputRecord);
500
- } catch (error) {
501
- // Log error with context
502
- context.log.error('Validation failed', {
503
- error: error.message,
504
- scopeName: context.scopeName,
505
- method: context.method
506
- });
507
-
508
- // Re-throw with user-friendly message
509
- throw new ValidationError('Invalid data provided');
510
- }
511
- });
512
- ```
513
-
514
- ### 3. Check Context Before Operating
515
-
516
- ```javascript
517
- api.addHook('finish', 'send-notifications', {}, async ({ context }) => {
518
- // Ensure required data exists
519
- if (!context.record?.id || !context.auth?.userId) {
520
- return; // Skip if missing required data
521
- }
522
-
523
- // Safe to proceed
524
- await notifyUser(context.auth.userId, context.record);
525
- });
526
- ```
527
-
528
- ### 4. Use Order for Dependencies
529
-
530
- ```javascript
531
- // Ensure auth runs before permissions
532
- api.addHook('checkPermissions', 'populate-user', { order: -100 },
533
- async ({ context }) => {
534
- if (context.auth?.userId) {
535
- context.user = await getUser(context.auth.userId);
536
- }
537
- }
538
- );
539
-
540
- api.addHook('checkPermissions', 'check-user-status', { order: 0 },
541
- async ({ context }) => {
542
- // Can safely use context.user set by previous handler
543
- if (context.user?.suspended) {
544
- throw new Error('Account suspended');
545
- }
546
- }
547
- );
548
- ```
549
-
550
- ### 5. Avoid Heavy Operations in Hooks
551
-
552
- ```javascript
553
- // ❌ Bad - Synchronous heavy operation
554
- api.addHook('finish', 'process-images', {}, async ({ context }) => {
555
- if (context.record?.images) {
556
- // This blocks the response
557
- await processImages(context.record.images);
558
- }
559
- });
560
-
561
- // ✅ Good - Queue for background processing
562
- api.addHook('finish', 'queue-image-processing', {}, async ({ context }) => {
563
- if (context.record?.images) {
564
- // Quick operation to queue the job
565
- await jobQueue.add('process-images', {
566
- recordId: context.record.id,
567
- images: context.record.images
568
- });
569
- }
570
- });
571
- ```
572
-
573
- ### 6. Document Hook Side Effects
574
-
575
- ```javascript
576
- /**
577
- * Auto-assigns articles to the default category if none specified.
578
- * Modifies: inputRecord.data.relationships.category
579
- * Depends on: categories table having a 'default' entry
580
- */
581
- api.addHook('beforeProcessing', 'auto-assign-category', {},
582
- async ({ context }) => {
583
- if (context.scopeName !== 'articles' || context.method !== 'post') {
584
- return;
585
- }
586
-
587
- const { relationships } = context.inputRecord.data;
588
- if (!relationships?.category) {
589
- context.inputRecord.data.relationships = {
590
- ...relationships,
591
- category: {
592
- data: { type: 'categories', id: 'default' }
593
- }
594
- };
595
- }
596
- }
597
- );
598
- ```
599
-
600
- ### 7. Use Type Guards
601
-
602
- ```javascript
603
- api.addHook('beforeData', 'type-safe-handler', {}, async ({ context }) => {
604
- // Check types before using
605
- if (typeof context.auth?.userId !== 'string') {
606
- return;
607
- }
608
-
609
- if (!Array.isArray(context.queryParams?.include)) {
610
- context.queryParams.include = [];
611
- }
612
-
613
- // Now safe to use
614
- context.queryParams.include.push('author');
615
- });
616
- ```
617
-
618
- ## Debugging Hooks
619
-
620
- Enable debug logging to see hook execution:
621
-
622
- ```javascript
623
- const api = new Api({
624
- name: 'my-api',
625
- log: { level: 'debug' }
626
- });
627
-
628
- // Hooks will log execution details
629
- // [DEBUG] Running hook 'checkPermissions' with 3 handlers
630
- // [DEBUG] Hook handler 'require-auth' completed in 2ms
631
- ```
632
-
633
- Add custom debugging:
634
-
635
- ```javascript
636
- api.addHook('beforeProcessing', 'debug-hook', { order: -9999 },
637
- async ({ context }) => {
638
- console.log('Hook context:', {
639
- method: context.method,
640
- scopeName: context.scopeName,
641
- auth: context.auth ? 'authenticated' : 'anonymous',
642
- hasInput: !!context.inputRecord
643
- });
644
- }
645
- );
646
- ```
647
-
@@ -1,23 +0,0 @@
1
- Session 1:
2
- ✔ Finish websockets properly, make it actually work @done(25-07-18 14:43)
3
- ✔ Get rid of runPluginHooks, runHooks, on (!!). Only have runHooks, with the runHooks of methods automatically setting context, params @done(25-07-18 14:43)
4
-
5
- Session 2:
6
- ✔ improve way in which jvt-auth works, no need for those vars and helpers @done(25-07-18 15:50)
7
- ✔ Improve enrichAttributes, split into helpers <--- ??????????????? @done(25-07-18 19:45)
8
-
9
- Sessoon 3:
10
- ✔ Add 'created_at' module, maybe have 'protected' fields @done(25-07-18 20:46)
11
- ✔ Create cors module @done(25-07-19 22:15)
12
-
13
- Session 4:
14
- ✔ Improve the CORS plugin documentation BY FAAAAR @done(25-07-20 08:35)
15
- ✔ Write test/documentation for positioning plugin @done(25-07-20 15:17)
16
-
17
-
18
- Sessions 4:
19
- ✔ Final decision on parameters/usage of non-method hooks (parameters? Context?) @done(25-07-20 17:21)
20
- ☐ Final decision on the way plugins get the parameters; DO NOT get them their own namespace
21
- ☐ Understant why state.authCheckers.set() seems to assign // Handle simplified/flat format (!!) return record[ownerField] === context.auth.userId;
22
-
23
- ☐ Document the lot properly