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.
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 +5 -9
  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
@@ -2,7 +2,7 @@ import { describe, it, before, after, beforeEach } from 'node:test';
2
2
  import assert from 'node:assert/strict';
3
3
  import knexLib from 'knex';
4
4
  import { io as ioClient } from 'socket.io-client';
5
- import jwt from 'jsonwebtoken';
5
+ import { SignJWT } from 'jose';
6
6
  import {
7
7
  validateJsonApiStructure,
8
8
  cleanTables,
@@ -13,6 +13,25 @@ import {
13
13
  } from './helpers/test-utils.js';
14
14
  import { createWebSocketApi } from './fixtures/api-configs.js';
15
15
 
16
+ // Create JWT token using jose
17
+ async function createToken(payload = {}, secret = 'test-secret-key') {
18
+ const encoder = new TextEncoder();
19
+ const key = encoder.encode(secret);
20
+
21
+ const jwt = new SignJWT({
22
+ sub: '123',
23
+ email: 'test@example.com',
24
+ roles: ['user'],
25
+ ...payload,
26
+ jti: `test-${Date.now()}`
27
+ })
28
+ .setProtectedHeader({ alg: 'HS256' })
29
+ .setIssuedAt()
30
+ .setExpirationTime('1h');
31
+
32
+ return await jwt.sign(key);
33
+ }
34
+
16
35
  // Create Knex instance for tests
17
36
  const knex = knexLib({
18
37
  client: 'better-sqlite3',
@@ -106,7 +125,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
106
125
  describe('Basic Subscription and Notifications', () => {
107
126
  it('should receive minimal notifications for subscribed resources', async () => {
108
127
  // Generate a real JWT token for testing
109
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
128
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
110
129
 
111
130
  const socket = ioClient(`http://localhost:${server.address().port}`, {
112
131
  path: '/api/socket.io',
@@ -170,7 +189,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
170
189
 
171
190
  it('should not receive notifications for non-matching filters', async () => {
172
191
  // Generate a real JWT token for testing
173
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
192
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
174
193
 
175
194
  const socket = ioClient(`http://localhost:${server.address().port}`, {
176
195
  path: '/api/socket.io',
@@ -234,7 +253,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
234
253
  describe('Filter Validation', () => {
235
254
  it('should validate filters against searchSchema', async () => {
236
255
  // Generate a real JWT token for testing
237
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
256
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
238
257
 
239
258
  const socket = ioClient(`http://localhost:${server.address().port}`, {
240
259
  path: '/api/socket.io',
@@ -265,7 +284,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
265
284
 
266
285
  it('should reject function filters without filterRecord', async () => {
267
286
  // Generate a real JWT token for testing
268
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
287
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
269
288
 
270
289
  const socket = ioClient(`http://localhost:${server.address().port}`, {
271
290
  path: '/api/socket.io',
@@ -294,7 +313,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
294
313
  const response = await new Promise((resolve) => {
295
314
  socket.emit('subscribe', {
296
315
  resource: 'books',
297
- filters: { country_id: countryResult.data.id }
316
+ filters: { country: countryResult.data.id }
298
317
  }, resolve);
299
318
  });
300
319
 
@@ -318,7 +337,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
318
337
  // This test verifies that broadcasts don't happen when operations fail
319
338
  // The library should rollback transactions and not send notifications
320
339
 
321
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
340
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
322
341
 
323
342
  const socket = ioClient(`http://localhost:${server.address().port}`, {
324
343
  path: '/api/socket.io',
@@ -375,7 +394,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
375
394
  // The library handles transactions internally and uses afterCommit hook
376
395
  // to ensure broadcasts happen only after successful commit
377
396
 
378
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
397
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
379
398
 
380
399
  const socket = ioClient(`http://localhost:${server.address().port}`, {
381
400
  path: '/api/socket.io',
@@ -439,7 +458,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
439
458
  describe('Multiple Subscriptions', () => {
440
459
  it('should handle multiple subscriptions from same client', async () => {
441
460
  // Generate a real JWT token for testing
442
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
461
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
443
462
 
444
463
  const socket = ioClient(`http://localhost:${server.address().port}`, {
445
464
  path: '/api/socket.io',
@@ -498,7 +517,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
498
517
 
499
518
  it('should unsubscribe correctly', async () => {
500
519
  // Generate a real JWT token for testing
501
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
520
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
502
521
 
503
522
  const socket = ioClient(`http://localhost:${server.address().port}`, {
504
523
  path: '/api/socket.io',
@@ -557,7 +576,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
557
576
  describe('Update and Delete Notifications', () => {
558
577
  it('should receive notifications for updates', async () => {
559
578
  // Generate a real JWT token for testing
560
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
579
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
561
580
 
562
581
  const socket = ioClient(`http://localhost:${server.address().port}`, {
563
582
  path: '/api/socket.io',
@@ -627,7 +646,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
627
646
 
628
647
  it('should receive notifications for deletes', async () => {
629
648
  // Generate a real JWT token for testing
630
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
649
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
631
650
 
632
651
  const socket = ioClient(`http://localhost:${server.address().port}`, {
633
652
  path: '/api/socket.io',
@@ -687,7 +706,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
687
706
  describe('Relationship Filters', () => {
688
707
  it('should filter by relationship fields', async () => {
689
708
  // Generate a real JWT token for testing
690
- const token = jwt.sign({ userId: 'test-user', role: 'user' }, 'test-secret-key', { expiresIn: '1h' });
709
+ const token = await createToken({ userId: 'test-user', role: 'user' }, 'test-secret-key');
691
710
 
692
711
  const socket = ioClient(`http://localhost:${server.address().port}`, {
693
712
  path: '/api/socket.io',
@@ -721,7 +740,7 @@ describe('WebSocket/Socket.IO Plugin', () => {
721
740
  await new Promise((resolve) => {
722
741
  socket.emit('subscribe', {
723
742
  resource: 'books',
724
- filters: { publisher_id: publisherResult.data.id }
743
+ filters: { publisher: publisherResult.data.id }
725
744
  }, resolve);
726
745
  });
727
746
 
@@ -29,7 +29,6 @@ describe('Virtual Fields Tests', () => {
29
29
  // Create a custom API with virtual fields
30
30
  api = new Api({
31
31
  name: 'virtual-fields-test',
32
- version: '1.0.0'
33
32
  });
34
33
 
35
34
  // Install plugins