json-rest-api 1.0.14 → 1.0.15

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 (118) hide show
  1. package/connectors_ideas/fastify.js +66 -68
  2. package/connectors_ideas/koa-plugin.js +50 -51
  3. package/eslint.config.js +11 -2
  4. package/examples/url-override-example.js +63 -63
  5. package/index.js +13 -13
  6. package/lib/enhanced-logger.js +51 -51
  7. package/lib/error-formatter.js +61 -61
  8. package/lib/rest-api-errors.js +28 -28
  9. package/package.json +1 -1
  10. package/plugins/core/bulk-operations-plugin.js +142 -145
  11. package/plugins/core/connectors/express-plugin.js +210 -217
  12. package/plugins/core/connectors/lib/busboy-detector.js +84 -84
  13. package/plugins/core/connectors/lib/formidable-detector.js +50 -50
  14. package/plugins/core/connectors/lib/request-helpers.js +74 -74
  15. package/plugins/core/file-handling-plugin.js +125 -125
  16. package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
  17. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
  18. package/plugins/core/lib/anyapi/schema-utils.js +101 -101
  19. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
  20. package/plugins/core/lib/dbTablesOperations.js +134 -135
  21. package/plugins/core/lib/fractional-positioning.js +74 -76
  22. package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
  23. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
  24. package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
  25. package/plugins/core/lib/querying/knex-process-includes.js +50 -50
  26. package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
  27. package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
  28. package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
  29. package/plugins/core/lib/querying/knex-window-queries.js +89 -89
  30. package/plugins/core/lib/querying/url-helpers.js +35 -35
  31. package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
  32. package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
  33. package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
  34. package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
  35. package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
  36. package/plugins/core/lib/querying-writing/field-utils.js +44 -42
  37. package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
  38. package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
  39. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
  40. package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
  41. package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
  42. package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
  43. package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
  44. package/plugins/core/lib/storage/storage-adapter.js +68 -68
  45. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
  46. package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
  47. package/plugins/core/lib/writing/relationship-processor.js +58 -58
  48. package/plugins/core/rest-api-access.js +8 -8
  49. package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
  50. package/plugins/core/rest-api-cors-plugin.js +170 -171
  51. package/plugins/core/rest-api-knex-plugin.js +482 -493
  52. package/plugins/core/rest-api-multihome-plugin.js +247 -251
  53. package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
  54. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
  55. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
  56. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
  57. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
  58. package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
  59. package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
  60. package/plugins/core/rest-api-plugin-methods/common.js +295 -297
  61. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
  62. package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
  63. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
  64. package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
  65. package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
  66. package/plugins/core/rest-api-plugin-methods/get.js +86 -86
  67. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
  68. package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
  69. package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
  70. package/plugins/core/rest-api-plugin-methods/post.js +61 -62
  71. package/plugins/core/rest-api-plugin-methods/put.js +86 -87
  72. package/plugins/core/rest-api-plugin-methods/query.js +88 -88
  73. package/plugins/core/rest-api-plugin-methods/release.js +5 -6
  74. package/plugins/core/rest-api-plugin.js +76 -78
  75. package/plugins/core/rest-api-positioning-plugin.js +251 -252
  76. package/plugins/core/socketio-plugin.js +240 -236
  77. package/plugins/storage/local-storage.js +103 -103
  78. package/plugins/storage/s3-storage.js +41 -42
  79. package/quickTest.js +37 -37
  80. package/scripts/debug-anyapi-review-filter.mjs +18 -18
  81. package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
  82. package/scripts/inspect-anyapi-descriptor.mjs +10 -10
  83. package/scripts/run-docs.js +112 -113
  84. package/test-debug.js +28 -28
  85. package/tests/TEST_TEMPLATE.test.js +51 -51
  86. package/tests/anyapi-basic.test.js +42 -42
  87. package/tests/anyapi-cursor-pagination.test.js +48 -48
  88. package/tests/bulk-operations.test.js +208 -208
  89. package/tests/computed-fields.test.js +204 -204
  90. package/tests/cors.test.js +412 -413
  91. package/tests/curl.test.js +275 -276
  92. package/tests/custom-idproperty-relationships.test.js +266 -266
  93. package/tests/field-getters.test.js +132 -132
  94. package/tests/field-setters.test.js +110 -110
  95. package/tests/fixtures/api-configs.js +699 -699
  96. package/tests/helpers/storage-mode.js +32 -32
  97. package/tests/helpers/test-utils.js +76 -76
  98. package/tests/include-depth-validation.test.js +76 -76
  99. package/tests/includes.test.js +145 -145
  100. package/tests/multihome.test.js +175 -175
  101. package/tests/nested-includes.test.js +255 -255
  102. package/tests/pagination-cursor-multifield.test.js +164 -164
  103. package/tests/pagination-enhanced.test.js +225 -225
  104. package/tests/polymorphic-relationships.test.js +96 -96
  105. package/tests/polymorphic-simple.test.js +44 -44
  106. package/tests/positioning-advanced.test.js +226 -226
  107. package/tests/positioning-extended.test.js +399 -399
  108. package/tests/positioning-multifilter.test.js +204 -204
  109. package/tests/positioning.test.js +266 -266
  110. package/tests/queries.test.js +184 -184
  111. package/tests/query-limits.test.js +154 -155
  112. package/tests/relationship-endpoints.test.js +127 -127
  113. package/tests/relationships.test.js +178 -179
  114. package/tests/rest-api-access.test.js +86 -86
  115. package/tests/rest-api.test.js +103 -103
  116. package/tests/searchschema-merge.test.js +93 -93
  117. package/tests/socketio.test.js +326 -326
  118. package/tests/virtual-fields.test.js +164 -164
