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,19 +1,19 @@
1
1
  /**
2
2
  * Busboy File Detector for HTTP Multipart Uploads
3
- *
3
+ *
4
4
  * This detector handles multipart/form-data uploads using the busboy library.
5
5
  * It's suitable for both HTTP and Express connectors.
6
- *
6
+ *
7
7
  * Features:
8
8
  * - Streaming parser (memory efficient)
9
9
  * - Configurable file size limits
10
10
  * - Automatic field parsing
11
11
  * - No temporary files by default (keeps in memory)
12
- *
12
+ *
13
13
  * Usage:
14
14
  * ```javascript
15
15
  * import { createBusboyDetector } from 'jsonrestapi/plugins/core/lib/busboy-detector.js';
16
- *
16
+ *
17
17
  * api.use(HttpPlugin, {
18
18
  * fileParser: 'busboy',
19
19
  * fileParserOptions: {
@@ -23,20 +23,20 @@
23
23
  * ```
24
24
  */
25
25
 
26
- import { requirePackage } from 'hooked-api';
27
- import { Readable } from 'stream';
26
+ import { requirePackage } from 'hooked-api'
27
+ import { Readable } from 'stream'
28
28
 
29
- let Busboy;
29
+ let Busboy
30
30
  try {
31
- Busboy = (await import('busboy')).default;
31
+ Busboy = (await import('busboy')).default
32
32
  } catch (e) {
33
- requirePackage('busboy', 'express/http-connector',
34
- 'Busboy is required for multipart/form-data file uploads. This is a peer dependency.');
33
+ requirePackage('busboy', 'express/http-connector',
34
+ 'Busboy is required for multipart/form-data file uploads. This is a peer dependency.')
35
35
  }
36
36
 
37
37
  /**
38
38
  * Creates a busboy-based file detector
39
- *
39
+ *
40
40
  * @param {Object} options - Busboy configuration options
41
41
  * @param {Object} options.limits - Size limits
42
42
  * @param {number} options.limits.fileSize - Max file size in bytes
@@ -44,132 +44,132 @@ try {
44
44
  * @param {number} options.limits.fields - Max number of fields
45
45
  * @returns {Object} Detector object with detect() and parse() methods
46
46
  */
47
- export function createBusboyDetector(options = {}) {
47
+ export function createBusboyDetector (options = {}) {
48
48
  return {
49
49
  name: 'busboy-multipart',
50
-
50
+
51
51
  /**
52
52
  * Check if this detector can handle the request
53
53
  * @param {Object} params - Request parameters
54
54
  * @returns {boolean} True if this is a multipart request
55
55
  */
56
56
  detect: (params) => {
57
- const req = params._httpReq || params._expressReq;
58
- if (!req || !req.headers) return false;
59
-
60
- const contentType = req.headers['content-type'] || '';
61
- return contentType.includes('multipart/form-data');
57
+ const req = params._httpReq || params._expressReq
58
+ if (!req || !req.headers) return false
59
+
60
+ const contentType = req.headers['content-type'] || ''
61
+ return contentType.includes('multipart/form-data')
62
62
  },
63
-
63
+
64
64
  /**
65
65
  * Parse multipart data from the request
66
66
  * @param {Object} params - Request parameters
67
67
  * @returns {Promise<{fields: Object, files: Object}>} Parsed data
68
68
  */
69
69
  parse: async (params) => {
70
- const req = params._httpReq || params._expressReq;
71
-
70
+ const req = params._httpReq || params._expressReq
71
+
72
72
  return new Promise((resolve, reject) => {
73
73
  const busboy = new Busboy({
74
74
  headers: req.headers,
75
75
  ...options
76
- });
77
-
78
- const fields = {};
79
- const files = {};
80
- const filePromises = [];
81
-
76
+ })
77
+
78
+ const fields = {}
79
+ const files = {}
80
+ const filePromises = []
81
+
82
82
  // Handle fields
83
83
  busboy.on('field', (fieldname, val) => {
84
84
  // Handle array notation (field[] or field[0])
85
- const arrayMatch = fieldname.match(/^(.+)\[\d*\]$/);
85
+ const arrayMatch = fieldname.match(/^(.+)\[\d*\]$/)
86
86
  if (arrayMatch) {
87
- const baseName = arrayMatch[1];
87
+ const baseName = arrayMatch[1]
88
88
  if (!fields[baseName]) {
89
- fields[baseName] = [];
89
+ fields[baseName] = []
90
90
  }
91
91
  if (Array.isArray(fields[baseName])) {
92
- fields[baseName].push(val);
92
+ fields[baseName].push(val)
93
93
  }
94
94
  } else {
95
- fields[fieldname] = val;
95
+ fields[fieldname] = val
96
96
  }
97
- });
98
-
97
+ })
98
+
99
99
  // Handle files
100
100
  busboy.on('file', (fieldname, file, filename, encoding, mimetype) => {
101
- const chunks = [];
102
- let size = 0;
103
-
101
+ const chunks = []
102
+ let size = 0
103
+
104
104
  const filePromise = new Promise((fileResolve, fileReject) => {
105
105
  file.on('data', (chunk) => {
106
- chunks.push(chunk);
107
- size += chunk.length;
108
- });
109
-
106
+ chunks.push(chunk)
107
+ size += chunk.length
108
+ })
109
+
110
110
  file.on('limit', () => {
111
- fileReject(new Error(`File size limit exceeded for field '${fieldname}'`));
112
- });
113
-
111
+ fileReject(new Error(`File size limit exceeded for field '${fieldname}'`))
112
+ })
113
+
114
114
  file.on('end', () => {
115
115
  files[fieldname] = {
116
- filename: filename,
117
- mimetype: mimetype,
118
- encoding: encoding,
119
- size: size,
116
+ filename,
117
+ mimetype,
118
+ encoding,
119
+ size,
120
120
  data: Buffer.concat(chunks)
121
- };
122
- fileResolve();
123
- });
124
-
125
- file.on('error', fileReject);
126
- });
127
-
128
- filePromises.push(filePromise);
129
- });
130
-
121
+ }
122
+ fileResolve()
123
+ })
124
+
125
+ file.on('error', fileReject)
126
+ })
127
+
128
+ filePromises.push(filePromise)
129
+ })
130
+
131
131
  // Handle completion
