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 { createFieldGettersApi } from './fixtures/api-configs.js';
5
- import { cleanTables } from './helpers/test-utils.js';
6
- import { storageMode } from './helpers/storage-mode.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 { createFieldGettersApi } from './fixtures/api-configs.js'
5
+ import { cleanTables } from './helpers/test-utils.js'
6
+ import { storageMode } from './helpers/storage-mode.js'
7
7
 
8
8
  // Create Knex instance for tests
9
9
  const knex = knexLib({
@@ -12,26 +12,26 @@ const knex = knexLib({
12
12
  filename: ':memory:'
13
13
  },
14
14
  useNullAsDefault: true
15
- });
15
+ })
16
16
 
17
17
  // API instance that persists across tests
18
- let api;
18
+ let api
19
19
 
20
20
  describe('Field Getters', () => {
21
21
  before(async () => {
22
22
  // Initialize API once
23
- api = await createFieldGettersApi(knex);
24
- });
23
+ api = await createFieldGettersApi(knex)
24
+ })
25
25
 
26
26
  after(async () => {
27
27
  // Close database connection to allow tests to exit
28
- await knex.destroy();
29
- });
28
+ await knex.destroy()
29
+ })
30
30
 
31
31
  describe('Basic Field Transformations', () => {
32
32
  beforeEach(async () => {
33
- await cleanTables(knex, ['getter_users']);
34
- });
33
+ await cleanTables(knex, ['getter_users'])
34
+ })
35
35
 
36
36
  it('should apply simple getter transformations', async () => {
37
37
  // Create user with data that needs transformation
@@ -41,23 +41,23 @@ describe('Field Getters', () => {
41
41
  phone: '1234567890',
42
42
  metadata_json: '{"key": "value", "num": 123}',
43
43
  tags_csv: 'tag1, tag2, tag3'
44
- });
44
+ })
45
45
 
46
46
  // Check transformations applied on create
47
- assert.equal(user.email, 'user@example.com');
48
- assert.equal(user.name, 'John Doe');
49
- assert.equal(user.phone, '(123) 456-7890');
50
- assert.deepEqual(user.metadata_json, { key: 'value', num: 123 });
51
- assert.deepEqual(user.tags_csv, ['tag1', 'tag2', 'tag3']);
47
+ assert.equal(user.email, 'user@example.com')
48
+ assert.equal(user.name, 'John Doe')
49
+ assert.equal(user.phone, '(123) 456-7890')
50
+ assert.deepEqual(user.metadata_json, { key: 'value', num: 123 })
51
+ assert.deepEqual(user.tags_csv, ['tag1', 'tag2', 'tag3'])
52
52
 
53
53
  // Verify getters apply on fetch too
54
- const fetched = await api.resources.users.get({ id: user.id });
55
- assert.equal(fetched.email, 'user@example.com');
56
- assert.equal(fetched.name, 'John Doe');
57
- assert.equal(fetched.phone, '(123) 456-7890');
58
- assert.deepEqual(fetched.metadata_json, { key: 'value', num: 123 });
59
- assert.deepEqual(fetched.tags_csv, ['tag1', 'tag2', 'tag3']);
60
- });
54
+ const fetched = await api.resources.users.get({ id: user.id })
55
+ assert.equal(fetched.email, 'user@example.com')
56
+ assert.equal(fetched.name, 'John Doe')
57
+ assert.equal(fetched.phone, '(123) 456-7890')
58
+ assert.deepEqual(fetched.metadata_json, { key: 'value', num: 123 })
59
+ assert.deepEqual(fetched.tags_csv, ['tag1', 'tag2', 'tag3'])
60
+ })
61
61
 
62
62
  it('should handle null and undefined values', async () => {
63
63
  const user = await api.resources.users.post({
@@ -66,14 +66,14 @@ describe('Field Getters', () => {
66
66
  phone: null,
67
67
  metadata_json: null,
68
68
  tags_csv: null
69
- });
69
+ })
70
70
 
71
- assert.equal(user.email, undefined); // null?.toLowerCase().trim() returns undefined
72
- assert.equal(user.name, undefined); // undefined is passed, getter returns undefined
73
- assert.equal(user.phone, null); // getter explicitly returns null for falsy values
74
- assert.deepEqual(user.metadata_json, {});
75
- assert.deepEqual(user.tags_csv, []);
76
- });
71
+ assert.equal(user.email, undefined) // null?.toLowerCase().trim() returns undefined
72
+ assert.equal(user.name, undefined) // undefined is passed, getter returns undefined
73
+ assert.equal(user.phone, null) // getter explicitly returns null for falsy values
74
+ assert.deepEqual(user.metadata_json, {})
75
+ assert.deepEqual(user.tags_csv, [])
76
+ })
77
77
 
