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,43 +21,43 @@ 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('Relationship 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('One-to-One (belongsTo) Relationships', () => {
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
  it('should create resource with belongsTo relationship', async () => {
51
51
  // Create a country first
52
52
  const countryDoc = createJsonApiDocument('countries', {
53
53
  name: 'United States',
54
54
  code: 'US'
55
- });
55
+ })
56
56
 
57
57
  const countryResult = await basicApi.resources.countries.post({
58
58
  inputRecord: countryDoc,
59
59
  simplified: false
60
- });
60
+ })
61
61
 
62
62
  // Create publisher with country relationship
63
63
  const publisherDoc = createJsonApiDocument('publishers',
@@ -67,18 +67,18 @@ describe('Relationship Operations', () => {
67
67
  {
68
68
  country: createRelationship(resourceIdentifier('countries', countryResult.data.id))
69
69
  }
70
- );
70
+ )
71
71
 
72
72
  const publisherResult = await basicApi.resources.publishers.post({
73
73
  inputRecord: publisherDoc,
74
74
  simplified: false
75
- });
75
+ })
76
76
 
77
77
  // Verify response
78
- validateJsonApiStructure(publisherResult, false);
79
- assert.equal(publisherResult.data.type, 'publishers');
78
+ validateJsonApiStructure(publisherResult, false)
79
+ assert.equal(publisherResult.data.type, 'publishers')
80
80
  assertResourceRelationship(publisherResult.data, 'country',
81
- resourceIdentifier('countries', countryResult.data.id));
81
+ resourceIdentifier('countries', countryResult.data.id))
82
82
 
83
83
  // Verify through GET with include
84
84
  const getResult = await basicApi.resources.publishers.get({
@@ -87,40 +87,40 @@ describe('Relationship Operations', () => {
87
87
  include: ['country']
88
88
  },
89
89
  simplified: false
90
- });
90
+ })
91
91
 
92
- validateJsonApiStructure(getResult, false);
93
- assert(getResult.included, 'Should have included data');
94
- assert.equal(getResult.included.length, 1);
95
- assert.equal(getResult.included[0].type, 'countries');
96
- assert.equal(getResult.included[0].attributes.name, 'United States');
97
- });
92
+ validateJsonApiStructure(getResult, false)
93
+ assert(getResult.included, 'Should have included data')
94
+ assert.equal(getResult.included.length, 1)
95
+ assert.equal(getResult.included[0].type, 'countries')
96
+ assert.equal(getResult.included[0].attributes.name, 'United States')
97
+ })
98
98
 
99
99
  it('should update belongsTo relationship via PATCH', async () => {
100
100
  // Create two countries
101
- const usDoc = createJsonApiDocument('countries', { name: 'United States', code: 'US' });
102
- const ukDoc = createJsonApiDocument('countries', { name: 'United Kingdom', code: 'UK' });
101
+ const usDoc = createJsonApiDocument('countries', { name: 'United States', code: 'US' })
102
+ const ukDoc = createJsonApiDocument('countries', { name: 'United Kingdom', code: 'UK' })
103
103
 
104
104
  const usResult = await basicApi.resources.countries.post({
105
105
  inputRecord: usDoc,
106
106
  simplified: false
107
- });
107
+ })
108
108
 
109
109
  const ukResult = await basicApi.resources.countries.post({
110
110
  inputRecord: ukDoc,
111
111
  simplified: false
112
- });
112
+ })
113
113
 
114
114
  // Create publisher with US country
115
115
  const publisherDoc = createJsonApiDocument('publishers',
116
116
  { name: 'Test Publisher' },
117
117
  { country: createRelationship(resourceIdentifier('countries', usResult.data.id)) }
118
- );
118
+ )
119
119
 
120
120
  const publisherResult = await basicApi.resources.publishers.post({
121
121
  inputRecord: publisherDoc,
122
122
  simplified: false
123
- });
123
+ })
124
124
 