@@ -1,9 +1,9 @@
1
- import { RestApiResourceError } from '../../../../lib/rest-api-errors.js';
2
- import { ROW_NUMBER_KEY, DEFAULT_QUERY_LIMIT, DEFAULT_MAX_QUERY_LIMIT, DEFAULT_MAX_INCLUDE_LIMIT } from '../querying-writing/knex-constants.js';
1
+ import { RestApiResourceError } from '../../../../lib/rest-api-errors.js'
2
+ import { ROW_NUMBER_KEY, DEFAULT_QUERY_LIMIT, DEFAULT_MAX_QUERY_LIMIT, DEFAULT_MAX_INCLUDE_LIMIT } from '../querying-writing/knex-constants.js'
3
3
 
4
4
  /**
5
5
  * Builds a window function query for limited includes per parent record
6
- *
6
+ *
7
7
  * @param {Object} knex - Knex instance
8
8
  * @param {string} tableName - Target table name
9
9
  * @param {string} foreignKey - Foreign key field
@@ -13,7 +13,7 @@ import { ROW_NUMBER_KEY, DEFAULT_QUERY_LIMIT, DEFAULT_MAX_QUERY_LIMIT, DEFAULT_M
13
13
  * @param {Object} capabilities - Database capabilities
14
14
  * @param {Object} scopeVars - Scope variables for defaults and limits
15
15
  * @returns {Object} Knex query
16
- *
16
+ *
17
17
  * @example
18
18
  * // Input: Load max 3 comments per article, ordered by newest first
19
19
  * const query = buildWindowedIncludeQuery(
@@ -26,22 +26,22 @@ import { ROW_NUMBER_KEY, DEFAULT_QUERY_LIMIT, DEFAULT_MAX_QUERY_LIMIT, DEFAULT_M
26
26
  * { windowFunctions: true },
27
27
  * { queryDefaultLimit: 10 }
28
28
  * );
29
- *
29
+ *
30
30
  * // Generated SQL:
31
31
  * // WITH _windowed AS (
32
32
  * // SELECT id, text, created_at, article_id,
33
33
  * // ROW_NUMBER() OVER (
34
- * // PARTITION BY article_id
34
+ * // PARTITION BY article_id
35
35
  * // ORDER BY created_at DESC
36
36
  * // ) as __$jsonrestapi_rn$__
37
37
  * // FROM comments
38
38
  * // WHERE article_id IN (1, 2, 3)
39
39
  * // )
40
- * // SELECT * FROM _windowed
40
+ * // SELECT * FROM _windowed
41
41
  * // WHERE __$jsonrestapi_rn$__ <= 3
42
- *
42
+ *
43
43
  * // Result: Each article gets max 3 comments, not 3 total
44
- *
44
+ *
45
45
  * @example
46
46
  * // Input: Database doesn't support window functions
47
47
  * const query = buildWindowedIncludeQuery(
@@ -53,23 +53,23 @@ import { ROW_NUMBER_KEY, DEFAULT_QUERY_LIMIT, DEFAULT_MAX_QUERY_LIMIT, DEFAULT_M
53
53
  * { limit: 5 },
54
54
  * { windowFunctions: false, dbInfo: { client: 'mysql', version: '5.7' } }
55
55
  * );
56
- *
56
+ *
57
57
  * // Throws RestApiResourceError:
58
- * // "Include limits require window function support. Your database (mysql 5.7)
59
- * // does not support this feature. Window functions are supported in:
58
+ * // "Include limits require window function support. Your database (mysql 5.7)
59
+ * // does not support this feature. Window functions are supported in:
60
60
  * // PostgreSQL 8.4+, MySQL 8.0+, MariaDB 10.2+, SQLite 3.25+, SQL Server 2005+"
61
- *
61
+ *
62
62
  * @description
63
63
  * Used by:
64
64
  * - loadHasMany when strategy: 'window' and database supports window functions
65
65
  * - Enables per-parent limits for one-to-many relationships
66
- *
66
+ *
67
67
  * Purpose:
68
68
  * - Solves the "N+1 limit" problem where you want X records per parent
69
69
  * - Without window functions, LIMIT 10 gives 10 total across all parents
70
70
  * - With window functions, each parent gets up to 10 related records
71
71
  * - Critical for consistent API responses with includes
72
- *
72
+ *
73
73
  * Data flow:
74
74
  * 1. Creates subquery with ROW_NUMBER() partitioned by foreign key
75
75
  * 2. Numbers rows within each partition based on orderBy
@@ -87,104 +87,104 @@ export const buildWindowedIncludeQuery = (
87
87
  capabilities,
88
88
  scopeVars = {}
89
89
  ) => {
90
- const { orderBy = [] } = includeConfig;
91
-
90
+ const { orderBy = [] } = includeConfig
91
+
92
92
  // Apply defaults for limit
93
- const effectiveLimit = includeConfig.limit ?? scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT;
94
-
93
+ const effectiveLimit = includeConfig.limit ?? scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT
94
+
95
95
  // Validate against max
96
96
  if (scopeVars.queryMaxLimit && effectiveLimit > scopeVars.queryMaxLimit) {
97
97
  throw new RestApiResourceError({
98
98
  title: 'Include Limit Exceeds Maximum',
99
99
  detail: `Requested include limit (${effectiveLimit}) exceeds queryMaxLimit (${scopeVars.queryMaxLimit})`,
100
100
  status: 400
101
- });
101
+ })
102
102
  }
103
-
103
+
104
104
  // Check if window functions are supported
105
105
  if (!capabilities.windowFunctions) {
106
- const { dbInfo } = capabilities;
106
+ const { dbInfo } = capabilities
107
107
  throw new RestApiResourceError(
108
108
  `Include limits require window function support. Your database (${dbInfo.client} ${dbInfo.version}) does not support this feature. ` +
109
- `Window functions are supported in: PostgreSQL 8.4+, MySQL 8.0+, MariaDB 10.2+, SQLite 3.25+, SQL Server 2005+`,
109
+ 'Window functions are supported in: PostgreSQL 8.4+, MySQL 8.0+, MariaDB 10.2+, SQLite 3.25+, SQL Server 2005+',
110
110
  {
111
111
  subtype: 'unsupported_operation',
112
112
  database: dbInfo.client,
113
113
  version: dbInfo.version,
114
114
  requiredFeature: 'window_functions'
115
115
  }
116
- );
116
+ )
117
117
  }
118
-
118
+
119
119
  // Build the window function query
120
120
  // This creates a subquery that partitions by the foreign key and numbers rows
121
121
  const subquery = knex(tableName)
122
122
  .select('*')
123
123
  .select(
124
124
  knex.raw(
125
- 'ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ' +
126
- buildOrderByClause(orderBy) +
125
+ 'ROW_NUMBER() OVER (PARTITION BY ?? ORDER BY ' +
126
+ buildOrderByClause(orderBy) +
127
127
  ') as ' + ROW_NUMBER_KEY,
128
128
  [foreignKey]
129
129
  )
130
130
  )
131
- .whereIn(foreignKey, parentIds);
132
-
131
+ .whereIn(foreignKey, parentIds)
132
+
133
133
  // Apply field selection if specified
134
134
  if (fieldsToSelect !== '*' && Array.isArray(fieldsToSelect)) {
135
135
  // Include the foreign key and row number in selection
136
- const fieldsWithFK = [...new Set([...fieldsToSelect, foreignKey, ROW_NUMBER_KEY])];
137
- subquery.select(fieldsWithFK);
136
+ const fieldsWithFK = [...new Set([...fieldsToSelect, foreignKey, ROW_NUMBER_KEY])]
137
+ subquery.select(fieldsWithFK)
138
138
  }
139
-
139
+
140
140
  // Wrap in outer query to filter by row number
141
141
  const query = knex
142
142
  .select('*')
143
143
  .from(subquery.as('_windowed'))
144
- .where(ROW_NUMBER_KEY, '<=', effectiveLimit);
145
-
146
- return query;
147
- };
144
+ .where(ROW_NUMBER_KEY, '<=', effectiveLimit)
145
+
146
+ return query
147
+ }
148
148
 
149
149
  /**
150
150
  * Builds ORDER BY clause from array of sort fields
151
- *
151
+ *
152
152
  * @param {Array<string>} orderBy - Array of field names, prefix with '-' for DESC
153
153
  * @param {string} tablePrefix - Optional table name to prefix fields
154
154
  * @returns {string} SQL ORDER BY clause
155
- *
155
+ *
156
156
  * @example
157
157
  * // Input: Simple ascending sort
158
158
  * const clause = buildOrderByClause(['name', 'created_at']);
159
159
  * // Output: "name ASC, created_at ASC"
160
- *
160
+ *
161
161
  * @example
162
162
  * // Input: Mixed ascending/descending with '-' prefix
163
163
  * const clause = buildOrderByClause(['name', '-created_at', 'status']);
164
164
  * // Output: "name ASC, created_at DESC, status ASC"
165
- *
165
+ *
166
166
  * @example
167
167
  * // Input: With table prefix for joins
168
168
  * const clause = buildOrderByClause(['-updated_at', 'title'], 'articles');
169
169
  * // Output: "articles.updated_at DESC, articles.title ASC"
170
- *
170
+ *
171
171
  * @example
172
172
  * // Input: Empty array (default to id)
173
173
  * const clause = buildOrderByClause([]);
174
174
  * // Output: "id ASC"
175
- *
175
+ *
176
176
  * @description
177
177
  * Used by:
178
178
  * - buildWindowedIncludeQuery for PARTITION BY ordering
179
179
  * - applyStandardIncludeConfig for regular ORDER BY
180
180
  * - Query builders that need consistent sort syntax
181
- *
181
+ *
182
182
  * Purpose:
183
183
  * - Converts API sort syntax (with '-' prefix) to SQL ORDER BY
184
184
  * - Handles table prefixing for queries with joins
185
185
  * - Provides default ordering by id when none specified
186
186
  * - Ensures consistent sort behavior across the API
187
- *
187
+ *
188
188
  * Data flow:
189
189
  * 1. Receives array of sort fields from API parameters
190
190
  * 2. Detects DESC sorts by '-' prefix
@@ -193,27 +193,27 @@ export const buildWindowedIncludeQuery = (
193
193
  */
