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,75 +21,75 @@ 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('Query Operations', () => {
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('Filtering', () => {
43
- let testData = {};
43
+ const testData = {}
44
44
 
45
45
  beforeEach(async () => {
46
46
  // Clean all tables
47
47
  await cleanTables(knex, [
48
48
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
49
- ]);
49
+ ])
50
50
 
51
51
  // Create test data set
52
52
  // Create 3 countries
53
- const countries = [];
53
+ const countries = []
54
54
  for (const [name, code] of [['United States', 'US'], ['United Kingdom', 'UK'], ['France', 'FR']]) {
55
- const doc = createJsonApiDocument('countries', { name, code });
55
+ const doc = createJsonApiDocument('countries', { name, code })
56
56
  const result = await basicApi.resources.countries.post({
57
57
  inputRecord: doc,
58
58
  simplified: false
59
- });
60
- countries.push(result.data);
59
+ })
60
+ countries.push(result.data)
61
61
  }
62
- testData.countries = countries;
62
+ testData.countries = countries
63
63
 
64
64
  // Create 2 publishers per country
65
- const publishers = [];
66
- let pubIndex = 0;
65
+ const publishers = []
66
+ let pubIndex = 0
67
67
  for (const country of countries) {
68
68
  for (let i = 0; i < 2; i++) {
69
69
  const doc = createJsonApiDocument('publishers',
70
70
  { name: `Publisher ${++pubIndex}` },
71
71
  { country: createRelationship(resourceIdentifier('countries', country.id)) }
72
- );
72
+ )
73
73
  const result = await basicApi.resources.publishers.post({
74
74
  inputRecord: doc,
75
75
  simplified: false
76
- });
77
- publishers.push({ ...result.data, countryId: country.id });
76
+ })
77
+ publishers.push({ ...result.data, countryId: country.id })
78
78
  }
79
79
  }
80
- testData.publishers = publishers;
80
+ testData.publishers = publishers
81
81
 
82
82
  // Create 3 authors
83
- const authors = [];
83
+ const authors = []
84
84
  for (const name of ['Author One', 'Author Two', 'Author Three']) {
85
- const doc = createJsonApiDocument('authors', { name });
85
+ const doc = createJsonApiDocument('authors', { name })
86
86
  const result = await basicApi.resources.authors.post({
87
87
  inputRecord: doc,
88
88
  simplified: false
89
- });
90
- authors.push(result.data);
89
+ })
90
+ authors.push(result.data)
91
91
  }
92
- testData.authors = authors;
92
+ testData.authors = authors
93
93
 
94
94
  // Create 5 books with various relationships
95
95
  const books = [
@@ -98,9 +98,9 @@ describe('Query Operations', () => {
98
98
  { title: 'Book C', countryId: countries[1].id, publisherId: publishers[2].id, authorIds: [authors[1].id] },
99
99
  { title: 'Book D', countryId: countries[1].id, publisherId: publishers[3].id, authorIds: [authors[2].id] },
100
100
  { title: 'Book E', countryId: countries[2].id, publisherId: publishers[4].id, authorIds: [authors[0].id, authors[2].id] }
101
- ];
101
+ ]
102
102
 
103
- testData.books = [];
103
+ testData.books = []
104
104
  for (const bookData of books) {
105
105
  const doc = createJsonApiDocument('books',
106
106
  {
@@ -113,14 +113,14 @@ describe('Query Operations', () => {
113
113
  bookData.authorIds.map(id => resourceIdentifier('authors', id))
114
114
  )
115
115
  }
116
- );
116
+ )
117
117
  const result = await basicApi.resources.books.post({
118
118
  inputRecord: doc,
119
119
  simplified: false
120
- });
121
- testData.books.push(result.data);
120
+ })
121
+ testData.books.push(result.data)
122
122
  }
123
- });
123
+ })
124
124
 