125
125
  // Update publisher to UK country
126
126
  const patchDoc = {
@@ -131,43 +131,43 @@ describe('Relationship Operations', () => {
131
131
  country: createRelationship(resourceIdentifier('countries', ukResult.data.id))
132
132
  }
133
133
  }
134
- };
134
+ }
135
135
 
136
136
  await basicApi.resources.publishers.patch({
137
137
  id: publisherResult.data.id,
138
138
  inputRecord: patchDoc,
139
139
  simplified: false
140
- });
140
+ })
141
141
 
142
142
  // Verify update
143
143
  const getResult = await basicApi.resources.publishers.get({
144
144
  id: publisherResult.data.id,
145
145
  queryParams: { include: ['country'] },
146
146
  simplified: false
147
- });
147
+ })
148
148
 
149
149
  assertResourceRelationship(getResult.data, 'country',
150
- resourceIdentifier('countries', ukResult.data.id));
151
- assert.equal(getResult.included[0].attributes.code, 'UK');
152
- });
150
+ resourceIdentifier('countries', ukResult.data.id))
151
+ assert.equal(getResult.included[0].attributes.code, 'UK')
152
+ })
153
153
 
154
154
  it('should clear belongsTo relationship with null', async () => {
155
155
  // Create country and publisher
156
- const countryDoc = createJsonApiDocument('countries', { name: 'France', code: 'FR' });
156
+ const countryDoc = createJsonApiDocument('countries', { name: 'France', code: 'FR' })
157
157
  const countryResult = await basicApi.resources.countries.post({
158
158
  inputRecord: countryDoc,
159
159
  simplified: false
160
- });
160
+ })
161
161
 
162
162
  const publisherDoc = createJsonApiDocument('publishers',
163
163
  { name: 'French Publisher' },
164
164
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
165
- );
165
+ )
166
166
 
167
167
  const publisherResult = await basicApi.resources.publishers.post({
168
168
  inputRecord: publisherDoc,
169
169
  simplified: false
170
- });
170
+ })
171
171
 
172
172
  // Clear the relationship
173
173
  const patchDoc = {
@@ -178,53 +178,53 @@ describe('Relationship Operations', () => {
178
178
  country: { data: null }
179
179
  }
180
180
  }
181
- };
181
+ }
182
182
 
183
183
  await basicApi.resources.publishers.patch({
184
184
  id: publisherResult.data.id,
185
185
  inputRecord: patchDoc,
186
186
  simplified: false
187
- });
187
+ })
188
188
 
189
189
  // Verify relationship cleared
190
190
  const getResult = await basicApi.resources.publishers.get({
191
191
  id: publisherResult.data.id,
192
192
  simplified: false
193
- });
193
+ })
194
194
 
195
- assert(getResult.data.relationships);
196
- assert.equal(getResult.data.relationships.country.data, null);
197
- });
198
- });
195
+ assert(getResult.data.relationships)
196
+ assert.equal(getResult.data.relationships.country.data, null)
197
+ })
198
+ })
199
199
 
200
200
  describe('Many-to-Many Relationships', () => {
201
201
  beforeEach(async () => {
202
202
  await cleanTables(knex, [
203
203
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
204
- ]);
205
- });
204
+ ])
205
+ })
206
206
 
207
207
  it('should create book with multiple authors (many-to-many)', async () => {
208
208
  // Create country for book
209
- const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' });
209
+ const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
210
210
  const countryResult = await basicApi.resources.countries.post({
211
211
  inputRecord: countryDoc,
212
212
  simplified: false
213
- });
213
+ })
214
214
 
215
215
  // Create authors
216
- const author1Doc = createJsonApiDocument('authors', { name: 'Stephen King' });
217
- const author2Doc = createJsonApiDocument('authors', { name: 'Peter Straub' });
216
+ const author1Doc = createJsonApiDocument('authors', { name: 'Stephen King' })
217
+ const author2Doc = createJsonApiDocument('authors', { name: 'Peter Straub' })
218
218
 
