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.
- package/.claude/agents/library-guide-expert.md +29 -0
- package/.claude/agents/rest-api-internals-expert.md +53 -0
- package/CLAUDE.md +139 -1
- package/README.md +2 -5
- package/docTest.js +238 -0
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +955 -0
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +54 -0
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +750 -0
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +494 -0
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +398 -0
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +882 -0
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +709 -0
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +1233 -0
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +796 -0
- package/{README_X_Relationship_Plugin.md → docs/GUIDE/GUIDE_2_9_Relationships_Urls.md} +1 -4
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +66 -0
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +1246 -0
- package/docs/GUIDE/GUIDE_5_Non-Db_Stores.md +0 -0
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +1490 -0
- package/{README_X_SocketIO.md → docs/GUIDE/GUIDE_X_SocketIO.md} +17 -17
- package/docs/GUIDE/index.md +58 -0
- package/docs/GUIDE.md +61 -2238
- package/docs/ONBOARDING/Computed_Properties_And_Sparse_Fields.md +398 -0
- package/docs/README.md +57 -2239
- package/{README_2_Computed_and_hidden_fields.md → docs/README_VARIOUS/GUIDE_2_Computed_and_hidden_fields.md} +22 -17
- package/docs/README_VARIOUS/GUIDE_IN_DOCS.md +2277 -0
- package/{README.ORIG.md → docs/README_VARIOUS/README.ORIG.md} +8 -8
- package/{README_8_Polymorphic_Relationships.md → docs/README_VARIOUS/README_8_Polymorphic_Relationships.md} +1 -1
- package/docs/README_VARIOUS/README_IN_DOCS.ms +2277 -0
- package/{RELATIONSHIPS_ADVANCED.md → docs/README_VARIOUS/RELATIONSHIPS_ADVANCED.md} +3 -3
- package/{correct-schema-format.md → docs/README_VARIOUS/correct-schema-format.md} +13 -13
- package/{pagination.md → docs/README_VARIOUS/pagination.md} +11 -8
- package/docs/assets/css/style.scss +78 -5
- package/docs/index.md +2 -12
- package/helper-reorganization-plan.md +85 -0
- package/helper-usage-analysis.md +94 -0
- package/package.json +5 -4
- package/plugins/core/bulk-operations-plugin.js +2 -2
- package/plugins/core/connectors/express-plugin.js +90 -32
- package/plugins/core/lib/querying/knex-cross-table-search.js +609 -0
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +491 -0
- package/plugins/core/lib/querying/knex-pagination-helpers.js +544 -0
- package/plugins/core/lib/{knex-process-includes.js → querying/knex-process-includes.js} +65 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +74 -0
- package/plugins/core/lib/{knex-query-helpers.js → querying/knex-query-helpers.js} +416 -180
- package/plugins/core/lib/{knex-relationship-includes.js → querying/knex-relationship-includes.js} +430 -114
- package/plugins/core/lib/querying/knex-window-queries.js +328 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +328 -0
- package/plugins/core/{utils → lib/querying-writing}/connectors-query-parser.js +62 -57
- package/plugins/core/lib/{database-capabilities.js → querying-writing/database-capabilities.js} +113 -7
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +369 -0
- package/plugins/core/lib/querying-writing/default-data-helpers.js +72 -0
- package/plugins/core/{utils → lib/querying-writing}/field-utils.js +41 -31
- package/plugins/core/{utils → lib/querying-writing}/knex-constants.js +1 -0
- package/plugins/core/lib/{knex-field-helpers.js → querying-writing/knex-field-helpers.js} +190 -57
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +162 -0
- package/plugins/core/lib/{payload-validators.js → querying-writing/payload-validators.js} +297 -217
- package/plugins/core/lib/querying-writing/schema-helpers.js +497 -0
- package/plugins/core/lib/querying-writing/scope-validations.js +431 -0
- package/plugins/core/lib/{simplified-helpers.js → querying-writing/simplified-helpers.js} +216 -176
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +110 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +300 -0
- package/plugins/core/lib/writing/relationship-processor.js +252 -0
- package/plugins/core/rest-api-knex-plugin.js +183 -62
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +7 -0
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +72 -0
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +102 -0
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +50 -0
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +39 -0
- package/plugins/core/rest-api-plugin-methods/add-route.js +23 -0
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +18 -0
- package/plugins/core/rest-api-plugin-methods/common.js +605 -0
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +115 -0
- package/plugins/core/rest-api-plugin-methods/delete.js +109 -0
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +186 -0
- package/plugins/core/rest-api-plugin-methods/get-related.js +295 -0
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +62 -0
- package/plugins/core/rest-api-plugin-methods/get.js +187 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +56 -0
- package/plugins/core/rest-api-plugin-methods/patch.js +193 -0
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +104 -0
- package/plugins/core/rest-api-plugin-methods/post.js +157 -0
- package/plugins/core/rest-api-plugin-methods/put.js +257 -0
- package/plugins/core/rest-api-plugin-methods/query.js +208 -0
- package/plugins/core/rest-api-plugin.js +108 -2498
- package/plugins/core/socketio-plugin.js +28 -5
- package/plugins/storage/local-storage.js +2 -2
- package/tests/computed-fields.test.js +483 -0
- package/tests/cors.test.js +4 -4
- package/tests/curl.test.js +5 -4
- package/tests/custom-idproperty-relationships.test.js +891 -0
- package/tests/field-getters.test.js +353 -0
- package/tests/field-setters.test.js +310 -0
- package/tests/fixtures/api-configs.js +800 -28
- package/tests/jwt-auth.test.js +0 -2
- package/tests/nested-includes.test.js +1 -1
- package/tests/pagination-cursor-multifield.test.js +514 -0
- package/tests/pagination-enhanced.test.js +12 -9
- package/tests/polymorphic-relationships.test.js +10 -8
- package/tests/polymorphic-simple.test.js +6 -4
- package/tests/positioning-advanced.test.js +12 -12
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +24 -24
- package/tests/positioning.test.js +3 -3
- package/tests/query-limits.test.js +24 -8
- package/tests/relationship-endpoints.test.js +4 -5
- package/tests/relationships.test.js +0 -1
- package/tests/socketio.test.js +12 -1
- package/tests/virtual-fields.test.js +13 -13
- package/PLAN_FOR_WEBSOCKETS.md +0 -398
- package/README_1_Initial_Setup.md +0 -393
- package/README_X_Hooks.md +0 -647
- package/TODO_rest-api-plugin-extras.todo +0 -23
- package/connectors_ideas/express-plugin.js +0 -120
- package/connectors_ideas/http-plugin.js +0 -138
- package/docs/examples/express-plugin.js +0 -366
- package/example.js +0 -301
- package/plugins/core/HELPER_MODULES.md +0 -285
- package/plugins/core/lib/compile-schemas.js +0 -245
- package/plugins/core/lib/database-value-normalizers.js +0 -192
- package/plugins/core/lib/default-data-helpers.js +0 -100
- package/plugins/core/lib/knex-cross-table-search.js +0 -442
- package/plugins/core/lib/knex-json-api-helpers.js +0 -84
- package/plugins/core/lib/knex-json-api-transformers.js +0 -449
- package/plugins/core/lib/knex-pagination-helpers.js +0 -193
- package/plugins/core/lib/knex-window-queries.js +0 -142
- package/plugins/core/lib/many-to-many-manipulations.js +0 -343
- package/plugins/core/lib/relationship-processor.js +0 -278
- package/plugins/core/lib/schema-helpers.js +0 -343
- package/plugins/core/lib/scope-validations.js +0 -252
- package/plugins/core/rest-api-relationships-plugin.js +0 -700
- package/plugins/core/utils/knex-query-helpers-base.js +0 -56
- /package/{README_X_Permissions.md → docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md} +0 -0
- /package/{README_X_Positioning.md → docs/GUIDE/GUIDE_6_Positioning.md} +0 -0
- /package/{README_X_Bulk_Operations.md → docs/GUIDE/GUIDE_X_Bulk_Operations.md} +0 -0
- /package/{README_X_Cors.md → docs/GUIDE/GUIDE_X_Cors.md} +0 -0
- /package/{README_X_Multihome.md → docs/GUIDE/GUIDE_X_Multihome.md} +0 -0
- /package/{README_X_Appendices.md → docs/GUIDE/GUIDE_Y_Appendices.md} +0 -0
- /package/{QUERY_DEPTH_PROTECTION.md → docs/README_VARIOUS/QUERY_DEPTH_PROTECTION.md} +0 -0
- /package/{RELATIONSHIPS.md → docs/README_VARIOUS/RELATIONSHIPS.md} +0 -0
- /package/{WEBSOCKET_IMPLEMENTATION_STATUS.md → docs/README_VARIOUS/WEBSOCKET_IMPLEMENTATION_STATUS.md} +0 -0
- /package/{helper-functions-chart.md → docs/README_VARIOUS/helper-functions-chart.md} +0 -0
- /package/docs/{implementation-hidden-computed-fields.md → README_VARIOUS/implementation-hidden-computed-fields.md} +0 -0
- /package/docs/{include-limits-migration-guide.md → README_VARIOUS/include-limits-migration-guide.md} +0 -0
- /package/{readmeSnippets → docs/README_VARIOUS}/test_put_behavior.js +0 -0
- /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 '../
|
|
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
|
-
|
|
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
|
-
//
|
|
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 === '
|
|
305
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
352
|
-
//
|
|
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
|
-
//
|
|
363
|
-
router
|
|
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:
|
|
426
|
+
path: path,
|
|
372
427
|
method: method.toLowerCase()
|
|
373
428
|
});
|
|
374
429
|
throw routeError;
|
|
375
430
|
}
|
|
376
431
|
|
|
377
|
-
|
|
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(
|
|
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 = {
|