125
125
  it('should filter by simple field', async () => {
126
126
  const result = await basicApi.resources.books.query({
@@ -128,20 +128,20 @@ describe('Query Operations', () => {
128
128
  filters: { country: testData.countries[0].id }
129
129
  },
130
130
  simplified: false
131
- });
131
+ })
132
+
133
+ validateJsonApiStructure(result, true)
134
+ assert.equal(result.data.length, 2, 'Should return 2 books from US')
132
135
 
133
- validateJsonApiStructure(result, true);
134
- assert.equal(result.data.length, 2, 'Should return 2 books from US');
135
-
136
136
  // Verify all returned books are from the correct country
137
137
  for (const book of result.data) {
138
- const bookData = testData.books.find(b => b.id === book.id);
139
- assert(bookData);
140
- const originalBook = testData.books.find(b => b.id === book.id);
141
- assert.equal(originalBook.attributes.title.startsWith('Book A') ||
142
- originalBook.attributes.title.startsWith('Book B'), true);
138
+ const bookData = testData.books.find(b => b.id === book.id)
139
+ assert(bookData)
140
+ const originalBook = testData.books.find(b => b.id === book.id)
141
+ assert.equal(originalBook.attributes.title.startsWith('Book A') ||
142
+ originalBook.attributes.title.startsWith('Book B'), true)
143
143
  }
144
- });
144
+ })
145
145
 
146
146
  it('should filter by multiple fields', async () => {
147
147
  const result = await basicApi.resources.books.query({
@@ -152,12 +152,12 @@ describe('Query Operations', () => {
152
152
  }
153
153
  },
154
154
  simplified: false
155
- });
155
+ })
156
156
 
157
- validateJsonApiStructure(result, true);
158
- assert.equal(result.data.length, 1, 'Should return 1 book matching both filters');
159
- assert.equal(result.data[0].attributes.title, 'Book A');
160
- });
157
+ validateJsonApiStructure(result, true)
158
+ assert.equal(result.data.length, 1, 'Should return 1 book matching both filters')
159
+ assert.equal(result.data[0].attributes.title, 'Book A')
160
+ })
161
161
 
162
162
  it('should return empty array when no matches', async () => {
163
163
  const result = await basicApi.resources.books.query({
@@ -165,11 +165,11 @@ describe('Query Operations', () => {
165
165
  filters: { country: 99999 }
166
166
  },
167
167
  simplified: false
168
- });
168
+ })
169
169
 
170
- validateJsonApiStructure(result, true);
171
- assert.equal(result.data.length, 0, 'Should return empty array');
172
- });
170
+ validateJsonApiStructure(result, true)
171
+ assert.equal(result.data.length, 0, 'Should return empty array')
172
+ })
173
173
 
174
174
  it('should throw error when filtering on non-searchable field', async () => {
175
175
  await assert.rejects(
@@ -179,44 +179,44 @@ describe('Query Operations', () => {
179
179
  filters: { invalid_field: 'test' }
180
180
  },
181
181
  simplified: false
182
- });
182
+ })
183
183
  },
184
184
  (err) => {
185
- return err.code === 'REST_API_VALIDATION';
185
+ return err.code === 'REST_API_VALIDATION'
186
186
  },
187
187
  'Should throw validation error for invalid filter field'
188
- );
189
- });
190
- });
188
+ )
189
+ })
190
+ })
191
191
 
192
192
  describe('Sorting', () => {
193
193
  beforeEach(async () => {
194
194
  await cleanTables(knex, [
195
195
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
196
- ]);
196
+ ])
197
197
 
198
198
  // Create test data
199
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' });
199
+ const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
200
200
  const countryResult = await basicApi.resources.countries.post({
201
201
  inputRecord: countryDoc,
202
202
  simplified: false
203
- });
203
+ })
204
204
 
205
205
  // Create books with different titles
206
- const titles = ['Zebra Book', 'Apple Book', 'Mango Book', 'Banana Book'];
206
+ const titles = ['Zebra Book', 'Apple Book', 'Mango Book', 'Banana Book']
207
207
  for (const title of titles) {
208
208
  const doc = createJsonApiDocument('books', {
209
209
  title
210
210
  },
211
211
  {
212
212
  country: createRelationship(resourceIdentifier('countries', countryResult.data.id))
213
- });
213
+ })
214
214
  await basicApi.resources.books.post({
215
215
  inputRecord: doc,
216
216
  simplified: false
217
- });
217
+ })
218
218
  }
219
- });
219
+ })
220
220
 
