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.
- package/connectors_ideas/fastify.js +66 -68
- package/connectors_ideas/koa-plugin.js +50 -51
- package/eslint.config.js +13 -0
- package/examples/url-override-example.js +63 -63
- package/index.js +13 -13
- package/lib/enhanced-logger.js +51 -51
- package/lib/error-formatter.js +61 -61
- package/lib/rest-api-errors.js +28 -28
- package/package.json +9 -5
- package/plugins/core/bulk-operations-plugin.js +142 -145
- package/plugins/core/connectors/express-plugin.js +210 -217
- package/plugins/core/connectors/lib/busboy-detector.js +84 -84
- package/plugins/core/connectors/lib/formidable-detector.js +50 -50
- package/plugins/core/connectors/lib/request-helpers.js +74 -74
- package/plugins/core/file-handling-plugin.js +125 -125
- package/plugins/core/lib/anyapi/anyapi-registry.js +254 -254
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +287 -287
- package/plugins/core/lib/anyapi/schema-utils.js +101 -101
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +41 -41
- package/plugins/core/lib/dbTablesOperations.js +134 -135
- package/plugins/core/lib/fractional-positioning.js +74 -76
- package/plugins/core/lib/querying/knex-cross-table-search.js +320 -320
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +144 -144
- package/plugins/core/lib/querying/knex-pagination-helpers.js +164 -164
- package/plugins/core/lib/querying/knex-process-includes.js +50 -50
- package/plugins/core/lib/querying/knex-query-helpers-base.js +31 -31
- package/plugins/core/lib/querying/knex-query-helpers.js +451 -452
- package/plugins/core/lib/querying/knex-relationship-includes.js +1160 -1170
- package/plugins/core/lib/querying/knex-window-queries.js +89 -89
- package/plugins/core/lib/querying/url-helpers.js +35 -35
- package/plugins/core/lib/querying-writing/compile-schemas.js +97 -98
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +31 -31
- package/plugins/core/lib/querying-writing/database-capabilities.js +94 -94
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +76 -76
- package/plugins/core/lib/querying-writing/default-data-helpers.js +27 -27
- package/plugins/core/lib/querying-writing/field-utils.js +44 -42
- package/plugins/core/lib/querying-writing/knex-constants.js +15 -16
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +117 -115
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +41 -41
- package/plugins/core/lib/querying-writing/payload-validators.js +275 -275
- package/plugins/core/lib/querying-writing/schema-helpers.js +129 -129
- package/plugins/core/lib/querying-writing/scope-validations.js +143 -146
- package/plugins/core/lib/querying-writing/simplified-helpers.js +133 -129
- package/plugins/core/lib/storage/storage-adapter.js +68 -68
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +31 -31
- package/plugins/core/lib/writing/many-to-many-manipulations.js +81 -81
- package/plugins/core/lib/writing/relationship-processor.js +58 -58
- package/plugins/core/rest-api-access.js +8 -8
- package/plugins/core/rest-api-anyapi-knex-plugin.js +920 -920
- package/plugins/core/rest-api-cors-plugin.js +170 -171
- package/plugins/core/rest-api-knex-plugin.js +482 -493
- package/plugins/core/rest-api-multihome-plugin.js +247 -251
- package/plugins/core/rest-api-plugin-hooks/compile-resource-schemas.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +20 -21
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +44 -44
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +23 -23
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +15 -15
- package/plugins/core/rest-api-plugin-methods/add-route.js +9 -9
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +5 -6
- package/plugins/core/rest-api-plugin-methods/common.js +295 -297
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +35 -37
- package/plugins/core/rest-api-plugin-methods/delete.js +57 -59
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +172 -171
- package/plugins/core/rest-api-plugin-methods/get-related.js +96 -96
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +16 -18
- package/plugins/core/rest-api-plugin-methods/get.js +86 -86
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +16 -17
- package/plugins/core/rest-api-plugin-methods/patch.js +63 -64
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +54 -55
- package/plugins/core/rest-api-plugin-methods/post.js +61 -62
- package/plugins/core/rest-api-plugin-methods/put.js +86 -87
- package/plugins/core/rest-api-plugin-methods/query.js +88 -88
- package/plugins/core/rest-api-plugin-methods/release.js +5 -6
- package/plugins/core/rest-api-plugin.js +76 -78
- package/plugins/core/rest-api-positioning-plugin.js +251 -252
- package/plugins/core/socketio-plugin.js +240 -236
- package/plugins/storage/local-storage.js +103 -103
- package/plugins/storage/s3-storage.js +41 -42
- package/quickTest.js +37 -37
- package/scripts/debug-anyapi-review-filter.mjs +18 -18
- package/scripts/inspect-anyapi-descriptor-country.mjs +10 -10
- package/scripts/inspect-anyapi-descriptor.mjs +10 -10
- package/scripts/run-docs.js +112 -113
- package/test-debug.js +28 -28
- package/tests/TEST_TEMPLATE.test.js +51 -51
- package/tests/anyapi-basic.test.js +42 -42
- package/tests/anyapi-cursor-pagination.test.js +48 -48
- package/tests/bulk-operations.test.js +208 -208
- package/tests/computed-fields.test.js +204 -204
- package/tests/cors.test.js +412 -413
- package/tests/curl.test.js +275 -276
- package/tests/custom-idproperty-relationships.test.js +266 -266
- package/tests/field-getters.test.js +132 -132
- package/tests/field-setters.test.js +110 -110
- package/tests/fixtures/api-configs.js +699 -699
- package/tests/helpers/storage-mode.js +32 -32
- package/tests/helpers/test-utils.js +76 -76
- package/tests/include-depth-validation.test.js +76 -76
- package/tests/includes.test.js +145 -145
- package/tests/multihome.test.js +175 -175
- package/tests/nested-includes.test.js +255 -255
- package/tests/pagination-cursor-multifield.test.js +164 -164
- package/tests/pagination-enhanced.test.js +225 -225
- package/tests/polymorphic-relationships.test.js +96 -96
- package/tests/polymorphic-simple.test.js +44 -44
- package/tests/positioning-advanced.test.js +226 -226
- package/tests/positioning-extended.test.js +399 -399
- package/tests/positioning-multifilter.test.js +204 -204
- package/tests/positioning.test.js +266 -266
- package/tests/queries.test.js +184 -184
- package/tests/query-limits.test.js +154 -155
- package/tests/relationship-endpoints.test.js +127 -127
- package/tests/relationships.test.js +178 -179
- package/tests/rest-api-access.test.js +86 -86
- package/tests/rest-api.test.js +103 -103
- package/tests/searchschema-merge.test.js +93 -93
- package/tests/socketio.test.js +326 -326
- package/tests/virtual-fields.test.js +164 -164
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
import { describe, it, before, beforeEach, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { Api } from 'hooked-api'
|
|
5
|
-
import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
|
|
6
|
-
import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.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 { Api } from 'hooked-api'
|
|
5
|
+
import { RestApiPlugin } from '../plugins/core/rest-api-plugin.js'
|
|
6
|
+
import { RestApiKnexPlugin } from '../plugins/core/rest-api-knex-plugin.js'
|
|
7
7
|
import {
|
|
8
8
|
cleanTables,
|
|
9
9
|
countRecords,
|
|
10
10
|
createJsonApiDocument,
|
|
11
11
|
createRelationship,
|
|
12
12
|
validateJsonApiStructure
|
|
13
|
-
} from './helpers/test-utils.js'
|
|
13
|
+
} from './helpers/test-utils.js'
|
|
14
14
|
|
|
15
15
|
// Create Knex instance for tests
|
|
16
16
|
const knex = knexLib({
|
|
@@ -19,16 +19,16 @@ const knex = knexLib({
|
|
|
19
19
|
filename: ':memory:'
|
|
20
20
|
},
|
|
21
21
|
useNullAsDefault: true
|
|
22
|
-
})
|
|
22
|
+
})
|
|
23
23
|
|
|
24
24
|
describe('Query Limits and Include Limits', () => {
|
|
25
|
-
let api
|
|
25
|
+
let api
|
|
26
26
|
|
|
27
27
|
before(async () => {
|
|
28
28
|
// Create API with specific limits for testing
|
|
29
29
|
api = new Api({
|
|
30
30
|
name: 'test-api-limits'
|
|
31
|
-
})
|
|
31
|
+
})
|
|
32
32
|
|
|
33
33
|
await api.use(RestApiPlugin, {
|
|
34
34
|
queryDefaultLimit: 10,
|
|
@@ -37,11 +37,11 @@ describe('Query Limits and Include Limits', () => {
|
|
|
37
37
|
returnFullRecord: {
|
|
38
38
|
post: true
|
|
39
39
|
}
|
|
40
|
-
})
|
|
40
|
+
})
|
|
41
41
|
|
|
42
42
|
await api.use(RestApiKnexPlugin, {
|
|
43
|
-
knex
|
|
44
|
-
})
|
|
43
|
+
knex
|
|
44
|
+
})
|
|
45
45
|
|
|
46
46
|
// Countries
|
|
47
47
|
await api.addResource('countries', {
|
|
@@ -50,8 +50,8 @@ describe('Query Limits and Include Limits', () => {
|
|
|
50
50
|
name: { type: 'string', required: true },
|
|
51
51
|
code: { type: 'string', required: true }
|
|
52
52
|
}
|
|
53
|
-
})
|
|
54
|
-
await api.resources.countries.createKnexTable()
|
|
53
|
+
})
|
|
54
|
+
await api.resources.countries.createKnexTable()
|
|
55
55
|
|
|
56
56
|
// Publishers
|
|
57
57
|
await api.addResource('publishers', {
|
|
@@ -61,9 +61,9 @@ describe('Query Limits and Include Limits', () => {
|
|
|
61
61
|
country_id: { type: 'number', belongsTo: 'countries', as: 'country' }
|
|
62
62
|
},
|
|
63
63
|
relationships: {
|
|
64
|
-
books: {
|
|
64
|
+
books: {
|
|
65
65
|
type: 'hasMany',
|
|
66
|
-
target: 'books',
|
|
66
|
+
target: 'books',
|
|
67
67
|
foreignKey: 'publisher_id',
|
|
68
68
|
// Relationships are always includable via ?include=
|
|
69
69
|
include: {
|
|
@@ -71,8 +71,8 @@ describe('Query Limits and Include Limits', () => {
|
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
|
-
})
|
|
75
|
-
await api.resources.publishers.createKnexTable()
|
|
74
|
+
})
|
|
75
|
+
await api.resources.publishers.createKnexTable()
|
|
76
76
|
|
|
77
77
|
// Authors
|
|
78
78
|
await api.addResource('authors', {
|
|
@@ -82,10 +82,10 @@ describe('Query Limits and Include Limits', () => {
|
|
|
82
82
|
country_id: { type: 'number', belongsTo: 'countries', as: 'country' }
|
|
83
83
|
},
|
|
84
84
|
relationships: {
|
|
85
|
-
books: {
|
|
85
|
+
books: {
|
|
86
86
|
type: 'manyToMany',
|
|
87
|
-
through: 'book_authors',
|
|
88
|
-
foreignKey: 'author_id',
|
|
87
|
+
through: 'book_authors',
|
|
88
|
+
foreignKey: 'author_id',
|
|
89
89
|
otherKey: 'book_id',
|
|
90
90
|
// Relationships are always includable via ?include=
|
|
91
91
|
include: {
|
|
@@ -94,8 +94,8 @@ describe('Query Limits and Include Limits', () => {
|
|
|
94
94
|
}
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
|
-
})
|
|
98
|
-
await api.resources.authors.createKnexTable()
|
|
97
|
+
})
|
|
98
|
+
await api.resources.authors.createKnexTable()
|
|
99
99
|
|
|
100
100
|
// Books
|
|
101
101
|
await api.addResource('books', {
|
|
@@ -105,17 +105,17 @@ describe('Query Limits and Include Limits', () => {
|
|
|
105
105
|
publisher_id: { type: 'number', belongsTo: 'publishers', as: 'publisher' }
|
|
106
106
|
},
|
|
107
107
|
relationships: {
|
|
108
|
-
authors: {
|
|
108
|
+
authors: {
|
|
109
109
|
type: 'manyToMany',
|
|
110
|
-
through: 'book_authors',
|
|
111
|
-
foreignKey: 'book_id',
|
|
110
|
+
through: 'book_authors',
|
|
111
|
+
foreignKey: 'book_id',
|
|
112
112
|
otherKey: 'author_id'
|
|
113
113
|
// Relationships are always includable via ?include=
|
|
114
114
|
},
|
|
115
115
|
reviews: { type: 'hasMany', target: 'reviews', via: 'reviewable' }
|
|
116
116
|
}
|
|
117
|
-
})
|
|
118
|
-
await api.resources.books.createKnexTable()
|
|
117
|
+
})
|
|
118
|
+
await api.resources.books.createKnexTable()
|
|
119
119
|
|
|
120
120
|
// Book Authors (pivot table)
|
|
121
121
|
await api.addResource('book_authors', {
|
|
@@ -124,8 +124,8 @@ describe('Query Limits and Include Limits', () => {
|
|
|
124
124
|
book_id: { type: 'number', belongsTo: 'books', as: 'book' },
|
|
125
125
|
author_id: { type: 'number', belongsTo: 'authors', as: 'author' }
|
|
126
126
|
}
|
|
127
|
-
})
|
|
128
|
-
await api.resources.book_authors.createKnexTable()
|
|
127
|
+
})
|
|
128
|
+
await api.resources.book_authors.createKnexTable()
|
|
129
129
|
|
|
130
130
|
// Reviews (polymorphic)
|
|
131
131
|
await api.addResource('reviews', {
|
|
@@ -146,19 +146,19 @@ describe('Query Limits and Include Limits', () => {
|
|
|
146
146
|
}
|
|
147
147
|
}
|
|
148
148
|
}
|
|
149
|
-
})
|
|
150
|
-
await api.resources.reviews.createKnexTable()
|
|
151
|
-
})
|
|
149
|
+
})
|
|
150
|
+
await api.resources.reviews.createKnexTable()
|
|
151
|
+
})
|
|
152
152
|
|
|
153
153
|
after(async () => {
|
|
154
|
-
await knex.destroy()
|
|
155
|
-
})
|
|
154
|
+
await knex.destroy()
|
|
155
|
+
})
|
|
156
156
|
|
|
157
157
|
beforeEach(async () => {
|
|
158
158
|
await cleanTables(knex, [
|
|
159
159
|
'countries', 'publishers', 'authors', 'books', 'book_authors', 'reviews'
|
|
160
|
-
])
|
|
161
|
-
})
|
|
160
|
+
])
|
|
161
|
+
})
|
|
162
162
|
|
|
163
163
|
describe('Query Default Limits', () => {
|
|
164
164
|
it('should apply queryDefaultLimit when no page size specified', async () => {
|
|
@@ -169,18 +169,18 @@ describe('Query Limits and Include Limits', () => {
|
|
|
169
169
|
name: `Country ${i}`,
|
|
170
170
|
code: `C${i}`
|
|
171
171
|
})
|
|
172
|
-
})
|
|
172
|
+
})
|
|
173
173
|
}
|
|
174
174
|
|
|
175
175
|
// Query without page size
|
|
176
176
|
const result = await api.resources.countries.query({
|
|
177
177
|
queryParams: {},
|
|
178
178
|
simplified: false
|
|
179
|
-
})
|
|
179
|
+
})
|
|
180
180
|
|
|
181
181
|
// Should use queryDefaultLimit of 10
|
|
182
|
-
assert.equal(result.data.length, 10, 'Should apply default limit of 10')
|
|
183
|
-
})
|
|
182
|
+
assert.equal(result.data.length, 10, 'Should apply default limit of 10')
|
|
183
|
+
})
|
|
184
184
|
|
|
185
185
|
it('should respect queryMaxLimit when page size exceeds it', async () => {
|
|
186
186
|
// Create 100 countries
|
|
@@ -190,7 +190,7 @@ describe('Query Limits and Include Limits', () => {
|
|
|
190
190
|
name: `Country ${i}`,
|
|
191
191
|
code: `C${i}`
|
|
192
192
|
})
|
|
193
|
-
})
|
|
193
|
+
})
|
|
194
194
|
}
|
|
195
195
|
|
|
196
196
|
// Query with page size exceeding max (use offset-based pagination)
|
|
@@ -199,12 +199,12 @@ describe('Query Limits and Include Limits', () => {
|
|
|
199
199
|
page: { size: 100, number: 1 } // Exceeds queryMaxLimit of 50
|
|
200
200
|
},
|
|
201
201
|
simplified: false
|
|
202
|
-
})
|
|
202
|
+
})
|
|
203
203
|
|
|
204
204
|
// Should be capped at queryMaxLimit of 50
|
|
205
|
-
assert.equal(result.data.length, 50, 'Should cap at queryMaxLimit of 50')
|
|
206
|
-
})
|
|
207
|
-
})
|
|
205
|
+
assert.equal(result.data.length, 50, 'Should cap at queryMaxLimit of 50')
|
|
206
|
+
})
|
|
207
|
+
})
|
|
208
208
|
|
|
209
209
|
describe('Include Limits - HasMany Relationships', () => {
|
|
210
210
|
beforeEach(async () => {
|
|
@@ -214,27 +214,27 @@ describe('Query Limits and Include Limits', () => {
|
|
|
214
214
|
name: 'USA',
|
|
215
215
|
code: 'US'
|
|
216
216
|
})
|
|
217
|
-
})
|
|
217
|
+
})
|
|
218
218
|
|
|
219
219
|
const publisher = await api.resources.publishers.post({
|
|
220
220
|
inputRecord: createJsonApiDocument('publishers', {
|
|
221
221
|
name: 'Test Publisher'
|
|
222
222
|
}, {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
})
|
|
223
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
226
|
|
|
227
227
|
// Create 25 books for the publisher
|
|
228
228
|
for (let i = 1; i <= 25; i++) {
|
|
229
229
|
await api.resources.books.post({
|
|
230
230
|
inputRecord: createJsonApiDocument('books', {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
})
|
|
231
|
+
title: `Book ${i}`
|
|
232
|
+
}, {
|
|
233
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
234
|
+
})
|
|
235
|
+
})
|
|
236
236
|
}
|
|
237
|
-
})
|
|
237
|
+
})
|
|
238
238
|
|
|
239
239
|
it('should apply default limit to hasMany includes when not specified', async () => {
|
|
240
240
|
// Query publishers with books (no explicit limit in include config)
|
|
@@ -243,17 +243,17 @@ describe('Query Limits and Include Limits', () => {
|
|
|
243
243
|
include: ['books']
|
|
244
244
|
},
|
|
245
245
|
simplified: false
|
|
246
|
-
})
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
assert(result.data, 'Result should have data array')
|
|
249
|
+
assert(result.data.length > 0, 'Should have at least one publisher')
|
|
247
250
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const publisher = result.data[0];
|
|
252
|
-
const publisherBooks = (result.included || []).filter(r => r.type === 'books');
|
|
251
|
+
const publisher = result.data[0]
|
|
252
|
+
const publisherBooks = (result.included || []).filter(r => r.type === 'books')
|
|
253
253
|
|
|
254
254
|
// Should apply queryDefaultLimit of 10
|
|
255
|
-
assert.equal(publisherBooks.length, 10, 'Should include only 10 books by default')
|
|
256
|
-
})
|
|
255
|
+
assert.equal(publisherBooks.length, 10, 'Should include only 10 books by default')
|
|
256
|
+
})
|
|
257
257
|
|
|
258
258
|
it('should respect explicit limit in relationship config', async () => {
|
|
259
259
|
// Create a country first
|
|
@@ -262,8 +262,8 @@ describe('Query Limits and Include Limits', () => {
|
|
|
262
262
|
name: 'Test Country',
|
|
263
263
|
code: 'TC'
|
|
264
264
|
})
|
|
265
|
-
})
|
|
266
|
-
|
|
265
|
+
})
|
|
266
|
+
|
|
267
267
|
// Create a publisher
|
|
268
268
|
const publisher = await api.resources.publishers.post({
|
|
269
269
|
inputRecord: createJsonApiDocument('publishers', {
|
|
@@ -271,33 +271,33 @@ describe('Query Limits and Include Limits', () => {
|
|
|
271
271
|
}, {
|
|
272
272
|
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
273
273
|
})
|
|
274
|
-
})
|
|
274
|
+
})
|
|
275
275
|
|
|
276
276
|
// Create authors with many books
|
|
277
277
|
const author = await api.resources.authors.post({
|
|
278
278
|
inputRecord: createJsonApiDocument('authors', {
|
|
279
279
|
name: 'Test Author'
|
|
280
280
|
}, {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
})
|
|
281
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
282
|
+
})
|
|
283
|
+
})
|
|
284
284
|
|
|
285
285
|
// Create 10 books and associate with author
|
|
286
286
|
for (let i = 1; i <= 10; i++) {
|
|
287
287
|
const book = await api.resources.books.post({
|
|
288
288
|
inputRecord: createJsonApiDocument('books', {
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
})
|
|
289
|
+
title: `Book ${i}`
|
|
290
|
+
}, {
|
|
291
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
292
|
+
})
|
|
293
|
+
})
|
|
294
294
|
|
|
295
295
|
await api.resources.book_authors.post({
|
|
296
296
|
inputRecord: createJsonApiDocument('book_authors', {}, {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
})
|
|
297
|
+
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
298
|
+
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
299
|
+
})
|
|
300
|
+
})
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
// Query authors with books (has explicit limit of 3)
|
|
@@ -306,12 +306,12 @@ describe('Query Limits and Include Limits', () => {
|
|
|
306
306
|
include: ['books']
|
|
307
307
|
},
|
|
308
308
|
simplified: false
|
|
309
|
-
})
|
|
309
|
+
})
|
|
310
310
|
|
|
311
|
-
const authorBooks = result.included.filter(r => r.type === 'books')
|
|
312
|
-
assert.equal(authorBooks.length, 3, 'Should respect explicit limit of 3')
|
|
313
|
-
})
|
|
314
|
-
})
|
|
311
|
+
const authorBooks = result.included.filter(r => r.type === 'books')
|
|
312
|
+
assert.equal(authorBooks.length, 3, 'Should respect explicit limit of 3')
|
|
313
|
+
})
|
|
314
|
+
})
|
|
315
315
|
|
|
316
316
|
describe('Include Limits - Polymorphic Relationships', () => {
|
|
317
317
|
beforeEach(async () => {
|
|
@@ -321,16 +321,16 @@ describe('Query Limits and Include Limits', () => {
|
|
|
321
321
|
name: 'Test Country',
|
|
322
322
|
code: 'TC'
|
|
323
323
|
})
|
|
324
|
-
})
|
|
324
|
+
})
|
|
325
325
|
|
|
326
326
|
// Create a publisher with country
|
|
327
327
|
const publisher = await api.resources.publishers.post({
|
|
328
328
|
inputRecord: createJsonApiDocument('publishers', {
|
|
329
329
|
name: 'Test Publisher'
|
|
330
330
|
}, {
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
})
|
|
331
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
332
|
+
})
|
|
333
|
+
})
|
|
334
334
|
|
|
335
335
|
// Create books with reviews
|
|
336
336
|
for (let bookNum = 1; bookNum <= 3; bookNum++) {
|
|
@@ -346,7 +346,7 @@ describe('Query Limits and Include Limits', () => {
|
|
|
346
346
|
}
|
|
347
347
|
}
|
|
348
348
|
}
|
|
349
|
-
})
|
|
349
|
+
})
|
|
350
350
|
|
|
351
351
|
// Create 15 reviews for each book
|
|
352
352
|
for (let i = 1; i <= 15; i++) {
|
|
@@ -367,10 +367,10 @@ describe('Query Limits and Include Limits', () => {
|
|
|
367
367
|
}
|
|
368
368
|
},
|
|
369
369
|
simplified: false
|
|
370
|
-
})
|
|
370
|
+
})
|
|
371
371
|
}
|
|
372
372
|
}
|
|
373
|
-
})
|
|
373
|
+
})
|
|
374
374
|
|
|
375
375
|
it('should apply default limit to polymorphic hasMany includes', async () => {
|
|
376
376
|
// Query books with reviews
|
|
@@ -379,21 +379,21 @@ describe('Query Limits and Include Limits', () => {
|
|
|
379
379
|
include: ['reviews']
|
|
380
380
|
},
|
|
381
381
|
simplified: false
|
|
382
|
-
})
|
|
382
|
+
})
|
|
383
383
|
|
|
384
384
|
// Without window strategy, the default strategy will limit total reviews
|
|
385
|
-
const totalReviews = result.included.filter(r => r.type === 'reviews')
|
|
386
|
-
|
|
385
|
+
const totalReviews = result.included.filter(r => r.type === 'reviews')
|
|
386
|
+
|
|
387
387
|
// Should apply queryDefaultLimit of 10 to total reviews
|
|
388
|
-
assert.equal(totalReviews.length, 10, 'Should limit total reviews to default of 10')
|
|
389
|
-
})
|
|
390
|
-
})
|
|
388
|
+
assert.equal(totalReviews.length, 10, 'Should limit total reviews to default of 10')
|
|
389
|
+
})
|
|
390
|
+
})
|
|
391
391
|
|
|
392
392
|
describe('Include Limit Validation', () => {
|
|
393
393
|
it('should throw error when include limit exceeds queryMaxLimit at resource definition', async () => {
|
|
394
394
|
// Create a new API instance with stricter limits
|
|
395
|
-
const strictApi = new Api({ name: 'strict-api' })
|
|
396
|
-
|
|
395
|
+
const strictApi = new Api({ name: 'strict-api' })
|
|
396
|
+
|
|
397
397
|
await strictApi.use(RestApiPlugin, {
|
|
398
398
|
queryDefaultLimit: 5,
|
|
399
399
|
queryMaxLimit: 20,
|
|
@@ -401,9 +401,9 @@ describe('Query Limits and Include Limits', () => {
|
|
|
401
401
|
returnFullRecord: {
|
|
402
402
|
post: true
|
|
403
403
|
}
|
|
404
|
-
})
|
|
405
|
-
|
|
406
|
-
await strictApi.use(RestApiKnexPlugin, { knex })
|
|
404
|
+
})
|
|
405
|
+
|
|
406
|
+
await strictApi.use(RestApiKnexPlugin, { knex })
|
|
407
407
|
|
|
408
408
|
// Try to create a resource with include limit exceeding max
|
|
409
409
|
await assert.rejects(
|
|
@@ -423,15 +423,15 @@ describe('Query Limits and Include Limits', () => {
|
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
}
|
|
426
|
-
})
|
|
426
|
+
})
|
|
427
427
|
},
|
|
428
428
|
(err) => {
|
|
429
|
-
return err.message && err.message.includes('limit') && err.message.includes('exceeds queryMaxLimit')
|
|
429
|
+
return err.message && err.message.includes('limit') && err.message.includes('exceeds queryMaxLimit')
|
|
430
430
|
},
|
|
431
431
|
'Should throw error for excessive include limit'
|
|
432
|
-
)
|
|
433
|
-
})
|
|
434
|
-
})
|
|
432
|
+
)
|
|
433
|
+
})
|
|
434
|
+
})
|
|
435
435
|
|
|
436
436
|
describe('Window Strategy with Defaults', () => {
|
|
437
437
|
beforeEach(async () => {
|
|
@@ -441,7 +441,7 @@ describe('Query Limits and Include Limits', () => {
|
|
|
441
441
|
name: 'USA',
|
|
442
442
|
code: 'US'
|
|
443
443
|
})
|
|
444
|
-
})
|
|
444
|
+
})
|
|
445
445
|
|
|
446
446
|
// Create multiple publishers with different numbers of books
|
|
447
447
|
for (let pubNum = 1; pubNum <= 3; pubNum++) {
|
|
@@ -450,13 +450,13 @@ describe('Query Limits and Include Limits', () => {
|
|
|
450
450
|
data: createJsonApiDocument('publishers', {
|
|
451
451
|
name: `Publisher ${pubNum}`
|
|
452
452
|
}, {
|
|
453
|
-
|
|
454
|
-
|
|
453
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
454
|
+
}).data
|
|
455
455
|
}
|
|
456
456
|
})
|
|
457
457
|
|
|
458
458
|
// Create different number of books for each publisher
|
|
459
|
-
const bookCount = pubNum * 8
|
|
459
|
+
const bookCount = pubNum * 8 // 8, 16, 24 books
|
|
460
460
|
for (let i = 1; i <= bookCount; i++) {
|
|
461
461
|
await api.resources.books.post({
|
|
462
462
|
inputRecord: {
|
|
@@ -470,10 +470,10 @@ describe('Query Limits and Include Limits', () => {
|
|
|
470
470
|
}
|
|
471
471
|
}
|
|
472
472
|
}
|
|
473
|
-
})
|
|
473
|
+
})
|
|
474
474
|
}
|
|
475
475
|
}
|
|
476
|
-
})
|
|
476
|
+
})
|
|
477
477
|
|
|
478
478
|
it('should apply per-parent limits with window strategy', async () => {
|
|
479
479
|
// Query all publishers with books (window strategy configured)
|
|
@@ -482,27 +482,27 @@ describe('Query Limits and Include Limits', () => {
|
|
|
482
482
|
include: ['books']
|
|
483
483
|
},
|
|
484
484
|
simplified: false
|
|
485
|
-
})
|
|
485
|
+
})
|
|
486
486
|
|
|
487
487
|
// Each publisher should have its own limit applied
|
|
488
488
|
for (const publisher of result.data) {
|
|
489
|
-
const publisherBooks = result.included.filter(r =>
|
|
490
|
-
r.type === 'books' &&
|
|
489
|
+
const publisherBooks = result.included.filter(r =>
|
|
490
|
+
r.type === 'books' &&
|
|
491
491
|
r.relationships.publisher.data.id === publisher.id
|
|
492
|
-
)
|
|
493
|
-
|
|
492
|
+
)
|
|
493
|
+
|
|
494
494
|
// Each publisher gets up to queryDefaultLimit (10) books
|
|
495
|
-
assert(publisherBooks.length <= 10,
|
|
496
|
-
`Publisher ${publisher.id} should have at most 10 books with window strategy`)
|
|
495
|
+
assert(publisherBooks.length <= 10,
|
|
496
|
+
`Publisher ${publisher.id} should have at most 10 books with window strategy`)
|
|
497
497
|
}
|
|
498
498
|
|
|
499
499
|
// Total books can exceed queryDefaultLimit since each parent gets its own limit
|
|
500
|
-
const totalBooks = result.included.filter(r => r.type === 'books').length
|
|
501
|
-
assert(totalBooks > 10, 'Total books should exceed 10 with window strategy')
|
|
500
|
+
const totalBooks = result.included.filter(r => r.type === 'books').length
|
|
501
|
+
assert(totalBooks > 10, 'Total books should exceed 10 with window strategy')
|
|
502
502
|
// Publisher 1 has 8 books, Publishers 2 and 3 are limited to 10 each
|
|
503
|
-
assert.equal(totalBooks, 28, 'Should have 28 total books (8 + 10 + 10)')
|
|
504
|
-
})
|
|
505
|
-
})
|
|
503
|
+
assert.equal(totalBooks, 28, 'Should have 28 total books (8 + 10 + 10)')
|
|
504
|
+
})
|
|
505
|
+
})
|
|
506
506
|
|
|
507
507
|
describe('Many-to-Many with Window Strategy', () => {
|
|
508
508
|
it('should apply window limits to many-to-many relationships', async () => {
|
|
@@ -512,38 +512,38 @@ describe('Query Limits and Include Limits', () => {
|
|
|
512
512
|
name: 'USA',
|
|
513
513
|
code: 'US'
|
|
514
514
|
})
|
|
515
|
-
})
|
|
515
|
+
})
|
|
516
516
|
|
|
517
517
|
const publisher = await api.resources.publishers.post({
|
|
518
518
|
inputRecord: createJsonApiDocument('publishers', {
|
|
519
519
|
name: 'Test Publisher'
|
|
520
520
|
}, {
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
})
|
|
521
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
522
|
+
})
|
|
523
|
+
})
|
|
524
524
|
|
|
525
525
|
// Create 2 authors
|
|
526
|
-
const authors = []
|
|
526
|
+
const authors = []
|
|
527
527
|
for (let i = 1; i <= 2; i++) {
|
|
528
528
|
const author = await api.resources.authors.post({
|
|
529
529
|
inputRecord: createJsonApiDocument('authors', {
|
|
530
530
|
name: `Author ${i}`
|
|
531
531
|
}, {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
})
|
|
535
|
-
authors.push(author)
|
|
532
|
+
country: createRelationship({ type: 'countries', id: String(country.id) })
|
|
533
|
+
})
|
|
534
|
+
})
|
|
535
|
+
authors.push(author)
|
|
536
536
|
}
|
|
537
537
|
|
|
538
538
|
// Create 10 books and associate ALL with BOTH authors
|
|
539
539
|
for (let i = 1; i <= 10; i++) {
|
|
540
540
|
const book = await api.resources.books.post({
|
|
541
541
|
inputRecord: createJsonApiDocument('books', {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
})
|
|
542
|
+
title: `Book ${i}`
|
|
543
|
+
}, {
|
|
544
|
+
publisher: createRelationship({ type: 'publishers', id: String(publisher.id) })
|
|
545
|
+
})
|
|
546
|
+
})
|
|
547
547
|
|
|
548
548
|
// Associate with both authors
|
|
549
549
|
for (const author of authors) {
|
|
@@ -552,7 +552,7 @@ describe('Query Limits and Include Limits', () => {
|
|
|
552
552
|
book: createRelationship({ type: 'books', id: String(book.id) }),
|
|
553
553
|
author: createRelationship({ type: 'authors', id: String(author.id) })
|
|
554
554
|
})
|
|
555
|
-
})
|
|
555
|
+
})
|
|
556
556
|
}
|
|
557
557
|
}
|
|
558
558
|
|
|
@@ -562,24 +562,23 @@ describe('Query Limits and Include Limits', () => {
|
|
|
562
562
|
include: ['books']
|
|
563
563
|
},
|
|
564
564
|
simplified: false
|
|
565
|
-
})
|
|
565
|
+
})
|
|
566
566
|
|
|
567
|
-
|
|
568
567
|
// Each author should get exactly 3 books (configured limit)
|
|
569
568
|
for (const author of result.data) {
|
|
570
569
|
// Check the author's relationships to see which books they have
|
|
571
|
-
const authorBookIds = author.relationships?.books?.data?.map(b => b.id) || []
|
|
572
|
-
|
|
570
|
+
const authorBookIds = author.relationships?.books?.data?.map(b => b.id) || []
|
|
571
|
+
|
|
573
572
|
// Verify the books exist in the included array
|
|
574
|
-
const authorBooks = result.included.filter(r =>
|
|
573
|
+
const authorBooks = result.included.filter(r =>
|
|
575
574
|
r.type === 'books' && authorBookIds.includes(r.id)
|
|
576
|
-
)
|
|
577
|
-
|
|
578
|
-
assert.equal(authorBookIds.length, 3,
|
|
579
|
-
`Author ${author.id} should have exactly 3 book relationships (configured limit)`)
|
|
580
|
-
assert.equal(authorBooks.length, 3,
|
|
581
|
-
`Author ${author.id} should have exactly 3 books in included array`)
|
|
575
|
+
)
|
|
576
|
+
|
|
577
|
+
assert.equal(authorBookIds.length, 3,
|
|
578
|
+
`Author ${author.id} should have exactly 3 book relationships (configured limit)`)
|
|
579
|
+
assert.equal(authorBooks.length, 3,
|
|
580
|
+
`Author ${author.id} should have exactly 3 books in included array`)
|
|
582
581
|
}
|
|
583
|
-
})
|
|
584
|
-
})
|
|
585
|
-
})
|
|
582
|
+
})
|
|
583
|
+
})
|
|
584
|
+
})
|