78
78
  it('should handle invalid data gracefully', async () => {
79
79
  const user = await api.resources.users.post({
@@ -82,20 +82,20 @@ describe('Field Getters', () => {
82
82
  phone: '123', // Too short
83
83
  metadata_json: 'invalid json',
84
84
  tags_csv: ', , empty, , values, '
85
- });
85
+ })
86
86
 
87
- assert.equal(user.email, 'mixed@case.com');
88
- assert.equal(user.name, '');
89
- assert.equal(user.phone, '123'); // Unchanged when can't format
90
- assert.deepEqual(user.metadata_json, {}); // Empty object on parse error
91
- assert.deepEqual(user.tags_csv, ['empty', 'values']); // Filters empty values
92
- });
93
- });
87
+ assert.equal(user.email, 'mixed@case.com')
88
+ assert.equal(user.name, '')
89
+ assert.equal(user.phone, '123') // Unchanged when can't format
90
+ assert.deepEqual(user.metadata_json, {}) // Empty object on parse error
91
+ assert.deepEqual(user.tags_csv, ['empty', 'values']) // Filters empty values
92
+ })
93
+ })
94
94
 
95
95
  describe('Getters with Computed Fields', () => {
96
96
  beforeEach(async () => {
97
- await cleanTables(knex, ['getter_products']);
98
- });
97
+ await cleanTables(knex, ['getter_products'])
98
+ })
99
99
 
100
100
  it('should apply getters before computed fields', async () => {
101
101
  const product = await api.resources.products.post({
@@ -103,18 +103,18 @@ describe('Field Getters', () => {
103
103
  description: 'This is a very long product description that should be truncated',
104
104
  price_str: '100.00',
105
105
  tax_rate_str: '0.20'
106
- });
106
+ })
107
107
 
108
108
  // Getters should transform values
109
- assert.equal(product.name, 'WIDGET');
110
- assert.equal(product.description, 'This is a very long product description that sh...');
111
- assert.equal(typeof product.price_str, 'number');
112
- assert.equal(product.price_str, 100);
113
- assert.equal(product.tax_rate_str, 0.20);
114
-
109
+ assert.equal(product.name, 'WIDGET')
110
+ assert.equal(product.description, 'This is a very long product description that sh...')
111
+ assert.equal(typeof product.price_str, 'number')
112
+ assert.equal(product.price_str, 100)
113
+ assert.equal(product.tax_rate_str, 0.20)
114
+
115
115
  // Computed field should see getter-transformed values
116
- assert.equal(product.total_price, 120);
117
- });
116
+ assert.equal(product.total_price, 120)
117
+ })
118
118
 
119
119
  it('should work with sparse fieldsets', async () => {
120
120
  const product = await api.resources.products.post({
@@ -122,7 +122,7 @@ describe('Field Getters', () => {
122
122
  description: 'Short desc',
123
123
  price_str: '50.00',
124
124
  tax_rate_str: '0.10'
125
- });
125
+ })
126
126
 
127
127
  // Fetch with sparse fieldsets
128
128
  const sparse = await api.resources.products.get({
@@ -130,95 +130,95 @@ describe('Field Getters', () => {
130
130
  queryParams: {
131
131
  fields: { products: 'name,total_price' }
132
132
  }
133
- });
133
+ })
134
134
 
135
- assert.equal(sparse.name, 'ANOTHER WIDGET');
136
- assert.ok(Math.abs(sparse.total_price - 55) < 0.0001, `Expected total_price to be ~55, got ${sparse.total_price}`);
137
- assert.equal(sparse.description, undefined);
138
- assert.equal(sparse.price_str, undefined);
139
- });
140
- });
135
+ assert.equal(sparse.name, 'ANOTHER WIDGET')
136
+ assert.ok(Math.abs(sparse.total_price - 55) < 0.0001, `Expected total_price to be ~55, got ${sparse.total_price}`)
137
+ assert.equal(sparse.description, undefined)
138
+ assert.equal(sparse.price_str, undefined)
139
+ })
140
+ })
141
141
 
142
142
  describe('Getter Dependencies', () => {
143
143
  beforeEach(async () => {
144
- await cleanTables(knex, ['getter_formatted']);
145
- });
144
+ await cleanTables(knex, ['getter_formatted'])
145
+ })
146
146
 
147
147
  it('should apply getters in dependency order', async () => {
148
148
  const data = await api.resources.formatted_data.post({
149
149
  step1: ' hello ',
150
150
  step2: 'world',
151
151
  step3: 'end'
152
- });
152
+ })
153
153
 
154
154
  // Check that getters ran in order