221
221
  it('should sort by single field ascending', async () => {
222
222
  const result = await basicApi.resources.books.query({
@@ -224,14 +224,14 @@ describe('Query Operations', () => {
224
224
  sort: ['title']
225
225
  },
226
226
  simplified: false
227
- });
227
+ })
228
+
229
+ validateJsonApiStructure(result, true)
230
+ assert.equal(result.data.length, 4)
228
231
 
229
- validateJsonApiStructure(result, true);
230
- assert.equal(result.data.length, 4);
231
-
232
- const titles = result.data.map(b => b.attributes.title);
233
- assert.deepEqual(titles, ['Apple Book', 'Banana Book', 'Mango Book', 'Zebra Book']);
234
- });
232
+ const titles = result.data.map(b => b.attributes.title)
233
+ assert.deepEqual(titles, ['Apple Book', 'Banana Book', 'Mango Book', 'Zebra Book'])
234
+ })
235
235
 
236
236
  it('should sort by single field descending', async () => {
237
237
  const result = await basicApi.resources.books.query({
@@ -239,29 +239,29 @@ describe('Query Operations', () => {
239
239
  sort: ['-title']
240
240
  },
241
241
  simplified: false
242
- });
242
+ })
243
+
244
+ validateJsonApiStructure(result, true)
245
+ assert.equal(result.data.length, 4)
243
246
 
244
- validateJsonApiStructure(result, true);
245
- assert.equal(result.data.length, 4);
246
-
247
- const titles = result.data.map(b => b.attributes.title);
248
- assert.deepEqual(titles, ['Zebra Book', 'Mango Book', 'Banana Book', 'Apple Book']);
249
- });
247
+ const titles = result.data.map(b => b.attributes.title)
248
+ assert.deepEqual(titles, ['Zebra Book', 'Mango Book', 'Banana Book', 'Apple Book'])
249
+ })
250
250
 
251
251
  it('should sort by multiple fields', async () => {
252
252
  // Create books with same country but different titles
253
- const countryDoc1 = createJsonApiDocument('countries', { name: 'Country A', code: 'CA' });
254
- const countryDoc2 = createJsonApiDocument('countries', { name: 'Country B', code: 'CB' });
255
-
253
+ const countryDoc1 = createJsonApiDocument('countries', { name: 'Country A', code: 'CA' })
254
+ const countryDoc2 = createJsonApiDocument('countries', { name: 'Country B', code: 'CB' })
255
+
256
256
  const country1Result = await basicApi.resources.countries.post({
257
257
  inputRecord: countryDoc1,
258
258
  simplified: false
259
- });
260
-
259
+ })
260
+
261
261
  const country2Result = await basicApi.resources.countries.post({
262
262
  inputRecord: countryDoc2,
263
263
  simplified: false
264
- });
264
+ })
265
265
 
266
266
  // Create books
267
267
  const booksToCreate = [
@@ -269,17 +269,17 @@ describe('Query Operations', () => {
269
269
  { title: 'Book A', countryId: country1Result.data.id },
270
270
  { title: 'Book Z', countryId: country2Result.data.id },
271
271
  { title: 'Book A', countryId: country2Result.data.id }
272
- ];
272
+ ]
273
273
 
274
274
  for (const bookData of booksToCreate) {
275
- const doc = createJsonApiDocument('books',
275
+ const doc = createJsonApiDocument('books',
276
276
  { title: bookData.title },
277
277
  { country: createRelationship(resourceIdentifier('countries', bookData.countryId)) }
278
- );
278
+ )
279
279
  await basicApi.resources.books.post({
280
280
  inputRecord: doc,
281
281
  simplified: false
282
- });
282
+ })
283
283
  }
284
284
 
285
285
  // Sort by country_id ascending, then title descending
@@ -288,68 +288,68 @@ describe('Query Operations', () => {
288
288
  sort: ['country', '-title']
289
289
  },
290
290
  simplified: false
291
- });
291
+ })
292
292
 
293
293
  // We expect 8 books total (4 from beforeEach + 4 from this test)
294
- assert.equal(result.data.length, 8);
295
-
294
+ assert.equal(result.data.length, 8)
295
+
296
296
  // Filter to only the books we created in this test