219
219
  const author1Result = await basicApi.resources.authors.post({
220
220
  inputRecord: author1Doc,
221
221
  simplified: false
222
- });
222
+ })
223
223
 
224
224
  const author2Result = await basicApi.resources.authors.post({
225
225
  inputRecord: author2Doc,
226
226
  simplified: false
227
- });
227
+ })
228
228
 
229
229
  // Create book with authors
230
230
  const bookDoc = createJsonApiDocument('books',
@@ -238,56 +238,56 @@ describe('Relationship Operations', () => {
238
238
  resourceIdentifier('authors', author2Result.data.id)
239
239
  ])
240
240
  }
241
- );
241
+ )
242
242
 
243
243
  const bookResult = await basicApi.resources.books.post({
244
244
  inputRecord: bookDoc,
245
245
  simplified: false
246
- });
246
+ })
247
247
 
248
248
  // Verify response
249
- validateJsonApiStructure(bookResult, false);
250
- assert.equal(bookResult.data.type, 'books');
249
+ validateJsonApiStructure(bookResult, false)
250
+ assert.equal(bookResult.data.type, 'books')
251
251
 
252
252
  // Verify pivot table records created
253
- const pivotCount = await countRecords(knex, 'basic_book_authors');
254
- assert.equal(pivotCount, 2, 'Should have created 2 pivot records');
253
+ const pivotCount = await countRecords(knex, 'basic_book_authors')
254
+ assert.equal(pivotCount, 2, 'Should have created 2 pivot records')
255
255
 
256
256
  // GET book with authors included
257
257
  const getResult = await basicApi.resources.books.get({
258
258
  id: bookResult.data.id,
259
259
  queryParams: { include: ['authors'] },
260
260
  simplified: false
261
- });
261
+ })
262
+
263
+ validateJsonApiStructure(getResult, false)
264
+ assert(getResult.data.relationships.authors)
265
+ assert.equal(getResult.data.relationships.authors.data.length, 2)
262
266
 
263
- validateJsonApiStructure(getResult, false);
264
- assert(getResult.data.relationships.authors);
265
- assert.equal(getResult.data.relationships.authors.data.length, 2);
266
-
267
267
  // Verify included authors
268
- assert(getResult.included);
269
- assert.equal(getResult.included.length, 2);
270
- const authorNames = getResult.included.map(a => a.attributes.name).sort();
271
- assert.deepEqual(authorNames, ['Peter Straub', 'Stephen King']);
272
- });
268
+ assert(getResult.included)
269
+ assert.equal(getResult.included.length, 2)
270
+ const authorNames = getResult.included.map(a => a.attributes.name).sort()
271
+ assert.deepEqual(authorNames, ['Peter Straub', 'Stephen King'])
272
+ })
273
273
 
274
274
  it('should update many-to-many relationships via PUT (complete replacement)', async () => {
275
275
  // Create country
276
- const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' });
276
+ const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
277
277
  const countryResult = await basicApi.resources.countries.post({
278
278
  inputRecord: countryDoc,
279
279
  simplified: false
280
- });
280
+ })
281
281
 
282
282
  // Create 4 authors
283
- const authors = [];
283
+ const authors = []
284
284
  for (const name of ['Author 1', 'Author 2', 'Author 3', 'Author 4']) {
285
- const doc = createJsonApiDocument('authors', { name });
285
+ const doc = createJsonApiDocument('authors', { name })
286
286
  const result = await basicApi.resources.authors.post({
287
287
  inputRecord: doc,
288
288
  simplified: false
289
- });
290
- authors.push(result.data);
289
+ })
290
+ authors.push(result.data)
291
291
  }
292
292
 
293
293
  // Create book with first 2 authors
@@ -302,16 +302,16 @@ describe('Relationship Operations', () => {
302
302
  resourceIdentifier('authors', authors[1].id)
303
303
  ])
