json-rest-api 1.0.13 → 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 +13 -0
  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 +9 -5
  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,13 +1,13 @@
1
- import { buildIncludedResources } from './knex-relationship-includes.js';
1
+ import { buildIncludedResources } from './knex-relationship-includes.js'
2
2
 
3
3
  /**
4
4
  * Processes the ?include= parameter to load related resources efficiently
5
- *
5
+ *
6
6
  * @param {Object} scope - The scope object for the primary resource
7
7
  * @param {Array<Object>} records - The primary records to load includes for
8
8
  * @param {Object} deps - Dependencies object containing log, scopes, knex, and context
9
9
  * @returns {Promise<Array<Object>>} Array of included resources in JSON:API format
10
- *
10
+ *
11
11
  * @example
12
12
  * // Input: Articles with author relationship
13
13
  * const articles = [
@@ -15,37 +15,37 @@ import { buildIncludedResources } from './knex-relationship-includes.js';
15
15
  * { id: 2, title: 'Second Article', author_id: 11 },
16
16
  * { id: 3, title: 'Third Article', author_id: 10 } // Same author as article 1
17
17
  * ];
18
- *
18
+ *
19
19
  * const deps = {
20
- * context: {
20
+ * context: {
21
21
  * scopeName: 'articles',
22
22
  * queryParams: { include: ['author'] },
23
23
  * schemaInfo: { idProperty: 'id' }
24
24
  * }
25
25
  * };
26
- *
26
+ *
27
27
  * const included = await processIncludes(scope, articles, deps);
28
- *
28
+ *
29
29
  * // Output: Deduplicated authors (only 2, not 3)
30
30
  * // [
31
31
  * // { type: 'users', id: '10', attributes: { name: 'Alice', email: 'alice@example.com' } },
32
32
  * // { type: 'users', id: '11', attributes: { name: 'Bob', email: 'bob@example.com' } }
33
33
  * // ]
34
- *
34
+ *
35
35
  * @example
36
36
  * // Input: Nested includes with dot notation
37
37
  * const articles = [{ id: 1, title: 'Article', author_id: 10 }];
38
- *
38
+ *
39
39
  * const deps = {
40
40
  * context: {
41
- * queryParams: {
41
+ * queryParams: {
42
42
  * include: ['author', 'comments.user'] // Load author AND comment users
43
43
  * }
44
44
  * }
45
45
  * };
46
- *
46
+ *
47
47
  * const included = await processIncludes(scope, articles, deps);
48
- *
48
+ *
49
49
  * // Output: All related resources in flat array
50
50
  * // [
51
51
  * // { type: 'users', id: '10', attributes: { name: 'Author' } },
@@ -54,41 +54,41 @@ import { buildIncludedResources } from './knex-relationship-includes.js';
54
54
  * // { type: 'users', id: '20', attributes: { name: 'Commenter1' } },
55
55
  * // { type: 'users', id: '21', attributes: { name: 'Commenter2' } }
56
56
  * // ]
57
- *
57
+ *
58
58
  * @example
59
59
  * // Input: With sparse fieldsets limiting included data
60
60
  * const deps = {
61
61
  * context: {
62
- * queryParams: {
62
+ * queryParams: {
63
63
  * include: ['author'],
64
- * fields: {
64
+ * fields: {
65
65
  * users: 'name' // Only include name field for users
66
66
  * }
67
67
  * }
68
68
  * }
69
69
  * };
70
- *
70
+ *
71
71
  * const included = await processIncludes(scope, articles, deps);
72
- *
72
+ *
73
73
  * // Output: Users with only requested fields
74
74
  * // [
75
75
  * // { type: 'users', id: '10', attributes: { name: 'Alice' } }
76
76
  * // // email field excluded due to sparse fieldset
77
77
  * // ]
78
- *
78
+ *
79
79
  * @description
80
80
  * Used by:
81
81
  * - rest-api-knex-plugin's dataGet method when includes are requested
82
82
  * - rest-api-knex-plugin's dataQuery method for collection includes
83
83
  * - Called after primary records are fetched but before response assembly
84
- *
84
+ *
85
85
  * Purpose:
86
86
  * - Implements JSON:API compound documents with primary data and includes
87
87
  * - Prevents N+1 queries by batch loading all related resources
88
88
  * - Handles complex nested includes like 'comments.author.company'
89
89
  * - Automatically deduplicates included resources
90
90
  * - Respects sparse fieldsets on included resources
91
- *
91
+ *
92
92
  * Data flow:
93
93
  * 1. Receives primary records and include directives
94
94
  * 2. Parses include parameter (comma-separated, possibly nested with dots)
@@ -98,27 +98,27 @@ import { buildIncludedResources } from './knex-relationship-includes.js';
98
98
  */
