json-rest-api 1.0.13 → 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 +13 -0
  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 +9 -5
  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,9 +1,9 @@
1
- import { describe, it, before, beforeEach, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { cleanTables } from './helpers/test-utils.js';
5
- import { storageMode } from './helpers/storage-mode.js';
6
- import { createComputedFieldsApi } 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 { cleanTables } from './helpers/test-utils.js'
5
+ import { storageMode } from './helpers/storage-mode.js'
6
+ import { createComputedFieldsApi } from './fixtures/api-configs.js'
7
7
 
8
8
  // Create Knex instance for tests
9
9
  const knex = knexLib({
@@ -12,31 +12,31 @@ const knex = knexLib({
12
12
  filename: ':memory:'
13
13
  },
14
14
  useNullAsDefault: true
15
- });
15
+ })
16
16
 
17
17
  describe('Computed Fields and Sparse Fieldsets', () => {
18
- let api;
19
- let testData = {};
18
+ let api
19
+ const testData = {}
20
20
 
21
21
  before(async () => {
22
- api = await createComputedFieldsApi(knex);
23
- });
22
+ api = await createComputedFieldsApi(knex)
23
+ })
24
24
 
25
25
  after(async () => {
26
- await knex.destroy();
27
- });
26
+ await knex.destroy()
27
+ })
28
28
 
29
29
  beforeEach(async () => {
30
- await cleanTables(knex, ['test_products', 'test_reviews']);
31
-
30
+ await cleanTables(knex, ['test_products', 'test_reviews'])
31
+
32
32
  // Create test data
33
33
  const product = await api.resources.products.post({
34
34
  name: 'Premium Widget',
35
35
  price: 99.99,
36
36
  cost: 45.00,
37
37
  internal_notes: 'Supplier: ABC Corp'
38
- });
39
- testData.product = product;
38
+ })
39
+ testData.product = product
40
40
 
41
41
  const review1 = await api.resources.reviews.post({
42
42
  product: product.id,
@@ -46,8 +46,8 @@ describe('Computed Fields and Sparse Fieldsets', () => {
46
46
  helpful_votes: 45,
47
47
  total_votes: 50,
48
48
  spam_score: 0.1
49
- });
50
- testData.review1 = review1;
49
+ })
50
+ testData.review1 = review1
51
51
 
52
52
  const review2 = await api.resources.reviews.post({
53
53
  product: product.id,
@@ -57,35 +57,35 @@ describe('Computed Fields and Sparse Fieldsets', () => {
57
57
  helpful_votes: 8,
58
58
  total_votes: 20,
59
59
  spam_score: 0.2
60
- });
61
- testData.review2 = review2;
62
- });
60
+ })
61
+ testData.review2 = review2
62
+ })
63
63
 
64
64
  describe('Basic Computed Fields', () => {
65
65
  it('should compute fields automatically when fetching', async () => {
66
- const product = await api.resources.products.get({ id: testData.product.id });
67
-
68
- assert.equal(product.name, 'Premium Widget');
69
- assert.equal(product.price, 99.99);
70
- assert.equal(product.profit_margin, 55.00);
71
- assert.equal(product.profit_amount, 54.99);
72
-
66
+ const product = await api.resources.products.get({ id: testData.product.id })
67
+
68
+ assert.equal(product.name, 'Premium Widget')
69
+ assert.equal(product.price, 99.99)
70
+ assert.equal(product.profit_margin, 55.00)
71
+ assert.equal(product.profit_amount, 54.99)
72
+
73
73
  // normallyHidden fields should not be included
74
- assert.equal(product.cost, undefined);
75
- assert.equal(product.internal_notes, undefined);
76
- });
74
+ assert.equal(product.cost, undefined)
75
+ assert.equal(product.internal_notes, undefined)
76
+ })
77
77
 