297
- const testBooks = result.data.filter(book =>
297
+ const testBooks = result.data.filter(book =>
298
298
  book.attributes.title === 'Book A' || book.attributes.title === 'Book Z'
299
- );
300
-
301
- assert.equal(testBooks.length, 4);
302
-
299
+ )
300
+
301
+ assert.equal(testBooks.length, 4)
302
+
303
303
  // Sort test books by country relationship for verification
304
304
  const sortedTestBooks = testBooks.sort((a, b) => {
305
- const countryA = a.relationships.country.data.id;
306
- const countryB = b.relationships.country.data.id;
305
+ const countryA = a.relationships.country.data.id
306
+ const countryB = b.relationships.country.data.id
307
307
  if (countryA !== countryB) {
308
- return countryA.localeCompare(countryB);
308
+ return countryA.localeCompare(countryB)
309
309
  }
310
310
  // Within same country, sort by title descending
311
- return b.attributes.title.localeCompare(a.attributes.title);
312
- });
313
-
311
+ return b.attributes.title.localeCompare(a.attributes.title)
312
+ })
313
+
314
314
  // Verify the books are from the correct countries and in correct order
315
- assert.equal(sortedTestBooks[0].relationships.country.data.id, country1Result.data.id);
316
- assert.equal(sortedTestBooks[1].relationships.country.data.id, country1Result.data.id);
317
- assert.equal(sortedTestBooks[2].relationships.country.data.id, country2Result.data.id);
318
- assert.equal(sortedTestBooks[3].relationships.country.data.id, country2Result.data.id);
319
-
315
+ assert.equal(sortedTestBooks[0].relationships.country.data.id, country1Result.data.id)
316
+ assert.equal(sortedTestBooks[1].relationships.country.data.id, country1Result.data.id)
317
+ assert.equal(sortedTestBooks[2].relationships.country.data.id, country2Result.data.id)
318
+ assert.equal(sortedTestBooks[3].relationships.country.data.id, country2Result.data.id)
319
+
320
320
  // Within each country, should be sorted by title descending
321
- assert.equal(sortedTestBooks[0].attributes.title, 'Book Z');
322
- assert.equal(sortedTestBooks[1].attributes.title, 'Book A');
323
- assert.equal(sortedTestBooks[2].attributes.title, 'Book Z');
324
- assert.equal(sortedTestBooks[3].attributes.title, 'Book A');
325
- });
326
- });
321
+ assert.equal(sortedTestBooks[0].attributes.title, 'Book Z')
322
+ assert.equal(sortedTestBooks[1].attributes.title, 'Book A')
323
+ assert.equal(sortedTestBooks[2].attributes.title, 'Book Z')
324
+ assert.equal(sortedTestBooks[3].attributes.title, 'Book A')
325
+ })
326
+ })
327
327
 
328
328
  describe('Pagination', () => {
329
329
  beforeEach(async () => {
330
330
  await cleanTables(knex, [
331
331
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
332
- ]);
332
+ ])
333
333
 
334
334
  // Create test data
335
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' });
335
+ const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
336
336
  const countryResult = await basicApi.resources.countries.post({
337
337
  inputRecord: countryDoc,
338
338
  simplified: false
339
- });
339
+ })
340
340
 
341
341
  // Create 10 books
342
342
  for (let i = 1; i <= 10; i++) {
343
- const doc = createJsonApiDocument('books',
343
+ const doc = createJsonApiDocument('books',
344
344
  { title: `Book ${String(i).padStart(2, '0')}` },
345
345
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
346
- );
346
+ )
347
347
  await basicApi.resources.books.post({
348
348
  inputRecord: doc,
349
349
  simplified: false
350
- });
350
+ })
351
351
  }
352
- });
352
+ })
353
353
 
354
354
  it('should paginate results', async () => {
355
355
  // Get first page
@@ -359,13 +359,13 @@ describe('Query Operations', () => {
359
359
  sort: ['title']
360
360
  },
361
361
  simplified: false
362
- });
362
+ })
363
363
 
364
- validateJsonApiStructure(page1, true);
365
- assert.equal(page1.data.length, 3, 'Should return 3 results for page 1');
366
-
367
- const page1Titles = page1.data.map(b => b.attributes.title);
368
- assert.deepEqual(page1Titles, ['Book 01', 'Book 02', 'Book 03']);
364
+ validateJsonApiStructure(page1, true)
365
+ assert.equal(page1.data.length, 3, 'Should return 3 results for page 1')
366
+
367
+ const page1Titles = page1.data.map(b => b.attributes.title)
368
+ assert.deepEqual(page1Titles, ['Book 01', 'Book 02', 'Book 03'])
369
369
 
