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,14 +1,14 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import {
5
- validateJsonApiStructure,
6
- cleanTables,
7
- createJsonApiDocument,
1
+ import { describe, it, before, after, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import {
5
+ validateJsonApiStructure,
6
+ cleanTables,
7
+ createJsonApiDocument,
8
8
  createRelationship,
9
- resourceIdentifier
10
- } from './helpers/test-utils.js';
11
- import { createPaginationApi } from './fixtures/api-configs.js';
9
+ resourceIdentifier
10
+ } from './helpers/test-utils.js'
11
+ import { createPaginationApi } from './fixtures/api-configs.js'
12
12
 
13
13
  // Create Knex instance for tests
14
14
  const knex = knexLib({
@@ -17,83 +17,83 @@ const knex = knexLib({
17
17
  filename: ':memory:'
18
18
  },
19
19
  useNullAsDefault: true
20
- });
20
+ })
21
21
 
22
22
  // API instance that persists across tests
23
- let api;
23
+ let api
24
24
 
25
25
  describe('Enhanced Pagination Features', () => {
26
26
  before(async () => {
27
27
  // Initialize API once with pagination features
28
- api = await createPaginationApi(knex);
29
- });
28
+ api = await createPaginationApi(knex)
29
+ })
30
30
 
31
31
  after(async () => {
32
32
  // Close database connection
33
- await knex.destroy();
34
- });
33
+ await knex.destroy()
34
+ })
35
35
 
36
36
  describe('Self Links', () => {
37
37
  beforeEach(async () => {
38
38
  await cleanTables(knex, [
39
39
  'pagination_countries', 'pagination_publishers', 'pagination_books'
40
- ]);
40
+ ])
41
41
 
42
42
  // Create test data
43
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' });
43
+ const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
44
44
  const countryResult = await api.resources.countries.post({
45
45
  inputRecord: countryDoc,
46
46
  simplified: false
47
- });
47
+ })
48
48
 
49
49
  // Create some books
50
50
  for (let i = 1; i <= 3; i++) {
51
- const doc = createJsonApiDocument('books',
51
+ const doc = createJsonApiDocument('books',
52
52
  { title: `Book ${i}` },
53
53
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
54
- );
54
+ )
55
55
  await api.resources.books.post({
56
56
  inputRecord: doc,
57
57
  simplified: false
58
- });
58
+ })
59
59
  }
60
- });
60
+ })
61
61
 
62
62
  it('should add self links to individual resources', async () => {
63
- const list = await api.resources.books.query({ simplified: false });
64
- const firstId = list.data[0]?.id;
63
+ const list = await api.resources.books.query({ simplified: false })
64
+ const firstId = list.data[0]?.id
65
65
 
66
66
  const result = await api.resources.books.get({
67
67
  id: firstId,
68
68
  simplified: false
69
- });
69
+ })
70
70
 
71
- validateJsonApiStructure(result, false);
72
- assert(result.data.links, 'Resource should have links');
73
- assert.equal(result.data.links.self, `/books/${firstId}`);
74
- assert(result.links, 'Response should have top-level links');
75
- assert.equal(result.links.self, `/books/${firstId}`);
76
- });
71
+ validateJsonApiStructure(result, false)
72
+ assert(result.data.links, 'Resource should have links')
73
+ assert.equal(result.data.links.self, `/books/${firstId}`)
74
+ assert(result.links, 'Response should have top-level links')
75
+ assert.equal(result.links.self, `/books/${firstId}`)
76
+ })
77
77
 
78
78
  it('should add self links to collection resources', async () => {
79
79
  const result = await api.resources.books.query({
80
80
  queryParams: {},
81
81
  simplified: false
82
- });
82
+ })
83
+
84
+ validateJsonApiStructure(result, true)
83
85
 
84
- validateJsonApiStructure(result, true);
85
-
86
86
  // Check individual resource links
87
87
  result.data.forEach(book => {
88
- assert(book.links, 'Each resource should have links');
89
- assert(book.links.self, 'Each resource should have self link');
90
- assert(book.links.self.startsWith('/books/'));
91
- });
88
+ assert(book.links, 'Each resource should have links')
89
+ assert(book.links.self, 'Each resource should have self link')
90
+ assert(book.links.self.startsWith('/books/'))
91
+ })
92
92
 
93
93
  // Check top-level links
94
- assert(result.links, 'Response should have top-level links');
95
- assert.equal(result.links.self, '/books');
96
- });
94
+ assert(result.links, 'Response should have top-level links')
95
+ assert.equal(result.links.self, '/books')
96
+ })
97
97
 