78
78
  it('should handle division by zero in computed fields', async () => {
79
79
  const freeProduct = await api.resources.products.post({
80
80
  name: 'Free Sample',
81
81
  price: 0,
82
82
  cost: 0
83
- });
83
+ })
84
84
 
85
- const fetched = await api.resources.products.get({ id: freeProduct.id });
86
- assert.equal(fetched.profit_margin, 0);
87
- assert.equal(fetched.profit_amount, 0);
88
- });
85
+ const fetched = await api.resources.products.get({ id: freeProduct.id })
86
+ assert.equal(fetched.profit_margin, 0)
87
+ assert.equal(fetched.profit_amount, 0)
88
+ })
89
89
 
90
90
  it('should handle null values in computed fields', async () => {
91
91
  const review = await api.resources.reviews.post({
@@ -95,13 +95,13 @@ describe('Computed Fields and Sparse Fieldsets', () => {
95
95
  comment: 'Average',
96
96
  helpful_votes: 0,
97
97
  total_votes: 0
98
- });
98
+ })
99
99
 
100
- const fetched = await api.resources.reviews.get({ id: review.id });
101
- assert.equal(fetched.helpfulness_score, null);
102
- assert.equal(fetched.is_helpful, null);
103
- });
104
- });
100
+ const fetched = await api.resources.reviews.get({ id: review.id })
101
+ assert.equal(fetched.helpfulness_score, null)
102
+ assert.equal(fetched.is_helpful, null)
103
+ })
104
+ })
105
105
 
106
106
  describe('Sparse Fieldsets with Computed Fields', () => {
107
107
  it('should return only requested fields including computed', async () => {
@@ -110,17 +110,17 @@ describe('Computed Fields and Sparse Fieldsets', () => {
110
110
  queryParams: {
111
111
  fields: { products: 'name,profit_margin' }
112
112
  }
113
- });
113
+ })
114
+
115
+ assert.equal(product.id, testData.product.id)
116
+ assert.equal(product.name, 'Premium Widget')
117
+ assert.equal(product.profit_margin, 55.00)
114
118
 
115
- assert.equal(product.id, testData.product.id);
116
- assert.equal(product.name, 'Premium Widget');
117
- assert.equal(product.profit_margin, 55.00);
118
-
119
119
  // Other fields should not be included
120
- assert.equal(product.price, undefined);
121
- assert.equal(product.profit_amount, undefined);
122
- assert.equal(product.cost, undefined);
123
- });
120
+ assert.equal(product.price, undefined)
121
+ assert.equal(product.profit_amount, undefined)
122
+ assert.equal(product.cost, undefined)
123
+ })
124
124
 
125
125
  it('should fetch dependencies but not include them in response', async () => {
126
126
  const product = await api.resources.products.get({
@@ -128,15 +128,15 @@ describe('Computed Fields and Sparse Fieldsets', () => {
128
128
  queryParams: {
129
129
  fields: { products: 'profit_margin' }
130
130
  }
131
- });
131
+ })
132
+
133
+ assert.equal(product.id, testData.product.id)
134
+ assert.equal(product.profit_margin, 55.00)
132
135
 
133
- assert.equal(product.id, testData.product.id);
134
- assert.equal(product.profit_margin, 55.00);
135
-
136
136
  // Dependencies should not be included
137
- assert.equal(product.price, undefined);
138
- assert.equal(product.cost, undefined);
139
- });
137
+ assert.equal(product.price, undefined)
138
+ assert.equal(product.cost, undefined)
139
+ })
140
140
 