304
304
  }
305
- );
305
+ )
306
306
 
307
307
  const bookResult = await basicApi.resources.books.post({
308
308
  inputRecord: bookDoc,
309
309
  simplified: false
310
- });
310
+ })
311
311
 
312
312
  // Verify initial state
313
- let pivotCount = await countRecords(knex, 'basic_book_authors');
314
- assert.equal(pivotCount, 2);
313
+ let pivotCount = await countRecords(knex, 'basic_book_authors')
314
+ assert.equal(pivotCount, 2)
315
315
 
316
316
  // PUT to replace with different 2 authors
317
317
  const putDoc = {
@@ -329,48 +329,47 @@ describe('Relationship Operations', () => {
329
329
  ])
330
330
  }
331
331
  }
332
- };
332
+ }
333
333
  console.log('THE NEXT LINE WILL BLOCK THE TEST')
334
334
  await basicApi.resources.books.put({
335
335
  id: bookResult.data.id,
336
336
  inputRecord: putDoc,
337
337
  simplified: false,
338
338
  returnFullRecord: false
339
- });
340
-
339
+ })
341
340
 
342
341
  // Verify old pivot records removed, new ones created
343
- pivotCount = await countRecords(knex, 'basic_book_authors');
344
- assert.equal(pivotCount, 2);
342
+ pivotCount = await countRecords(knex, 'basic_book_authors')
343
+ assert.equal(pivotCount, 2)
345
344
 
346
345
  // GET to verify new relationships
347
346
  const getResult = await basicApi.resources.books.get({
348
347
  id: bookResult.data.id,
349
348
  queryParams: { include: ['authors'] },
350
349
  simplified: false
351
- });
350
+ })
352
351
 
353
- const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort();
354
- assert.deepEqual(authorIds, [authors[2].id, authors[3].id].sort());
355
- });
352
+ const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort()
353
+ assert.deepEqual(authorIds, [authors[2].id, authors[3].id].sort())
354
+ })
356
355
 
357
356
  it('should update many-to-many relationships via PATCH (partial update)', async () => {
358
357
  // Create country
359
- const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' });
358
+ const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
360
359
  const countryResult = await basicApi.resources.countries.post({
361
360
  inputRecord: countryDoc,
362
361
  simplified: false
363
- });
362
+ })
364
363
 
365
364
  // Create 4 authors
366
- const authors = [];
365
+ const authors = []
367
366
  for (const name of ['Author A', 'Author B', 'Author C', 'Author D']) {
368
- const doc = createJsonApiDocument('authors', { name });
367
+ const doc = createJsonApiDocument('authors', { name })
369
368
  const result = await basicApi.resources.authors.post({
370
369
  inputRecord: doc,
371
370
  simplified: false
372
- });
373
- authors.push(result.data);
371
+ })
372
+ authors.push(result.data)
374
373
  }
375
374
 
376
375
  // Create book with first 3 authors
@@ -386,12 +385,12 @@ describe('Relationship Operations', () => {
386
385
  resourceIdentifier('authors', authors[2].id)
387
386
  ])
388
387
  }
389
- );
388
+ )
390
389
 
391
390
  const bookResult = await basicApi.resources.books.post({
392
391
  inputRecord: bookDoc,
393
392
  simplified: false
394
- });
393
+ })
395
394
 
396
395
  // PATCH to change to [B, C, D] - should remove A, keep B&C, add D
397
396
  const patchDoc = {
@@ -406,51 +405,51 @@ describe('Relationship Operations', () => {
406
405
  ])
407
406
  }
408
407
  }
409
- };
408
+ }
410
409
 
411
410
  await basicApi.resources.books.patch({
412
411
  id: bookResult.data.id,
413
412
  inputRecord: patchDoc,
414
413
  simplified: false
415
- });
414
+ })
416
415
 
417
416
  // Verify the update