98
98
  it('should add self links to included resources', async () => {
99
99
  const result = await api.resources.books.query({
@@ -101,63 +101,63 @@ describe('Enhanced Pagination Features', () => {
101
101
  include: ['country']
102
102
  },
103
103
  simplified: false
104
- });
104
+ })
105
+
106
+ validateJsonApiStructure(result, true)
107
+ assert(result.included, 'Should have included resources')
105
108
 
106
- validateJsonApiStructure(result, true);
107
- assert(result.included, 'Should have included resources');
108
-
109
109
  result.included.forEach(resource => {
110
- assert(resource.links, 'Included resource should have links');
111
- assert(resource.links.self, 'Included resource should have self link');
112
- assert(resource.links.self.startsWith('/'));
113
- });
114
- });
110
+ assert(resource.links, 'Included resource should have links')
111
+ assert(resource.links.self, 'Included resource should have self link')
112
+ assert(resource.links.self.startsWith('/'))
113
+ })
114
+ })
115
115
 
116
116
  it('should add relative self links when no URL prefix configured', async () => {
117
117
  // URLs are now always relative unless overridden via context
118
118
  const result = await api.resources.books.query({
119
119
  queryParams: {},
120
120
  simplified: false
121
- });
121
+ })
122
+
123
+ validateJsonApiStructure(result, true)
122
124
 
123
- validateJsonApiStructure(result, true);
124
-
125
125
  // Links should be present and relative
126
126
  result.data.forEach(book => {
127
- assert(book.links, 'Resources should have links');
128
- assert(book.links.self, 'Each resource should have self link');
129
- assert(book.links.self.startsWith('/books/'), 'Self link should be relative');
130
- });
131
- assert(result.links, 'Response should have top-level links');
132
- assert(result.links.self.startsWith('/books'), 'Top-level self link should be relative');
133
- });
134
- });
127
+ assert(book.links, 'Resources should have links')
128
+ assert(book.links.self, 'Each resource should have self link')
129
+ assert(book.links.self.startsWith('/books/'), 'Self link should be relative')
130
+ })
131
+ assert(result.links, 'Response should have top-level links')
132
+ assert(result.links.self.startsWith('/books'), 'Top-level self link should be relative')
133
+ })
134
+ })
135
135
 
136
136
  describe('Pagination Metadata', () => {
137
137
  beforeEach(async () => {
138
138
  await cleanTables(knex, [
139
139
  'pagination_countries', 'pagination_publishers', 'pagination_books'
140
- ]);
140
+ ])
141
141
 
142
142
  // Create test data
143
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' });
143
+ const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
144
144
  const countryResult = await api.resources.countries.post({
145
145
  inputRecord: countryDoc,
146
146
  simplified: false
147
- });
147
+ })
148
148
 
149
149
  // Create 10 books for pagination testing
150
150
  for (let i = 1; i <= 10; i++) {
151
- const doc = createJsonApiDocument('books',
151
+ const doc = createJsonApiDocument('books',
152
152
  { title: `Book ${String(i).padStart(2, '0')}` },
153
153
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
154
- );
154
+ )
155
155
  await api.resources.books.post({
156
156
  inputRecord: doc,
157
157
  simplified: false
158
- });
158
+ })
159
159
  }
160
- });
160
+ })
161
161
 
162
162
  it('should include pagination metadata in response', async () => {
163
163
  const result = await api.resources.books.query({
@@ -165,19 +165,19 @@ describe('Enhanced Pagination Features', () => {
165
165
  page: { number: 2, size: 3 }
166
166
  },
167
167
  simplified: false
168
- });
169
-
170
- validateJsonApiStructure(result, true);
171
- assert(result.meta, 'Response should have meta');
172
- assert(result.meta.pagination, 'Meta should have pagination');
173
-
174
- const pagination = result.meta.pagination;
175
- assert.equal(pagination.page, 2);
176
- assert.equal(pagination.pageSize, 3);
177
- assert.equal(pagination.total, 10);
178
- assert.equal(pagination.pageCount, 4);
179
- assert.equal(pagination.hasMore, true);
180
- });
168
+ })
169
+
170
+ validateJsonApiStructure(result, true)
171
+ assert(result.meta, 'Response should have meta')
172
+ assert(result.meta.pagination, 'Meta should have pagination')
173
+
174
+ const pagination = result.meta.pagination
175
+ assert.equal(pagination.page, 2)
176
+ assert.equal(pagination.pageSize, 3)
177
+ assert.equal(pagination.total, 10)
178
+ assert.equal(pagination.pageCount, 4)
179
+ assert.equal(pagination.hasMore, true)
180
+ })
181
181
 