155
- assert.equal(data.step1, 'hello'); // Trimmed
156
- assert.equal(data.step2, 'world [step1: hello]'); // Sees trimmed step1
157
- assert.equal(data.step3, 'end [step2: world [step1: hello]]'); // Sees processed step2
158
- });
155
+ assert.equal(data.step1, 'hello') // Trimmed
156
+ assert.equal(data.step2, 'world [step1: hello]') // Sees trimmed step1
157
+ assert.equal(data.step3, 'end [step2: world [step1: hello]]') // Sees processed step2
158
+ })
159
159
 
160
160
  it('should handle missing dependencies gracefully', async () => {
161
161
  const data = await api.resources.formatted_data.post({
162
162
  step3: 'only step3'
163
163
  // step1 and step2 are missing
164
- });
164
+ })
165
165
 
166
- assert.equal(data.step1, undefined);
167
- assert.equal(data.step2, null);
168
- assert.equal(data.step3, 'only step3 [step2: null]');
169
- });
170
- });
166
+ assert.equal(data.step1, undefined)
167
+ assert.equal(data.step2, null)
168
+ assert.equal(data.step3, 'only step3 [step2: null]')
169
+ })
170
+ })
171
171
 
172
172
  describe('Async Getters', () => {
173
173
  beforeEach(async () => {
174
- await cleanTables(knex, ['getter_encrypted']);
175
- });
174
+ await cleanTables(knex, ['getter_encrypted'])
175
+ })
176
176
 
177
177
  it('should support async getter functions', async () => {
178
178
  // Helper to simulate encryption
179
- const encrypt = (value) => Buffer.from(value).toString('base64');
179
+ const encrypt = (value) => Buffer.from(value).toString('base64')
180
180
 
181
181
  const payload = {
182
182
  secret: encrypt('my-secret-value'),
183
183
  data: encrypt('sensitive-info')
184
- };
184
+ }
185
185
 
186
- let id;
186
+ let id
187
187
  if (storageMode.isAnyApi()) {
188
188
  // AnyAPI mode stores records in canonical slots, so use the API to ensure persistence
189
- const created = await api.resources.encrypted_data.post(payload);
190
- id = created.id;
189
+ const created = await api.resources.encrypted_data.post(payload)
190
+ id = created.id
191
191
  } else {
192
192
  // Legacy mode can insert directly into the backing table for the same effect
193
193
  const inserted = await knex('getter_encrypted')
194
194
  .insert(payload)
195
195
  .returning('id')
196
- .then((rows) => rows);
197
- const first = Array.isArray(inserted) ? inserted[0] : inserted;
198
- id = typeof first === 'object' && first !== null ? first.id : first;
196
+ .then((rows) => rows)
197
+ const first = Array.isArray(inserted) ? inserted[0] : inserted
198
+ id = typeof first === 'object' && first !== null ? first.id : first
199
199
  }
200
200
 
201
201
  // Fetch should decrypt via async getters
202
- const record = await api.resources.encrypted_data.get({ id });
203
- assert.equal(record.secret, 'my-secret-value');
204
- assert.equal(record.data, '[encrypted_data] sensitive-info');
205
- });
202
+ const record = await api.resources.encrypted_data.get({ id })
203
+ assert.equal(record.secret, 'my-secret-value')
204
+ assert.equal(record.data, '[encrypted_data] sensitive-info')
205
+ })
206
206
 
207
207
  it('should handle null values in async getters', async () => {
208
208
  const record = await api.resources.encrypted_data.post({
209
209
  secret: null,
210
210
  data: null
211
- });
211
+ })
212
212
 
213
- assert.equal(record.secret, null);
214
- assert.equal(record.data, null);
215
- });
216
- });
213
+ assert.equal(record.secret, null)
214
+ assert.equal(record.data, null)
215
+ })
216
+ })
217
217
 
218
218
  describe('Getters in Included Resources', () => {
219
219
  beforeEach(async () => {
220
- await cleanTables(knex, ['getter_products', 'getter_reviews']);
221
- });
220
+ await cleanTables(knex, ['getter_products', 'getter_reviews'])
221
+ })
222
222
 
223
223
  it('should apply getters to included resources', async () => {
224
224
  const product = await api.resources.products.post({
@@ -226,20 +226,20 @@ describe('Field Getters', () => {
226
226
  description: 'A product',
227
227
  price_str: '99.99',
228
228
  tax_rate_str: '0'
229
- });
229
+ })
230
230
 
231
231
  // Add reviews
232
232
  await api.resources.reviews.post({
233
233
  product: product.id,
234
234
  content: 'Great product!',
235
235
  rating: 5
236
- });
236
+ })
237
237
 
238
238
  await api.resources.reviews.post({
239
239
  product: product.id,
240
240
  content: 'Not bad',
241
241
  rating: 3
242
- });
242
+ })
243
243
 
244
244
  // Fetch with includes
