json-rest-api 1.0.6 → 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 +99 -33
  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
@@ -14,8 +14,8 @@
14
14
  * - File upload support with busboy or formidable
15
15
  */
16
16
 
17
- import express from 'express';
18
- import { parseJsonApiQuery } from '../utils/connectors-query-parser.js';
17
+ import { requirePackage } from 'hooked-api';
18
+ import { parseJsonApiQuery } from '../lib/querying-writing/connectors-query-parser.js';
19
19
  import { createContext } from './lib/request-helpers.js';
20
20
  import { createEnhancedLogger } from '../../../lib/enhanced-logger.js';
21
21
 
@@ -24,6 +24,14 @@ export const ExpressPlugin = {
24
24
  dependencies: ['rest-api'],
25
25
 
26
26
  async install({ on, vars, helpers, pluginOptions, log, scopes, api, runHooks, addHook }) {
27
+ // Dynamic import for Express
28
+ let express;
29
+ try {
30
+ express = (await import('express')).default;
31
+ } catch (e) {
32
+ requirePackage('express', 'express',
33
+ 'Express.js is required for HTTP server functionality. This is a peer dependency.');
34
+ }
27
35
  // Enhance the logger
28
36
  const enhancedLog = createEnhancedLogger(log, {
29
37
  logFullErrors: true,
@@ -38,14 +46,17 @@ export const ExpressPlugin = {
38
46
 
39
47
  const expressOptions = pluginOptions || {};
40
48
 
41
- const basePath = expressOptions.basePath || '';
49
+ // Get mountPath from options (this is now a transport concern)
50
+ const mountPath = expressOptions.mountPath || '';
51
+ const basePath = mountPath; // Keep basePath for internal use
42
52
  const strictContentType = expressOptions.strictContentType !== false;
43
53
  const requestSizeLimit = expressOptions.requestSizeLimit || '1mb';
44
54
 
45
55
  // Set transport information for other plugins
46
56
  vars.transport = {
47
57
  type: 'express',
48
- matchAll: '*' // Express wildcard pattern for matching all routes
58
+ matchAll: '*', // Express wildcard pattern for matching all routes
59
+ mountPath: mountPath // Transport-specific mount path
49
60
  };
50
61
 
51
62
  // Register file detector if enabled
@@ -102,6 +113,16 @@ export const ExpressPlugin = {
102
113
  router.use(async (req, res, next) => {
103
114
  const context = createContext(req, res, 'express');
104
115
 
116
+ // Auto-detect publicBaseUrl if not explicitly set
117
+ if (!vars.publicBaseUrl) {
118
+ // Use protocol from X-Forwarded-Proto if behind proxy, otherwise req.protocol
119
+ const protocol = req.get('x-forwarded-proto') || req.protocol || 'http';
120
+ const host = req.get('x-forwarded-host') || req.get('host');
121
+ if (host) {
122
+ vars.publicBaseUrl = `${protocol}://${host}${basePath}`;
123
+ }
124
+ }
125
+
105
126
  // Transport-specific data for hooks
106
127
  const transportData = {
107
128
  request: {
@@ -260,20 +281,9 @@ export const ExpressPlugin = {
260
281
  // Apply any global before middleware
261
282
  const beforeMiddleware = expressOptions.middleware?.beforeAll || [];
262
283
 
263
- // Create the Express route
264
- // Special handling for wildcard paths when basePath is set
265
- let expressPath;
266
- if (path === vars.transport.matchAll && basePath) {
267
- // When we have a basePath and a wildcard, we need to use proper Express wildcard syntax
268
- expressPath = basePath + '/*';
269
- } else {
270
- expressPath = basePath + path;
271
- }
272
-
273
-
274
284
  try {
275
285
 
276
- // 1. Extract the handler logic into a shared function to keep it DRY (Don't Repeat Yourself).
286
+ // Extract the handler logic into a shared function to keep it DRY (Don't Repeat Yourself).
277
287
  const expressHandler = async (req, res) => {
278
288
  try {
279
289
  // Extract request data
@@ -293,10 +303,18 @@ export const ExpressPlugin = {
293
303
  let responseStatus = 200;
294
304
  if (result && typeof result.statusCode === 'number') {
295
305
  responseStatus = result.statusCode;
296
- } else if (req.method === 'DELETE' && !result) {
297
- responseStatus = 204;
298
- } else if (req.method === 'POST') {
306
+ } else if (req.method === 'POST' && result) {
307
+ // POST with content returns 201 Created
299
308
  responseStatus = 201;
309
+ } else if (req.method === 'POST' && !result) {
310
+ // POST without content returns 204 No Content
311
+ responseStatus = 204;
312
+ } else if (req.method === 'DELETE') {
313
+ // DELETE always returns 204 No Content
314
+ responseStatus = 204;
315
+ } else if ((req.method === 'PUT' || req.method === 'PATCH') && !result) {
316
+ // PUT/PATCH return 204 when no content is returned
317
+ responseStatus = 204;
300
318
  }
301
319
 
302
320
  // Apply headers from the handler result
@@ -318,6 +336,20 @@ export const ExpressPlugin = {
318
336
  // Set content type
319
337
  res.set('Content-Type', 'application/vnd.api+json');
320
338
 
339
+ // Set Location header for successful POST requests
340
+ if (req.method === 'POST' && context.id) {
341
+ // Extract scopeName from the path (e.g., /api/countries -> countries)
342
+ const pathParts = path.split('/');
343
+ const scopeName = pathParts[pathParts.length - 1];
344
+
345
+ // Set Location header for any successful POST (201 or 204)
346
+ if (helpers.getLocation) {
347
+ const location = helpers.getLocation({ scopeName, id: context.id });
348
+ const baseUrl = vars.publicBaseUrl || basePath;
349
+ res.set('Location', `${baseUrl}${location}`);
350
+ }
351
+ }
352
+
321
353
  // Handle response based on status
322
354
  if (responseStatus === 204) {
323
355
  res.sendStatus(204);
@@ -325,12 +357,6 @@ export const ExpressPlugin = {
325
357
  // If result has a body property, that's what we should send
326
358
  // This happens when handler returns { statusCode, body, headers }
327
359
  const responseBody = result && result.body !== undefined ? result.body : result;
328
-
329
- if (req.method === 'POST' && responseBody?.data?.id && helpers.getLocation) {
330
- const scopeName = path.split('/')[2];
331
- const location = helpers.getLocation({ scopeName, id: responseBody.data.id });
332
- res.set('Location', `${basePath}/api${location}`);
333
- }
334
360
  res.status(responseStatus).json(responseBody);
335
361
  }
336
362
  } catch (error) {
@@ -338,35 +364,75 @@ export const ExpressPlugin = {
338
364
  }
339
365
  };
340
366
 
341
- // 2. Check for the wildcard path and use the appropriate Express method.
367
+ // CRITICAL: Express routing method selection - wildcard vs specific routes
368
+ //
369
+ // Express provides two different ways to register routes:
370
+ // 1. router.METHOD(path, handler) - e.g., router.get('/users', handler)
371
+ // - Only responds to the SPECIFIC HTTP method
372
+ // - Perfect for normal REST endpoints
373
+ //
374
+ // 2. router.use(path, handler)
375
+ // - Responds to ALL HTTP methods
376
+ // - Needed for wildcard paths that must handle any method
377
+ //
378
+ // The CORS plugin needs wildcard routes because it must handle OPTIONS
379
+ // requests for ANY path under the API prefix, even paths that don't exist
380
+ // as defined routes. For example:
381
+ // - Defined route: GET /api/users
382
+ // - Browser might send: OPTIONS /api/users/invalid/path
383
+ // - CORS must still respond with proper headers
384
+ //
342
385
  if (path === vars.transport.matchAll) {
343
- // For wildcard paths in Express 5, we need to use middleware approach
344
- // Create a middleware that only responds to the specific method
386
+ // This is a wildcard route (path = '*')
387
+ // We MUST use router.use() because:
388
+ // - We need to catch ALL paths (using '*' or '/api/*')
389
+ // - We need to handle a SPECIFIC method (e.g., OPTIONS)
390
+ // - router.options('*') would NOT work for paths like '/api/some/nested/path'
391
+
392
+
393
+ // Since router.use() responds to ALL methods, we need a wrapper
394
+ // that only handles our specific method (e.g., OPTIONS)
345
395
  const methodSpecificMiddleware = (req, res, next) => {
346
396
  if (req.method.toLowerCase() === method.toLowerCase()) {
347
397
  expressHandler(req, res);
348
398
  } else {
399
+ // Not our method, pass to next middleware
349
400
  next();
350
401
  }
351
402
  };
403
+ // IMPORTANT: We do NOT need to pass a path to router.use() here!
404
+ // When no path is provided, router.use() matches ALL requests
405
+ // This is exactly what we want for wildcard routes
352
406
  router.use(...beforeMiddleware, methodSpecificMiddleware);
353
407
  } else {
354
- // Use the specific method (get, post, all, etc.) for all other defined routes
355
- router[method.toLowerCase()](expressPath, ...beforeMiddleware, expressHandler);
408
+ // This is a normal route with a specific path (e.g., '/api/users')
409
+ // We use router.METHOD() because:
410
+ // - We want to respond to ONLY this specific HTTP method
411
+ // - The path is exact, not a wildcard
412
+ // - This is more efficient than router.use() with method checking
413
+ //
414
+ // Note: Routes from RestApiPlugin already include the full path with mountPath
415
+ // So we use them as-is without adding basePath to avoid double-prefixing
416
+
417
+ router[method.toLowerCase()](path, ...beforeMiddleware, expressHandler);
356
418
  }
357
419
 
358
420
 
359
421
  } catch (routeError) {
422
+ console.log('WWWWWWWWWWWWWWTTTTTTTTTTTTFFFFFFFFFFFFFFFFF', context)
360
423
  console.log('[EXPRESS DEBUG] Error creating route:', {
361
424
  error: routeError.message,
362
425
  stack: routeError.stack,
363
- path: expressPath,
426
+ path: path,
364
427
  method: method.toLowerCase()
365
428
  });
366
429
  throw routeError;
367
430
  }
368
431
 
369
- log.trace(`Express route created: ${method} ${expressPath}`);
432
+ const logPath = path === vars.transport.matchAll
433
+ ? '(all paths)' // router.use() with no path matches everything
434
+ : path;
435
+ log.trace(`Express route created: ${method} ${logPath}`);
370
436
  });
371
437
 
372
438
  // Apply global middleware if configured
@@ -381,7 +447,7 @@ export const ExpressPlugin = {
381
447
  // Set up 404 handler in separate router (unless disabled)
382
448
  if (expressOptions.handle404 !== false) {
383
449
  notFoundRouter.use(async (req, res, next) => {
384
- if (req.path.startsWith('/api')) {
450
+ if (basePath && req.path.startsWith(basePath)) {
385
451
  // Create minimal context for 404
386
452
  const context = createContext(req, res, 'express');
387
453
  const transportData = {