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,7 +1,7 @@
1
- import { describe, it, before, beforeEach, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { createBasicApi, createExtendedApi } from './fixtures/api-configs.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 { createBasicApi, createExtendedApi } from './fixtures/api-configs.js'
5
5
  import {
6
6
  validateJsonApiStructure,
7
7
  resourceIdentifier,
@@ -12,7 +12,7 @@ import {
12
12
  createToManyRelationship,
13
13
  assertResourceAttributes,
14
14
  assertResourceRelationship
15
- } from './helpers/test-utils.js';
15
+ } from './helpers/test-utils.js'
16
16
 
17
17
  // Create Knex instance for tests
18
18
  const knex = knexLib({
@@ -21,107 +21,107 @@ const knex = knexLib({
21
21
  filename: ':memory:'
22
22
  },
23
23
  useNullAsDefault: true
24
- });
24
+ })
25
25
 
26
26
  // API instances that persist across tests
27
- let basicApi;
28
- let extendedApi;
27
+ let basicApi
28
+ let extendedApi
29
29
 
30
30
  describe('REST API Tests', () => {
31
31
  before(async () => {
32
32
  // Initialize APIs once
33
- basicApi = await createBasicApi(knex);
34
- extendedApi = await createExtendedApi(knex);
35
- });
33
+ basicApi = await createBasicApi(knex)
34
+ extendedApi = await createExtendedApi(knex)
35
+ })
36
36
 
37
37
  after(async () => {
38
38
  // Close database connection to allow tests to exit
39
- await knex.destroy();
40
- });
39
+ await knex.destroy()
40
+ })
41
41
 
42
42
  describe('Basic CRUD Operations', () => {
43
43
  beforeEach(async () => {
44
44
  // Clean all tables before each test
45
45
  await cleanTables(knex, [
46
46
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
47
- ]);
48
- });
47
+ ])
48
+ })
49
49
 
50
50
  describe('POST - Create Resources', () => {
51
51
  it('should create a country', async () => {
52
52
  const countryDoc = createJsonApiDocument('countries', {
53
53
  name: 'United States',
54
54
  code: 'US'
55
- });
55
+ })
56
56
 
57
57
  const result = await basicApi.resources.countries.post({
58
58
  inputRecord: countryDoc,
59
59
  simplified: false
60
- });
60
+ })
61
61
 
62
62
  // Verify response structure
63
- validateJsonApiStructure(result, false);
64
- assert.equal(result.data.type, 'countries');
65
- assert(result.data.id, 'Should have an ID');
63
+ validateJsonApiStructure(result, false)
64
+ assert.equal(result.data.type, 'countries')
65
+ assert(result.data.id, 'Should have an ID')
66
66
  assertResourceAttributes(result.data, {
67
67
  name: 'United States',
68
68
  code: 'US'
69
- });
69
+ })
70
70
 
71
71
  // Verify record was created in database
72
- const count = await countRecords(knex, 'basic_countries');
73
- assert.equal(count, 1, 'Should have created one country');
72
+ const count = await countRecords(knex, 'basic_countries')
73
+ assert.equal(count, 1, 'Should have created one country')
74
74
 
75
75
  // Verify data through API GET
76
76
  const getResult = await basicApi.resources.countries.get({
77
77
  id: result.data.id,
78
78
  simplified: false
79
- });
79
+ })
80
80
 
81
- validateJsonApiStructure(getResult, false);
82
- assert.equal(getResult.data.type, 'countries');
83
- assert.equal(getResult.data.id, result.data.id);
81
+ validateJsonApiStructure(getResult, false)
82
+ assert.equal(getResult.data.type, 'countries')
83
+ assert.equal(getResult.data.id, result.data.id)
84
84
  assertResourceAttributes(getResult.data, {
85
85
  name: 'United States',
86
86
  code: 'US'
87
- });
88
- });
87
+ })
88
+ })
89
89
 