182
182
  it('should calculate hasMore correctly for last page', async () => {
183
183
  const result = await api.resources.books.query({
@@ -185,12 +185,12 @@ describe('Enhanced Pagination Features', () => {
185
185
  page: { number: 4, size: 3 }
186
186
  },
187
187
  simplified: false
188
- });
188
+ })
189
189
 
190
- assert.equal(result.meta.pagination.hasMore, false);
191
- assert.equal(result.meta.pagination.page, 4);
192
- assert.equal(result.meta.pagination.pageCount, 4);
193
- });
190
+ assert.equal(result.meta.pagination.hasMore, false)
191
+ assert.equal(result.meta.pagination.page, 4)
192
+ assert.equal(result.meta.pagination.pageCount, 4)
193
+ })
194
194
 
195
195
  it('should include pagination links', async () => {
196
196
  const result = await api.resources.books.query({
@@ -198,23 +198,23 @@ describe('Enhanced Pagination Features', () => {
198
198
  page: { number: 2, size: 3 }
199
199
  },
200
200
  simplified: false
201
- });
201
+ })
202
202
 
203
- assert(result.links, 'Response should have links');
204
- assert(result.links.self, 'Should have self link');
205
- assert(result.links.first, 'Should have first link');
206
- assert(result.links.last, 'Should have last link');
207
- assert(result.links.prev, 'Should have prev link');
208
- assert(result.links.next, 'Should have next link');
203
+ assert(result.links, 'Response should have links')
204
+ assert(result.links.self, 'Should have self link')
205
+ assert(result.links.first, 'Should have first link')
206
+ assert(result.links.last, 'Should have last link')
207
+ assert(result.links.prev, 'Should have prev link')
208
+ assert(result.links.next, 'Should have next link')
209
209
 
210
210
  // Verify link structure
211
- assert(result.links.self.includes('page[number]=2'));
212
- assert(result.links.self.includes('page[size]=3'));
213
- assert(result.links.first.includes('page[number]=1'));
214
- assert(result.links.last.includes('page[number]=4'));
215
- assert(result.links.prev.includes('page[number]=1'));
216
- assert(result.links.next.includes('page[number]=3'));
217
- });
211
+ assert(result.links.self.includes('page[number]=2'))
212
+ assert(result.links.self.includes('page[size]=3'))
213
+ assert(result.links.first.includes('page[number]=1'))
214
+ assert(result.links.last.includes('page[number]=4'))
215
+ assert(result.links.prev.includes('page[number]=1'))
216
+ assert(result.links.next.includes('page[number]=3'))
217
+ })
218
218
 
219
219
  it('should not include prev link on first page', async () => {
220
220
  const result = await api.resources.books.query({
@@ -222,12 +222,12 @@ describe('Enhanced Pagination Features', () => {
222
222
  page: { number: 1, size: 3 }
223
223
  },
224
224
  simplified: false
225
- });
225
+ })
226
226
 
227
- assert(result.links.first);
228
- assert(result.links.next);
229
- assert(!result.links.prev, 'First page should not have prev link');
230
- });
227
+ assert(result.links.first)
228
+ assert(result.links.next)
229
+ assert(!result.links.prev, 'First page should not have prev link')
230
+ })
231
231
 
232
232
  it('should not include next link on last page', async () => {
233
233
  const result = await api.resources.books.query({
@@ -235,12 +235,12 @@ describe('Enhanced Pagination Features', () => {
235
235
  page: { number: 4, size: 3 }
236
236
  },
237
237
  simplified: false
238
- });
238
+ })
239
239
 
240
- assert(result.links.first);
241
- assert(result.links.prev);
242
- assert(!result.links.next, 'Last page should not have next link');
243
- });
240
+ assert(result.links.first)
241
+ assert(result.links.prev)
242
+ assert(!result.links.next, 'Last page should not have next link')
243
+ })
244
244
 