99
99
  export const processIncludes = async (scope, records, deps) => {
100
100
  try {
101
- const { log, scopes, knex, context, api } = deps;
102
-
103
- const scopeName = context.scopeName;
104
-
101
+ const { log, scopes, knex, context, api } = deps
102
+
103
+ const scopeName = context.scopeName
104
+
105
105
  if (!scopeName) {
106
- log.error('[PROCESS-INCLUDES] scopeName is undefined in context!', {
106
+ log.error('[PROCESS-INCLUDES] scopeName is undefined in context!', {
107
107
  contextKeys: Object.keys(context || {})
108
- });
109
- throw new Error('scopeName is undefined in context');
108
+ })
109
+ throw new Error('scopeName is undefined in context')
110
110
  }
111
-
112
- const db = context.transaction || knex;
113
- const queryParams = context.queryParams;
114
- const idProperty = context.schemaInfo.idProperty;
115
-
111
+
112
+ const db = context.transaction || knex
113
+ const queryParams = context.queryParams
114
+ const idProperty = context.schemaInfo.idProperty
115
+
116
116
  if (!queryParams.include) {
117
- return [];
117
+ return []
118
118
  }
119
-
120
- log.debug('[PROCESS-INCLUDES] Processing includes:', queryParams.include);
121
-
119
+
120
+ log.debug('[PROCESS-INCLUDES] Processing includes:', queryParams.include)
121
+
122
122
  const includeResult = await buildIncludedResources(
123
123
  {
124
124
  records,
@@ -135,17 +135,17 @@ export const processIncludes = async (scope, records, deps) => {
135
135
  capabilities: api?.knex?.capabilities
136
136
  }
137
137
  }
138
- );
139
-
138
+ )
139
+
140
140
  log.debug('[PROCESS-INCLUDES] Include result:', {
141
141
  includedCount: includeResult.included.length,
142
142
  types: [...new Set(includeResult.included.map(r => r.type))]
143
- });
144
-
145
- return includeResult.included;
143
+ })
144
+
145
+ return includeResult.included
146
146
  } catch (error) {
147
- const { log, context } = deps || {};
148
-
147
+ const { log, context } = deps || {}
148
+
149
149
  if (log) {
150
150
  log.error('[PROCESS-INCLUDES] Error processing includes:', {
151
151
  scopeName: context?.scopeName,
@@ -153,15 +153,15 @@ export const processIncludes = async (scope, records, deps) => {
153
153
  includeParam: context?.queryParams?.include,
154
154
  error: error.message,
155
155
  stack: error.stack
156
- });
156
+ })
157
157
  } else {
158
- console.error('[PROCESS-INCLUDES] Error processing includes:', error);
158
+ console.error('[PROCESS-INCLUDES] Error processing includes:', error)
159
159
  }
160
-
160
+
161
161
  const enhancedError = new Error(
162
162
  `Failed to process includes${context?.scopeName ? ` for scope '${context.scopeName}'` : ''}: ${error.message}`
163
- );
164
- enhancedError.originalError = error;
165
- throw enhancedError;
163
+ )
164
+ enhancedError.originalError = error
165
+ throw enhancedError
166
166
  }
