json-rest-api 1.0.10 → 1.0.13

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 (117) hide show
  1. package/.claude/settings.local.json +9 -1
  2. package/AGENTS.md +34 -0
  3. package/CLAUDE.md +26 -1
  4. package/CRUSH.md +55 -0
  5. package/README.md +3 -3
  6. package/TODO.todo +8 -0
  7. package/docs/API.md +17 -18
  8. package/docs/COMPARISON.md +18 -13
  9. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +17 -21
  10. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +33 -2
  11. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +61 -7
  12. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +58 -61
  13. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +37 -37
  14. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +103 -87
  15. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +37 -34
  16. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +45 -42
  17. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +3 -3
  18. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +1 -1
  19. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +5 -5
  20. package/docs/GUIDE/GUIDE_4_Authentication_And_Permissions.md +498 -755
  21. package/docs/GUIDE/GUIDE_6_Positioning.md +1 -3
  22. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +4 -5
  23. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +15 -6
  24. package/docs/GUIDE/GUIDE_X_Cors.md +2 -5
  25. package/docs/GUIDE/GUIDE_X_File_Uploads.md +2 -3
  26. package/docs/GUIDE/GUIDE_X_Multihome.md +1 -4
  27. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -2
  28. package/docs/GUIDE/GUIDE_X_URL_Management.md +317 -0
  29. package/docs/GUIDE/index.md +3 -0
  30. package/docs/QUICKSTART.md +12 -12
  31. package/docs/index.md +2 -2
  32. package/docs/mini-guides/file-uploads-complete.md +0 -1
  33. package/examples/url-override-example.js +137 -0
  34. package/index.js +7 -1
  35. package/package.json +7 -11
  36. package/plugins/core/bulk-operations-plugin.js +2 -0
  37. package/plugins/core/connectors/express-plugin.js +43 -24
  38. package/plugins/core/connectors/lib/request-helpers.js +2 -2
  39. package/plugins/core/lib/anyapi/anyapi-registry.js +821 -0
  40. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +647 -0
  41. package/plugins/core/lib/anyapi/schema-utils.js +186 -0
  42. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +113 -0
  43. package/plugins/core/lib/dbTablesOperations.js +305 -0
  44. package/plugins/core/lib/querying/knex-cross-table-search.js +64 -108
  45. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +21 -27
  46. package/plugins/core/lib/querying/knex-pagination-helpers.js +36 -20
  47. package/plugins/core/lib/querying/knex-query-helpers-base.js +49 -14
  48. package/plugins/core/lib/querying/knex-query-helpers.js +378 -222
  49. package/plugins/core/lib/querying/knex-relationship-includes.js +262 -87
  50. package/plugins/core/lib/querying/url-helpers.js +119 -0
  51. package/plugins/core/lib/querying-writing/compile-schemas.js +36 -28
  52. package/plugins/core/lib/querying-writing/database-capabilities.js +10 -9
  53. package/plugins/core/lib/querying-writing/knex-field-helpers.js +9 -3
  54. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +2 -2
  55. package/plugins/core/lib/querying-writing/payload-validators.js +10 -16
  56. package/plugins/core/lib/querying-writing/schema-helpers.js +52 -60
  57. package/plugins/core/lib/querying-writing/scope-validations.js +78 -22
  58. package/plugins/core/lib/querying-writing/simplified-helpers.js +208 -61
  59. package/plugins/core/lib/storage/storage-adapter.js +160 -0
  60. package/plugins/core/lib/writing/relationship-processor.js +4 -11
  61. package/plugins/core/rest-api-access.js +456 -0
  62. package/plugins/core/rest-api-anyapi-knex-plugin.js +2285 -0
  63. package/plugins/core/rest-api-knex-plugin.js +226 -87
  64. package/plugins/core/rest-api-multihome-plugin.js +89 -27
  65. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +0 -1
  66. package/plugins/core/rest-api-plugin-methods/check-permissions.js +1 -5
  67. package/plugins/core/rest-api-plugin-methods/common.js +46 -10
  68. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +31 -27
  69. package/plugins/core/rest-api-plugin-methods/delete.js +1 -4
  70. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +13 -1
  71. package/plugins/core/rest-api-plugin-methods/get-related.js +114 -56
  72. package/plugins/core/rest-api-plugin-methods/get-relationship.js +4 -3
  73. package/plugins/core/rest-api-plugin-methods/get.js +2 -5
  74. package/plugins/core/rest-api-plugin-methods/patch.js +15 -16
  75. package/plugins/core/rest-api-plugin-methods/post-relationship.js +21 -14
  76. package/plugins/core/rest-api-plugin-methods/post.js +23 -11
  77. package/plugins/core/rest-api-plugin-methods/put.js +18 -21
  78. package/plugins/core/rest-api-plugin-methods/query.js +3 -4
  79. package/plugins/core/rest-api-plugin-methods/release.js +13 -0
  80. package/plugins/core/rest-api-plugin.js +3 -4
  81. package/plugins/core/rest-api-positioning-plugin.js +253 -76
  82. package/plugins/core/socketio-plugin.js +436 -502
  83. package/quickTest.js +2 -2
  84. package/scripts/debug-anyapi-review-filter.mjs +63 -0
  85. package/scripts/inspect-anyapi-descriptor-country.mjs +19 -0
  86. package/scripts/inspect-anyapi-descriptor.mjs +19 -0
  87. package/test-debug.js +59 -0
  88. package/tests/README.md +7 -1
  89. package/tests/anyapi-basic.test.js +102 -0
  90. package/tests/anyapi-cursor-pagination.test.js +144 -0
  91. package/tests/computed-fields.test.js +27 -12
  92. package/tests/cors.test.js +5 -6
  93. package/tests/curl.test.js +1 -1
  94. package/tests/custom-idproperty-relationships.test.js +37 -29
  95. package/tests/field-getters.test.js +25 -7
  96. package/tests/fixtures/api-configs.js +576 -150
  97. package/tests/helpers/storage-mode.js +65 -0
  98. package/tests/helpers/test-utils.js +85 -3
  99. package/tests/include-depth-validation.test.js +35 -1
  100. package/tests/nested-includes.test.js +130 -103
  101. package/tests/pagination-cursor-multifield.test.js +17 -13
  102. package/tests/pagination-enhanced.test.js +19 -24
  103. package/tests/polymorphic-simple.test.js +9 -9
  104. package/tests/positioning-advanced.test.js +19 -19
  105. package/tests/positioning-extended.test.js +25 -25
  106. package/tests/positioning-multifilter.test.js +62 -62
  107. package/tests/positioning.test.js +7 -7
  108. package/tests/queries.test.js +5 -5
  109. package/tests/query-limits.test.js +77 -92
  110. package/tests/rest-api-access.test.js +206 -0
  111. package/tests/searchschema-merge.test.js +182 -0
  112. package/tests/socketio.test.js +33 -14
  113. package/tests/virtual-fields.test.js +0 -1
  114. package/docs/DOCS_ALL.md +0 -21894
  115. package/plugins/core/jwt-auth-plugin.js +0 -897
  116. package/plugins/core/lib/jwt-auth-helpers.js +0 -130
  117. 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