141
141
  it('should allow explicitly requesting normallyHidden dependencies', async () => {
142
142
  const product = await api.resources.products.get({
@@ -144,15 +144,15 @@ describe('Computed Fields and Sparse Fieldsets', () => {
144
144
  queryParams: {
145
145
  fields: { products: 'name,cost,profit_margin' }
146
146
  }
147
- });
147
+ })
148
+
149
+ assert.equal(product.name, 'Premium Widget')
150
+ assert.equal(product.cost, 45) // Explicitly requested
151
+ assert.equal(product.profit_margin, 55.00)
148
152
 
149
- assert.equal(product.name, 'Premium Widget');
150
- assert.equal(product.cost, 45); // Explicitly requested
151
- assert.equal(product.profit_margin, 55.00);
152
-
153
153
  // Price was not requested
154
- assert.equal(product.price, undefined);
155
- });
154
+ assert.equal(product.price, undefined)
155
+ })
156
156
 
157
157
  it('should handle multiple computed fields with overlapping dependencies', async () => {
158
158
  const product = await api.resources.products.get({
@@ -160,16 +160,16 @@ describe('Computed Fields and Sparse Fieldsets', () => {
160
160
  queryParams: {
161
161
  fields: { products: 'profit_margin,profit_amount' }
162
162
  }
163
- });
163
+ })
164
+
165
+ assert.equal(product.profit_margin, 55.00)
166
+ assert.equal(product.profit_amount, 54.99)
164
167
 
165
- assert.equal(product.profit_margin, 55.00);
166
- assert.equal(product.profit_amount, 54.99);
167
-
168
168
  // Shared dependencies should not be included
169
- assert.equal(product.price, undefined);
170
- assert.equal(product.cost, undefined);
171
- });
172
- });
169
+ assert.equal(product.price, undefined)
170
+ assert.equal(product.cost, undefined)
171
+ })
172
+ })
173
173
 
174
174
  describe('Computed Fields in Included Resources', () => {
175
175
  it('should compute fields in hasMany included resources', async () => {
@@ -178,19 +178,19 @@ describe('Computed Fields and Sparse Fieldsets', () => {
178
178
  queryParams: {
179
179
  include: ['reviews']
180
180
  }
181
- });
182
-
183
- assert.equal(product.reviews.length, 2);
184
-
185
- const review1 = product.reviews.find(r => r.reviewer_name === 'Alice');
186
- assert.equal(review1.helpfulness_score, 90);
187
- assert.equal(review1.is_helpful, true);
188
- assert.equal(review1.spam_score, undefined); // normallyHidden
189
-
190
- const review2 = product.reviews.find(r => r.reviewer_name === 'Bob');
191
- assert.equal(review2.helpfulness_score, 40);
192
- assert.equal(review2.is_helpful, false); // Has enough votes but not helpful enough
193
- });
181
+ })
182
+
183
+ assert.equal(product.reviews.length, 2)
184
+
185
+ const review1 = product.reviews.find(r => r.reviewer_name === 'Alice')
186
+ assert.equal(review1.helpfulness_score, 90)
187
+ assert.equal(review1.is_helpful, true)
188
+ assert.equal(review1.spam_score, undefined) // normallyHidden
189
+
190
+ const review2 = product.reviews.find(r => r.reviewer_name === 'Bob')
191
+ assert.equal(review2.helpfulness_score, 40)
192
+ assert.equal(review2.is_helpful, false) // Has enough votes but not helpful enough
193
+ })
194
194
 
195
195
  it('should apply sparse fieldsets to included resources with computed fields', async () => {
196
196
  const product = await api.resources.products.get({
@@ -202,23 +202,23 @@ describe('Computed Fields and Sparse Fieldsets', () => {
202
202
  reviews: 'reviewer_name,rating,helpfulness_score'
203
203
  }
204
204
  }
205
- });
205
+ })
206
206
 
207
- assert.equal(product.name, 'Premium Widget');
208
- assert.equal(product.price, undefined);
209
- assert.equal(product.profit_margin, undefined);
207
+ assert.equal(product.name, 'Premium Widget')
208
+ assert.equal(product.price, undefined)
209
+ assert.equal(product.profit_margin, undefined)
210
+
211
+ const review = product.reviews[0]
212
+ assert.ok(review.reviewer_name)
213
+ assert.ok(review.rating)
214
+ assert.ok(review.helpfulness_score !== undefined)
210
215
 