90
90
  it('should create a publisher with country relationship', async () => {
91
91
  // First create a country
92
92
  const countryDoc = createJsonApiDocument('countries', {
93
93
  name: 'United Kingdom',
94
94
  code: 'UK'
95
- });
95
+ })
96
96
 
97
97
  const countryResult = await basicApi.resources.countries.post({
98
98
  inputRecord: countryDoc,
99
99
  simplified: false
100
- });
100
+ })
101
101
 
102
102
  // Create publisher with country relationship
103
- const publisherDoc = createJsonApiDocument('publishers',
103
+ const publisherDoc = createJsonApiDocument('publishers',
104
104
  {
105
105
  name: 'Penguin Random House'
106
106
  },
107
107
  {
108
108
  country: createRelationship(resourceIdentifier('countries', countryResult.data.id))
109
109
  }
110
- );
110
+ )
111
111
 
112
112
  const publisherResult = await basicApi.resources.publishers.post({
113
113
  inputRecord: publisherDoc,
114
114
  simplified: false
115
- });
115
+ })
116
116
 
117
117
  // Verify response
118
- validateJsonApiStructure(publisherResult, false);
119
- assert.equal(publisherResult.data.type, 'publishers');
120
- assert(publisherResult.data.id, 'Should have an ID');
118
+ validateJsonApiStructure(publisherResult, false)
119
+ assert.equal(publisherResult.data.type, 'publishers')
120
+ assert(publisherResult.data.id, 'Should have an ID')
121
121
 
122
122
  // Verify records in database
123
- const publisherCount = await countRecords(knex, 'basic_publishers');
124
- assert.equal(publisherCount, 1, 'Should have created one publisher');
123
+ const publisherCount = await countRecords(knex, 'basic_publishers')
124
+ assert.equal(publisherCount, 1, 'Should have created one publisher')
125
125
 
126
126
  // Verify data and relationship through API
127
127
  const getResult = await basicApi.resources.publishers.get({
@@ -130,27 +130,27 @@ describe('REST API Tests', () => {
130
130
  include: ['country']
131
131
  },
132
132
  simplified: false
133
- });
133
+ })
134
134
 
135
- validateJsonApiStructure(getResult, false);
135
+ validateJsonApiStructure(getResult, false)
136
136
  assertResourceAttributes(getResult.data, {
137
137
  name: 'Penguin Random House'
138
- });
139
- assertResourceRelationship(getResult.data, 'country',
140
- resourceIdentifier('countries', countryResult.data.id));
138
+ })
139
+ assertResourceRelationship(getResult.data, 'country',
140
+ resourceIdentifier('countries', countryResult.data.id))
141
141
 
142
142
  // Verify included data
143
- assert(getResult.included, 'Should have included data');
144
- assert.equal(getResult.included.length, 1, 'Should include one resource');
145
- assert.equal(getResult.included[0].type, 'countries');
146
- assert.equal(getResult.included[0].attributes.name, 'United Kingdom');
147
- });
143
+ assert(getResult.included, 'Should have included data')
144
+ assert.equal(getResult.included.length, 1, 'Should include one resource')
145
+ assert.equal(getResult.included[0].type, 'countries')
146
+ assert.equal(getResult.included[0].attributes.name, 'United Kingdom')
147
+ })
148
148
 
149
149
  it('should fail to create resource with missing required field', async () => {
150
150
  const invalidDoc = createJsonApiDocument('countries', {
151
151
  code: 'FR'
152
152
  // Missing required 'name' field
153
- });
153
+ })
154
154
 
155
155
  await assert.rejects(
156
156
  async () => {
@@ -158,19 +158,19 @@ describe('REST API Tests', () => {
158
158
  inputRecord: invalidDoc,
159
159
  simplified: false,
160
160
  returnFullRecord: false
161
- });
161
+ })
162
162
  },
163
163
  (err) => {
164
- return err.code === 'REST_API_VALIDATION';
164
+ return err.code === 'REST_API_VALIDATION'
165
165
  },