370
370
  // Get second page
371
371
  const page2 = await basicApi.resources.books.query({
@@ -374,19 +374,19 @@ describe('Query Operations', () => {
374
374
  sort: ['title']
375
375
  },
376
376
  simplified: false
377
- });
377
+ })
378
+
379
+ validateJsonApiStructure(page2, true)
380
+ assert.equal(page2.data.length, 3, 'Should return 3 results for page 2')
378
381
 
379
- validateJsonApiStructure(page2, true);
380
- assert.equal(page2.data.length, 3, 'Should return 3 results for page 2');
381
-
382
- const page2Titles = page2.data.map(b => b.attributes.title);
383
- assert.deepEqual(page2Titles, ['Book 04', 'Book 05', 'Book 06']);
382
+ const page2Titles = page2.data.map(b => b.attributes.title)
383
+ assert.deepEqual(page2Titles, ['Book 04', 'Book 05', 'Book 06'])
384
384
 
385
385
  // Verify different results
386
- const page1Ids = page1.data.map(b => b.id);
387
- const page2Ids = page2.data.map(b => b.id);
388
- assert.equal(page1Ids.some(id => page2Ids.includes(id)), false, 'Pages should have different results');
389
- });
386
+ const page1Ids = page1.data.map(b => b.id)
387
+ const page2Ids = page2.data.map(b => b.id)
388
+ assert.equal(page1Ids.some(id => page2Ids.includes(id)), false, 'Pages should have different results')
389
+ })
390
390
 
391
391
  it('should handle last page with fewer results', async () => {
392
392
  const lastPage = await basicApi.resources.books.query({
@@ -395,12 +395,12 @@ describe('Query Operations', () => {
395
395
  sort: ['title']
396
396
  },
397
397
  simplified: false
398
- });
398
+ })
399
399
 
400
- validateJsonApiStructure(lastPage, true);
401
- assert.equal(lastPage.data.length, 1, 'Should return 1 result for last page');
402
- assert.equal(lastPage.data[0].attributes.title, 'Book 10');
403
- });
400
+ validateJsonApiStructure(lastPage, true)
401
+ assert.equal(lastPage.data.length, 1, 'Should return 1 result for last page')
402
+ assert.equal(lastPage.data[0].attributes.title, 'Book 10')
403
+ })
404
404
 
405
405
  it('should handle page beyond available data', async () => {
406
406
  const emptyPage = await basicApi.resources.books.query({
@@ -408,11 +408,11 @@ describe('Query Operations', () => {
408
408
  page: { number: 10, size: 5 }
409
409
  },
410
410
  simplified: false
411
- });
411
+ })
412
412
 
413
- validateJsonApiStructure(emptyPage, true);
414
- assert.equal(emptyPage.data.length, 0, 'Should return empty array for page beyond data');
415
- });
413
+ validateJsonApiStructure(emptyPage, true)
414
+ assert.equal(emptyPage.data.length, 0, 'Should return empty array for page beyond data')
415
+ })
416
416
 
417
417
  it('should respect queryMaxLimit limit', async () => {
418
418
  // Assuming queryMaxLimit is 100 (from vars)
@@ -421,34 +421,34 @@ describe('Query Operations', () => {
421
421
  page: { number: 1, size: 200 } // Request more than max
422
422
  },
423
423
  simplified: false
424
- });
424
+ })
425
425
 
426
- validateJsonApiStructure(result, true);
427
- assert(result.data.length <= 100, 'Should not exceed queryMaxLimit');
428
- });
429
- });
426
+ validateJsonApiStructure(result, true)
427
+ assert(result.data.length <= 100, 'Should not exceed queryMaxLimit')
428
+ })
429
+ })
430
430
 
