json-rest-api 1.0.14 → 1.0.16

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 +99 -99
  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 +11 -15
  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,8 +1,8 @@
1
- import { describe, it, before, beforeEach, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { createFieldSettersApi } from './fixtures/api-configs.js';
5
- import { cleanTables } from './helpers/test-utils.js';
1
+ import { describe, it, before, beforeEach, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { createFieldSettersApi } from './fixtures/api-configs.js'
5
+ import { cleanTables } from './helpers/test-utils.js'
6
6
 
7
7
  // Create Knex instance for tests
8
8
  const knex = knexLib({
@@ -11,26 +11,26 @@ const knex = knexLib({
11
11
  filename: ':memory:'
12
12
  },
13
13
  useNullAsDefault: true
14
- });
14
+ })
15
15
 
16
16
  // API instance that persists across tests
17
- let api;
17
+ let api
18
18
 
19
19
  describe('Field Setters', () => {
20
20
  before(async () => {
21
21
  // Initialize API once
22
- api = await createFieldSettersApi(knex);
23
- });
22
+ api = await createFieldSettersApi(knex)
23
+ })
24
24
 
25
25
  after(async () => {
26
26
  // Close database connection to allow tests to exit
27
- await knex.destroy();
28
- });
27
+ await knex.destroy()
28
+ })
29
29
 
30
30
  describe('Basic Field Transformations', () => {
31
31
  beforeEach(async () => {
32
- await cleanTables(knex, ['setter_users']);
33
- });
32
+ await cleanTables(knex, ['setter_users'])
33
+ })
34
34
 
35
35
  it('should apply simple setter transformations on create', async () => {
36
36
  // Create user with data that needs transformation
@@ -39,17 +39,17 @@ describe('Field Setters', () => {
39
39
  username: ' JohnDoe ',
40
40
  tags: 'tag1,tag2,tag3', // Pass as string
41
41
  preferences: '{"theme":"dark","notifications":true}' // Pass as JSON string
42
- });
42
+ })
43
43
 
44
44
  // Get the user to verify setters were applied
45
- const fetchedUser = await api.resources.users.get({ id: user.id });
46
-
45
+ const fetchedUser = await api.resources.users.get({ id: user.id })
46
+
47
47
  // The setters transformed the data before storage
48
- assert.equal(fetchedUser.email, 'user@example.com');
49
- assert.equal(fetchedUser.username, 'johndoe');
50
- assert.equal(fetchedUser.tags, 'tag1,tag2,tag3');
51
- assert.equal(fetchedUser.preferences, '{"theme":"dark","notifications":true}');
52
- });
48
+ assert.equal(fetchedUser.email, 'user@example.com')
49
+ assert.equal(fetchedUser.username, 'johndoe')
50
+ assert.equal(fetchedUser.tags, 'tag1,tag2,tag3')
51
+ assert.equal(fetchedUser.preferences, '{"theme":"dark","notifications":true}')
52
+ })
53
53
 
54
54
  it('should apply setter transformations on update (PUT)', async () => {
55
55
  // Create initial user
@@ -58,7 +58,7 @@ describe('Field Setters', () => {
58
58
  username: 'originaluser',
59
59
  tags: 'old',
60
60
  preferences: '{"theme":"light"}'
61
- });
61
+ })
62
62
 
63
63
  // Update with PUT
64
64
  await api.resources.users.put({
@@ -67,15 +67,15 @@ describe('Field Setters', () => {
67
67
  username: ' UpdatedUser ',
68
68
  tags: 'new1,new2',
69
69
  preferences: '{"theme":"dark","lang":"en"}'
70
- });
70
+ })
71
71
 
72
72
  // Get the user to verify setters were applied