194
194
  export const buildOrderByClause = (orderBy, tablePrefix) => {
195
195
  if (!orderBy || orderBy.length === 0) {
196
- const defaultField = tablePrefix ? `${tablePrefix}.id` : 'id';
197
- return `${defaultField} ASC`; // Default ordering
196
+ const defaultField = tablePrefix ? `${tablePrefix}.id` : 'id'
197
+ return `${defaultField} ASC` // Default ordering
198
198
  }
199
-
199
+
200
200
  return orderBy.map(field => {
201
- const isDesc = field.startsWith('-');
202
- const fieldName = isDesc ? field.substring(1) : field;
203
- const qualifiedField = tablePrefix ? `${tablePrefix}.${fieldName}` : fieldName;
204
- return `${qualifiedField} ${isDesc ? 'DESC' : 'ASC'}`;
205
- }).join(', ');
206
- };
201
+ const isDesc = field.startsWith('-')
202
+ const fieldName = isDesc ? field.substring(1) : field
203
+ const qualifiedField = tablePrefix ? `${tablePrefix}.${fieldName}` : fieldName
204
+ return `${qualifiedField} ${isDesc ? 'DESC' : 'ASC'}`
205
+ }).join(', ')
206
+ }
207
207
 
208
208
  /**
209
209
  * Applies standard (non-windowed) include configuration to a query
210
- *
210
+ *
211
211
  * @param {Object} query - Knex query builder instance
212
212
  * @param {Object} includeConfig - Include configuration object
213
213
  * @param {Object} scopeVars - Scope variables with defaults and limits
214
214
  * @param {Object} log - Logger instance
215
215
  * @returns {Object} Modified query builder
216
- *
216
+ *
217
217
  * @example
218
218
  * // Input: Basic include with limit and ordering
219
219
  * let query = knex('comments').whereIn('article_id', [1, 2, 3]);
@@ -223,11 +223,11 @@ export const buildOrderByClause = (orderBy, tablePrefix) => {
223
223
  * { queryDefaultLimit: 100, queryMaxLimit: 1000 },
224
224
  * logger
225
225
  * );
226
- *
226
+ *
227
227
  * // Result: Query modified with:
228
228
  * // ORDER BY created_at DESC, id ASC
229
229
  * // LIMIT 20
230
- *
230
+ *
231
231
  * @example
232
232
  * // Input: No explicit limit, uses defaults
233
233
  * let query = knex('tags');
@@ -237,12 +237,12 @@ export const buildOrderByClause = (orderBy, tablePrefix) => {
237
237
  * { queryDefaultLimit: 50, queryMaxLimit: 500 },
238
238
  * logger
239
239
  * );
240
- *
240
+ *
241
241
  * // Result: Uses default limit
242
242
  * // ORDER BY name ASC
243
243
  * // LIMIT 50
244
244
  * // Log: "Using default limit"
245
- *
245
+ *
246
246
  * @example
247
247
  * // Input: Limit exceeds maximum
248
248
  * let query = knex('reviews');
@@ -250,12 +250,12 @@ export const buildOrderByClause = (orderBy, tablePrefix) => {
250
250
  * query,
251
251
  * { limit: 5000 }, // Exceeds max
252
252
  * { queryMaxLimit: 1000, maxIncludeLimit: 500 },
253
- * logger
253
+ * logger
254
254
  * );
255
- *
255
+ *
256
256
  * // Result: Clamped to effective maximum
257
257
  * // LIMIT 500 (min of maxIncludeLimit and queryMaxLimit)
258
- *
258
+ *
259
259
  * @example
260
260
  * // Input: Explicitly disable limit
261
261
  * let query = knex('categories');
@@ -265,23 +265,23 @@ export const buildOrderByClause = (orderBy, tablePrefix) => {
265
265
  * { queryDefaultLimit: 100 },
266
266
  * logger
267
267
  * );
268
- *
268
+ *
269
269
  * // Result: No LIMIT clause added
270
270
  * // Log: "No limit applied to include query (explicitly disabled)"
271
- *
271
+ *
272
272
  * @description
273
273
  * Used by:
274
274
  * - loadHasMany when window functions not available or not requested
275
275
  * - loadReversePolymorphic for standard relationship queries
276
276
  * - Any include loader that needs consistent limit/order handling
277
- *
277
+ *
278
278
  * Purpose:
279
279
  * - Provides fallback when window functions unavailable
280
280
  * - Applies consistent ordering based on API sort syntax
281
281
  * - Enforces limit hierarchy: explicit > default > max
282
282
  * - Respects both queryMaxLimit and maxIncludeLimit
283
283
  * - Logs decisions for debugging
284
- *
284
+ *
285
285
  * Data flow:
286
286
  * 1. Applies ORDER BY for each field in orderBy array
287
287
  * 2. Determines effective limit from explicit/default/max values
@@ -290,39 +290,39 @@ export const buildOrderByClause = (orderBy, tablePrefix) => {
290
290
  * 5. Logs the reasoning for the applied limit
291
291
  */