418
417
  const getResult = await basicApi.resources.books.get({
419
418
  id: bookResult.data.id,
420
419
  queryParams: { include: ['authors'] },
421
420
  simplified: false
422
- });
421
+ })
423
422
 
424
- const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort();
425
- const expectedIds = [authors[1].id, authors[2].id, authors[3].id].sort();
426
- assert.deepEqual(authorIds, expectedIds);
423
+ const authorIds = getResult.data.relationships.authors.data.map(a => a.id).sort()
424
+ const expectedIds = [authors[1].id, authors[2].id, authors[3].id].sort()
425
+ assert.deepEqual(authorIds, expectedIds)
427
426
 
428
427
  // Verify correct number of pivot records
429
- const pivotCount = await countRecords(knex, 'basic_book_authors');
430
- assert.equal(pivotCount, 3);
431
- });
428
+ const pivotCount = await countRecords(knex, 'basic_book_authors')
429
+ assert.equal(pivotCount, 3)
430
+ })
432
431
 
433
432
  it('should clear all many-to-many relationships with empty array', async () => {
434
433
  // Create country
435
- const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' });
434
+ const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
436
435
  const countryResult = await basicApi.resources.countries.post({
437
436
  inputRecord: countryDoc,
438
437
  simplified: false
439
- });
438
+ })
440
439
 
441
440
  // Create authors
442
- const author1Doc = createJsonApiDocument('authors', { name: 'Author One' });
443
- const author2Doc = createJsonApiDocument('authors', { name: 'Author Two' });
441
+ const author1Doc = createJsonApiDocument('authors', { name: 'Author One' })
442
+ const author2Doc = createJsonApiDocument('authors', { name: 'Author Two' })
444
443
 
445
444
  const author1Result = await basicApi.resources.authors.post({
446
445
  inputRecord: author1Doc,
447
446
  simplified: false
448
- });
447
+ })
449
448
 
450
449
  const author2Result = await basicApi.resources.authors.post({
451
450
  inputRecord: author2Doc,
452
451
  simplified: false
453
- });
452
+ })
454
453
 
455
454
  // Create book with authors
456
455
  const bookDoc = createJsonApiDocument('books',
@@ -464,16 +463,16 @@ describe('Relationship Operations', () => {
464
463
  resourceIdentifier('authors', author2Result.data.id)
465
464
  ])
466
465
  }
467
- );
466
+ )
468
467
 
469
468
  const bookResult = await basicApi.resources.books.post({
470
469
  inputRecord: bookDoc,
471
470
  simplified: false
472
- });
471
+ })
473
472
 
474
473
  // Verify initial state
475
- let pivotCount = await countRecords(knex, 'basic_book_authors');
476
- assert.equal(pivotCount, 2);
474
+ let pivotCount = await countRecords(knex, 'basic_book_authors')
475
+ assert.equal(pivotCount, 2)
477
476
 
478
477
  // Clear all relationships
479
478
  const patchDoc = {
@@ -484,64 +483,64 @@ describe('Relationship Operations', () => {
484
483
  authors: { data: [] }
485
484
  }
486
485
  }
487
- };
486
+ }
488
487
 
489
488
  await basicApi.resources.books.patch({
490
489
  id: bookResult.data.id,
491
490
  inputRecord: patchDoc,
492
491
  simplified: false
493
- });
492
+ })
494
493
 
495
494
  // Verify all pivot records removed
496
- pivotCount = await countRecords(knex, 'basic_book_authors');
497
- assert.equal(pivotCount, 0);
495
+ pivotCount = await countRecords(knex, 'basic_book_authors')
496
+ assert.equal(pivotCount, 0)
498
497
 
499
498
  // Verify through GET
500
499
  const getResult = await basicApi.resources.books.get({
501
500
  id: bookResult.data.id,
502
501
  queryParams: { include: ['authors'] },
503
502
  simplified: false
504
- });
503
+ })
505
504
 
