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,8 +1,8 @@
1
- import { describe, it, before, after } from 'node:test';
2
- import assert from 'node:assert/strict';
3
- import knexLib from 'knex';
4
- import { createBasicApi, createLimitedDepthApi } from './fixtures/api-configs.js';
5
- import { cleanTables } from './helpers/test-utils.js';
1
+ import { describe, it, before, after } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import knexLib from 'knex'
4
+ import { createBasicApi, createLimitedDepthApi } from './fixtures/api-configs.js'
5
+ import { cleanTables } from './helpers/test-utils.js'
6
6
 
7
7
  // Create Knex instance for tests
8
8
  const knex = knexLib({
@@ -11,10 +11,10 @@ const knex = knexLib({
11
11
  filename: ':memory:'
12
12
  },
13
13
  useNullAsDefault: true
14
- });
14
+ })
15
15
 
16
16
  describe('Include Depth Validation', () => {
17
- let api;
17
+ let api
18
18
 
19
19
  before(async () => {
20
20
  // Initialize API with custom includeDepthLimit
@@ -22,13 +22,13 @@ describe('Include Depth Validation', () => {
22
22
  'rest-api': {
23
23
  includeDepthLimit: 3 // Default is 3, but being explicit
24
24
  }
25
- });
25
+ })
26
26
 
27
27
  // Set up test data with nested relationships
28
28
  // Country -> Publisher -> Author -> Book
29
29
  await cleanTables(knex, [
30
30
  'basic_countries', 'basic_publishers', 'basic_authors', 'basic_books'
31
- ]);
31
+ ])
32
32
 
33
33
  const country = await api.resources.countries.post({
34
34
  inputRecord: {
@@ -37,7 +37,7 @@ describe('Include Depth Validation', () => {
37
37
  attributes: { name: 'USA', code: 'US' }
38
38
  }
39
39
  }
40
- });
40
+ })
41
41
 
42
42
  const publisher = await api.resources.publishers.post({
43
43
  inputRecord: {
@@ -49,7 +49,7 @@ describe('Include Depth Validation', () => {
49
49
  }
50
50
  }
51
51
  }
52
- });
52
+ })
53
53
 
54
54
  const author = await api.resources.authors.post({
55
55
  inputRecord: {
@@ -61,7 +61,7 @@ describe('Include Depth Validation', () => {
61
61
  }
62
62
  }
63
63
  }
64
- });
64
+ })
65
65
 
66
66
  await api.resources.books.post({
67
67
  inputRecord: {
@@ -74,137 +74,137 @@ describe('Include Depth Validation', () => {
74
74
  }
75
75
  }
76
76
  }
77
- });
78
- });
77
+ })
78
+ })
79
79
 
80
80
  after(async () => {
81
- await knex.destroy();
82
- });
81
+ await knex.destroy()
82
+ })
83
83
 
84
84
  describe('Query endpoint validation', () => {
85
85
  it('should allow includes within depth limit', async () => {
86
86
  // Depth 1
87
87
  const depth1 = await api.resources.publishers.query({
88
88
  queryParams: { include: ['country'] }
89
- });
90
- assert(depth1.data.length > 0, 'Should return publishers');
91
- assert(depth1.included, 'Should have included resources');
89
+ })
90
+ assert(depth1.data.length > 0, 'Should return publishers')
91
+ assert(depth1.included, 'Should have included resources')
92
92
 
93
93
  // Depth 2
94
94
  const depth2 = await api.resources.authors.query({
95
95
  queryParams: { include: ['publisher.country'] }
96
- });
97
- assert(depth2.data.length > 0, 'Should return authors');
96
+ })
97
+ assert(depth2.data.length > 0, 'Should return authors')
98
98
  // The included array may or may not be present depending on whether relationships exist
99
99
 
100
100
  // Depth 3 (at limit)
101
101
  const depth3 = await api.resources.books.query({
102
102
  queryParams: { include: ['publisher.country.publishers'] }
103
- });
104
- assert(depth3.data.length > 0, 'Should return books');
103
+ })
104
+ assert(depth3.data.length > 0, 'Should return books')
105
105
  // Note: The included array might be present even if some relationships don't have data
106
- });
106
+ })
107
107
 