73
- const updatedUser = await api.resources.users.get({ id: user.id });
74
- assert.equal(updatedUser.email, 'updated@example.com');
75
- assert.equal(updatedUser.username, 'updateduser');
76
- assert.equal(updatedUser.tags, 'new1,new2');
77
- assert.equal(updatedUser.preferences, '{"theme":"dark","lang":"en"}');
78
- });
73
+ const updatedUser = await api.resources.users.get({ id: user.id })
74
+ assert.equal(updatedUser.email, 'updated@example.com')
75
+ assert.equal(updatedUser.username, 'updateduser')
76
+ assert.equal(updatedUser.tags, 'new1,new2')
77
+ assert.equal(updatedUser.preferences, '{"theme":"dark","lang":"en"}')
78
+ })
79
79
 
80
80
  it('should apply setter transformations on partial update (PATCH)', async () => {
81
81
  // Create initial user
@@ -84,25 +84,25 @@ describe('Field Setters', () => {
84
84
  username: 'originaluser',
85
85
  tags: 'old',
86
86
  preferences: '{"theme":"light"}'
87
- });
87
+ })
88
88
 
89
89
  // Update only email with PATCH
90
90
  await api.resources.users.patch({
91
91
  id: user.id,
92
92
  email: ' PATCHED@EXAMPLE.COM '
93
- });
93
+ })
94
94
 
95
95
  // Get the user to verify setter was applied only to patched field
96
- const patchedUser = await api.resources.users.get({ id: user.id });
97
- assert.equal(patchedUser.email, 'patched@example.com');
98
- assert.equal(patchedUser.username, 'originaluser'); // Unchanged
99
- });
100
- });
96
+ const patchedUser = await api.resources.users.get({ id: user.id })
97
+ assert.equal(patchedUser.email, 'patched@example.com')
98
+ assert.equal(patchedUser.username, 'originaluser') // Unchanged
99
+ })
100
+ })
101
101
 
102
102
  describe('Type Conversion Setters', () => {
103
103
  beforeEach(async () => {
104
- await cleanTables(knex, ['setter_products']);
105
- });
104
+ await cleanTables(knex, ['setter_products'])
105
+ })
106
106
 
107
107
  it('should apply type conversion setters after validation', async () => {
108
108
  const product = await api.resources.products.post({
@@ -110,51 +110,51 @@ describe('Field Setters', () => {
110
110
  price: 99.999, // Will be rounded to cents
111
111
  discount_percent: 15.678, // Will be rounded to integer
112
112
  metadata: { key: 'value' } // Will be stringified
113
- });
113
+ })
114
114
 
115
115
  // Get the product to verify setters were applied
116
- const fetchedProduct = await api.resources.products.get({ id: product.id });
117
- assert.equal(fetchedProduct.price, 10000); // 99.999 * 100 rounded = 10000 cents
118
- assert.equal(fetchedProduct.discount_percent, 16); // 15.678 rounded
119
- assert.equal(fetchedProduct.metadata, '{"key":"value"}');
120
- });
121
- });
116
+ const fetchedProduct = await api.resources.products.get({ id: product.id })
117
+ assert.equal(fetchedProduct.price, 10000) // 99.999 * 100 rounded = 10000 cents
118
+ assert.equal(fetchedProduct.discount_percent, 16) // 15.678 rounded
119
+ assert.equal(fetchedProduct.metadata, '{"key":"value"}')
120
+ })
121
+ })
122
122
 
123
123
  describe('Async Setters', () => {
124
124
  beforeEach(async () => {
125
- await cleanTables(knex, ['setter_secure']);
126
- });
125
+ await cleanTables(knex, ['setter_secure'])
126
+ })
127
127
 
128
128
  it('should support async setter functions', async () => {
129
129
  const record = await api.resources.secure_data.post({
130
130
  password: 'mysecretpassword',
131
131
  api_key: 'test-key-123',
132
132
  data: 'sensitive information'
133
- });
133
+ })
134
134
 
135
135
  console.log('**********************************************')
136
136
  console.log(record)
137
137
  // Check if record has id
138
- assert.ok(record.id, 'Record should have an id');
138
+ assert.ok(record.id, 'Record should have an id')
139
139
 
140
140
  // Get the record to verify async setters were applied
141
- const fetchedRecord = await api.resources.secure_data.get({ id: record.id });
142
-
141
+ const fetchedRecord = await api.resources.secure_data.get({ id: record.id })
142
+
143
143
  // Password should be hashed (mock hash)
