json-rest-api 1.0.10 → 1.0.11
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/.claude/settings.local.json +9 -1
- package/AGENTS.md +34 -0
- package/CLAUDE.md +26 -1
- package/CRUSH.md +55 -0
- package/README.md +3 -3
- package/TODO.todo +8 -0
- package/docs/API.md +17 -18
- package/docs/COMPARISON.md +18 -13
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +17 -21
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +33 -2
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +61 -7
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +58 -61
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +37 -37
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +103 -87
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +37 -34
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +45 -42
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +3 -3
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +1 -1
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -5
- package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +498 -755
- package/docs/GUIDE/GUIDE_6_Positioning.md +1 -3
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +4 -5
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +15 -6
- package/docs/GUIDE/GUIDE_X_Cors.md +2 -5
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +2 -3
- package/docs/GUIDE/GUIDE_X_Multihome.md +1 -4
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -2
- package/docs/GUIDE/GUIDE_X_URL_Management.md +317 -0
- package/docs/GUIDE/index.md +3 -0
- package/docs/QUICKSTART.md +12 -12
- package/docs/index.md +2 -2
- package/docs/mini-guides/file-uploads-complete.md +0 -1
- package/examples/url-override-example.js +137 -0
- package/index.js +7 -1
- package/package.json +5 -9
- package/plugins/core/bulk-operations-plugin.js +2 -0
- package/plugins/core/connectors/express-plugin.js +43 -24
- package/plugins/core/connectors/lib/request-helpers.js +2 -2
- package/plugins/core/lib/anyapi/anyapi-registry.js +821 -0
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +647 -0
- package/plugins/core/lib/anyapi/schema-utils.js +186 -0
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +113 -0
- package/plugins/core/lib/dbTablesOperations.js +305 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +64 -108
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +21 -27
- package/plugins/core/lib/querying/knex-pagination-helpers.js +36 -20
- package/plugins/core/lib/querying/knex-query-helpers-base.js +49 -14
- package/plugins/core/lib/querying/knex-query-helpers.js +378 -222
- package/plugins/core/lib/querying/knex-relationship-includes.js +262 -87
- package/plugins/core/lib/querying/url-helpers.js +119 -0
- package/plugins/core/lib/querying-writing/compile-schemas.js +36 -28
- package/plugins/core/lib/querying-writing/database-capabilities.js +10 -9
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +9 -3
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +2 -2
- package/plugins/core/lib/querying-writing/payload-validators.js +10 -16
- package/plugins/core/lib/querying-writing/schema-helpers.js +52 -60
- package/plugins/core/lib/querying-writing/scope-validations.js +78 -22
- package/plugins/core/lib/querying-writing/simplified-helpers.js +208 -61
- package/plugins/core/lib/storage/storage-adapter.js +160 -0
- package/plugins/core/lib/writing/relationship-processor.js +4 -11
- package/plugins/core/rest-api-access.js +456 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +2285 -0
- package/plugins/core/rest-api-knex-plugin.js +226 -87
- package/plugins/core/rest-api-multihome-plugin.js +89 -27
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +0 -1
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +1 -5
- package/plugins/core/rest-api-plugin-methods/common.js +46 -10
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +31 -27
- package/plugins/core/rest-api-plugin-methods/delete.js +1 -4
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +13 -1
- package/plugins/core/rest-api-plugin-methods/get-related.js +114 -56
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +4 -3
- package/plugins/core/rest-api-plugin-methods/get.js +2 -5
- package/plugins/core/rest-api-plugin-methods/patch.js +15 -16
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +21 -14
- package/plugins/core/rest-api-plugin-methods/post.js +23 -11
- package/plugins/core/rest-api-plugin-methods/put.js +18 -21
- package/plugins/core/rest-api-plugin-methods/query.js +3 -4
- package/plugins/core/rest-api-plugin-methods/release.js +13 -0
- package/plugins/core/rest-api-plugin.js +3 -4
- package/plugins/core/rest-api-positioning-plugin.js +253 -76
- package/plugins/core/socketio-plugin.js +436 -502
- package/quickTest.js +2 -2
- package/scripts/debug-anyapi-review-filter.mjs +63 -0
- package/scripts/inspect-anyapi-descriptor-country.mjs +19 -0
- package/scripts/inspect-anyapi-descriptor.mjs +19 -0
- package/test-debug.js +59 -0
- package/tests/README.md +7 -1
- package/tests/anyapi-basic.test.js +102 -0
- package/tests/anyapi-cursor-pagination.test.js +144 -0
- package/tests/computed-fields.test.js +27 -12
- package/tests/cors.test.js +5 -6
- package/tests/curl.test.js +1 -1
- package/tests/custom-idproperty-relationships.test.js +37 -29
- package/tests/field-getters.test.js +25 -7
- package/tests/fixtures/api-configs.js +576 -150
- package/tests/helpers/storage-mode.js +65 -0
- package/tests/helpers/test-utils.js +85 -3
- package/tests/include-depth-validation.test.js +35 -1
- package/tests/nested-includes.test.js +130 -103
- package/tests/pagination-cursor-multifield.test.js +17 -13
- package/tests/pagination-enhanced.test.js +19 -24
- package/tests/polymorphic-simple.test.js +9 -9
- package/tests/positioning-advanced.test.js +19 -19
- package/tests/positioning-extended.test.js +25 -25
- package/tests/positioning-multifilter.test.js +62 -62
- package/tests/positioning.test.js +7 -7
- package/tests/queries.test.js +5 -5
- package/tests/query-limits.test.js +77 -92
- package/tests/rest-api-access.test.js +206 -0
- package/tests/searchschema-merge.test.js +182 -0
- package/tests/socketio.test.js +33 -14
- package/tests/virtual-fields.test.js +0 -1
- package/docs/DOCS_ALL.md +0 -21894
- package/plugins/core/jwt-auth-plugin.js +0 -897
- package/plugins/core/lib/jwt-auth-helpers.js +0 -130
- package/tests/jwt-auth.test.js +0 -692
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
assertResourceAttributes,
|
|
14
14
|
assertResourceRelationship
|
|
15
15
|
} from './helpers/test-utils.js';
|
|
16
|
+
import { storageMode } from './helpers/storage-mode.js';
|
|
16
17
|
|
|
17
18
|
// Create Knex instance for tests
|
|
18
19
|
const knex = knexLib({
|
|
@@ -59,10 +60,12 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
59
60
|
assert(countryResult.data.id, 'Should have id field');
|
|
60
61
|
assert(!countryResult.data.attributes.country_id, 'Should not expose country_id in attributes');
|
|
61
62
|
|
|
62
|
-
// Verify database has correct column
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
63
|
+
// Verify database has correct column when using legacy storage
|
|
64
|
+
if (!storageMode.isAnyApi()) {
|
|
65
|
+
const dbCountry = await knex('custom_id_countries').first();
|
|
66
|
+
assert(dbCountry.country_id, 'Database should have country_id column');
|
|
67
|
+
assert.equal(String(dbCountry.country_id), countryResult.data.id);
|
|
68
|
+
}
|
|
66
69
|
|
|
67
70
|
// Test GET
|
|
68
71
|
const getResult = await api.resources.countries.get({
|
|
@@ -139,9 +142,11 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
139
142
|
assertResourceRelationship(publisherResult.data, 'country',
|
|
140
143
|
resourceIdentifier('countries', countryResult.data.id));
|
|
141
144
|
|
|
142
|
-
// Verify database foreign key uses country_id
|
|
143
|
-
|
|
144
|
-
|
|
145
|
+
// Verify database foreign key uses country_id in legacy mode
|
|
146
|
+
if (!storageMode.isAnyApi()) {
|
|
147
|
+
const dbPublisher = await knex('custom_id_publishers').first();
|
|
148
|
+
assert.equal(String(dbPublisher.country_id), countryResult.data.id);
|
|
149
|
+
}
|
|
145
150
|
|
|
146
151
|
// Verify through GET with include
|
|
147
152
|
const getResult = await api.resources.publishers.get({
|
|
@@ -266,15 +271,19 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
266
271
|
bookId = bookResult.data.id;
|
|
267
272
|
|
|
268
273
|
// Verify pivot table has correct foreign keys
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
274
|
+
if (!storageMode.isAnyApi()) {
|
|
275
|
+
const pivotRecords = await knex('custom_id_book_authors').select('*');
|
|
276
|
+
assert.equal(pivotRecords.length, 2);
|
|
277
|
+
|
|
278
|
+
const bookIds = pivotRecords.map((r) => r.book_id);
|
|
279
|
+
const authorIds = pivotRecords.map((r) => r.author_id).sort();
|
|
280
|
+
|
|
281
|
+
assert(bookIds.every((id) => String(id) === bookId));
|
|
282
|
+
assert.deepEqual(authorIds.map(String).sort(), [author1Id, author2Id].sort());
|
|
283
|
+
} else {
|
|
284
|
+
const pivotCount = await countRecords(knex, 'custom_id_book_authors');
|
|
285
|
+
assert.equal(pivotCount, 2);
|
|
286
|
+
}
|
|
278
287
|
|
|
279
288
|
// Verify through GET with include
|
|
280
289
|
const getResult = await api.resources.books.get({
|
|
@@ -388,11 +397,12 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
388
397
|
assert.equal(review.data.attributes.reviewable_type, 'books');
|
|
389
398
|
assert.equal(String(review.data.attributes.reviewable_id), bookId);
|
|
390
399
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
400
|
+
if (!storageMode.isAnyApi()) {
|
|
401
|
+
const dbReview = await knex('custom_id_reviews').first();
|
|
402
|
+
assert.equal(dbReview.reviewable_type, 'books');
|
|
403
|
+
assert.equal(String(dbReview.reviewable_id), bookId);
|
|
404
|
+
assert(dbReview.review_id, 'Should have custom review_id column');
|
|
405
|
+
}
|
|
396
406
|
});
|
|
397
407
|
|
|
398
408
|
it('should create polymorphic review for author with custom IDs', async () => {
|
|
@@ -441,8 +451,6 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
441
451
|
filters: { reviewable_type: 'books' }
|
|
442
452
|
}
|
|
443
453
|
});
|
|
444
|
-
|
|
445
|
-
debugger
|
|
446
454
|
assert.equal(bookReviews.data.length, 1, `Expected 1 book review, got ${bookReviews.data.length}`);
|
|
447
455
|
assert.equal(bookReviews.data[0].attributes.reviewable_type, 'books');
|
|
448
456
|
});
|
|
@@ -756,7 +764,7 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
756
764
|
// Complex query: US books with their publishers and authors
|
|
757
765
|
const result = await api.resources.books.query({
|
|
758
766
|
queryParams: {
|
|
759
|
-
filters: {
|
|
767
|
+
filters: { country: usa.data.id },
|
|
760
768
|
include: ['publisher.country', 'authors'],
|
|
761
769
|
sort: ['title']
|
|
762
770
|
}
|
|
@@ -808,8 +816,8 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
808
816
|
assert.equal(bookWithAuthors.included.length, 3);
|
|
809
817
|
|
|
810
818
|
// Verify pivot table
|
|
811
|
-
const pivotCount = await knex
|
|
812
|
-
assert.equal(pivotCount
|
|
819
|
+
const pivotCount = await countRecords(knex, 'custom_id_book_authors');
|
|
820
|
+
assert.equal(pivotCount, 3);
|
|
813
821
|
});
|
|
814
822
|
|
|
815
823
|
it('should handle DELETE operations with custom IDs', async () => {
|
|
@@ -845,8 +853,8 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
845
853
|
// The REST API correctly does NOT cascade delete pivot records
|
|
846
854
|
// This is intentional - automatic cascade deletion would be dangerous
|
|
847
855
|
// Pivot records remain and should be cleaned up by application logic if needed
|
|
848
|
-
const pivotCount = await knex
|
|
849
|
-
assert.equal(pivotCount
|
|
856
|
+
const pivotCount = await countRecords(knex, 'custom_id_book_authors');
|
|
857
|
+
assert.equal(pivotCount, 1, 'Pivot record should still exist');
|
|
850
858
|
|
|
851
859
|
// Verify author still exists
|
|
852
860
|
const authorStillExists = await api.resources.authors.get({ id: author.data.id });
|
|
@@ -888,4 +896,4 @@ describe('Custom idProperty Relationship Operations', () => {
|
|
|
888
896
|
resourceIdentifier('countries', country.data.id));
|
|
889
897
|
});
|
|
890
898
|
});
|
|
891
|
-
});
|
|
899
|
+
});
|
|
@@ -3,6 +3,7 @@ import assert from 'node:assert/strict';
|
|
|
3
3
|
import knexLib from 'knex';
|
|
4
4
|
import { createFieldGettersApi } from './fixtures/api-configs.js';
|
|
5
5
|
import { cleanTables } from './helpers/test-utils.js';
|
|
6
|
+
import { storageMode } from './helpers/storage-mode.js';
|
|
6
7
|
|
|
7
8
|
// Create Knex instance for tests
|
|
8
9
|
const knex = knexLib({
|
|
@@ -177,11 +178,25 @@ describe('Field Getters', () => {
|
|
|
177
178
|
// Helper to simulate encryption
|
|
178
179
|
const encrypt = (value) => Buffer.from(value).toString('base64');
|
|
179
180
|
|
|
180
|
-
|
|
181
|
-
const id = await knex('getter_encrypted').insert({
|
|
181
|
+
const payload = {
|
|
182
182
|
secret: encrypt('my-secret-value'),
|
|
183
183
|
data: encrypt('sensitive-info')
|
|
184
|
-
}
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
let id;
|
|
187
|
+
if (storageMode.isAnyApi()) {
|
|
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;
|
|
191
|
+
} else {
|
|
192
|
+
// Legacy mode can insert directly into the backing table for the same effect
|
|
193
|
+
const inserted = await knex('getter_encrypted')
|
|
194
|
+
.insert(payload)
|
|
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;
|
|
199
|
+
}
|
|
185
200
|
|
|
186
201
|
// Fetch should decrypt via async getters
|
|
187
202
|
const record = await api.resources.encrypted_data.get({ id });
|
|
@@ -215,13 +230,13 @@ describe('Field Getters', () => {
|
|
|
215
230
|
|
|
216
231
|
// Add reviews
|
|
217
232
|
await api.resources.reviews.post({
|
|
218
|
-
|
|
233
|
+
product: product.id,
|
|
219
234
|
content: 'Great product!',
|
|
220
235
|
rating: 5
|
|
221
236
|
});
|
|
222
237
|
|
|
223
238
|
await api.resources.reviews.post({
|
|
224
|
-
|
|
239
|
+
product: product.id,
|
|
225
240
|
content: 'Not bad',
|
|
226
241
|
rating: 3
|
|
227
242
|
});
|
|
@@ -252,7 +267,7 @@ describe('Field Getters', () => {
|
|
|
252
267
|
});
|
|
253
268
|
|
|
254
269
|
await api.resources.reviews.post({
|
|
255
|
-
|
|
270
|
+
product: product.id,
|
|
256
271
|
content: 'Review text',
|
|
257
272
|
rating: 4
|
|
258
273
|
});
|
|
@@ -297,6 +312,9 @@ describe('Field Getters', () => {
|
|
|
297
312
|
tableName: 'getter_errors'
|
|
298
313
|
});
|
|
299
314
|
await api.resources.error_test.createKnexTable();
|
|
315
|
+
if (storageMode.isAnyApi()) {
|
|
316
|
+
storageMode.registerTable('getter_errors', 'error_test');
|
|
317
|
+
}
|
|
300
318
|
|
|
301
319
|
const record = await api.resources.error_test.post({
|
|
302
320
|
good_field: 'hello',
|
|
@@ -350,4 +368,4 @@ describe('Field Getters', () => {
|
|
|
350
368
|
);
|
|
351
369
|
});
|
|
352
370
|
});
|
|
353
|
-
});
|
|
371
|
+
});
|