108
108
  it('should reject includes exceeding depth limit', async () => {
109
109
  await assert.rejects(
110
110
  async () => {
111
111
  await api.resources.books.query({
112
112
  queryParams: { include: ['publisher.country.publishers.books'] }
113
- });
113
+ })
114
114
  },
115
115
  {
116
116
  name: 'RestApiValidationError',
117
117
  message: /Include path.*exceeds maximum depth of 3/
118
118
  },
119
119
  'Should reject depth 4'
120
- );
120
+ )
121
121
 
122
122
  await assert.rejects(
123
123
  async () => {
124
124
  await api.resources.books.query({
125
125
  queryParams: { include: ['publisher.country.publishers.books.authors'] }
126
- });
126
+ })
127
127
  },
128
128
  {
129
129
  name: 'RestApiValidationError',
130
130
  message: /Include path.*exceeds maximum depth of 3/
131
131
  },
132
132
  'Should reject depth 5'
133
- );
134
- });
133
+ )
134
+ })
135
135
 
136
136
  it('should validate each include path independently', async () => {
137
137
  await assert.rejects(
138
138
  async () => {
139
139
  await api.resources.books.query({
140
- queryParams: {
140
+ queryParams: {
141
141
  include: [
142
142
  'publisher', // Valid (depth 1)
143
143
  'publisher.country', // Valid (depth 2)
144
144
  'publisher.country.publishers.books' // Invalid (depth 4)
145
- ]
145
+ ]
146
146
  }
147
- });
147
+ })
148
148
  },
149
149
  {
150
150
  name: 'RestApiValidationError',
151
151
  message: /publisher\.country\.publishers\.books.*exceeds maximum depth of 3/
152
152
  },
153
153
  'Should reject when any path exceeds limit'
154
- );
155
- });
156
- });
154
+ )
155
+ })
156
+ })
157
157
 
158
158
  describe('Get endpoint validation', () => {
159
159
  it('should allow includes within depth limit', async () => {
160
- const books = await api.resources.books.query();
161
- const bookId = books.data[0].id;
160
+ const books = await api.resources.books.query()
161
+ const bookId = books.data[0].id
162
162
 
163
163
  // Depth 1
164
164
  const depth1 = await api.resources.books.get({
165
165
  id: bookId,
166
166
  queryParams: { include: ['publisher'] }
167
- });
168
- assert(depth1.data.id === bookId, 'Should return correct book');
169
- assert(depth1.included, 'Should have included resources');
167
+ })
168
+ assert(depth1.data.id === bookId, 'Should return correct book')
169
+ assert(depth1.included, 'Should have included resources')
170
170
 
171
171
  // Depth 3 (at limit)
172
172
  const depth3 = await api.resources.books.get({
173
173
  id: bookId,
174
174
  queryParams: { include: ['publisher.country.publishers'] }
175
- });
176
- assert(depth3.data.id === bookId, 'Should return correct book');
177
- });
175
+ })
176
+ assert(depth3.data.id === bookId, 'Should return correct book')
177
+ })
178
178
 
179
179
  it('should reject includes exceeding depth limit', async () => {
180
- const books = await api.resources.books.query();
181
- const bookId = books.data[0].id;
180
+ const books = await api.resources.books.query()
181
+ const bookId = books.data[0].id
182
182
 
183
183
  await assert.rejects(
184
184
  async () => {
185
185
  await api.resources.books.get({
186
186
  id: bookId,
187
187
  queryParams: { include: ['publisher.country.publishers.books'] }
188
- });
188
+ })
189
189
  },
190
190
  {
191
191
  name: 'RestApiValidationError',
192
192
  message: /Include path.*exceeds maximum depth of 3/
193
193
  },
194
194
  'Should reject depth 4 on GET'
195
- );
196
- });
197
- });
195
+ )
196
+ })
197
+ })
198
198
 