- const dbCountry = await knex('custom_id_countries').first();
64
- assert(dbCountry.country_id, 'Database should have country_id column');
65
- assert.equal(String(dbCountry.country_id), countryResult.data.id);
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
- const dbPublisher = await knex('custom_id_publishers').first();
144
- assert.equal(String(dbPublisher.country_id), countryResult.data.id);
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
- const pivotRecords = await knex('custom_id_book_authors').select('*');
270
- assert.equal(pivotRecords.length, 2);
271
-
272
- // Check that book_id and author_id columns contain the right values
273
- const bookIds = pivotRecords.map(r => r.book_id);
274
- const authorIds = pivotRecords.map(r => r.author_id).sort();
275
-
276
- assert(bookIds.every(id => String(id) === bookId));
277
- assert.deepEqual(authorIds.map(String).sort(), [author1Id, author2Id].sort());
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
- // Verify database
392
- const dbReview = await knex('custom_id_reviews').first();
393
- assert.equal(dbReview.reviewable_type, 'books');
394
- assert.equal(String(dbReview.reviewable_id), bookId);
395
- assert(dbReview.review_id, 'Should have custom review_id column');
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: { country_id: usa.data.id },
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('custom_id_book_authors').count('* as count').first();
812
- assert.equal(pivotCount.count, 3);
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('custom_id_book_authors').count('* as count').first();
849
- assert.equal(pivotCount.count, 1, 'Pivot record should still exist');
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
- // Store encrypted data directly in DB
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
- }).returning('id').then(r => r[0].id);
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
- product_id: product.id,
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
- product_id: product.id,
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
- product_id: product.id,
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
+ });