245
245
  it('should preserve other query parameters in pagination links', async () => {
246
246
  const result = await api.resources.books.query({
@@ -249,43 +249,43 @@ describe('Enhanced Pagination Features', () => {
249
249
  sort: ['-title']
250
250
  },
251
251
  simplified: false
252
- });
252
+ })
253
253
 
254
- assert(result.links, 'Should have links');
255
- assert(result.links.next, 'Should have next link since page 2 of 4');
256
- assert(result.links.next.includes('sort=-title'));
257
- assert(result.links.next.includes('page[number]=3'));
258
- assert(result.links.next.includes('page[size]=3'));
259
- });
260
- });
254
+ assert(result.links, 'Should have links')
255
+ assert(result.links.next, 'Should have next link since page 2 of 4')
256
+ assert(result.links.next.includes('sort=-title'))
257
+ assert(result.links.next.includes('page[number]=3'))
258
+ assert(result.links.next.includes('page[size]=3'))
259
+ })
260
+ })
261
261
 
262
262
  describe('Cursor-based Pagination', () => {
263
263
  beforeEach(async () => {
264
264
  await cleanTables(knex, [
265
265
  'pagination_countries', 'pagination_publishers', 'pagination_books'
266
- ]);
266
+ ])
267
267
 
268
268
  // Create test data
269
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' });
269
+ const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
270
270
  const countryResult = await api.resources.countries.post({
271
271
  inputRecord: countryDoc,
272
272
  simplified: false
273
- });
273
+ })
274
274
 
275
275
  // Create books
276
276
  for (let i = 1; i <= 15; i++) {
277
- const doc = createJsonApiDocument('books',
278
- {
277
+ const doc = createJsonApiDocument('books',
278
+ {
279
279
  title: `Book ${String(i).padStart(2, '0')}`
280
280
  },
281
281
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
282
- );
282
+ )
283
283
  await api.resources.books.post({
284
284
  inputRecord: doc,
285
285
  simplified: false
286
- });
286
+ })
287
287
  }
288
- });
288
+ })
289
289
 
290
290
  it('should support cursor-based pagination with after parameter', async () => {
291
291
  // Get first page
@@ -295,33 +295,33 @@ describe('Enhanced Pagination Features', () => {
295
295
  sort: ['-id']
296
296
  },
297
297
  simplified: false
298
- });
298
+ })
299
+
300
+ validateJsonApiStructure(firstPage, true)
301
+ assert.equal(firstPage.data.length, 5)
302
+ assert(firstPage.meta.pagination.hasMore)
303
+ assert(firstPage.meta.pagination.cursor?.next)
299
304
 
300
- validateJsonApiStructure(firstPage, true);
301
- assert.equal(firstPage.data.length, 5);
302
- assert(firstPage.meta.pagination.hasMore);
303
- assert(firstPage.meta.pagination.cursor?.next);
304
-
305
305
  // Get next page using cursor
306
306
  const secondPage = await api.resources.books.query({
307
307
  queryParams: {
308
- page: {
308
+ page: {
309
309
  size: 5,
310
310
  after: firstPage.meta.pagination.cursor.next
311
311
  },
312
312
  sort: ['-id']
313
313
  },
314
314
  simplified: false
315
- });
315
+ })
316
+
317
+ validateJsonApiStructure(secondPage, true)
318
+ assert.equal(secondPage.data.length, 5)
316
319
 
317
- validateJsonApiStructure(secondPage, true);
318
- assert.equal(secondPage.data.length, 5);
319
-
320
320
  // Verify different records
321
- const firstPageIds = firstPage.data.map(b => b.id);
322
- const secondPageIds = secondPage.data.map(b => b.id);
323
- assert(!firstPageIds.some(id => secondPageIds.includes(id)), 'Pages should have different records');
324
- });
321
+ const firstPageIds = firstPage.data.map(b => b.id)
322
+ const secondPageIds = secondPage.data.map(b => b.id)
323
+ assert(!firstPageIds.some(id => secondPageIds.includes(id)), 'Pages should have different records')
324
+ })
325
325
 
326
326
  it('should include cursor pagination links', async () => {
327
327
  const result = await api.resources.books.query({
@@ -329,69 +329,69 @@ describe('Enhanced Pagination Features', () => {
329
329
  page: { size: 5 }
330
330
  },
331
331
  simplified: false
332
- });
332
+ })
333
333
 
334
- assert(result.links);
335
- assert(result.links.self);
336
- assert(result.links.first);
337
- assert(result.links.next);
338
- assert(result.links.next.includes('page[after]='), 'Next link should include cursor parameter');
339
- });
334
+ assert(result.links)
335
+ assert(result.links.self)
336
+ assert(result.links.first)
337
+ assert(result.links.next)
338
+ assert(result.links.next.includes('page[after]='), 'Next link should include cursor parameter')
339
+ })
340
340
 