144
- assert.equal(fetchedRecord.password, 'hashed:mysecretpassword');
145
-
144
+ assert.equal(fetchedRecord.password, 'hashed:mysecretpassword')
145
+
146
146
  // API key should be encrypted (base64)
147
- assert.equal(fetchedRecord.api_key, Buffer.from('test-key-123').toString('base64'));
148
-
147
+ assert.equal(fetchedRecord.api_key, Buffer.from('test-key-123').toString('base64'))
148
+
149
149
  // Data should be encrypted
150
- assert.equal(fetchedRecord.data, Buffer.from('sensitive information').toString('base64'));
151
- });
152
- });
150
+ assert.equal(fetchedRecord.data, Buffer.from('sensitive information').toString('base64'))
151
+ })
152
+ })
153
153
 
154
154
  describe('Setter Dependencies', () => {
155
155
  beforeEach(async () => {
156
- await cleanTables(knex, ['setter_computed']);
157
- });
156
+ await cleanTables(knex, ['setter_computed'])
157
+ })
158
158
 
159
159
  it('should apply setters in dependency order', async () => {
160
160
  const data = await api.resources.computed_data.post({
@@ -163,21 +163,21 @@ describe('Field Setters', () => {
163
163
  adjustment: 10
164
164
  // calculated_value depends on base_value and multiplier
165
165
  // final_value depends on calculated_value and adjustment
166
- });
166
+ })
167
167
 
168
- const fetchedData = await api.resources.computed_data.get({ id: data.id });
169
- assert.equal(fetchedData.base_value, 100);
170
- assert.equal(fetchedData.multiplier, 2);
171
- assert.equal(fetchedData.adjustment, 10);
172
- assert.equal(fetchedData.calculated_value, 200); // 100 * 2
173
- assert.equal(fetchedData.final_value, 210); // 200 + 10
174
- });
175
- });
168
+ const fetchedData = await api.resources.computed_data.get({ id: data.id })
169
+ assert.equal(fetchedData.base_value, 100)
170
+ assert.equal(fetchedData.multiplier, 2)
171
+ assert.equal(fetchedData.adjustment, 10)
172
+ assert.equal(fetchedData.calculated_value, 200) // 100 * 2
173
+ assert.equal(fetchedData.final_value, 210) // 200 + 10
174
+ })
175
+ })
176
176
 
177
177
  describe('Error Handling', () => {
178
178
  beforeEach(async () => {
179
- await cleanTables(knex, ['setter_users']);
180
- });
179
+ await cleanTables(knex, ['setter_users'])
180
+ })
181
181
 
182
182
  it('should handle setter errors gracefully', async () => {
183
183
  // Create a resource with a failing setter
@@ -191,33 +191,33 @@ describe('Field Setters', () => {
191
191
  bad_field: {
192
192
  type: 'string',
193
193
  setter: (value) => {
194
- throw new Error('Setter failed!');
194
+ throw new Error('Setter failed!')
195
195
  }
196
196
  }
197
197
  },
198
198
  tableName: 'setter_errors'
199
- });
200
- await api.resources.error_test.createKnexTable();
199
+ })
200
+ await api.resources.error_test.createKnexTable()
201
201
 
202
202
  const record = await api.resources.error_test.post({
203
203
  good_field: 'HELLO',
204
204
  bad_field: 'world'
205
- });
205
+ })
206
206
 
207
- const fetchedRecord = await api.resources.error_test.get({ id: record.id });
207
+ const fetchedRecord = await api.resources.error_test.get({ id: record.id })
208
208
  // Good field should be transformed
209
- assert.equal(fetchedRecord.good_field, 'hello');
209
+ assert.equal(fetchedRecord.good_field, 'hello')
210
210
  // Bad field should keep validated value (error logged but not thrown)
211
- assert.equal(fetchedRecord.bad_field, 'world');
211
+ assert.equal(fetchedRecord.bad_field, 'world')
212
212
 
213
- await cleanTables(knex, ['setter_errors']);
214
- });
215
- });
213
+ await cleanTables(knex, ['setter_errors'])
214
+ })
215
+ })
216
216
 