211
- const review = product.reviews[0];
212
- assert.ok(review.reviewer_name);
213
- assert.ok(review.rating);
214
- assert.ok(review.helpfulness_score !== undefined);
215
-
216
216
  // Other fields should not be included
217
- assert.equal(review.comment, undefined);
218
- assert.equal(review.helpful_votes, undefined);
219
- assert.equal(review.total_votes, undefined);
220
- assert.equal(review.is_helpful, undefined); // Not requested
221
- });
217
+ assert.equal(review.comment, undefined)
218
+ assert.equal(review.helpful_votes, undefined)
219
+ assert.equal(review.total_votes, undefined)
220
+ assert.equal(review.is_helpful, undefined) // Not requested
221
+ })
222
222
 
223
223
  it('should handle computed dependencies in included resources', async () => {
224
224
  const product = await api.resources.products.get({
@@ -230,41 +230,41 @@ describe('Computed Fields and Sparse Fieldsets', () => {
230
230
  reviews: 'is_helpful' // Depends on helpful_votes, total_votes, spam_score
231
231
  }
232
232
  }
233
- });
233
+ })
234
+
235
+ const review = product.reviews.find(r => r.id === testData.review1.id)
236
+ assert.equal(review.is_helpful, true)
234
237
 
235
- const review = product.reviews.find(r => r.id === testData.review1.id);
236
- assert.equal(review.is_helpful, true);
237
-
238
238
  // Dependencies should not be included
239
- assert.equal(review.helpful_votes, undefined);
240
- assert.equal(review.total_votes, undefined);
241
- assert.equal(review.spam_score, undefined);
242
- });
243
- });
239
+ assert.equal(review.helpful_votes, undefined)
240
+ assert.equal(review.total_votes, undefined)
241
+ assert.equal(review.spam_score, undefined)
242
+ })
243
+ })
244
244
 
245
245
  describe('Collection Queries with Computed Fields', () => {
246
246
  it('should compute fields for all records in collection', async () => {
247
- const products = await api.resources.products.query();
248
-
249
- assert.equal(products.data.length, 1);
250
- const product = products.data[0];
251
- assert.equal(product.profit_margin, 55.00);
252
- assert.equal(product.profit_amount, 54.99);
253
- });
247
+ const products = await api.resources.products.query()
248
+
249
+ assert.equal(products.data.length, 1)
250
+ const product = products.data[0]
251
+ assert.equal(product.profit_margin, 55.00)
252
+ assert.equal(product.profit_amount, 54.99)
253
+ })
254
254
 
255
255
  it('should apply sparse fieldsets to collections', async () => {
256
256
  const products = await api.resources.products.query({
257
257
  queryParams: {
258
258
  fields: { products: 'name,profit_margin' }
259
259
  }
260
- });
260
+ })
261
261
 
262
- const product = products.data[0];
263
- assert.equal(product.name, 'Premium Widget');
264
- assert.equal(product.profit_margin, 55.00);
265
- assert.equal(product.price, undefined);
266
- assert.equal(product.cost, undefined);
267
- });
262
+ const product = products.data[0]
263
+ assert.equal(product.name, 'Premium Widget')
264
+ assert.equal(product.profit_margin, 55.00)
265
+ assert.equal(product.price, undefined)
266
+ assert.equal(product.cost, undefined)
267
+ })
268
268
 
269
269
  it('should handle includes with computed fields in collections', async () => {
270
270
  const products = await api.resources.products.query({
@@ -275,19 +275,19 @@ describe('Computed Fields and Sparse Fieldsets', () => {
275
275
  reviews: 'rating,helpfulness_score'
276
276
  }
277
277
  }
278
- });
278
+ })
279
+
280
+ const product = products.data[0]
281
+ assert.equal(product.reviews.length, 2)
279
282
 