166
166
  'Should throw validation error for missing required field'
167
- );
167
+ )
168
168
 
169
169
  // Verify no record was created
170
- const count = await countRecords(knex, 'basic_countries');
171
- assert.equal(count, 0, 'Should not have created any countries');
172
- });
173
- });
170
+ const count = await countRecords(knex, 'basic_countries')
171
+ assert.equal(count, 0, 'Should not have created any countries')
172
+ })
173
+ })
174
174
 
175
175
  describe('GET - Retrieve Resources', () => {
176
176
  it('should retrieve a single resource', async () => {
@@ -178,27 +178,27 @@ describe('REST API Tests', () => {
178
178
  const countryDoc = createJsonApiDocument('countries', {
179
179
  name: 'Canada',
180
180
  code: 'CA'
181
- });
181
+ })
182
182
 
183
183
  const createResult = await basicApi.resources.countries.post({
184
184
  inputRecord: countryDoc,
185
185
  simplified: false
186
- });
186
+ })
187
187
 
188
188
  // Get the resource
189
189
  const getResult = await basicApi.resources.countries.get({
190
190
  id: createResult.data.id,
191
191
  simplified: false
192
- });
192
+ })
193
193
 
194
- validateJsonApiStructure(getResult, false);
195
- assert.equal(getResult.data.type, 'countries');
196
- assert.equal(getResult.data.id, createResult.data.id);
194
+ validateJsonApiStructure(getResult, false)
195
+ assert.equal(getResult.data.type, 'countries')
196
+ assert.equal(getResult.data.id, createResult.data.id)
197
197
  assertResourceAttributes(getResult.data, {
198
198
  name: 'Canada',
199
199
  code: 'CA'
200
- });
201
- });
200
+ })
201
+ })
202
202
 
203
203
  it('should fail to retrieve non-existent resource', async () => {
204
204
  await assert.rejects(
@@ -206,14 +206,14 @@ describe('REST API Tests', () => {
206
206
  await basicApi.resources.countries.get({
207
207
  id: 99999,
208
208
  simplified: false
209
- });
209
+ })
210
210
  },
211
211
  (err) => {
212
- return err.code === 'REST_API_RESOURCE' && err.subtype === 'not_found';
212
+ return err.code === 'REST_API_RESOURCE' && err.subtype === 'not_found'
213
213
  },
214
214
  'Should throw not found error'
215
- );
216
- });
215
+ )
216
+ })
217
217
 
218
218
  it('should retrieve collection of resources', async () => {
219
219
  // Create multiple countries
@@ -221,29 +221,29 @@ describe('REST API Tests', () => {
221
221
  { name: 'France', code: 'FR' },
222
222
  { name: 'Germany', code: 'DE' },
223
223
  { name: 'Italy', code: 'IT' }
224
- ];
224
+ ]
225
225
 
226
226
  for (const country of countries) {
227
- const doc = createJsonApiDocument('countries', country);
227
+ const doc = createJsonApiDocument('countries', country)
228
228
  await basicApi.resources.countries.post({
229
229
  inputRecord: doc,
230
230
  simplified: false
231
- });
231
+ })
232
232
  }
233
233
 
234
234
  // Query all countries
235
235
  const queryResult = await basicApi.resources.countries.query({
236
236
  simplified: false
237
- });
237
+ })
238
+
239
+ validateJsonApiStructure(queryResult, true)
240
+ assert.equal(queryResult.data.length, 3, 'Should return 3 countries')
238
241
 
239
- validateJsonApiStructure(queryResult, true);
240
- assert.equal(queryResult.data.length, 3, 'Should return 3 countries');
241
-
242
242
  // Verify all countries are present
243
- const names = queryResult.data.map(c => c.attributes.name).sort();
244
- assert.deepEqual(names, ['France', 'Germany', 'Italy']);
245
- });
246
- });
243
+ const names = queryResult.data.map(c => c.attributes.name).sort()
244
+ assert.deepEqual(names, ['France', 'Germany', 'Italy'])
245
+ })
246
+ })
247
247
 
