json-rest-api 1.0.28 → 2.0.0

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 (289) hide show
  1. package/LICENSE +10 -670
  2. package/LICENSE-GPL-3.0 +674 -0
  3. package/LICENSE-MIT +21 -0
  4. package/README.md +39 -30
  5. package/docs/API.md +746 -1273
  6. package/docs/GUIDE/01-getting-started.md +338 -0
  7. package/docs/GUIDE/02-resources-and-relationships.md +33 -0
  8. package/docs/GUIDE/03-running-example.md +87 -0
  9. package/docs/GUIDE/04-creating-and-querying.md +181 -0
  10. package/docs/GUIDE/05-belongs-to.md +177 -0
  11. package/docs/GUIDE/06-has-many.md +184 -0
  12. package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
  13. package/docs/GUIDE/08-many-to-many.md +184 -0
  14. package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
  15. package/docs/GUIDE/10-put-and-patch.md +174 -0
  16. package/docs/GUIDE/11-relationship-endpoints.md +174 -0
  17. package/docs/GUIDE/12-field-transformations.md +287 -0
  18. package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
  19. package/docs/GUIDE/14-custom-resource-methods.md +125 -0
  20. package/docs/GUIDE/15-query-projections.md +266 -0
  21. package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
  22. package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
  23. package/docs/GUIDE/18-labels.md +86 -0
  24. package/docs/GUIDE/19-managed-transactions.md +179 -0
  25. package/docs/GUIDE/20-transaction-outcomes.md +207 -0
  26. package/docs/GUIDE/21-schema-and-migrations.md +634 -0
  27. package/docs/GUIDE/22-version-field-migrations.md +208 -0
  28. package/docs/GUIDE/23-fastify.md +179 -0
  29. package/docs/GUIDE/24-cors.md +172 -0
  30. package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
  31. package/docs/GUIDE/26-file-uploads.md +278 -0
  32. package/docs/GUIDE/27-bulk-operations.md +277 -0
  33. package/docs/GUIDE/28-socketio.md +325 -0
  34. package/docs/GUIDE/29-writing-plugins.md +278 -0
  35. package/docs/GUIDE/30-backend-capabilities.md +100 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +90 -31
  38. package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
  39. package/docs/GUIDE/index.md +51 -59
  40. package/docs/QUICKSTART.md +120 -287
  41. package/docs/architecture.md +75 -0
  42. package/docs/contributing.md +178 -0
  43. package/index.d.ts +36 -0
  44. package/index.js +1 -22
  45. package/lib/enhanced-logger.js +91 -85
  46. package/lib/error-context.js +445 -5
  47. package/lib/error-formatter.js +219 -59
  48. package/lib/knex-transaction.js +64 -0
  49. package/lib/logger-types.d.ts +22 -0
  50. package/lib/missing-package.js +4 -0
  51. package/lib/rest-api-errors.js +103 -8
  52. package/lib/runtime/json-rest-api.js +172 -0
  53. package/lib/transaction-types.d.ts +59 -0
  54. package/package.json +97 -27
  55. package/plugins/core/bulk-operations-plugin.js +191 -184
  56. package/plugins/core/connectors/express-plugin.js +148 -219
  57. package/plugins/core/connectors/fastify-plugin.js +125 -82
  58. package/plugins/core/connectors/lib/busboy-detector.js +74 -156
  59. package/plugins/core/connectors/lib/connector-core.js +144 -20
  60. package/plugins/core/connectors/lib/formidable-detector.js +63 -120
  61. package/plugins/core/connectors/lib/http-validators.js +77 -0
  62. package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
  63. package/plugins/core/connectors/lib/request-helpers.js +10 -1
  64. package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
  65. package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
  66. package/plugins/core/file-handling-plugin.js +87 -174
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
  68. package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
  69. package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
  70. package/plugins/core/lib/anyapi/schema-utils.js +25 -16
  71. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  72. package/plugins/core/lib/dbIntrospection.js +228 -21
  73. package/plugins/core/lib/dbTablesOperations.js +409 -92
  74. package/plugins/core/lib/fractional-positioning.js +12 -48
  75. package/plugins/core/lib/querying/identifier-query.js +19 -0
  76. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  77. package/plugins/core/lib/querying/include-to-many.js +428 -0
  78. package/plugins/core/lib/querying/include-to-one.js +579 -0
  79. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  80. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  81. package/plugins/core/lib/querying/knex-pagination-helpers.js +67 -330
  82. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  83. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  84. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  85. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  86. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  87. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  88. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  89. package/plugins/core/lib/querying/query-constraint.js +27 -0
  90. package/plugins/core/lib/querying/query-field-sort-helpers.js +205 -30
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +20 -1
  93. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  94. package/plugins/core/lib/querying/url-helpers.js +11 -4
  95. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  96. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  97. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  98. package/plugins/core/lib/querying-writing/database-value-normalizers.js +238 -187
  99. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  100. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  101. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  102. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  103. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  104. package/plugins/core/lib/querying-writing/knex-field-helpers.js +111 -287
  105. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  106. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  107. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  108. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  109. package/plugins/core/lib/querying-writing/request-contracts.js +109 -44
  110. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  111. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  112. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  113. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  114. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  115. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  116. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  117. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  118. package/plugins/core/lib/storage/storage-adapter.js +142 -62
  119. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  120. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  121. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  122. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  123. package/plugins/core/lib/writing/resource-version.js +172 -0
  124. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  125. package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2509
  126. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  127. package/plugins/core/rest-api-cors-plugin.js +52 -205
  128. package/plugins/core/rest-api-knex-plugin.js +45 -1069
  129. package/plugins/core/rest-api-label-plugin.js +25 -38
  130. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  131. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  132. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  133. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  134. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  135. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  136. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  137. package/plugins/core/rest-api-plugin-methods/common.js +291 -385
  138. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  139. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  140. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -145
  141. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  142. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  143. package/plugins/core/rest-api-plugin-methods/get.js +38 -69
  144. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  145. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  146. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  147. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  148. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  149. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  150. package/plugins/core/rest-api-plugin-methods/query.js +39 -82
  151. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  152. package/plugins/core/rest-api-plugin.js +32 -46
  153. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  154. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  155. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  156. package/plugins/core/socketio-plugin.js +364 -344
  157. package/plugins/storage/local-storage.js +40 -36
  158. package/types/autofilter.d.ts +57 -0
  159. package/types/bulk-methods.d.ts +63 -0
  160. package/types/errors.d.ts +118 -0
  161. package/types/fastify-options.d.ts +19 -0
  162. package/types/file-detectors.d.ts +33 -0
  163. package/types/file-storage.d.ts +48 -0
  164. package/types/hook-context.d.ts +180 -0
  165. package/types/plugin-options.d.ts +53 -0
  166. package/types/relationship-methods.d.ts +74 -0
  167. package/types/representations.d.ts +50 -0
  168. package/types/resource-methods.d.ts +95 -0
  169. package/types/resource-schema.d.ts +157 -0
  170. package/types/row-policy.d.ts +31 -0
  171. package/types/runtime.d.ts +132 -0
  172. package/types/transactions.d.ts +21 -0
  173. package/.claude/agents/library-guide-expert.md +0 -30
  174. package/.claude/agents/rest-api-internals-expert.md +0 -54
  175. package/.claude/settings.local.json +0 -70
  176. package/AGENTS.md +0 -34
  177. package/CLAUDE.md +0 -160
  178. package/CRUSH.md +0 -55
  179. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  180. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  181. package/TODO.todo +0 -13
  182. package/connectors_ideas/fastify.js +0 -217
  183. package/connectors_ideas/koa-plugin.js +0 -112
  184. package/docs/.bundle/config +0 -2
  185. package/docs/COMPARISON.md +0 -433
  186. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  187. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  188. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  189. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  190. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  191. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  192. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  193. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1238
  194. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  195. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  196. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  197. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  198. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  199. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  200. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  201. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  202. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  203. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  204. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  205. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  206. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  207. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  208. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  209. package/docs/Gemfile +0 -6
  210. package/docs/Gemfile.lock +0 -308
  211. package/docs/ONBOARDING.md +0 -315
  212. package/docs/_config.yml +0 -36
  213. package/docs/_layouts/default.html +0 -123
  214. package/docs/apple-touch-icon.svg +0 -4
  215. package/docs/assets/css/style.scss +0 -392
  216. package/docs/favicon.svg +0 -4
  217. package/docs/index.md +0 -164
  218. package/docs/mini-guides/file-uploads-complete.md +0 -161
  219. package/eslint.config.js +0 -13
  220. package/examples/url-override-example.js +0 -137
  221. package/fixing_plan.md +0 -112
  222. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  223. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  224. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  225. package/plugins/storage/s3-storage.js +0 -93
  226. package/quickTest.js +0 -136
  227. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  228. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  229. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  230. package/scripts/run-docs.js +0 -133
  231. package/test-debug.js +0 -59
  232. package/tests/README.md +0 -202
  233. package/tests/TEST_TEMPLATE.test.js +0 -178
  234. package/tests/anyapi-basic.test.js +0 -105
  235. package/tests/anyapi-cursor-pagination.test.js +0 -150
  236. package/tests/anyapi-custom-idproperty.test.js +0 -163
  237. package/tests/autofilter.test.js +0 -463
  238. package/tests/bulk-operations.test.js +0 -657
  239. package/tests/computed-fields.test.js +0 -586
  240. package/tests/cors.test.js +0 -834
  241. package/tests/curl.test.js +0 -467
  242. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  243. package/tests/custom-id-simplified-post.test.js +0 -75
  244. package/tests/custom-idproperty-relationships.test.js +0 -932
  245. package/tests/db-introspection.test.js +0 -521
  246. package/tests/db-table-operations.test.js +0 -798
  247. package/tests/fastify-plugin.test.js +0 -184
  248. package/tests/field-getters.test.js +0 -370
  249. package/tests/field-setters.test.js +0 -306
  250. package/tests/file-handling.test.js +0 -118
  251. package/tests/fixtures/api-configs.js +0 -2509
  252. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  253. package/tests/helpers/fake-fastify.js +0 -99
  254. package/tests/helpers/storage-mode.js +0 -65
  255. package/tests/helpers/test-utils.js +0 -203
  256. package/tests/http-connectors-parity.test.js +0 -395
  257. package/tests/id-normalization.test.js +0 -444
  258. package/tests/include-depth-validation.test.js +0 -331
  259. package/tests/includes.test.js +0 -427
  260. package/tests/local-storage.test.js +0 -96
  261. package/tests/logical-id-write-validation.test.js +0 -151
  262. package/tests/nested-includes.test.js +0 -617
  263. package/tests/pagination-cursor-multifield.test.js +0 -643
  264. package/tests/pagination-enhanced.test.js +0 -563
  265. package/tests/polymorphic-relationships.test.js +0 -347
  266. package/tests/polymorphic-simple.test.js +0 -148
  267. package/tests/positioning-advanced.test.js +0 -692
  268. package/tests/positioning-extended.test.js +0 -915
  269. package/tests/positioning-multifilter.test.js +0 -699
  270. package/tests/positioning.test.js +0 -560
  271. package/tests/post-commit-hooks.test.js +0 -120
  272. package/tests/projected-fields.test.js +0 -319
  273. package/tests/public-exports.test.js +0 -15
  274. package/tests/put-create-parity.test.js +0 -216
  275. package/tests/queries.test.js +0 -521
  276. package/tests/query-limits.test.js +0 -584
  277. package/tests/relationship-endpoints.test.js +0 -620
  278. package/tests/relationship-include-storage-adapter.test.js +0 -56
  279. package/tests/relationships.test.js +0 -681
  280. package/tests/rest-api.test.js +0 -337
  281. package/tests/return-record-settings.test.js +0 -184
  282. package/tests/row-policy.test.js +0 -489
  283. package/tests/s3-storage.test.js +0 -38
  284. package/tests/searchschema-knex-behavior.test.js +0 -187
  285. package/tests/searchschema-merge.test.js +0 -151
  286. package/tests/socketio.test.js +0 -860
  287. package/tests/storage-mapping.test.js +0 -362
  288. package/tests/test-development-guide.md +0 -370
  289. package/tests/virtual-fields.test.js +0 -404