167
- };
167
+ }
@@ -1,53 +1,53 @@
1
1
  /**
2
2
  * Builds the SELECT clause for a Knex query, handling field selection and table prefixing
3
- *
3
+ *
4
4
  * @param {Object} query - The Knex query builder instance
5
5
  * @param {string} tableName - The table name
6
6
  * @param {Array<string>|string} fieldsToSelect - Fields to select or '*'
7
7
  * @param {boolean} useTablePrefix - Whether to prefix fields with table name
8
8
  * @returns {Object} The modified query builder
9
- *
9
+ *
10
10
  * @example
11
11
  * // Input: Basic query without specific fields
12
12
  * const query = knex('articles');
13
13
  * buildQuerySelection(query, 'articles', '*', false);
14
- *
14
+ *
15
15
  * // Effect on query: No modification, Knex defaults to SELECT *
16
16
  * // SQL generated: SELECT * FROM articles
17
- *
17
+ *
18
18
  * @example
19
19
  * // Input: Query with sparse fieldset
20
20
  * const query = knex('articles');
21
21
  * const fields = ['id', 'title', 'author_id'];
22
22
  * buildQuerySelection(query, 'articles', fields, false);
23
- *
23
+ *
24
24
  * // Effect on query: Adds select() with specific fields
25
25
  * // SQL generated: SELECT id, title, author_id FROM articles
26
26
  * // Response will only include these 3 fields, reducing payload size
27
- *
27
+ *
28
28
  * @example
29
29
  * // Input: Query with joins needing table prefixes
30
30
  * const query = knex('articles')
31
31
  * .join('users', 'articles.author_id', 'users.id');
32
32
  * const fields = ['id', 'title', 'created_at as published'];
33
33
  * buildQuerySelection(query, 'articles', fields, true);
34
- *
34
+ *
35
35
  * // Effect on query: Prefixes all fields with table name
36
36
  * // SQL generated: SELECT articles.id, articles.title, articles.created_at as published
37
37
  * // Prevents "column 'id' is ambiguous" errors since both tables have id
38
- *
38
+ *
39
39
  * @description
40
40
  * Used by:
41
41
  * - knex-query-helpers.js calls this when building queries with sparse fieldsets
42
42
  * - rest-api-knex-plugin uses this in dataQuery to optimize SELECT clauses
43
43
  * - Called whenever fields parameter limits which attributes to return
44
- *
44
+ *
45
45
  * Purpose:
46
46
  * - Implements JSON:API sparse fieldsets by selecting only requested fields
47
47
  * - Prevents ambiguous column errors in queries with joins
48
48
  * - Reduces database transfer by not selecting unnecessary columns
49
49
  * - Handles field aliases (e.g., "created_at as published")
50
- *
50
+ *
51
51
  * Data flow:
52
52
  * 1. Query parser extracts fields parameter (e.g., fields[articles]=title,author)
53
53
  * 2. This function adds appropriate SELECT clause to query
@@ -60,50 +60,50 @@ export const buildQuerySelection = (
60
60
  tableName,
61
61
  fieldsToSelect,
62
62
  useTablePrefix = false,
63
- options = {},
63
+ options = {}
64
64
  ) => {
65
- const translateColumn = options.translateColumn;
65
+ const translateColumn = options.translateColumn
66
66
 
67
67
  const applyTranslation = (field, aliasForField) => {
68
68
  if (translateColumn) {
69
- const translated = translateColumn(field, aliasForField);
69
+ const translated = translateColumn(field, aliasForField)
70
70
  if (translated) {
71
- return translated;
71
+ return translated
72
72
  }
73
73
  }
74
74
 
75
75
  if (aliasForField) {
76
- return `${aliasForField}.${field}`;
76
+ return `${aliasForField}.${field}`
77
77
  }
78
- return field;
79
- };
78
+ return field
79
+ }
80
80
 
81
- const targetAlias = useTablePrefix ? tableName : null;
81
+ const targetAlias = useTablePrefix ? tableName : null
82
82
 
83
83
  if (fieldsToSelect === '*') {
84
- const translated = translateColumn ? translateColumn('*', targetAlias) : null;
84
+ const translated = translateColumn ? translateColumn('*', targetAlias) : null
85
85
  if (translated) {
86
- return query.select(translated);
86
+ return query.select(translated)
87
87
  }
88
- return useTablePrefix ? query.select(`${tableName}.*`) : query;
88
+ return useTablePrefix ? query.select(`${tableName}.*`) : query
89
89
  }
90
90
 
91
91
  const fieldsArray = Array.isArray(fieldsToSelect)
92
92
  ? fieldsToSelect
93
- : Array.from(fieldsToSelect || []);
93
+ : Array.from(fieldsToSelect || [])
94
94
 
95
95
  const translatedFields = fieldsArray.map((field) => {
96
- if (typeof field !== 'string') return field;
96
+ if (typeof field !== 'string') return field
97
97
 
98
- const aliasMatch = field.match(/\s+as\s+/i);
98
+ const aliasMatch = field.match(/\s+as\s+/i)
99
99
  if (aliasMatch) {
100
- const [source, alias] = field.split(/\s+as\s+/i);
101
- const translatedSource = applyTranslation(source.trim(), targetAlias);
102
- return `${translatedSource} as ${alias.trim()}`;
100
+ const [source, alias] = field.split(/\s+as\s+/i)
101
+ const translatedSource = applyTranslation(source.trim(), targetAlias)
102
+ return `${translatedSource} as ${alias.trim()}`
103
103
  }
104
104
 
105
- return applyTranslation(field.trim(), targetAlias);
106
- });
105
+ return applyTranslation(field.trim(), targetAlias)
106
+ })
107
107
 
108
- return query.select(translatedFields);
109
- };
108
+ return query.select(translatedFields)
109
+ }