341
341
  it('should handle last page with cursor pagination', async () => {
342
342
  // Get to last page
343
- let currentCursor = null;
344
- let result;
345
-
343
+ let currentCursor = null
344
+ let result
345
+
346
346
  // Navigate through pages
347
347
  for (let i = 0; i < 3; i++) {
348
348
  result = await api.resources.books.query({
349
349
  queryParams: {
350
- page: {
350
+ page: {
351
351
  size: 5,
352
352
  ...(currentCursor && { after: currentCursor })
353
353
  }
354
354
  },
355
355
  simplified: false
356
- });
357
-
356
+ })
357
+
358
358
  if (result.meta.pagination.cursor?.next) {
359
- currentCursor = result.meta.pagination.cursor.next;
359
+ currentCursor = result.meta.pagination.cursor.next
360
360
  }
361
361
  }
362
362
 
363
363
  // Last page should have no next cursor
364
- assert.equal(result.meta.pagination.hasMore, false);
365
- assert(!result.meta.pagination.cursor?.next);
366
- assert(!result.links.next);
367
- });
368
- });
364
+ assert.equal(result.meta.pagination.hasMore, false)
365
+ assert(!result.meta.pagination.cursor?.next)
366
+ assert(!result.links.next)
367
+ })
368
+ })
369
369
 
370
370
  describe('Count Query Configuration', () => {
371
371
  beforeEach(async () => {
372
372
  await cleanTables(knex, [
373
373
  'pagination_countries', 'pagination_publishers', 'pagination_books'
374
- ]);
374
+ ])
375
375
 
376
376
  // Create test data
377
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' });
377
+ const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
378
378
  const countryResult = await api.resources.countries.post({
379
379
  inputRecord: countryDoc,
380
380
  simplified: false
381
- });
381
+ })
382
382
 
383
383
  // Create books
384
384
  for (let i = 1; i <= 20; i++) {
385
- const doc = createJsonApiDocument('books',
385
+ const doc = createJsonApiDocument('books',
386
386
  { title: `Book ${i}` },
387
387
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
388
- );
388
+ )
389
389
  await api.resources.books.post({
390
390
  inputRecord: doc,
391
391
  simplified: false
392
- });
392
+ })
393
393
  }
394
- });
394
+ })
395
395
 
396
396
  it('should include count by default', async () => {
397
397
  const result = await api.resources.books.query({
@@ -399,75 +399,75 @@ describe('Enhanced Pagination Features', () => {
399
399
  page: { number: 1, size: 5 }
400
400
  },
401
401
  simplified: false
402
- });
402
+ })
403
403
 
404
- assert(result.meta.pagination.total !== undefined);
405
- assert.equal(result.meta.pagination.total, 20);
406
- assert.equal(result.meta.pagination.pageCount, 4);
407
- });
404
+ assert(result.meta.pagination.total !== undefined)
405
+ assert.equal(result.meta.pagination.total, 20)
406
+ assert.equal(result.meta.pagination.pageCount, 4)
407
+ })
408
408
 
409
409
  it('should respect enablePaginationCounts when set to false', async () => {
410
410
  // Override the enablePaginationCounts setting for this scope
411
- const booksScope = api.resources.books;
412
- const originalSetting = booksScope.vars.enablePaginationCounts;
413
- booksScope.vars.enablePaginationCounts = false;
414
-
411
+ const booksScope = api.resources.books
412
+ const originalSetting = booksScope.vars.enablePaginationCounts
413
+ booksScope.vars.enablePaginationCounts = false
414
+
415
415
  const result = await api.resources.books.query({
416
416
  queryParams: {
417
417
  page: { number: 1, size: 5 }
418
418
  },
419
419
  simplified: false
420
- });
421
-
422
- assert(result.meta.pagination);
423
- assert.equal(result.meta.pagination.page, 1);
424
- assert.equal(result.meta.pagination.pageSize, 5);
425
- assert(result.meta.pagination.total === undefined, 'Should not include total when counts disabled');
426
- assert(result.meta.pagination.pageCount === undefined, 'Should not include pageCount when counts disabled');
427
- assert(result.meta.pagination.hasMore === undefined, 'Should not include hasMore when counts disabled');
428
-
420
+ })
421
+
422
+ assert(result.meta.pagination)
423
+ assert.equal(result.meta.pagination.page, 1)
424
+ assert.equal(result.meta.pagination.pageSize, 5)
425
+ assert(result.meta.pagination.total === undefined, 'Should not include total when counts disabled')
426
+ assert(result.meta.pagination.pageCount === undefined, 'Should not include pageCount when counts disabled')
427
+ assert(result.meta.pagination.hasMore === undefined, 'Should not include hasMore when counts disabled')
428
+
429
429
  // Restore the setting