199
199
  describe('Custom depth limits', () => {
200
200
  it('should respect custom includeDepthLimit configuration', async () => {
201
201
  // Create API with depth limit of 2 and different table names
202
- const limitedApi = await createLimitedDepthApi(knex);
202
+ const limitedApi = await createLimitedDepthApi(knex)
203
203
 
204
204
  // Set up test data for limited API
205
205
  await cleanTables(knex, [
206
206
  'limited_countries', 'limited_publishers', 'limited_authors', 'limited_books', 'limited_book_authors'
207
- ]);
207
+ ])
208
208
 
209
209
  const country = await limitedApi.resources.countries.post({
210
210
  inputRecord: {
@@ -213,7 +213,7 @@ describe('Include Depth Validation', () => {
213
213
  attributes: { name: 'Limited Country', code: 'LC' }
214
214
  }
215
215
  }
216
- });
216
+ })
217
217
 
218
218
  const publisher = await limitedApi.resources.publishers.post({
219
219
  inputRecord: {
@@ -225,7 +225,7 @@ describe('Include Depth Validation', () => {
225
225
  }
226
226
  }
227
227
  }
228
- });
228
+ })
229
229
 
230
230
  const author = await limitedApi.resources.authors.post({
231
231
  inputRecord: {
@@ -237,38 +237,38 @@ describe('Include Depth Validation', () => {
237
237
  }
238
238
  }
239
239
  }
240
- });
240
+ })
241
241
 
242
242
  // Depth 2 should work
243
243
  const depth2 = await limitedApi.resources.authors.query({
244
244
  queryParams: { include: ['publisher.country'] }
245
- });
246
- assert(depth2.data.length > 0, 'Should return authors');
245
+ })
246
+ assert(depth2.data.length > 0, 'Should return authors')
247
247
 
248
248
  // Depth 3 should fail
249
249
  await assert.rejects(
250
250
  async () => {
251
251
  await limitedApi.resources.books.query({
252
252
  queryParams: { include: ['publisher.country.publishers'] }
253
- });
253
+ })
254
254
  },
255
255
  {
256
256
  name: 'RestApiValidationError',
257
257
  message: /Include path.*exceeds maximum depth of 2/
258
258
  },
259
259
  'Should reject depth 3 with custom limit of 2'
260
- );
261
- });
262
- });
260
+ )
261
+ })
262
+ })
263
263
 
264
264
  describe('Edge cases', () => {
265
265
  it('should handle empty include arrays', async () => {
266
266
  const result = await api.resources.books.query({
267
267
  queryParams: { include: [] }
268
- });
269
- assert(result.data.length > 0, 'Should return books');
270
- assert(!result.included, 'Should not have included resources');
271
- });
268
+ })
269
+ assert(result.data.length > 0, 'Should return books')
270
+ assert(!result.included, 'Should not have included resources')
271
+ })
272
272
 
273
273
  it('should handle single-level paths correctly', async () => {
274
274
  const country = await api.resources.countries.post({
@@ -278,7 +278,7 @@ describe('Include Depth Validation', () => {
278
278
  attributes: { name: 'Single Level Country', code: `S${Date.now().toString().slice(-2)}` }
279
279
  }
280
280
  }
281
- });
281
+ })
282
282
 
283
283
  const publisher = await api.resources.publishers.post({
284
284
  inputRecord: {
@@ -290,7 +290,7 @@ describe('Include Depth Validation', () => {
290
290
  }
291
291
  }
292
292
  }
293
- });
293
+ })
294
294
 
295
295
  await api.resources.books.post({
296
296
  inputRecord: {
@@ -303,14 +303,14 @@ describe('Include Depth Validation', () => {
303
303
  }
304
304
  }
305
305
  }
306
- });
306
+ })
307
307
 
308
308
  const result = await api.resources.books.query({
309
309
  queryParams: { include: ['publisher'] }
310
- });
311
- assert(result.data.length > 0, 'Should return books');
312
- assert(result.included, 'Should have included resources');
313
- });
310
+ })
311
+ assert(result.data.length > 0, 'Should return books')
312
+ assert(result.included, 'Should have included resources')
313
+ })
314
314
 
315
315
  it('should count depth correctly with dots in path', async () => {
316
316
  // "a.b.c" = depth 3
@@ -318,14 +318,14 @@ describe('Include Depth Validation', () => {
318
318
  async () => {
319
319
  await api.resources.books.query({
320
320
  queryParams: { include: ['a.b.c.d'] }
321
- });
321
+ })
322
322
  },
323
323
  {
324
324
  name: 'RestApiValidationError',
325
325
  message: /Include path 'a\.b\.c\.d' exceeds maximum depth of 3/
326
326
  },
327
327
  'Should count dots correctly'
328
- );
329
- });
330
- });
331
- });
328
+ )
329
+ })
330
+ })
331
+ })