506
- assert(getResult.data.relationships.authors);
507
- assert.equal(getResult.data.relationships.authors.data.length, 0);
508
- });
509
- });
505
+ assert(getResult.data.relationships.authors)
506
+ assert.equal(getResult.data.relationships.authors.data.length, 0)
507
+ })
508
+ })
510
509
 
511
510
  describe('Complex Relationship Scenarios', () => {
512
511
  beforeEach(async () => {
513
512
  await cleanTables(knex, [
514
513
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'
515
- ]);
516
- });
514
+ ])
515
+ })
517
516
 
518
517
  it('should create resource with multiple relationship types', async () => {
519
518
  // Create supporting resources
520
- const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' });
519
+ const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
521
520
  const countryResult = await basicApi.resources.countries.post({
522
521
  inputRecord: countryDoc,
523
522
  simplified: false
524
- });
523
+ })
525
524
 
526
525
  const publisherDoc = createJsonApiDocument('publishers',
527
526
  { name: 'Big Publisher' },
528
527
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
529
- );
528
+ )
530
529
  const publisherResult = await basicApi.resources.publishers.post({
531
530
  inputRecord: publisherDoc,
532
531
  simplified: false
533
- });
532
+ })
534
533
 
535
- const author1Doc = createJsonApiDocument('authors', { name: 'Main Author' });
536
- const author2Doc = createJsonApiDocument('authors', { name: 'Co-Author' });
534
+ const author1Doc = createJsonApiDocument('authors', { name: 'Main Author' })
535
+ const author2Doc = createJsonApiDocument('authors', { name: 'Co-Author' })
537
536
  const author1Result = await basicApi.resources.authors.post({
538
537
  inputRecord: author1Doc,
539
538
  simplified: false
540
- });
539
+ })
541
540
  const author2Result = await basicApi.resources.authors.post({
542
541
  inputRecord: author2Doc,
543
542
  simplified: false
544
- });
543
+ })
545
544
 
546
545
  // Create book with all relationships
547
546
  const bookDoc = createJsonApiDocument('books',
@@ -556,12 +555,12 @@ describe('Relationship Operations', () => {
556
555
  resourceIdentifier('authors', author2Result.data.id)
557
556
  ])
558
557
  }
559
- );
558
+ )
560
559
 
561
560
  const bookResult = await basicApi.resources.books.post({
562
561
  inputRecord: bookDoc,
563
562
  simplified: false
564
- });
563
+ })
565
564
 
566
565
  // Verify with full includes
567
566
  const getResult = await basicApi.resources.books.get({
@@ -570,49 +569,49 @@ describe('Relationship Operations', () => {
570
569
  include: ['publisher', 'publisher.country', 'authors', 'country']
571
570
  },
572
571
  simplified: false
573
- });
572
+ })
573
+
574
+ validateJsonApiStructure(getResult, false)
574
575
 
575
- validateJsonApiStructure(getResult, false);
576
-
577
576
  // Verify all relationships
578
- assert(getResult.data.relationships.publisher);
579
- assert(getResult.data.relationships.authors);
580
- assert(getResult.data.relationships.country);
581
-
577
+ assert(getResult.data.relationships.publisher)
578
+ assert(getResult.data.relationships.authors)
579
+ assert(getResult.data.relationships.country)
580
+
582
581
  // Verify included resources
583
- assert(getResult.included);
584
- const includedTypes = getResult.included.map(r => r.type);
585
- assert(includedTypes.includes('publishers'));
586
- assert(includedTypes.includes('authors'));
587
- assert(includedTypes.includes('countries'));
588
-
582
+ assert(getResult.included)
583
+ const includedTypes = getResult.included.map(r => r.type)
584
+ assert(includedTypes.includes('publishers'))
585
+ assert(includedTypes.includes('authors'))
586
+ assert(includedTypes.includes('countries'))
587
+
589
588
  // Verify nested relationship (publisher.country)