280
- const product = products.data[0];
281
- assert.equal(product.reviews.length, 2);
282
-
283
283
  product.reviews.forEach(review => {
284
- assert.ok(review.rating);
285
- assert.ok(review.helpfulness_score !== undefined);
286
- assert.equal(review.reviewer_name, undefined);
287
- assert.equal(review.comment, undefined);
288
- });
289
- });
290
- });
284
+ assert.ok(review.rating)
285
+ assert.ok(review.helpfulness_score !== undefined)
286
+ assert.equal(review.reviewer_name, undefined)
287
+ assert.equal(review.comment, undefined)
288
+ })
289
+ })
290
+ })
291
291
 
292
292
  describe('Relationship Fields', () => {
293
293
  it('should always include minimal relationship objects regardless of sparse fieldsets', async () => {
@@ -296,36 +296,36 @@ describe('Computed Fields and Sparse Fieldsets', () => {
296
296
  queryParams: {
297
297
  fields: { products: 'name' }
298
298
  }
299
- });
299
+ })
300
300
 
301
301
  // Minimal relationship objects should still be included
302
- assert.ok(Array.isArray(product.reviews));
303
- assert.equal(product.reviews.length, 2);
304
- assert.equal(product.reviews[0].id, testData.review1.id);
305
- assert.equal(product.reviews[1].id, testData.review2.id);
302
+ assert.ok(Array.isArray(product.reviews))
303
+ assert.equal(product.reviews.length, 2)
304
+ assert.equal(product.reviews[0].id, testData.review1.id)
305
+ assert.equal(product.reviews[1].id, testData.review2.id)
306
306
  // Should only have id property (minimal object)
307
- assert.equal(Object.keys(product.reviews[0]).length, 1);
308
- assert.equal(Object.keys(product.reviews[1]).length, 1);
309
- });
307
+ assert.equal(Object.keys(product.reviews[0]).length, 1)
308
+ assert.equal(Object.keys(product.reviews[1]).length, 1)
309
+ })
310
310
 
311
311
  it('should include empty arrays for empty relationships', async () => {
312
312
  const newProduct = await api.resources.products.post({
313
313
  name: 'New Product',
314
314
  price: 50,
315
315
  cost: 25
316
- });
316
+ })
317
317
 
318
318
  const fetched = await api.resources.products.get({
319
319
  id: newProduct.id,
320
320
  queryParams: {
321
321
  fields: { products: 'name' }
322
322
  }
323
- });
323
+ })
324
324
 
325
- assert.ok(Array.isArray(fetched.reviews));
326
- assert.equal(fetched.reviews.length, 0);
327
- });
328
- });
325
+ assert.ok(Array.isArray(fetched.reviews))
326
+ assert.equal(fetched.reviews.length, 0)
327
+ })
328
+ })
329
329
 
330
330
  describe('Error Handling', () => {
331
331
  it('should handle errors in compute functions gracefully', async () => {
@@ -339,23 +339,23 @@ describe('Computed Fields and Sparse Fieldsets', () => {
339
339
  computed: true,
340
340
  dependencies: ['value'],
341
341
  compute: ({ attributes }) => {
342
- throw new Error('Computation failed');
342
+ throw new Error('Computation failed')
343
343
  }
344
344
  }
345
345
  },
346
346
  tableName: 'test_faulty_products'
347
- });
348
- await api.resources.faulty_products.createKnexTable();
347
+ })
348
+ await api.resources.faulty_products.createKnexTable()
349
349
  if (storageMode.isAnyApi()) {
350
- storageMode.registerTable('test_faulty_products', 'faulty_products');
350
+ storageMode.registerTable('test_faulty_products', 'faulty_products')
351
351
  }
352
352
 
353
- const faulty = await api.resources.faulty_products.post({ value: 42 });
354
- const fetched = await api.resources.faulty_products.get({ id: faulty.id });
353
+ const faulty = await api.resources.faulty_products.post({ value: 42 })
354
+ const fetched = await api.resources.faulty_products.get({ id: faulty.id })
355
355
 
