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,94 +1,94 @@
1
1
  /**
2
2
  * Local File Storage Adapter
3
- *
3
+ *
4
4
  * Production-ready local storage with secure filename handling
5
5
  */
6
6
 
7
- import { promises as fs } from 'fs';
8
- import path from 'path';
9
- import crypto from 'crypto';
7
+ import { promises as fs } from 'fs'
8
+ import path from 'path'
9
+ import crypto from 'crypto'
10
10
 
11
11
  export class LocalStorage {
12
- constructor(options = {}) {
13
- this.directory = path.resolve(options.directory || './uploads');
14
- this.fileBaseUrl = options.fileBaseUrl || '/uploads';
15
- this.nameStrategy = options.nameStrategy || 'hash'; // 'hash', 'timestamp', 'original', 'custom'
16
- this.nameGenerator = options.nameGenerator; // Custom function
17
- this.preserveExtension = options.preserveExtension !== false;
18
- this.allowedExtensions = options.allowedExtensions; // Whitelist
19
- this.maxFilenameLength = options.maxFilenameLength || 255;
12
+ constructor (options = {}) {
13
+ this.directory = path.resolve(options.directory || './uploads')
14
+ this.fileBaseUrl = options.fileBaseUrl || '/uploads'
15
+ this.nameStrategy = options.nameStrategy || 'hash' // 'hash', 'timestamp', 'original', 'custom'
16
+ this.nameGenerator = options.nameGenerator // Custom function
17
+ this.preserveExtension = options.preserveExtension !== false
18
+ this.allowedExtensions = options.allowedExtensions // Whitelist
19
+ this.maxFilenameLength = options.maxFilenameLength || 255
20
20
  }
21
-
21
+
22
22
  /**
23
23
  * Generate a safe filename
24
24
  */
25
- async generateFilename(file) {
26
- let basename, extension;
27
-
25
+ async generateFilename (file) {
26
+ let basename, extension
27
+
28
28
  // Extract and validate extension
29
- const originalExt = path.extname(file.filename).toLowerCase();
29
+ const originalExt = path.extname(file.filename).toLowerCase()
30
30
  if (this.preserveExtension && originalExt) {
31
31
  // Validate extension against whitelist if provided
32
32
  if (this.allowedExtensions && !this.allowedExtensions.includes(originalExt)) {
33
- throw new Error(`File extension '${originalExt}' is not allowed`);
33
+ throw new Error(`File extension '${originalExt}' is not allowed`)
34
34
  }
35
- extension = originalExt;
35
+ extension = originalExt
36
36
  } else {
37
37
  // Determine extension from MIME type for safety
38
- extension = this.getExtensionFromMimeType(file.mimetype);
38
+ extension = this.getExtensionFromMimeType(file.mimetype)
39
39
  }
40
-
40
+
41
41
  // Generate basename based on strategy
42
42
  switch (this.nameStrategy) {
43
43
  case 'hash':
44
44
  // Cryptographically secure random name
45
- basename = crypto.randomBytes(16).toString('hex');
46
- break;
47
-
45
+ basename = crypto.randomBytes(16).toString('hex')
46
+ break
47
+
48
48
  case 'timestamp':
49
49
  // Timestamp + random suffix to prevent collisions
50
- basename = `${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
51
- break;
52
-
50
+ basename = `${Date.now()}_${crypto.randomBytes(4).toString('hex')}`
51
+ break
52
+
53
53
  case 'original':
54
54
  // Sanitized original filename
55
55
  basename = this.sanitizeFilename(
56
56
  path.basename(file.filename, originalExt)
57
- );
58
- break;
59
-
57
+ )
58
+ break
59
+
60
60
  case 'custom':
61
61
  if (!this.nameGenerator) {
62
- throw new Error('nameGenerator function required for custom strategy');
62
+ throw new Error('nameGenerator function required for custom strategy')
63
63
  }
64
- basename = await this.nameGenerator(file);
65
- break;
66
-
64
+ basename = await this.nameGenerator(file)
65
+ break
66
+
67
67
  default:
68
- throw new Error(`Unknown name strategy: ${this.nameStrategy}`);
68
+ throw new Error(`Unknown name strategy: ${this.nameStrategy}`)
69
69
  }
70
-
70
+
71
71
  // Combine basename and extension
72
- let filename = extension ? `${basename}${extension}` : basename;
73
-
72
+ let filename = extension ? `${basename}${extension}` : basename
73
+
74
74
  // Ensure filename doesn't exceed max length
75
75
  if (filename.length > this.maxFilenameLength) {
76
76
  // Truncate basename to fit
77
- const maxBasename = this.maxFilenameLength - (extension?.length || 0);
78
- basename = basename.substring(0, maxBasename);
79
- filename = extension ? `${basename}${extension}` : basename;
77
+ const maxBasename = this.maxFilenameLength - (extension?.length || 0)
78
+ basename = basename.substring(0, maxBasename)
79
+ filename = extension ? `${basename}${extension}` : basename
80
80
  }
81
-
81
+
82
82
  // Handle duplicates
83
- filename = await this.ensureUnique(filename);
84
-
85
- return filename;
83
+ filename = await this.ensureUnique(filename)
84
+
85
+ return filename
86
86
  }
87
-
87
+
88
88
  /**
89
89
  * Sanitize filename for safe storage
90
90
  */
91
- sanitizeFilename(filename) {
91
+ sanitizeFilename (filename) {
92
92
  return filename
93
93
  // Remove path traversal attempts
94
94
  .replace(/\.\./g, '')
@@ -98,53 +98,53 @@ export class LocalStorage {
98
98
  // Remove leading/trailing dots and spaces
99
99
  .replace(/^[\s.]+|[\s.]+$/g, '')
100
100
  // Collapse multiple underscores
101
- .replace(/_+/g, '_')
101
+ .replace(/_+/g, '_') ||
102
102
  // Default if empty
103
- || 'unnamed';
103
+ 'unnamed'
104
104
  }
105
-
105
+
106
106
  /**
107
107
  * Ensure filename is unique
108
108
  */
109
- async ensureUnique(filename) {
110
- const filepath = path.join(this.directory, filename);
111
-
109
+ async ensureUnique (filename) {
110
+ const filepath = path.join(this.directory, filename)
111
+
112
112
  try {
113
- await fs.access(filepath);
113
+ await fs.access(filepath)
114
114
  // File exists, generate unique name
115
- const ext = path.extname(filename);
116
- const base = path.basename(filename, ext);
117
- let counter = 1;
118
- let newFilename;
119
-
115
+ const ext = path.extname(filename)
116
+ const base = path.basename(filename, ext)
117
+ let counter = 1
118
+ let newFilename
119
+
120
120
  do {
121
- newFilename = `${base}_${counter}${ext}`;
122
- counter++;
123
- } while (await this.fileExists(path.join(this.directory, newFilename)));
124
-
125
- return newFilename;
121
+ newFilename = `${base}_${counter}${ext}`
122
+ counter++
123
+ } while (await this.fileExists(path.join(this.directory, newFilename)))
124
+
125
+ return newFilename
126
126
  } catch (error) {
127
127
  // File doesn't exist, name is unique
128
- return filename;
128
+ return filename
129
129
  }
130
130
  }
131
-
131
+
132
132
  /**
133
133
  * Check if file exists
134
134
  */
135
- async fileExists(filepath) {
135
+ async fileExists (filepath) {
136
136
  try {
137
- await fs.access(filepath);
138
- return true;
137
+ await fs.access(filepath)
138
+ return true
139
139
  } catch {
140
- return false;
140
+ return false
141
141
  }
142
142
  }
143
-
143
+
144
144
  /**
145
145
  * Get safe extension from MIME type
146
146
  */
147
- getExtensionFromMimeType(mimetype) {
147
+ getExtensionFromMimeType (mimetype) {
148
148
  // Safe mappings only
149
149
  const mimeToExt = {
150
150
  'image/jpeg': '.jpg',
@@ -157,62 +157,62 @@ export class LocalStorage {
157
157
  'video/mp4': '.mp4',
158
158
  'audio/mpeg': '.mp3',
159
159
  'application/zip': '.zip'
160
- };
161
-
162
- return mimeToExt[mimetype] || '.bin';
160
+ }
161
+
162
+ return mimeToExt[mimetype] || '.bin'
163
163
  }
164
-
164
+
165
165
  /**
166
166
  * Upload file with secure filename
167
167
  */
168
- async upload(file) {
168
+ async upload (file) {
169
169
  // Ensure directory exists
170
- await fs.mkdir(this.directory, { recursive: true });
171
-
170
+ await fs.mkdir(this.directory, { recursive: true })
171
+
172
172
  // Generate secure filename
173
- const filename = await this.generateFilename(file);
174
- const filepath = path.join(this.directory, filename);
175
-
173
+ const filename = await this.generateFilename(file)
174
+ const filepath = path.join(this.directory, filename)
175
+
176
176
  // Ensure we're not writing outside our directory (defense in depth)
177
- const resolvedPath = path.resolve(filepath);
178
- const resolvedDir = path.resolve(this.directory);
177
+ const resolvedPath = path.resolve(filepath)
178
+ const resolvedDir = path.resolve(this.directory)
179
179
  if (!resolvedPath.startsWith(resolvedDir)) {
180
- throw new Error('Invalid file path');
180
+ throw new Error('Invalid file path')
181
181
  }
182
-
182
+
183
183
  // Write file
184
184
  if (file.data) {
185
- await fs.writeFile(filepath, file.data);
185
+ await fs.writeFile(filepath, file.data)
186
186
  } else if (file.filepath) {
187
- await fs.rename(file.filepath, filepath);
187
+ await fs.rename(file.filepath, filepath)
188
188
  } else {
189
- throw new Error('File has no data or filepath');
189
+ throw new Error('File has no data or filepath')
190
190
  }
191
-
191
+
192
192
  // Return public URL
193
- return `${this.fileBaseUrl}/${filename}`;
193
+ return `${this.fileBaseUrl}/${filename}`
194
194
  }
195
-
195
+
196
196
  /**
197
197
  * Delete a file
198
198
  */
199
- async delete(url) {
200
- const filename = path.basename(url);
201
- const filepath = path.join(this.directory, filename);
202
-
199
+ async delete (url) {
200
+ const filename = path.basename(url)
201
+ const filepath = path.join(this.directory, filename)
202
+
203
203
  // Security check
204
- const resolvedPath = path.resolve(filepath);
205
- const resolvedDir = path.resolve(this.directory);
204
+ const resolvedPath = path.resolve(filepath)
205
+ const resolvedDir = path.resolve(this.directory)
206
206
  if (!resolvedPath.startsWith(resolvedDir)) {
207
- throw new Error('Invalid file path');
207
+ throw new Error('Invalid file path')
208
208
  }
209
-
209
+
210
210
  try {
211
- await fs.unlink(filepath);
211
+ await fs.unlink(filepath)
212
212
  } catch (error) {
213
213
  if (error.code !== 'ENOENT') {
214
- throw error;
214
+ throw error
215
215
  }
216
216
  }
217
217
  }
218
- }
218
+ }
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * S3 Storage Adapter
3
- *
3
+ *
4
4
  * Stores uploaded files in Amazon S3 or S3-compatible storage.
5
- *
5
+ *
6
6
  * Usage:
7
7
  * ```javascript
8
8
  * import { S3Storage } from 'jsonrestapi/lib/storage/s3-storage.js';
9
- *
9
+ *
10
10
  * const storage = new S3Storage({
11
11
  * bucket: 'my-uploads',
12
12
  * region: 'us-east-1',
@@ -14,63 +14,63 @@
14
14
  * secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
15
15
  * prefix: 'uploads/' // optional path prefix
16
16
  * });
17
- *
17
+ *
18
18
  * const schema = {
19
19
  * image: { type: 'file', storage }
20
20
  * };
21
21
  * ```
22
22
  */
23
23
 
24
- import crypto from 'crypto';
25
- import path from 'path';
24
+ import crypto from 'crypto'
25
+ import path from 'path'
26
26
 
27
27
  export class S3Storage {
28
- constructor(options = {}) {
29
- this.bucket = options.bucket;
30
- this.region = options.region || 'us-east-1';
31
- this.prefix = options.prefix || '';
32
- this.acl = options.acl || 'public-read';
33
-
28
+ constructor (options = {}) {
29
+ this.bucket = options.bucket
30
+ this.region = options.region || 'us-east-1'
31
+ this.prefix = options.prefix || ''
32
+ this.acl = options.acl || 'public-read'
33
+
34
34
  if (!this.bucket) {
35
- throw new Error('S3Storage requires a bucket name');
35
+ throw new Error('S3Storage requires a bucket name')
36
36
  }
37
-
37
+
38
38
  // Note: In a real implementation, you would initialize the AWS SDK here
39
39
  // For this example, we'll just mock the behavior
40
- this.mockMode = options.mockMode !== false; // Default to mock mode
41
-
40
+ this.mockMode = options.mockMode !== false // Default to mock mode
41
+
42
42
  if (!this.mockMode) {
43
43
  // Real S3 initialization would go here
44
44
  // const { S3Client } = require('@aws-sdk/client-s3');
45
45
  // this.s3 = new S3Client({ region: this.region });
46
46
  }
47
47
  }
48
-
48
+
49
49
  /**
50
50
  * Upload a file to S3
51
51
  * @param {Object} file - File object from detector
52
52
  * @returns {Promise<string>} URL of the uploaded file
53
53
  */
54
- async upload(file) {
54
+ async upload (file) {
55
55
  // Generate S3 key
56
- const ext = path.extname(file.filename);
57
- const hash = crypto.randomBytes(16).toString('hex');
58
- const key = `${this.prefix}${hash}${ext}`;
59
-
56
+ const ext = path.extname(file.filename)
57
+ const hash = crypto.randomBytes(16).toString('hex')
58
+ const key = `${this.prefix}${hash}${ext}`
59
+
60
60
  if (this.mockMode) {
61
61
  // Mock mode - just return a fake URL
62
- const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`;
63
-
62
+ const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`
63
+
64
64
  // Simulate upload delay
65
- await new Promise(resolve => setTimeout(resolve, 100));
66
-
67
- return url;
65
+ await new Promise(resolve => setTimeout(resolve, 100))
66
+
67
+ return url
68
68
  }
69
-
69
+
70
70
  // Real S3 upload would go here
71
71
  /*
72
72
  const { PutObjectCommand } = require('@aws-sdk/client-s3');
73
-
73
+
74
74
  const command = new PutObjectCommand({
75
75
  Bucket: this.bucket,
76
76
  Key: key,
@@ -81,9 +81,9 @@ export class S3Storage {
81
81
  originalName: file.filename
82
82
  }
83
83
  });
84
-
84
+
85
85
  await this.s3.send(command);
86
-
86
+
87
87
  // Return public URL
88
88
  if (this.acl === 'public-read') {
89
89
  return `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`;
@@ -93,33 +93,32 @@ export class S3Storage {
93
93
  }
94
94
  */
95
95
  }
96
-
96
+
97
97
  /**
98
98
  * Delete a file from S3
99
99
  * @param {string} url - URL returned by upload()
100
100
  * @returns {Promise<void>}
101
101
  */
102
- async delete(url) {
102
+ async delete (url) {
103
103
  // Extract key from URL
104
- const urlObj = new URL(url);
105
- const key = urlObj.pathname.substring(1); // Remove leading /
106
-
104
+ const urlObj = new URL(url)
105
+ const key = urlObj.pathname.substring(1) // Remove leading /
106
+
107
107
  if (this.mockMode) {
108
108
  // Mock mode - just simulate deletion
109
- await new Promise(resolve => setTimeout(resolve, 50));
110
- return;
109
+ await new Promise(resolve => setTimeout(resolve, 50))
111
110
  }
112
-
111
+
113
112
  // Real S3 delete would go here
114
113
  /*
115
114
  const { DeleteObjectCommand } = require('@aws-sdk/client-s3');
116
-
115
+
117
116
  const command = new DeleteObjectCommand({
118
117
  Bucket: this.bucket,
119
118
  Key: key
120
119
  });
121
-
120
+
122
121
  await this.s3.send(command);
123
122
  */
124
123
  }
125
- }
124
+ }
package/quickTest.js CHANGED
@@ -1,11 +1,11 @@
1
1
  //
2
2
  // index.js
3
3
  //
4
- import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from './index.js'; // Added: ExpressPlugin
5
- import { Api } from 'hooked-api';
6
- import knexLib from 'knex';
7
- import util from 'util';
8
- import express from 'express'; // Added: Express
4
+ import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from './index.js' // Added: ExpressPlugin
5
+ import { Api } from 'hooked-api'
6
+ import knexLib from 'knex'
7
+ import util from 'util'
8
+ import express from 'express' // Added: Express
9
9
 
10
10
  // Utility used throughout this guide
11
11
  const inspect = (obj) => util.inspect(obj, { depth: 8 })
@@ -17,15 +17,15 @@ const knex = knexLib({
17
17
  filename: ':memory:'
18
18
  },
19
19
  useNullAsDefault: true
20
- });
20
+ })
21
21
 
22
22
  // Create API instance
23
- const api = new Api({ name: 'book-catalog-api', logLevel: 'trace' });
23
+ const api = new Api({ name: 'book-catalog-api', logLevel: 'trace' })
24
24
 
25
25
  // Install plugins
26
- await api.use(RestApiPlugin, {returnBasePath: '/api' });
27
- await api.use(RestApiKnexPlugin, { knex });
28
- await api.use(ExpressPlugin, { mountPath: '/api' }); // Added: Express Plugin
26
+ await api.use(RestApiPlugin, { returnBasePath: '/api' })
27
+ await api.use(RestApiKnexPlugin, { knex })
28
+ await api.use(ExpressPlugin, { mountPath: '/api' }) // Added: Express Plugin
29
29
 
30
30
  // Define publishers resource
31
31
  await api.addResource('publishers', {
@@ -39,8 +39,8 @@ await api.addResource('publishers', {
39
39
  searchSchema: { // Adding search schema for publishers
40
40
  name: { type: 'string', filterOperator: 'like' }
41
41
  }
42
- });
43
- await api.resources.publishers.createKnexTable();
42
+ })
43
+ await api.resources.publishers.createKnexTable()
44
44
 
45
45
  // Define authors resource, which belongs to a publisher
46
46
  await api.addResource('authors', {
@@ -55,21 +55,21 @@ await api.addResource('authors', {
55
55
  publisher: { type: 'id', actualField: 'publisher_id', nullable: true },
56
56
  publisherName: { type: 'string', actualField: 'publishers.name', filterOperator: 'like' } // Cross-table search
57
57
  }
58
- });
59
- await api.resources.authors.createKnexTable();
58
+ })
59
+ await api.resources.authors.createKnexTable()
60
60
 
61
61
  // Method 1: Simplified mode without inputRecord (most concise)
62
62
  const penguinResult = await api.resources.publishers.post({
63
63
  name: 'Penguin Random House'
64
- });
65
- console.log('Created publisher:', inspect(penguinResult));
64
+ })
65
+ console.log('Created publisher:', inspect(penguinResult))
66
66
 
67
67
  // Method 2: Simplified mode with inputRecord (explicit)
68
68
  const harperResult = await api.resources.publishers.post({
69
69
  inputRecord: {
70
70
  name: 'HarperCollins'
71
71
  }
72
- });
72
+ })
73
73
 
74
74
  // Method 3: Full JSON:API mode (standards compliant)
75
75
  const oxfordResult = await api.resources.publishers.post({
@@ -82,54 +82,54 @@ const oxfordResult = await api.resources.publishers.post({
82
82
  }
83
83
  },
84
84
  simplified: false
85
- });
86
- console.log('JSON:API response:', inspect(oxfordResult));
85
+ })
86
+ console.log('JSON:API response:', inspect(oxfordResult))
87
87
 
88
88
  // Create an author linked to the first publisher (simplified)
89
89
  const authorResult = await api.resources.authors.post({
90
90
  name: 'George',
91
91
  surname: 'Orwell',
92
92
  publisher_id: penguinResult.id
93
- });
94
- console.log('Created author:', inspect(authorResult));
93
+ })
94
+ console.log('Created author:', inspect(authorResult))
95
95
 
96
96
  // Get all publishers
97
- const allPublishers = await api.resources.publishers.query({});
98
- console.log('All publishers:', inspect(allPublishers));
97
+ const allPublishers = await api.resources.publishers.query({})
98
+ console.log('All publishers:', inspect(allPublishers))
99
99
 
100
100
  // Get publisher with included authors
101
101
  const publisherWithAuthors = await api.resources.publishers.get({
102
102
  id: penguinResult.id,
103
103
  include: ['authors']
104
- });
105
- console.log('Publisher with authors:', inspect(publisherWithAuthors));
104
+ })
105
+ console.log('Publisher with authors:', inspect(publisherWithAuthors))
106
106
 
107
107
  // Search authors by name
108
108
  const searchResult = await api.resources.authors.query({
109
109
  filter: { name: 'George' }
110
- });
111
- console.log('Search results:', inspect(searchResult));
110
+ })
111
+ console.log('Search results:', inspect(searchResult))
112
112
 
113
113
  // Update an author
114
114
  const updateResult = await api.resources.authors.patch({
115
115
  id: authorResult.id,
116
116
  surname: 'Orwell (Eric Blair)'
117
- });
118
- console.log('Updated author:', inspect(updateResult));
117
+ })
118
+ console.log('Updated author:', inspect(updateResult))
119
119
 
120
120
  /// *** ...programmatic calls here... ***
121
121
 
122
- // Create the express server and add the API's routes
123
- const app = express();
124
- app.use(api.http.express.router);
125
- app.use(api.http.express.notFoundRouter);
122
+ // Create the express server and add the API's routes
123
+ const app = express()
124
+ app.use(api.http.express.router)
125
+ app.use(api.http.express.notFoundRouter)
126
126
 
127
127
  app.listen(3000, () => {
128
- console.log('Express server started on port 3000. API available at http://localhost:3000/api');
128
+ console.log('Express server started on port 3000. API available at http://localhost:3000/api')
129
129
  }).on('error', (err) => {
130
- console.error('Failed to start server:', err);
130
+ console.error('Failed to start server:', err)
131
131
  process.exit(1)
132
132
  }).on('error', (err) => {
133
- console.error('Failed to start server:', err);
133
+ console.error('Failed to start server:', err)
134
134
  process.exit(1)
135
- });
135
+ })