245
245
  const result = await api.resources.products.get({
@@ -247,16 +247,16 @@ describe('Field Getters', () => {
247
247
  queryParams: {
248
248
  include: ['reviews']
249
249
  }
250
- });
250
+ })
251
251
 
252
252
  // Product getters should apply
253
- assert.equal(result.name, 'TEST PRODUCT');
254
-
253
+ assert.equal(result.name, 'TEST PRODUCT')
254
+
255
255
  // Review getters should apply to included resources
256
- assert.equal(result.reviews.length, 2);
257
- assert.equal(result.reviews[0].content, '[REVIEW] Great product!');
258
- assert.equal(result.reviews[1].content, '[REVIEW] Not bad');
259
- });
256
+ assert.equal(result.reviews.length, 2)
257
+ assert.equal(result.reviews[0].content, '[REVIEW] Great product!')
258
+ assert.equal(result.reviews[1].content, '[REVIEW] Not bad')
259
+ })
260
260
 
261
261
  it('should work with sparse fieldsets on included resources', async () => {
262
262
  const product = await api.resources.products.post({
@@ -264,13 +264,13 @@ describe('Field Getters', () => {
264
264
  description: 'Description',
265
265
  price_str: '10',
266
266
  tax_rate_str: '0'
267
- });
267
+ })
268
268
 
269
269
  await api.resources.reviews.post({
270
270
  product: product.id,
271
271
  content: 'Review text',
272
272
  rating: 4
273
- });
273
+ })
274
274
 
275
275
  const result = await api.resources.products.get({
276
276
  id: product.id,
@@ -281,16 +281,16 @@ describe('Field Getters', () => {
281
281
  reviews: 'content' // Only content, not rating
282
282
  }
283
283
  }
284
- });
284
+ })
285
+
286
+ assert.equal(result.name, 'ANOTHER PRODUCT')
287
+ assert.equal(result.description, undefined)
285
288
 
286
- assert.equal(result.name, 'ANOTHER PRODUCT');
287
- assert.equal(result.description, undefined);
288
-
289
- assert.equal(result.reviews.length, 1);
290
- assert.equal(result.reviews[0].content, '[REVIEW] Review text');
291
- assert.equal(result.reviews[0].rating, undefined);
292
- });
293
- });
289
+ assert.equal(result.reviews.length, 1)
290
+ assert.equal(result.reviews[0].content, '[REVIEW] Review text')
291
+ assert.equal(result.reviews[0].rating, undefined)
292
+ })
293
+ })
294
294
 
295
295
  describe('Getter Error Handling', () => {
296
296
  it('should handle getter errors gracefully', async () => {
@@ -305,30 +305,30 @@ describe('Field Getters', () => {
305
305
  bad_field: {
306
306
  type: 'string',
307
307
  getter: (value) => {
308
- throw new Error('Getter failed!');
308
+ throw new Error('Getter failed!')
309
309
  }
310
310
  }
311
311
  },
312
312
  tableName: 'getter_errors'
313
- });
314
- await api.resources.error_test.createKnexTable();
313
+ })
314
+ await api.resources.error_test.createKnexTable()
315
315
  if (storageMode.isAnyApi()) {
316
- storageMode.registerTable('getter_errors', 'error_test');
316
+ storageMode.registerTable('getter_errors', 'error_test')
317
317
  }
318
318
 
319
319
  const record = await api.resources.error_test.post({
320
320
  good_field: 'hello',
321
321
  bad_field: 'world'
322
- });
322
+ })
323
323
 
324
324
  // Good field should be transformed
325
- assert.equal(record.good_field, 'HELLO');
325
+ assert.equal(record.good_field, 'HELLO')
326
326
  // Bad field should keep original value (error logged but not thrown)
327
- assert.equal(record.bad_field, 'world');
327
+ assert.equal(record.bad_field, 'world')
328
328
 
329
- await cleanTables(knex, ['getter_errors']);
330
- });
331
- });
329
+ await cleanTables(knex, ['getter_errors'])
330
+ })
331
+ })
332
332
 
333
333
  describe('Circular Dependencies', () => {
334
334
  it('should detect circular getter dependencies', async () => {
@@ -349,8 +349,8 @@ describe('Field Getters', () => {
349
349
  tableName: 'circular_test'
350
350
  }),
351
351
  /Circular dependency detected/
352
- );
353
- });
352
+ )
353
+ })
354
354
 
355
355
  it('should detect unknown getter dependencies', async () => {
356
356
  await assert.rejects(
@@ -365,7 +365,7 @@ describe('Field Getters', () => {
365
365
  tableName: 'unknown_test'
366
366
  }),
367
367
  /getter dependency 'nonexistent' that does not exist/
368
- );
369
- });
370
- });
371
- });
368
+ )
369
+ })
370
+ })
371
+ })