430
- booksScope.vars.enablePaginationCounts = originalSetting;
431
- });
432
- });
430
+ booksScope.vars.enablePaginationCounts = originalSetting
431
+ })
432
+ })
433
433
 
434
434
  describe('Combined Features', () => {
435
- let usCountryId;
436
-
435
+ let usCountryId
436
+
437
437
  beforeEach(async () => {
438
438
  await cleanTables(knex, [
439
439
  'pagination_countries', 'pagination_publishers', 'pagination_books'
440
- ]);
440
+ ])
441
441
 
442
442
  // Create multiple countries
443
- const countries = [];
443
+ const countries = []
444
444
  for (const code of ['US', 'UK', 'FR']) {
445
- const doc = createJsonApiDocument('countries', { name: `Country ${code}`, code });
445
+ const doc = createJsonApiDocument('countries', { name: `Country ${code}`, code })
446
446
  const result = await api.resources.countries.post({
447
447
  inputRecord: doc,
448
448
  simplified: false
449
- });
450
- countries.push(result.data);
449
+ })
450
+ countries.push(result.data)
451
451
  if (code === 'US') {
452
- usCountryId = result.data.id;
452
+ usCountryId = result.data.id
453
453
  }
454
454
  }
455
455
 
456
456
  // Create books distributed across countries
457
- let bookIndex = 1;
457
+ let bookIndex = 1
458
458
  for (const country of countries) {
459
459
  for (let i = 0; i < 8; i++) {
460
- const doc = createJsonApiDocument('books',
460
+ const doc = createJsonApiDocument('books',
461
461
  { title: `Book ${String(bookIndex++).padStart(2, '0')}` },
462
462
  { country: createRelationship(resourceIdentifier('countries', country.id)) }
463
- );
463
+ )
464
464
  await api.resources.books.post({
465
465
  inputRecord: doc,
466
466
  simplified: false
467
- });
467
+ })
468
468
  }
469
469
  }
470
- });
470
+ })
471
471
 
472
472
  it('should combine pagination with filtering and includes', async () => {
473
473
  const result = await api.resources.books.query({
@@ -480,33 +480,33 @@ describe('Enhanced Pagination Features', () => {
480
480
  sort: ['title']
481
481
  },
482
482
  simplified: false
483
- });
483
+ })
484
+
485
+ validateJsonApiStructure(result, true)
484
486
 
485
- validateJsonApiStructure(result, true);
486
-
487
487
  // Check pagination worked
488
- assert.equal(result.data.length, 5);
489
- assert.equal(result.meta.pagination.total, 8);
490
- assert.equal(result.meta.pagination.pageCount, 2);
491
-
488
+ assert.equal(result.data.length, 5)
489
+ assert.equal(result.meta.pagination.total, 8)
490
+ assert.equal(result.meta.pagination.pageCount, 2)
491
+
492
492
  // Check filtering worked
493
- const countryIds = result.data.map(b => b.relationships.country.data.id);
494
- assert(countryIds.every(id => id === countryIds[0]), 'All books should be from same country');
495
-
493
+ const countryIds = result.data.map(b => b.relationships.country.data.id)
494
+ assert(countryIds.every(id => id === countryIds[0]), 'All books should be from same country')
495
+
496
496
  // Check includes worked
497
- assert(result.included);
498
- const usCountry = result.included.find(r => r.type === 'countries' && r.attributes.code === 'US');
499
- assert(usCountry);
500
-
497
+ assert(result.included)
498
+ const usCountry = result.included.find(r => r.type === 'countries' && r.attributes.code === 'US')
499
+ assert(usCountry)
500
+
501
501
  // Check self links present
502
502
  result.data.forEach(book => {
503
- assert(book.links.self);
504
- });
505
-
503
+ assert(book.links.self)
504
+ })
505
+
506
506
  // Check pagination links include all parameters
507
- assert(result.links.next.includes(`filters[country]=${usCountryId}`));
508
- assert(result.links.next.includes('include=country'));
509
- assert(result.links.next.includes('sort=title'));
510
- });
511
- });
512
- });
507
+ assert(result.links.next.includes(`filters[country]=${usCountryId}`))
508
+ assert(result.links.next.includes('include=country'))
509
+ assert(result.links.next.includes('sort=title'))
510
+ })
511
+ })
512
+ })