356
- assert.equal(fetched.value, 42);
357
- assert.equal(fetched.bad_compute, null); // Error results in null
358
- });
356
+ assert.equal(fetched.value, 42)
357
+ assert.equal(fetched.bad_compute, null) // Error results in null
358
+ })
359
359
 
360
360
  it('should reject unknown fields in sparse fieldsets', async () => {
361
361
  await assert.rejects(
@@ -366,8 +366,8 @@ describe('Computed Fields and Sparse Fieldsets', () => {
366
366
  }
367
367
  }),
368
368
  /Unknown sparse field 'unknown_field'/
369
- );
370
- });
369
+ )
370
+ })
371
371
 
372
372
  it('should reject requests for _ids fields in sparse fieldsets', async () => {
373
373
  await assert.rejects(
@@ -378,9 +378,9 @@ describe('Computed Fields and Sparse Fieldsets', () => {
378
378
  }
379
379
  }),
380
380
  /Unknown sparse field 'reviews_ids'/
381
- );
382
- });
383
- });
381
+ )
382
+ })
383
+ })
384
384
 
385
385
  describe('Async Computed Fields', () => {
386
386
  it('should support async compute functions', async () => {
@@ -396,8 +396,8 @@ describe('Computed Fields and Sparse Fieldsets', () => {
396
396
  dependencies: ['external_id'],
397
397
  compute: async ({ attributes }) => {
398
398
  // Simulate async operation (e.g., external API call)
399
- await new Promise(resolve => setTimeout(resolve, 10));
400
- return `fetched-${attributes.external_id}`;
399
+ await new Promise(resolve => setTimeout(resolve, 10))
400
+ return `fetched-${attributes.external_id}`
401
401
  }
402
402
  },
403
403
  computed_name: {
@@ -406,27 +406,27 @@ describe('Computed Fields and Sparse Fieldsets', () => {
406
406
  dependencies: ['name'],
407
407
  compute: async ({ attributes }) => {
408
408
  // Another async operation
409
- await new Promise(resolve => setTimeout(resolve, 5));
410
- return attributes.name.toUpperCase();
409
+ await new Promise(resolve => setTimeout(resolve, 5))
410
+ return attributes.name.toUpperCase()
411
411
  }
412
412
  }
413
413
  },
414
414
  tableName: 'test_async_products'
415
- });
416
- await api.resources.async_products.createKnexTable();
415
+ })
416
+ await api.resources.async_products.createKnexTable()
417
417
  if (storageMode.isAnyApi()) {
418
- storageMode.registerTable('test_async_products', 'async_products');
418
+ storageMode.registerTable('test_async_products', 'async_products')
419
419
  }
420
420
 
421
421
  const product = await api.resources.async_products.post({
422
422
  name: 'Async Product',
423
423
  external_id: 'ext-123'
424
- });
424
+ })
425
425
 
426
- const fetched = await api.resources.async_products.get({ id: product.id });
427
- assert.equal(fetched.external_data, 'fetched-ext-123');
428
- assert.equal(fetched.computed_name, 'ASYNC PRODUCT');
429
- });
426
+ const fetched = await api.resources.async_products.get({ id: product.id })
427
+ assert.equal(fetched.external_data, 'fetched-ext-123')
428
+ assert.equal(fetched.computed_name, 'ASYNC PRODUCT')
429
+ })
430
430
 