292
292
  export const applyStandardIncludeConfig = (query, includeConfig, scopeVars, log) => {
293
- const { orderBy = [] } = includeConfig;
294
-
293
+ const { orderBy = [] } = includeConfig
294
+
295
295
  // Apply ordering
296
296
  orderBy.forEach(field => {
297
- const desc = field.startsWith('-');
298
- const column = desc ? field.substring(1) : field;
299
- query = query.orderBy(column, desc ? 'desc' : 'asc');
300
- });
301
-
297
+ const desc = field.startsWith('-')
298
+ const column = desc ? field.substring(1) : field
299
+ query = query.orderBy(column, desc ? 'desc' : 'asc')
300
+ })
301
+
302
302
  // Apply limit with defaults
303
- const requestedLimit = includeConfig.limit;
304
- const defaultLimit = scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT;
305
- const limit = requestedLimit ?? defaultLimit;
306
-
303
+ const requestedLimit = includeConfig.limit
304
+ const defaultLimit = scopeVars.queryDefaultLimit ?? DEFAULT_QUERY_LIMIT
305
+ const limit = requestedLimit ?? defaultLimit
306
+
307
307
  // Allow explicit null/false to mean no limit
308
308
  if (limit !== null && limit !== false) {
309
- const maxInclude = scopeVars.maxIncludeLimit || DEFAULT_MAX_INCLUDE_LIMIT;
310
- const maxQuery = scopeVars.queryMaxLimit || DEFAULT_MAX_QUERY_LIMIT;
311
- const effectiveMax = Math.min(maxInclude, maxQuery);
312
- const effectiveLimit = Math.min(limit, effectiveMax);
313
-
314
- query = query.limit(effectiveLimit);
315
-
309
+ const maxInclude = scopeVars.maxIncludeLimit || DEFAULT_MAX_INCLUDE_LIMIT
310
+ const maxQuery = scopeVars.queryMaxLimit || DEFAULT_MAX_QUERY_LIMIT
311
+ const effectiveMax = Math.min(maxInclude, maxQuery)
312
+ const effectiveLimit = Math.min(limit, effectiveMax)
313
+
314
+ query = query.limit(effectiveLimit)
315
+
316
316
  log.debug('Applied include limit:', {
317
317
  requested: requestedLimit,
318
318
  default: defaultLimit,
319
319
  effective: effectiveLimit,
320
320
  maxAllowed: effectiveMax,
321
321
  note: requestedLimit !== undefined ? 'Using explicit limit' : 'Using default limit'
322
- });
322
+ })
323
323
  } else {
324
- log.debug('No limit applied to include query (explicitly disabled)');
324
+ log.debug('No limit applied to include query (explicitly disabled)')
325
325
  }