248
248
  describe('PATCH - Update Resources', () => {
249
249
  it('should partially update a resource', async () => {
@@ -251,12 +251,12 @@ describe('REST API Tests', () => {
251
251
  const countryDoc = createJsonApiDocument('countries', {
252
252
  name: 'Spain',
253
253
  code: 'ES'
254
- });
254
+ })
255
255
 
256
256
  const createResult = await basicApi.resources.countries.post({
257
257
  inputRecord: countryDoc,
258
258
  simplified: false
259
- });
259
+ })
260
260
 
261
261
  // Update only the name
262
262
  const updateDoc = {
@@ -267,26 +267,26 @@ describe('REST API Tests', () => {
267
267
  name: 'España'
268
268
  }
269
269
  }
270
- };
270
+ }
271
271
 
272
272
  await basicApi.resources.countries.patch({
273
273
  id: createResult.data.id,
274
274
  inputRecord: updateDoc,
275
275
  simplified: false
276
- });
276
+ })
277
277
 
278
278
  // Verify the update
279
279
  const getResult = await basicApi.resources.countries.get({
280
280
  id: createResult.data.id,
281
281
  simplified: false
282
- });
282
+ })
283
283
 
284
284
  assertResourceAttributes(getResult.data, {
285
285
  name: 'España',
286
286
  code: 'ES' // Should remain unchanged
287
- });
288
- });
289
- });
287
+ })
288
+ })
289
+ })
290
290
 
291
291
  describe('DELETE - Remove Resources', () => {
292
292
  it('should delete an existing resource', async () => {
@@ -294,29 +294,29 @@ describe('REST API Tests', () => {
294
294
  const countryDoc = createJsonApiDocument('countries', {
295
295
  name: 'Portugal',
296
296
  code: 'PT'
297
- });
297
+ })
298
298
 
299
299
  const createResult = await basicApi.resources.countries.post({
300
300
  inputRecord: countryDoc,
301
301
  simplified: false
302
- });
302
+ })
303
303
 
304
304
  // Verify it exists
305
- let count = await countRecords(knex, 'basic_countries');
306
- assert.equal(count, 1, 'Should have one country before delete');
305
+ let count = await countRecords(knex, 'basic_countries')
306
+ assert.equal(count, 1, 'Should have one country before delete')
307
307
 
308
308
  // Delete the resource
309
309
  const deleteResult = await basicApi.resources.countries.delete({
310
310
  id: createResult.data.id,
311
311
  simplified: false
312
- });
312
+ })
313
313
 
314
314
  // DELETE returns undefined (204 No Content)
315
- assert.equal(deleteResult, undefined, 'Delete should return undefined');
315
+ assert.equal(deleteResult, undefined, 'Delete should return undefined')
316
316
 
317
317
  // Verify it's gone
318
- count = await countRecords(knex, 'basic_countries');
319
- assert.equal(count, 0, 'Should have no countries after delete');
318
+ count = await countRecords(knex, 'basic_countries')
319
+ assert.equal(count, 0, 'Should have no countries after delete')
320
320
 
321
321
  // Verify GET fails
322
322
  await assert.rejects(
@@ -324,14 +324,14 @@ describe('REST API Tests', () => {
324
324
  await basicApi.resources.countries.get({
325
325
  id: createResult.data.id,
326
326
  simplified: false
327
- });
327
+ })
328
328
  },
329
329
  (err) => {
330
- return err.code === 'REST_API_RESOURCE' && err.subtype === 'not_found';
330
+ return err.code === 'REST_API_RESOURCE' && err.subtype === 'not_found'
331
331
  },
332
332
  'Should throw not found error after delete'
333
- );
334
- });
335
- });
336
- });
337
- });
333
+ )
334
+ })
335
+ })
336
+ })
337
+ })