431
431
  it('should handle errors in async compute functions', async () => {
432
432
  // Add a resource with failing async computed field
@@ -439,45 +439,45 @@ describe('Computed Fields and Sparse Fieldsets', () => {
439
439
  computed: true,
440
440
  dependencies: ['value'],
441
441
  compute: async ({ attributes }) => {
442
- await new Promise(resolve => setTimeout(resolve, 5));
443
- throw new Error('Async computation failed');
442
+ await new Promise(resolve => setTimeout(resolve, 5))
443
+ throw new Error('Async computation failed')
444
444
  }
445
445
  }
446
446
  },
447
447
  tableName: 'test_failing_async_products'
448
- });
449
- await api.resources.failing_async_products.createKnexTable();
448
+ })
449
+ await api.resources.failing_async_products.createKnexTable()
450
450
  if (storageMode.isAnyApi()) {
451
- storageMode.registerTable('test_failing_async_products', 'failing_async_products');
451
+ storageMode.registerTable('test_failing_async_products', 'failing_async_products')
452
452
  }
453
453
 
454
- const product = await api.resources.failing_async_products.post({ value: 42 });
455
- const fetched = await api.resources.failing_async_products.get({ id: product.id });
454
+ const product = await api.resources.failing_async_products.post({ value: 42 })
455
+ const fetched = await api.resources.failing_async_products.get({ id: product.id })
456
456
 
457
- assert.equal(fetched.value, 42);
458
- assert.equal(fetched.failing_async, null); // Error results in null
459
- });
460
- });
457
+ assert.equal(fetched.value, 42)
458
+ assert.equal(fetched.failing_async, null) // Error results in null
459
+ })
460
+ })
461
461
 
462
462
  describe('Performance Considerations', () => {
463
463
  it('should only compute requested computed fields', async () => {
464
464
  // Track compute calls
465
- let profitMarginCalls = 0;
466
- let profitAmountCalls = 0;
465
+ let profitMarginCalls = 0
466
+ let profitAmountCalls = 0
467
467
 
468
468
  // Override compute functions to track calls
469
- const originalProfitMargin = api.resources.products.vars.schemaInfo.computed.profit_margin.compute;
470
- const originalProfitAmount = api.resources.products.vars.schemaInfo.computed.profit_amount.compute;
469
+ const originalProfitMargin = api.resources.products.vars.schemaInfo.computed.profit_margin.compute
470
+ const originalProfitAmount = api.resources.products.vars.schemaInfo.computed.profit_amount.compute
471
471
 
472
472
  api.resources.products.vars.schemaInfo.computed.profit_margin.compute = (ctx) => {
473
- profitMarginCalls++;
474
- return originalProfitMargin(ctx);
475
- };
473
+ profitMarginCalls++
474
+ return originalProfitMargin(ctx)
475
+ }
476
476
 
477
477
  api.resources.products.vars.schemaInfo.computed.profit_amount.compute = (ctx) => {
478
- profitAmountCalls++;
479
- return originalProfitAmount(ctx);
480
- };
478
+ profitAmountCalls++
479
+ return originalProfitAmount(ctx)
480
+ }
481
481
 
482
482
  // Request only profit_margin
483
483
  await api.resources.products.get({
@@ -485,14 +485,14 @@ describe('Computed Fields and Sparse Fieldsets', () => {
485
485
  queryParams: {
486
486
  fields: { products: 'name,profit_margin' }
487
487
  }
488
- });
488
+ })
489
489
 
490
- assert.equal(profitMarginCalls, 1);
491
- assert.equal(profitAmountCalls, 0); // Should not be computed
490
+ assert.equal(profitMarginCalls, 1)
491
+ assert.equal(profitAmountCalls, 0) // Should not be computed
492
492
 
493
493
  // Restore original functions
494
- api.resources.products.vars.schemaInfo.computed.profit_margin.compute = originalProfitMargin;
495
- api.resources.products.vars.schemaInfo.computed.profit_amount.compute = originalProfitAmount;
496
- });
497
- });
498
- });
494
+ api.resources.products.vars.schemaInfo.computed.profit_margin.compute = originalProfitMargin
495
+ api.resources.products.vars.schemaInfo.computed.profit_amount.compute = originalProfitAmount
496
+ })
497
+ })
498
+ })