132
132
  busboy.on('finish', async () => {
133
133
  try {
134
134
  // Wait for all files to be fully read
135
- await Promise.all(filePromises);
136
- resolve({ fields, files });
135
+ await Promise.all(filePromises)
136
+ resolve({ fields, files })
137
137
  } catch (error) {
138
- reject(error);
138
+ reject(error)
139
139
  }
140
- });
141
-
140
+ })
141
+
142
142
  // Handle errors
143
143
  busboy.on('error', (error) => {
144
- reject(error);
145
- });
146
-
144
+ reject(error)
145
+ })
146
+
147
147
  // Handle limit errors
148
148
  busboy.on('partsLimit', () => {
149
- reject(new Error('Parts limit exceeded'));
150
- });
151
-
149
+ reject(new Error('Parts limit exceeded'))
150
+ })
151
+
152
152
  busboy.on('filesLimit', () => {
153
- reject(new Error('Files limit exceeded'));
154
- });
155
-
153
+ reject(new Error('Files limit exceeded'))
154
+ })
155
+
156
156
  busboy.on('fieldsLimit', () => {
157
- reject(new Error('Fields limit exceeded'));
158
- });
159
-
157
+ reject(new Error('Fields limit exceeded'))
158
+ })
159
+
160
160
  // Pipe request to busboy
161
161
  if (req.pipe) {
162
- req.pipe(busboy);
162
+ req.pipe(busboy)
163
163
  } else if (req.on) {
164
164
  // For already buffered requests
165
- const stream = new Readable();
166
- stream.push(req.body || req);
167
- stream.push(null);
168
- stream.pipe(busboy);
165
+ const stream = new Readable()
166
+ stream.push(req.body || req)
167
+ stream.push(null)
168
+ stream.pipe(busboy)
169
169
  } else {
170
- reject(new Error('Request object is not a stream'));
170
+ reject(new Error('Request object is not a stream'))
171
171
  }
172
- });
172
+ })
173
173
  }
174
- };
175
- }
174
+ }
175
+ }
@@ -1,20 +1,20 @@
1
1
  /**
2
2
  * Formidable File Detector for HTTP Multipart Uploads
3
- *
3
+ *
4
4
  * This detector handles multipart/form-data uploads using the formidable library.
5
5
  * Unlike busboy, formidable saves files to disk temporarily, which is better
6
6
  * for large files.
7
- *
7
+ *
8
8
  * Features:
9
9
  * - Disk-based storage (better for large files)
10
10
  * - Automatic temp file cleanup
11
11
  * - Progress tracking support
12
12
  * - Built-in file type detection
13
- *
13
+ *
14
14
  * Usage:
15
15
  * ```javascript
16
16
  * import { createFormidableDetector } from 'jsonrestapi/plugins/core/lib/formidable-detector.js';
17
- *
17
+ *
18
18
  * api.use(ExpressPlugin, {
19
19
  * fileParser: 'formidable',
20
20
  * fileParserOptions: {
@@ -26,21 +26,21 @@
26
26
  * ```
27
27
  */
28
28
 
29
- import { requirePackage } from 'hooked-api';
30
- import { promises as fs } from 'fs';
31
- import path from 'path';
29
+ import { requirePackage } from 'hooked-api'
30
+ import { promises as fs } from 'fs'
31
+ import path from 'path'
32
32
 
33
- let formidable;
33
+ let formidable
34
34
  try {
35
- formidable = (await import('formidable')).default;
35
+ formidable = (await import('formidable')).default
36
36
  } catch (e) {
37
- requirePackage('formidable', 'express/http-connector',
38
- 'Formidable is required for multipart/form-data file uploads with disk storage. This is a peer dependency.');
37
+ requirePackage('formidable', 'express/http-connector',
38
+ 'Formidable is required for multipart/form-data file uploads with disk storage. This is a peer dependency.')
39
39
  }
