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
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { requirePackage } from 'hooked-api';
18
- import { parseJsonApiQuery } from '../utils/connectors-query-parser.js';
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
 
@@ -46,14 +46,17 @@ export const ExpressPlugin = {
46
46
 
47
47
  const expressOptions = pluginOptions || {};
48
48
 
49
- 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
50
52
  const strictContentType = expressOptions.strictContentType !== false;
51
53
  const requestSizeLimit = expressOptions.requestSizeLimit || '1mb';
52
54
 
53
55
  // Set transport information for other plugins
54
56
  vars.transport = {
55
57
  type: 'express',
56
- matchAll: '*' // Express wildcard pattern for matching all routes
58
+ matchAll: '*', // Express wildcard pattern for matching all routes
59
+ mountPath: mountPath // Transport-specific mount path
57
60
  };
58
61
 
59
62
  // Register file detector if enabled
@@ -110,6 +113,16 @@ export const ExpressPlugin = {
110
113
  router.use(async (req, res, next) => {
111
114
  const context = createContext(req, res, 'express');
112
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
+
113
126
  // Transport-specific data for hooks
114
127
  const transportData = {
115
128
  request: {
@@ -268,20 +281,9 @@ export const ExpressPlugin = {
268
281
  // Apply any global before middleware
269
282
  const beforeMiddleware = expressOptions.middleware?.beforeAll || [];
270
283
 
271
- // Create the Express route
272
- // Special handling for wildcard paths when basePath is set
273
- let expressPath;
274
- if (path === vars.transport.matchAll && basePath) {
275
- // When we have a basePath and a wildcard, we need to use proper Express wildcard syntax
276
- expressPath = basePath + '/*';
277
- } else {
278
- expressPath = basePath + path;
279
- }
280
-
281
-
282
284
  try {
283
285
 
284
- // 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).
285
287
  const expressHandler = async (req, res) => {
286
288
  try {
287
289
  // Extract request data
@@ -301,10 +303,18 @@ export const ExpressPlugin = {
301
303
  let responseStatus = 200;
302
304
  if (result && typeof result.statusCode === 'number') {
303
305
  responseStatus = result.statusCode;
304
- } else if (req.method === 'DELETE' && !result) {
305
- responseStatus = 204;
306
- } else if (req.method === 'POST') {
306
+ } else if (req.method === 'POST' && result) {
307
+ // POST with content returns 201 Created
307
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;
308
318
  }
309
319
 
310
320
  // Apply headers from the handler result
@@ -326,6 +336,20 @@ export const ExpressPlugin = {
326
336
  // Set content type
327
337
  res.set('Content-Type', 'application/vnd.api+json');
328
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
+
329
353
  // Handle response based on status
330
354
  if (responseStatus === 204) {
331
355
  res.sendStatus(204);
@@ -333,12 +357,6 @@ export const ExpressPlugin = {
333
357
  // If result has a body property, that's what we should send
334
358
  // This happens when handler returns { statusCode, body, headers }
335
359
  const responseBody = result && result.body !== undefined ? result.body : result;
336
-
337
- if (req.method === 'POST' && responseBody?.data?.id && helpers.getLocation) {
338
- const scopeName = path.split('/')[2];
339
- const location = helpers.getLocation({ scopeName, id: responseBody.data.id });
340
- res.set('Location', `${basePath}/api${location}`);
341
- }
342
360
  res.status(responseStatus).json(responseBody);
343
361
  }
344
362
  } catch (error) {
@@ -346,35 +364,75 @@ export const ExpressPlugin = {
346
364
  }
347
365
  };
348
366
 
349
- // 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
+ //
350
385
  if (path === vars.transport.matchAll) {
351
- // For wildcard paths in Express 5, we need to use middleware approach
352
- // 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)
353
395
  const methodSpecificMiddleware = (req, res, next) => {
354
396
  if (req.method.toLowerCase() === method.toLowerCase()) {
355
397
  expressHandler(req, res);
356
398
  } else {
399
+ // Not our method, pass to next middleware
357
400
  next();
358
401
  }
359
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
360
406
  router.use(...beforeMiddleware, methodSpecificMiddleware);
361
407
  } else {
362
- // Use the specific method (get, post, all, etc.) for all other defined routes
363
- 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);
364
418
  }
365
419
 
366
420
 
367
421
  } catch (routeError) {
422
+ console.log('WWWWWWWWWWWWWWTTTTTTTTTTTTFFFFFFFFFFFFFFFFF', context)
368
423
  console.log('[EXPRESS DEBUG] Error creating route:', {
369
424
  error: routeError.message,
370
425
  stack: routeError.stack,
371
- path: expressPath,
426
+ path: path,
372
427
  method: method.toLowerCase()
373
428
  });
374
429
  throw routeError;
375
430
  }
376
431
 
377
- 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}`);
378
436
  });
379
437
 
380
438
  // Apply global middleware if configured
@@ -389,7 +447,7 @@ export const ExpressPlugin = {
389
447
  // Set up 404 handler in separate router (unless disabled)
390
448
  if (expressOptions.handle404 !== false) {
391
449
  notFoundRouter.use(async (req, res, next) => {
392
- if (req.path.startsWith('/api')) {
450
+ if (basePath && req.path.startsWith(basePath)) {
393
451
  // Create minimal context for 404
394
452
  const context = createContext(req, res, 'express');
395
453
  const transportData = {