326
-
327
- return query;
328
- };
326
+
327
+ return query
328
+ }
@@ -1,80 +1,80 @@
1
1
  /**
2
2
  * URL Helper Functions
3
- *
3
+ *
4
4
  * Centralized functions for URL generation to avoid code duplication
5
5
  * and support flexible URL prefix override capabilities.
6
6
  */
7
7
 
8
8
  /**
9
9
  * Get the complete URL prefix, handling all calculation and override logic
10
- *
10
+ *
11
11
  * Priority order:
12
12
  * 1. context.urlPrefixOverride - Explicit override (from hooks or middleware)
13
13
  * 2. context.urlPrefix - Pre-calculated URL prefix
14
14
  * 3. Calculate from request if provided
15
15
  * 4. scope.vars.transport?.mountPath - Fallback to mount path
16
16
  * 5. '' - Empty string as final fallback
17
- *
17
+ *
18
18
  * @param {Object} context - Request context (may have urlPrefixOverride or urlPrefix)
19
19
  * @param {Object} scope - Scope/resource object with transport vars
20
20
  * @param {Object} req - Optional Express request object for auto-calculation
21
21
  * @returns {string} The final URL prefix to use
22
- *
22
+ *
23
23
  * @example
24
24
  * // With full context
25
25
  * const urlPrefix = getUrlPrefix(context, scope);
26
- *
26
+ *
27
27
  * @example
28
28
  * // With request object for auto-calculation
29
29
  * const urlPrefix = getUrlPrefix({}, api, req);
30
30
  */