40
40
 
41
41
  /**
42
42
  * Creates a formidable-based file detector
43
- *
43
+ *
44
44
  * @param {Object} options - Formidable configuration options
45
45
  * @param {string} options.uploadDir - Directory for temporary files
46
46
  * @param {boolean} options.keepExtensions - Keep file extensions
@@ -48,91 +48,91 @@ try {
48
48
  * @param {boolean} options.multiples - Allow multiple files per field
49
49
  * @returns {Object} Detector object with detect() and parse() methods
50
50
  */
51
- export function createFormidableDetector(options = {}) {
51
+ export function createFormidableDetector (options = {}) {
52
52
  // Set default upload directory
53
- const uploadDir = options.uploadDir || path.join(process.cwd(), 'uploads', 'temp');
54
-
53
+ const uploadDir = options.uploadDir || path.join(process.cwd(), 'uploads', 'temp')
54
+
55
55
  return {
56
56
  name: 'formidable-multipart',
57
-
57
+
58
58
  /**
59
59
  * Check if this detector can handle the request
60
60
  * @param {Object} params - Request parameters
61
61
  * @returns {boolean} True if this is a multipart request
62
62
  */
63
63
  detect: (params) => {
64
- const req = params._httpReq || params._expressReq;
65
- if (!req || !req.headers) return false;
66
-
67
- const contentType = req.headers['content-type'] || '';
68
- return contentType.includes('multipart/form-data');
64
+ const req = params._httpReq || params._expressReq
65
+ if (!req || !req.headers) return false
66
+
67
+ const contentType = req.headers['content-type'] || ''
68
+ return contentType.includes('multipart/form-data')
69
69
  },
70
-
70
+
71
71
  /**
72
72
  * Parse multipart data from the request
73
73
  * @param {Object} params - Request parameters
74
74
  * @returns {Promise<{fields: Object, files: Object}>} Parsed data
75
75
  */
76
76
  parse: async (params) => {
77
- const req = params._httpReq || params._expressReq;
78
-
77
+ const req = params._httpReq || params._expressReq
78
+
79
79
  // Ensure upload directory exists
80
- await fs.mkdir(uploadDir, { recursive: true });
81
-
80
+ await fs.mkdir(uploadDir, { recursive: true })
81
+
82
82
  // Create form parser
83
83
  const form = formidable({
84
84
  uploadDir,
85
85
  keepExtensions: true,
86
86
  ...options
87
- });
88
-
87
+ })
88
+
89
89
  // Parse the request
90
- const [fields, fileUploads] = await form.parse(req);
91
-
90
+ const [fields, fileUploads] = await form.parse(req)
91
+
92
92
  // Normalize formidable's file format to our standard format
93
- const files = {};
94
-
93
+ const files = {}
94
+
95
95
  for (const [fieldname, uploadedFiles] of Object.entries(fileUploads)) {
96
96
  // Formidable returns arrays for multiple files
97
- const fileArray = Array.isArray(uploadedFiles) ? uploadedFiles : [uploadedFiles];
98
-
97
+ const fileArray = Array.isArray(uploadedFiles) ? uploadedFiles : [uploadedFiles]
98
+
99
99
  // For now, just take the first file (TODO: handle multiple files per field)
100
- const file = fileArray[0];
101
-
100
+ const file = fileArray[0]
101
+
102
102
  if (file) {
103
103
  // Read file data into memory (for small files)
104
104
  // For large files, you might want to keep them on disk
105
- const data = await fs.readFile(file.filepath);
106
-
105
+ const data = await fs.readFile(file.filepath)
106
+
107
107
  files[fieldname] = {
108
108
  filename: file.originalFilename || 'unknown',
109
109
  mimetype: file.mimetype || 'application/octet-stream',
110
110
  size: file.size,
111
- data: data,
111
+ data,
112
112
  filepath: file.filepath, // Keep for reference
113
-
113
+
114
114
  // Cleanup function to remove temp file
115
115
  cleanup: async () => {
116
116
  try {
117
- await fs.unlink(file.filepath);
117
+ await fs.unlink(file.filepath)
118
118
  } catch (error) {
119
119
  // File might already be deleted, ignore
120
120
  if (error.code !== 'ENOENT') {
121
- throw error;
121
+ throw error
122
122
  }
123
123
  }
124
124
  }
125
- };
125
+ }
126
126
  }
127
127
  }
128
-
128
+
129
129
  // Normalize fields (formidable returns arrays for repeated fields)
130
- const normalizedFields = {};
130
+ const normalizedFields = {}
131
131
  for (const [key, value] of Object.entries(fields)) {
132
- normalizedFields[key] = Array.isArray(value) && value.length === 1 ? value[0] : value;
132
+ normalizedFields[key] = Array.isArray(value) && value.length === 1 ? value[0] : value
133
133
  }
134
-
135
- return { fields: normalizedFields, files };
134
+
135
+ return { fields: normalizedFields, files }
136
136
  }
137
- };
138
- }
137
+ }
138
+ }