590
- const publisher = getResult.included.find(r => r.type === 'publishers');
591
- assert(publisher.relationships.country);
592
- });
589
+ const publisher = getResult.included.find(r => r.type === 'publishers')
590
+ assert(publisher.relationships.country)
591
+ })
593
592
 
594
593
  it('should handle PUT with partial relationships object', async () => {
595
594
  // Create resources
596
- const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' });
595
+ const countryDoc = createJsonApiDocument('countries', { name: 'USA', code: 'US' })
597
596
  const countryResult = await basicApi.resources.countries.post({
598
597
  inputRecord: countryDoc,
599
598
  simplified: false
600
- });
599
+ })
601
600
 
602
601
  const publisherDoc = createJsonApiDocument('publishers',
603
602
  { name: 'Original Publisher' },
604
603
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
605
- );
604
+ )
606
605
  const publisherResult = await basicApi.resources.publishers.post({
607
606
  inputRecord: publisherDoc,
608
607
  simplified: false
609
- });
608
+ })
610
609
 
611
- const authorDoc = createJsonApiDocument('authors', { name: 'Original Author' });
610
+ const authorDoc = createJsonApiDocument('authors', { name: 'Original Author' })
612
611
  const authorResult = await basicApi.resources.authors.post({
613
612
  inputRecord: authorDoc,
614
613
  simplified: false
615
- });
614
+ })
616
615
 
617
616
  // Create book with all relationships
618
617
  const bookDoc = createJsonApiDocument('books',
@@ -624,22 +623,22 @@ describe('Relationship Operations', () => {
624
623
  publisher: createRelationship(resourceIdentifier('publishers', publisherResult.data.id)),
625
624
  authors: createToManyRelationship([resourceIdentifier('authors', authorResult.data.id)])
626
625
  }
627
- );
626
+ )
628
627
 
629
628
  const bookResult = await basicApi.resources.books.post({
630
629
  inputRecord: bookDoc,
631
630
  simplified: false
632
- });
631
+ })
633
632
 
634
633
  // Create new publisher for update
635
634
  const newPublisherDoc = createJsonApiDocument('publishers',
636
635
  { name: 'New Publisher' },
637
636
  { country: createRelationship(resourceIdentifier('countries', countryResult.data.id)) }
638
- );
637
+ )
639
638
  const newPublisherResult = await basicApi.resources.publishers.post({
640
639
  inputRecord: newPublisherDoc,
641
640
  simplified: false
642
- });
641
+ })
643
642
 
644
643
  // PUT with only publisher relationship (should preserve authors since relationships object is partial)
645
644
  const putDoc = {
@@ -655,28 +654,28 @@ describe('Relationship Operations', () => {
655
654
  // Note: authors not mentioned, but relationships object is present
656
655
  }
657
656
  }
658
- };
657
+ }
659
658
 
660
659
  await basicApi.resources.books.put({
661
660
  id: bookResult.data.id,
662
661
  inputRecord: putDoc,
663
662
  simplified: false,
664
663
  returnFullRecord: false
665
- });
664
+ })
666
665
 
667
666
  // Verify update
668
667
  const getResult = await basicApi.resources.books.get({
669
668
  id: bookResult.data.id,
670
669
  queryParams: { include: ['publisher', 'authors'] },
671
670
  simplified: false
672
- });
671
+ })
673
672
 
674
673
  // Publisher should be updated
675
- assert.equal(getResult.data.relationships.publisher.data.id, newPublisherResult.data.id);
676
-
674
+ assert.equal(getResult.data.relationships.publisher.data.id, newPublisherResult.data.id)
675
+
677
676
  // Authors should be cleared (PUT with relationships object clears unmentioned relationships)
678
- assert(getResult.data.relationships.authors);
679
- assert.equal(getResult.data.relationships.authors.data.length, 0);
680
- });
681
- });
682
- });
677
+ assert(getResult.data.relationships.authors)
678
+ assert.equal(getResult.data.relationships.authors.data.length, 0)
679
+ })
680
+ })
681
+ })