31
- export function getUrlPrefix(context, scope, req = null) {
31
+ export function getUrlPrefix (context, scope, req = null) {
32
32
  // Priority 1: Explicit override
33
33
  if (context?.urlPrefixOverride) {
34
- return context.urlPrefixOverride;
34
+ return context.urlPrefixOverride
35
35
  }
36
-
36
+
37
37
  // Priority 2: Pre-calculated urlPrefix
38
38
  if (context?.urlPrefix) {
39
- return context.urlPrefix;
39
+ return context.urlPrefix
40
40
  }
41
-
41
+
42
42
  // Priority 3: Calculate from request if provided
43
43
  if (req) {
44
- const protocol = req.get?.('x-forwarded-proto') || req.protocol || 'http';
45
- const host = req.get?.('x-forwarded-host') || req.get?.('host');
46
- const mountPath = scope?.vars?.transport?.mountPath || '';
47
-
44
+ const protocol = req.get?.('x-forwarded-proto') || req.protocol || 'http'
45
+ const host = req.get?.('x-forwarded-host') || req.get?.('host')
46
+ const mountPath = scope?.vars?.transport?.mountPath || ''
47
+
48
48
  if (host) {
49
- return `${protocol}://${host}${mountPath}`;
49
+ return `${protocol}://${host}${mountPath}`
50
50
  }
51
51
  }
52
-
52
+
53
53
  // Priority 4: Fallback to mount path
54
- return scope?.vars?.transport?.mountPath || '';
54
+ return scope?.vars?.transport?.mountPath || ''
55
55
  }
56
56
 
57
57
  /**
58
58
  * Build a complete resource URL
59
- *
59
+ *
60
60
  * @param {Object} context - Request context
61
61
  * @param {Object} scope - Scope containing transport vars
62
62
  * @param {string} scopeName - Resource type name
63
63
  * @param {string} id - Resource ID
64
64
  * @returns {string} Complete resource URL
65
- *
65
+ *
66
66
  * @example
67
67
  * const url = buildResourceUrl(context, scope, 'books', '123');
68
68
  * // Returns: "https://api.example.com/api/books/123"
69
69
  */
70
- export function buildResourceUrl(context, scope, scopeName, id) {
71
- const urlPrefix = getUrlPrefix(context, scope);
72
- return `${urlPrefix}/${scopeName}/${id}`;
70
+ export function buildResourceUrl (context, scope, scopeName, id) {
71
+ const urlPrefix = getUrlPrefix(context, scope)
72
+ return `${urlPrefix}/${scopeName}/${id}`
73
73
  }
74
74
 
75
75
  /**
76
76
  * Build a relationship URL
77
- *
77
+ *
78
78
  * @param {Object} context - Request context
79
79
  * @param {Object} scope - Scope containing transport vars
80
80
  * @param {string} scopeName - Resource type name
@@ -82,38 +82,38 @@ export function buildResourceUrl(context, scope, scopeName, id) {
82
82
  * @param {string} relationshipName - Name of the relationship
83
83
  * @param {boolean} isRelationshipEndpoint - If true, builds /relationships/ URL
84
84
  * @returns {string} Complete relationship URL
85
- *
85
+ *
86
86
  * @example
87
87
  * // For relationship linkage endpoint
88
88
  * buildRelationshipUrl(context, scope, 'books', '1', 'author', true);
89
89
  * // Returns: "https://api.example.com/api/books/1/relationships/author"
90
- *
90
+ *
91
91
  * // For related resource endpoint
92
92
  * buildRelationshipUrl(context, scope, 'books', '1', 'author', false);
93
93
  * // Returns: "https://api.example.com/api/books/1/author"
94
94
  */
95
- export function buildRelationshipUrl(context, scope, scopeName, id, relationshipName, isRelationshipEndpoint = false) {
96
- const urlPrefix = getUrlPrefix(context, scope);
95
+ export function buildRelationshipUrl (context, scope, scopeName, id, relationshipName, isRelationshipEndpoint = false) {
96
+ const urlPrefix = getUrlPrefix(context, scope)
97
97
  if (isRelationshipEndpoint) {
98
- return `${urlPrefix}/${scopeName}/${id}/relationships/${relationshipName}`;
98
+ return `${urlPrefix}/${scopeName}/${id}/relationships/${relationshipName}`
99
99
  }
100
- return `${urlPrefix}/${scopeName}/${id}/${relationshipName}`;
100
+ return `${urlPrefix}/${scopeName}/${id}/${relationshipName}`
101
101
  }
102
102
 
103
103
  /**
104
104
  * Build a collection URL with optional query string
105
- *
105
+ *
106
106
  * @param {Object} context - Request context
107
107
  * @param {Object} scope - Scope containing transport vars
108
108
  * @param {string} scopeName - Resource type name
109
109
  * @param {string} queryString - Optional query string (with leading ?)
110
110
  * @returns {string} Complete collection URL
111
- *
111
+ *
112
112
  * @example
113
113
  * buildCollectionUrl(context, scope, 'books', '?page[size]=10');
114
114
  * // Returns: "https://api.example.com/api/books?page[size]=10"
115
115
  */
116
- export function buildCollectionUrl(context, scope, scopeName, queryString = '') {
117
- const urlPrefix = getUrlPrefix(context, scope);
118
- return `${urlPrefix}/${scopeName}${queryString}`;
119
- }
116
+ export function buildCollectionUrl (context, scope, scopeName, queryString = '') {
117
+ const urlPrefix = getUrlPrefix(context, scope)
118
+ return `${urlPrefix}/${scopeName}${queryString}`
119
+ }