package/tests/README.md DELETED
@@ -1,202 +0,0 @@
1
- # JSON-REST-API Test Suite
2
-
3
- This directory contains comprehensive tests for the JSON-REST-API library. All tests follow strict patterns to ensure proper JSON:API compliance and database integrity.
4
-
5
- ## Test Files
6
-
7
- - **rest-api.test.js** - Basic CRUD operations (POST, GET, PATCH, DELETE)
8
- - **relationships.test.js** - One-to-one and many-to-many relationship operations
9
- - **queries.test.js** - Filtering, sorting, and pagination tests
10
- - **fixtures/api-configs.js** - API configuration factories
11
- - **helpers/test-utils.js** - Utility functions for JSON:API validation
12
-
13
- ## Key Testing Principles
14
-
15
- ### 1. Strict Mode
16
-
17
- All tests MUST use `simplified: false` to ensure JSON:API compliance:
18
-
19
- ```javascript
20
- const result = await api.resources.resourceName.method({
21
- inputRecord: jsonApiDocument,
22
- simplified: false,
23
- returnFullRecord: 'full' // Use 'no', 'minimal', or 'full' based on needs
24
- });
25
- ```
26
-
27
- ### 2. Relationship Handling
28
-
29
- Foreign keys must be set via relationships, NOT attributes:
30
-
31
- ```javascript
32
- // ❌ WRONG - Will fail in strict mode
33
- const bookDoc = createJsonApiDocument('books', {
34
- title: 'My Book',
35
- country_id: countryId // Foreign key in attributes
36
- });
37
-
38
- // ✅ CORRECT - Use relationships
39
- const bookDoc = createJsonApiDocument('books',
40
- { title: 'My Book' },
41
- {
42
- country: createRelationship(resourceIdentifier('countries', countryId))
43
- }
44
- );
45
- ```
46
-
47
- ### 3. Database Operations
48
-
49
- Only these Knex operations are allowed directly:
50
-
51
- ```javascript
52
- // Counting records
53
- const count = await countRecords(knex, 'table_name');
54
-
55
- // Cleaning tables between tests
56
- await cleanTables(knex, ['table1', 'table2']);
57
- ```
58
-
59
- All other operations MUST go through the API.
60
-
61
- ### 4. API Configuration
62
-
63
- Always use the same schema pattern (Countries, Publishers, Authors, Books):
64
-
65
- ```javascript
66
- // Each API config must use unique table prefixes
67
- tableName: 'basic_countries' // For basicApi
68
- tableName: 'ext_countries' // For extendedApi
69
- ```
70
-
71
- ### 5. Search & Sort Configuration
72
-
73
- Enable search on fields that need filtering:
74
-
75
- ```javascript
76
- schema: {
77
- title: { type: 'string', search: true },
78
- country_id: { type: 'number', belongsTo: 'countries', as: 'country', search: true }
79
- }
80
- ```
81
-
82
- Configure sortable fields in plugin options:
83
-
84
- ```javascript
85
- await api.use(RestApiPlugin, {
86
- sortableFields: ['id', 'title', 'country_id', 'price']
87
- });
88
- ```
89
-
90
- ## Common Test Patterns
91
-
92
- ### Creating Resources with Relationships
93
-
94
- ```javascript
95
- // One-to-one (belongsTo)
96
- const publisherDoc = createJsonApiDocument('publishers',
97
- { name: 'Publisher Name' },
98
- {
99
- country: createRelationship(resourceIdentifier('countries', countryId))
100
- }
101
- );
102
-
103
- // Many-to-many
104
- const bookDoc = createJsonApiDocument('books',
105
- { title: 'Book Title' },
106
- {
107
- authors: createToManyRelationship([
108
- resourceIdentifier('authors', author1Id),
109
- resourceIdentifier('authors', author2Id)
110
- ])
111
- }
112
- );
113
- ```
114
-
115
- ### Validating Responses
116
-
117
- ```javascript
118
- // Validate JSON:API structure
119
- validateJsonApiStructure(result, false); // false = single resource
120
-
121
- // Validate attributes
122
- assertResourceAttributes(result.data, {
123
- name: 'Expected Name',
124
- code: 'EX'
125
- });
126
-
127
- // Validate relationships
128
- assertResourceRelationship(result.data, 'country',
129
- resourceIdentifier('countries', countryId));
130
- ```
131
-
132
- ### Query Operations
133
-
134
- ```javascript
135
- // Filtering
136
- const result = await api.resources.books.query({
137
- queryParams: {
138
- filters: { language: 'en', price: '9.99' }
139
- },
140
- simplified: false
141
- });
142
-
143
- // Sorting
144
- const result = await api.resources.books.query({
145
- queryParams: {
146
- sort: ['title', '-price'] // title ASC, price DESC
147
- },
148
- simplified: false
149
- });
150
-
151
- // Pagination
152
- const result = await api.resources.books.query({
153
- queryParams: {
154
- page: { number: 2, size: 10 }
155
- },
156
- simplified: false
157
- });
158
- ```
159
-
160
- ## Running Tests
161
-
162
- ```bash
163
- # Run all tests
164
- npm test
165
-
166
- # Run the focused normalizeId verification path plus docs rebuild
167
- npm run verify
168
-
169
- # Run all tests against the AnyAPI storage backend
170
- npm run test:anyapi
171
-
172
- # Run only the normalizeId contract suites
173
- npm run test:id-contracts
174
- npm run test:id-contracts:anyapi
175
-
176
- # Windows PowerShell (AnyAPI run)
177
- $Env:JSON_REST_API_STORAGE = 'anyapi'; npm test
178
-
179
- # Run specific test file
180
- npm test -- tests/relationships.test.js
181
-
182
- # Run with verbose output
183
- DEBUG=* npm test
184
- ```
185
-
186
- ## Adding New Tests
187
-
188
- 1. Create test file following naming convention: `feature-name.test.js`
189
- 2. Import necessary utilities from `helpers/test-utils.js`
190
- 3. Use API configurations from `fixtures/api-configs.js`
191
- 4. Follow the established patterns for:
192
- - Setting up test data
193
- - Making API calls with `simplified: false`
194
- - Validating JSON:API responses
195
- - Cleaning data between tests
196
-
197
- ## Important Notes
198
-
199
- - Tests intentionally generate ERROR logs for validation testing
200
- - All tests should pass with exit code 0
201
- - Database connections are properly closed in `after` hooks
202
- - Pivot table operations use `simplified: true` internally for foreign keys
@@ -1,178 +0,0 @@
1
- import { describe, it, before, after, beforeEach } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import knexLib from 'knex'
4
- import {
5
- validateJsonApiStructure,
6
- cleanTables,
7
- createJsonApiDocument,
8
- assertResourceAttributes
9
- } from './helpers/test-utils.js'
10
- import { createBasicApi } from './fixtures/api-configs.js'
11
-
12
- /**
13
- * TEST TEMPLATE - BEST PRACTICES LEARNED
14
- *
15
- * CRITICAL RULES:
16
- * 1. Create API instance ONLY ONCE in before() - NOT in beforeEach()
17
- * 2. Use cleanTables() in beforeEach() to reset data between tests
18
- * 3. Always pass context as SECOND parameter to resource methods
19
- * 4. Clean up any intervals/timers in after() hook
20
- * 5. SQLite returns numeric IDs - JSON:API expects string IDs
21
- * 6. For auth tests, context.auth must be set via JWT token or passed manually
22
- *
23
- * NOTICE TO CLAUDE/AI ASSISTANTS:
24
- *
25
- * API CONFIGURATION RULES - EXTREMELY IMPORTANT:
26
- * 1. NEVER add resources with api.addResource() in test files!
27
- * 2. ALL resources MUST be defined in api-configs.js
28
- * 3. NEVER create resources in test files - this is a CRITICAL RULE!
29
- * 4. If createBasicApi doesn't have the resources you need:
30
- * - DO NOT modify createBasicApi
31
- * - Create a NEW function in api-configs.js like:
32
- * export async function createExtendedApi(knex, pluginOptions) {
33
- * const api = await createApi();
34
- * // Install plugins
35
- * // Add YOUR resources here with api.addResource()
36
- * // Create tables
37
- * return api;
38
- * }
39
- * 5. Example structure in api-configs.js:
40
- * - createBasicApi() - has countries, publishers, authors, books
41
- * - createAuthApi() - might have users, roles, permissions
42
- * - createYourCustomApi() - whatever resources you need
43
- *
44
- * RESOURCE CREATION RULE (REPEAT FOR EMPHASIS):
45
- * Resources are ONLY created in api-configs.js, NEVER in test files!
46
- * If you need new resources, create a new function in api-configs.js!
47
- *
48
- * DATA ACCESS RULES - EXTREMELY IMPORTANT:
49
- * 1. NEVER query the database directly with knex('table').select()
50
- * 2. ALWAYS use API methods to check data:
51
- * - Use api.resources.[resource].get() to fetch a single item
52
- * - Use api.resources.[resource].query() to fetch multiple items
53
- * - Use api.resources.[resource].post() to create items
54
- * 3. Why? The API methods:
55
- * - Apply proper schema transformations
56
- * - Handle relationships correctly
57
- * - Return data in the expected JSON:API format
58
- * - Include computed fields and enriched attributes
59
- * 4. Direct DB queries will give you raw data that doesn't match what the API returns!
60
- *
61
- * BAD EXAMPLE:
62
- * const record = await knex('basic_countries').where('id', 1).first(); // NO!
63
- *
64
- * GOOD EXAMPLE:
65
- * const result = await api.resources.countries.get({ id: 1 });
66
- * const record = result.data; // YES!
67
- */
68
-
69
- // Create Knex instance for tests - always use SQLite in-memory
70
- const knex = knexLib({
71
- client: 'better-sqlite3',
72
- connection: {
73
- filename: ':memory:'
74
- },
75
- useNullAsDefault: true
76
- })
77
-
78
- // API instance that persists across ALL tests
79
- let api
80
-
81
- describe('Your Feature Name (Template Example)', () => {
82
- // IMPORTANT: before() runs ONCE for the entire test suite
83
- before(async () => {
84
- // Create API instance ONCE - this is reused for all tests
85
- // NOTE: If you need different resources than what createBasicApi provides,
86
- // create a new function in api-configs.js!
87
- api = await createBasicApi(knex)
88
-
89
- // Install your plugin if testing a specific plugin
90
- // await api.use(YourPlugin, {
91
- // option1: 'value1',
92
- // option2: 'value2'
93
- // });
94
-
95
- // NO api.addResource() HERE! Resources are defined in api-configs.js!
96
- // createBasicApi already created tables for: countries, publishers, authors, books
97
- })
98
-
99
- // IMPORTANT: after() cleans up resources
100
- after(async () => {
101
- // Clean up any intervals/timers your plugin might have created
102
- // Example from JWT plugin:
103
- // if (api.vars.yourPluginCleanupJob) {
104
- // clearInterval(api.vars.yourPluginCleanupJob);
105
- // }
106
-
107
- // Always destroy knex connection to allow tests to exit
108
- await knex.destroy()
109
- })
110
-
111
- // IMPORTANT: beforeEach() cleans data but does NOT recreate API
112
- beforeEach(async () => {
113
- // Clean all tables - list ALL tables your tests use
114
- await cleanTables(knex, [
115
- 'basic_countries', // From createBasicApi
116
- 'basic_publishers', // From createBasicApi
117
- 'basic_authors', // From createBasicApi
118
- 'basic_books', // From createBasicApi
119
- 'basic_book_authors' // From createBasicApi (junction table)
120
- // Add any additional tables used by your tests here
121
- ])
122
- })
123
-
124
- // ONE SIMPLE EXAMPLE TEST
125
- it('should create and retrieve a country', async () => {
126
- // Create a country using the API
127
- const doc = createJsonApiDocument('countries', {
128
- name: 'Test Country',
129
- code: 'TC'
130
- })
131
-
132
- // Create the country
133
- // IMPORTANT: Context is SECOND parameter, not inside first parameter!
134
- const createResult = await api.resources.countries.post({
135
- inputRecord: doc,
136
- simplified: false
137
- }, { /* optional context goes here */ })
138
-
139
- // Validate the creation response
140
- validateJsonApiStructure(createResult)
141
- assert.equal(createResult.data.type, 'countries')
142
- assertResourceAttributes(createResult.data, {
143
- name: 'Test Country',
144
- code: 'TC'
145
- })
146
-
147
- // Retrieve the country we just created
148
- const getResult = await api.resources.countries.get({
149
- id: createResult.data.id,
150
- simplified: false
151
- })
152
-
153
- // Validate the retrieved country
154
- validateJsonApiStructure(getResult)
155
- assert.equal(getResult.data.id, createResult.data.id)
156
- assert.equal(getResult.data.attributes.name, 'Test Country')
157
- assert.equal(getResult.data.attributes.code, 'TC')
158
- })
159
- })
160
-
161
- /**
162
- * COMMON PITFALLS TO AVOID:
163
- *
164
- * 1. DON'T create API in beforeEach - it's expensive and unnecessary
165
- * 2. DON'T forget to clean tables between tests
166
- * 3. DON'T pass context inside params object - it's a separate parameter
167
- * 4. DON'T assume IDs are strings - SQLite returns numbers
168
- * 5. DON'T forget to clean up intervals/timers
169
- * 6. DON'T skip error cases - test both success and failure paths
170
- *
171
- * DEBUGGING TIPS:
172
- *
173
- * 1. Use console.log liberally when debugging, but remove before committing
174
- * 2. Check if your auth context is properly structured
175
- * 3. Validate that required fields are present in your test data
176
- * 4. Remember that HTTP routes have /api/ prefix by default
177
- * 5. Use debugger statements to pause execution and inspect state
178
- */
@@ -1,105 +0,0 @@
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
-
6
- import { RestApiPlugin, RestApiAnyapiKnexPlugin } from '../index.js'
7
- import { ensureAnyApiSchema } from '../plugins/core/lib/anyapi/schema-utils.js'
8
-
9
- const knex = knexLib({
10
- client: 'better-sqlite3',
11
- connection: { filename: ':memory:' },
12
- useNullAsDefault: true,
13
- })
14
-
15
- let api
16
-
17
- describe('AnyAPI Knex Plugin - Basic Attributes', () => {
18
- before(async () => {
19
- try {
20
- await ensureAnyApiSchema(knex)
21
-
22
- api = new Api({ name: 'anyapi-test', log: { level: 'warn' } })
23
- await api.use(RestApiPlugin, { simplifiedApi: false, simplifiedTransport: false })
24
- await api.use(RestApiAnyapiKnexPlugin, { knex })
25
-
26
- await api.addResource('countries', {
27
- schema: {
28
- id: { type: 'id' },
29
- name: { type: 'string', required: true },
30
- code: { type: 'string', required: true },
31
- },
32
- })
33
-
34
- if (api.resources.countries?.createKnexTable) {
35
- await api.resources.countries.createKnexTable()
36
- }
37
- } catch (error) {
38
- console.error('Setup failed', error)
39
- throw error
40
- }
41
- })
42
-
43
- after(async () => {
44
- await api.release()
45
- await knex.destroy()
46
- })
47
-
48
- beforeEach(async () => {
49
- await knex('any_records').truncate()
50
- })
51
-
52
- it('creates and retrieves records', async () => {
53
- const result = await api.resources.countries.post({
54
- inputRecord: {
55
- data: {
56
- type: 'countries',
57
- attributes: {
58
- name: 'Australia',
59
- code: 'AU',
60
- },
61
- },
62
- },
63
- simplified: false,
64
- }).catch((error) => {
65
- console.error('POST failed', error)
66
- throw error
67
- })
68
-
69
- assert.ok(result.data.id, 'should return new id')
70
- assert.equal(result.data.attributes.name, 'Australia')
71
-
72
- const stored = await knex('any_records').first()
73
- assert.equal(stored.logical_id, result.data.id)
74
- assert.equal(stored.string_1, 'Australia')
75
- assert.equal(stored.string_2, 'AU')
76
-
77
- const fetched = await api.resources.countries.get({ id: result.data.id, simplified: false })
78
- .catch((error) => {
79
- console.error('GET failed', error)
80
- throw error
81
- })
82
- assert.equal(fetched.data.attributes.name, 'Australia')
83
- })
84
-
85
- it('lists records', async () => {
86
- await knex('any_records').insert([{
87
- logical_id: '1',
88
- tenant_id: 'default',
89
- resource: 'countries',
90
- string_1: 'Canada',
91
- string_2: 'CA',
92
- }, {
93
- logical_id: '2',
94
- tenant_id: 'default',
95
- resource: 'countries',
96
- string_1: 'Brazil',
97
- string_2: 'BR',
98
- }])
99
-
100
- const result = await api.resources.countries.query({ simplified: false })
101
- assert.equal(result.data.length, 2)
102
- const names = result.data.map((entry) => entry.attributes.name).sort()
103
- assert.deepEqual(names, ['Brazil', 'Canada'])
104
- })
105
- })
@@ -1,150 +0,0 @@
1
- import { describe, it, before, beforeEach, after } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import knexLib from 'knex'
4
- import { createPaginationApi } from './fixtures/api-configs.js'
5
- import {
6
- cleanTables,
7
- createJsonApiDocument,
8
- createRelationship,
9
- resourceIdentifier,
10
- } from './helpers/test-utils.js'
11
- import { storageMode } from './helpers/storage-mode.js'
12
- import { parseJsonApiQuery } from '../plugins/core/lib/querying-writing/connectors-query-parser.js'
13
-
14
- const isAnyApi = storageMode.isAnyApi()
15
-
16
- const maybeDescribe = isAnyApi ? describe : describe.skip
17
-
18
- const parseLinkQuery = (link) => {
19
- const url = new URL(link, 'https://example.test')
20
- return parseJsonApiQuery(url.search.slice(1))
21
- }
22
-
23
- maybeDescribe('AnyAPI Cursor Pagination', () => {
24
- const knex = knexLib({
25
- client: 'better-sqlite3',
26
- connection: { filename: ':memory:' },
27
- useNullAsDefault: true,
28
- })
29
-
30
- let api
31
-
32
- before(async () => {
33
- api = await createPaginationApi(knex)
34
- })
35
-
36
- after(async () => {
37
- await knex.destroy()
38
- })
39
-
40
- beforeEach(async () => {
41
- await cleanTables(knex, [
42
- 'pagination_countries',
43
- 'pagination_publishers',
44
- 'pagination_books',
45
- ])
46
-
47
- const country = await api.resources.countries.post({
48
- inputRecord: createJsonApiDocument('countries', { name: 'Cursorland', code: 'CL' }),
49
- simplified: false,
50
- })
51
-
52
- const publisher = await api.resources.publishers.post({
53
- inputRecord: createJsonApiDocument(
54
- 'publishers',
55
- { name: 'Cursor Press' },
56
- { country: createRelationship(resourceIdentifier('countries', country.data.id)) }
57
- ),
58
- simplified: false,
59
- })
60
-
61
- const titles = ['Book A', 'Book B', 'Book C', 'Book D', 'Book E', 'Book F']
62
- for (const title of titles) {
63
- await api.resources.books.post({
64
- inputRecord: createJsonApiDocument(
65
- 'books',
66
- { title },
67
- {
68
- country: createRelationship(resourceIdentifier('countries', country.data.id)),
69
- publisher: createRelationship(resourceIdentifier('publishers', publisher.data.id)),
70
- }
71
- ),
72
- simplified: false,
73
- })
74
- }
75
- })
76
-
77
- it('returns cursor metadata and links when requesting the first page', async () => {
78
- const result = await api.resources.books.query({
79
- queryParams: {
80
- page: { size: 2 },
81
- },
82
- simplified: false,
83
- })
84
-
85
- assert.equal(result.data.length, 2)
86
- assert(result.meta?.pagination?.cursor?.next, 'Should include next cursor')
87
- assert(parseLinkQuery(result.links?.next).page.after, 'Next link should include cursor parameter')
88
- })
89
-
90
- it('paginates forward using page[after]', async () => {
91
- const firstPage = await api.resources.books.query({
92
- queryParams: {
93
- page: { size: 2 },
94
- },
95
- simplified: false,
96
- })
97
-
98
- const cursor = firstPage.meta.pagination.cursor.next
99
- const secondPage = await api.resources.books.query({
100
- queryParams: {
101
- page: { size: 2, after: cursor },
102
- },
103
- simplified: false,
104
- })
105
-
106
- assert.equal(secondPage.data.length, 2)
107
- const firstIds = firstPage.data.map((book) => book.id)
108
- const secondIds = secondPage.data.map((book) => book.id)
109
- assert(!secondIds.some((id) => firstIds.includes(id)), 'Second page should not repeat first page records')
110
- })
111
-
112
- it('paginates backward using page[before]', async () => {
113
- const firstPage = await api.resources.books.query({
114
- queryParams: {
115
- page: { size: 2 },
116
- },
117
- simplified: false,
118
- })
119
-
120
- const secondPage = await api.resources.books.query({
121
- queryParams: {
122
- page: {
123
- size: 2,
124
- after: firstPage.meta.pagination.cursor.next,
125
- },
126
- },
127
- simplified: false,
128
- })
129
-
130
- const beforeCursor =
131
- secondPage.meta.pagination.cursor?.next ||
132
- `id:${encodeURIComponent(secondPage.data[0].id)}`
133
-
134
- const previousPage = await api.resources.books.query({
135
- queryParams: {
136
- page: {
137
- size: 2,
138
- before: beforeCursor,
139
- },
140
- },
141
- simplified: false,
142
- })
143
-
144
- const secondIds = secondPage.data.map((book) => book.id)
145
- const previousIds = previousPage.data.map((book) => book.id)
146
- assert(!previousIds.some((id) => secondIds.includes(id)), 'Page[before] should return different records')
147
- const firstIds = firstPage.data.map((book) => book.id)
148
- assert(previousIds.every((id) => firstIds.includes(id)), 'Page[before] should return earlier records')
149
- })
150
- })