431
431
  describe('Combined Query Features', () => {
432
432
  beforeEach(async () => {
433
433
  await cleanTables(knex, [
434
434
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors',
435
435
  'ext_countries', 'ext_publishers', 'ext_authors', 'ext_books', 'ext_book_authors'
436
- ]);
436
+ ])
437
437
 
438
438
  // Create test data for extended API with more fields
439
- const countries = [];
439
+ const countries = []
440
440
  for (const [name, code, pop] of [['USA', 'US', 300000000], ['UK', 'GB', 60000000], ['France', 'FR', 65000000]]) {
441
441
  const doc = createJsonApiDocument('countries', {
442
442
  name,
443
443
  code,
444
444
  population: pop,
445
445
  currency: code === 'US' ? 'USD' : code === 'GB' ? 'GBP' : 'EUR'
446
- });
446
+ })
447
447
  const result = await extendedApi.resources.countries.post({
448
448
  inputRecord: doc,
449
449
  simplified: false
450
- });
451
- countries.push(result.data);
450
+ })
451
+ countries.push(result.data)
452
452
  }
453
453
 
454
454
  // Create books with prices
@@ -458,23 +458,23 @@ describe('Query Operations', () => {
458
458
  { title: 'Medium Book C', price: 19.99, countryId: countries[1].id, language: 'en' },
459
459
  { title: 'Cheap Book D', price: 12.99, countryId: countries[1].id, language: 'fr' },
460
460
  { title: 'Expensive Book E', price: 39.99, countryId: countries[2].id, language: 'fr' }
461
- ];
461
+ ]
462
462
 
463
463
  for (const data of bookData) {
464
- const doc = createJsonApiDocument('books',
464
+ const doc = createJsonApiDocument('books',
465
465
  {
466
466
  title: data.title,
467
467
  price: data.price,
468
468
  language: data.language
469
469
  },
470
470
  { country: createRelationship(resourceIdentifier('countries', data.countryId)) }
471
- );
471
+ )
472
472
  await extendedApi.resources.books.post({
473
473
  inputRecord: doc,
474
474
  simplified: false
475
- });
475
+ })
476
476
  }
477
- });
477
+ })
478
478
 
479
479
  it('should filter, sort, and paginate together', async () => {
480
480
  const result = await extendedApi.resources.books.query({
@@ -484,15 +484,15 @@ describe('Query Operations', () => {
484
484
  page: { number: 1, size: 2 }
485
485
  },
486
486
  simplified: false
487
- });
487
+ })
488
+
489
+ validateJsonApiStructure(result, true)
490
+ assert.equal(result.data.length, 2)
488
491
 
489
- validateJsonApiStructure(result, true);
490
- assert.equal(result.data.length, 2);
491
-
492
492
  // Should get the 2 most expensive English books
493
- assert.equal(result.data[0].attributes.title, 'Expensive Book B');
494
- assert.equal(result.data[1].attributes.title, 'Medium Book C');
495
- });
493
+ assert.equal(result.data[0].attributes.title, 'Expensive Book B')
494
+ assert.equal(result.data[1].attributes.title, 'Medium Book C')
495
+ })
496
496
 
497
497
  it('should filter and include relationships', async () => {
498
498
  const result = await extendedApi.resources.books.query({
@@ -501,21 +501,21 @@ describe('Query Operations', () => {
501
501
  include: ['country']
502
502
  },
503
503
  simplified: false
504
- });
504
+ })
505
+
506
+ validateJsonApiStructure(result, true)
507
+ assert.equal(result.data.length, 2, 'Should return 2 French books')
505
508
 
506
- validateJsonApiStructure(result, true);
507
- assert.equal(result.data.length, 2, 'Should return 2 French books');
508
-
509
509
  // Verify includes
510
- assert(result.included, 'Should have included data');
511
- const includedCountries = result.included.filter(r => r.type === 'countries');
512
- assert(includedCountries.length >= 1, 'Should include at least one country');
513
-
510
+ assert(result.included, 'Should have included data')
511
+ const includedCountries = result.included.filter(r => r.type === 'countries')
512
+ assert(includedCountries.length >= 1, 'Should include at least one country')
513
+
514
514
  // Verify included countries have the expected fields
515
515
  for (const country of includedCountries) {
516
- assert(country.attributes.name);
517
- assert(country.attributes.currency);
516
+ assert(country.attributes.name)
517
+ assert(country.attributes.currency)
518
518
  }
519
- });
520
- });
521
- });
519
+ })
520
+ })
521
+ })