217
217
  describe('Null and Undefined Handling', () => {
218
218
  beforeEach(async () => {
219
- await cleanTables(knex, ['setter_nullable']);
220
- });
219
+ await cleanTables(knex, ['setter_nullable'])
220
+ })
221
221
 
222
222
  it('should handle null and undefined values in setters', async () => {
223
223
  const data = await api.resources.nullable_data.post({
@@ -225,20 +225,20 @@ describe('Field Setters', () => {
225
225
  field2: undefined,
226
226
  field3: '',
227
227
  field4: 0
228
- });
228
+ })
229
229
 
230
- const fetchedData = await api.resources.nullable_data.get({ id: data.id });
231
- assert.equal(fetchedData.field1, null);
232
- assert.equal(fetchedData.field2, null);
233
- assert.equal(fetchedData.field3, 'empty'); // Empty string transformed
234
- assert.equal(fetchedData.field4, -1); // Zero transformed
235
- });
236
- });
230
+ const fetchedData = await api.resources.nullable_data.get({ id: data.id })
231
+ assert.equal(fetchedData.field1, null)
232
+ assert.equal(fetchedData.field2, null)
233
+ assert.equal(fetchedData.field3, 'empty') // Empty string transformed
234
+ assert.equal(fetchedData.field4, -1) // Zero transformed
235
+ })
236
+ })
237
237
 
238
238
  describe('Validation Before Setters', () => {
239
239
  beforeEach(async () => {
240
- await cleanTables(knex, ['setter_validated']);
241
- });
240
+ await cleanTables(knex, ['setter_validated'])
241
+ })
242
242
 
243
243
  it('should run setters only after successful validation', async () => {
244
244
  // Try to create with invalid data
@@ -248,26 +248,26 @@ describe('Field Setters', () => {
248
248
  age: 150 // Should fail max validation
249
249
  }),
250
250
  /Schema validation failed/
251
- );
251
+ )
252
252
 
253
253
  // No records should be created - verify with query
254
- const records = await api.resources.validated_data.query();
255
- assert.equal(records?.length || 0, 0);
256
- });
254
+ const records = await api.resources.validated_data.query()
255
+ assert.equal(records?.length || 0, 0)
256
+ })
257
257
 
258
258
  it('should apply setters to validated type-cast data', async () => {
259
259
  const data = await api.resources.validated_data.post({
260
260
  email: ' VALID@EXAMPLE.COM ',
261
261
  age: '25', // String will be cast to number
262
262
  score: '98.7' // String will be cast to number
263
- });
263
+ })
264
264
 
265
- const fetchedData = await api.resources.validated_data.get({ id: data.id });
266
- assert.equal(fetchedData.email, 'valid@example.com');
267
- assert.equal(fetchedData.age, 25); // Setter sees number, not string
268
- assert.equal(fetchedData.score, 99); // Rounded up by setter
269
- });
270
- });
265
+ const fetchedData = await api.resources.validated_data.get({ id: data.id })
266
+ assert.equal(fetchedData.email, 'valid@example.com')
267
+ assert.equal(fetchedData.age, 25) // Setter sees number, not string
268
+ assert.equal(fetchedData.score, 99) // Rounded up by setter
269
+ })
270
+ })
271
271
 
272
272
  describe('Circular Dependencies', () => {
273
273
  it('should detect circular setter dependencies', async () => {
@@ -288,8 +288,8 @@ describe('Field Setters', () => {
288
288
  tableName: 'circular_setters'
289
289
  }),
290
290
  /Circular dependency detected/
291
- );
292
- });
291
+ )
292
+ })
293
293
 
294
294
  it('should detect unknown setter dependencies', async () => {
295
295
  await assert.rejects(
@@ -304,7 +304,7 @@ describe('Field Setters', () => {
304
304
  tableName: 'unknown_deps'
305
305
  }),
306
306
  /setter dependency 'nonexistent' that does not exist/
307
- );
308
- });
309
- });
310
- });
307
+ )
308
+ })
309
+ })
310
+ })