json-rest-api 1.0.29 → 2.1.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 (294) 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 +744 -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 +733 -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 +103 -0
  36. package/docs/GUIDE/31-positioning.md +181 -0
  37. package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
  38. package/docs/GUIDE/33-migrating-to-v2.md +3838 -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 +37 -0
  44. package/index.js +3 -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 +70 -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 -177
  67. package/plugins/core/lib/anyapi/anyapi-registry.js +586 -418
  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 +120 -56
  71. package/plugins/core/lib/anyapi/stored-definition.js +109 -0
  72. package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
  73. package/plugins/core/lib/dbIntrospection.js +228 -21
  74. package/plugins/core/lib/dbTablesOperations.js +409 -92
  75. package/plugins/core/lib/fractional-positioning.js +12 -48
  76. package/plugins/core/lib/querying/identifier-query.js +19 -0
  77. package/plugins/core/lib/querying/include-query-helpers.js +364 -0
  78. package/plugins/core/lib/querying/include-to-many.js +428 -0
  79. package/plugins/core/lib/querying/include-to-one.js +579 -0
  80. package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
  81. package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
  82. package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
  83. package/plugins/core/lib/querying/knex-process-includes.js +5 -110
  84. package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
  85. package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
  86. package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
  87. package/plugins/core/lib/querying/knex-window-queries.js +62 -253
  88. package/plugins/core/lib/querying/query-builder-utils.js +44 -4
  89. package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
  90. package/plugins/core/lib/querying/query-constraint.js +27 -0
  91. package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
  92. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  93. package/plugins/core/lib/querying/sort-helpers.js +13 -1
  94. package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
  95. package/plugins/core/lib/querying/url-helpers.js +11 -4
  96. package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
  97. package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
  98. package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
  99. package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
  100. package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
  101. package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
  102. package/plugins/core/lib/querying-writing/field-utils.js +113 -92
  103. package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
  104. package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
  105. package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
  106. package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
  107. package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
  108. package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
  109. package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
  110. package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
  111. package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
  112. package/plugins/core/lib/querying-writing/response-options.js +35 -0
  113. package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
  114. package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
  115. package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
  116. package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
  117. package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
  118. package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
  119. package/plugins/core/lib/storage/storage-adapter.js +128 -62
  120. package/plugins/core/lib/storage/storage-mapping.js +78 -28
  121. package/plugins/core/lib/storage/storage-types.d.ts +502 -0
  122. package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
  123. package/plugins/core/lib/writing/relationship-processor.js +94 -158
  124. package/plugins/core/lib/writing/resource-version.js +172 -0
  125. package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
  126. package/plugins/core/rest-api-anyapi-knex-plugin.js +290 -2475
  127. package/plugins/core/rest-api-autofilter-plugin.js +17 -19
  128. package/plugins/core/rest-api-cors-plugin.js +52 -205
  129. package/plugins/core/rest-api-knex-plugin.js +45 -1077
  130. package/plugins/core/rest-api-label-plugin.js +25 -38
  131. package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
  132. package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
  133. package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
  134. package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
  135. package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
  136. package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
  137. package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
  138. package/plugins/core/rest-api-plugin-methods/common.js +291 -395
  139. package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
  140. package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
  141. package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
  142. package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
  143. package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
  144. package/plugins/core/rest-api-plugin-methods/get.js +36 -68
  145. package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
  146. package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
  147. package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
  148. package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
  149. package/plugins/core/rest-api-plugin-methods/post.js +51 -51
  150. package/plugins/core/rest-api-plugin-methods/put.js +69 -148
  151. package/plugins/core/rest-api-plugin-methods/query.js +37 -81
  152. package/plugins/core/rest-api-plugin-methods/release.js +2 -1
  153. package/plugins/core/rest-api-plugin.js +32 -46
  154. package/plugins/core/rest-api-positioning-plugin.js +183 -628
  155. package/plugins/core/rest-api-query-projections-plugin.js +19 -102
  156. package/plugins/core/rest-api-row-policy-plugin.js +3 -3
  157. package/plugins/core/socketio-plugin.js +364 -344
  158. package/plugins/storage/local-storage.js +40 -36
  159. package/types/anyapi.d.ts +30 -0
  160. package/types/autofilter.d.ts +57 -0
  161. package/types/bulk-methods.d.ts +63 -0
  162. package/types/errors.d.ts +118 -0
  163. package/types/fastify-options.d.ts +19 -0
  164. package/types/file-detectors.d.ts +33 -0
  165. package/types/file-storage.d.ts +48 -0
  166. package/types/hook-context.d.ts +180 -0
  167. package/types/plugin-options.d.ts +65 -0
  168. package/types/relationship-methods.d.ts +74 -0
  169. package/types/representations.d.ts +50 -0
  170. package/types/resource-methods.d.ts +95 -0
  171. package/types/resource-schema.d.ts +157 -0
  172. package/types/row-policy.d.ts +31 -0
  173. package/types/runtime.d.ts +132 -0
  174. package/types/transactions.d.ts +21 -0
  175. package/.claude/agents/library-guide-expert.md +0 -30
  176. package/.claude/agents/rest-api-internals-expert.md +0 -54
  177. package/.claude/settings.local.json +0 -70
  178. package/AGENTS.md +0 -34
  179. package/CLAUDE.md +0 -160
  180. package/CRUSH.md +0 -55
  181. package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
  182. package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
  183. package/TODO.todo +0 -13
  184. package/connectors_ideas/fastify.js +0 -217
  185. package/connectors_ideas/koa-plugin.js +0 -112
  186. package/docs/.bundle/config +0 -2
  187. package/docs/COMPARISON.md +0 -433
  188. package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
  189. package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
  190. package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
  191. package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
  192. package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
  193. package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
  194. package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
  195. package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
  196. package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
  197. package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
  198. package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
  199. package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
  200. package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
  201. package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
  202. package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
  203. package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
  204. package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
  205. package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
  206. package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
  207. package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
  208. package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
  209. package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
  210. package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
  211. package/docs/Gemfile +0 -6
  212. package/docs/Gemfile.lock +0 -308
  213. package/docs/ONBOARDING.md +0 -315
  214. package/docs/_config.yml +0 -36
  215. package/docs/_layouts/default.html +0 -123
  216. package/docs/apple-touch-icon.svg +0 -4
  217. package/docs/assets/css/style.scss +0 -392
  218. package/docs/favicon.svg +0 -4
  219. package/docs/index.md +0 -164
  220. package/docs/mini-guides/file-uploads-complete.md +0 -161
  221. package/eslint.config.js +0 -13
  222. package/examples/url-override-example.js +0 -137
  223. package/fixing_plan.md +0 -112
  224. package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
  225. package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
  226. package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
  227. package/plugins/storage/s3-storage.js +0 -93
  228. package/quickTest.js +0 -136
  229. package/scripts/debug-anyapi-review-filter.mjs +0 -63
  230. package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
  231. package/scripts/inspect-anyapi-descriptor.mjs +0 -19
  232. package/scripts/run-docs.js +0 -133
  233. package/test-debug.js +0 -59
  234. package/tests/README.md +0 -202
  235. package/tests/TEST_TEMPLATE.test.js +0 -178
  236. package/tests/anyapi-basic.test.js +0 -105
  237. package/tests/anyapi-cursor-pagination.test.js +0 -148
  238. package/tests/anyapi-custom-idproperty.test.js +0 -163
  239. package/tests/autofilter.test.js +0 -463
  240. package/tests/bulk-operations.test.js +0 -657
  241. package/tests/computed-fields.test.js +0 -586
  242. package/tests/cors.test.js +0 -834
  243. package/tests/curl.test.js +0 -467
  244. package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
  245. package/tests/custom-id-simplified-post.test.js +0 -75
  246. package/tests/custom-idproperty-relationships.test.js +0 -932
  247. package/tests/db-introspection.test.js +0 -521
  248. package/tests/db-table-operations.test.js +0 -798
  249. package/tests/fastify-plugin.test.js +0 -184
  250. package/tests/field-getters.test.js +0 -370
  251. package/tests/field-setters.test.js +0 -306
  252. package/tests/file-handling.test.js +0 -155
  253. package/tests/fixtures/api-configs.js +0 -2566
  254. package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
  255. package/tests/helpers/fake-fastify.js +0 -99
  256. package/tests/helpers/storage-mode.js +0 -65
  257. package/tests/helpers/test-utils.js +0 -203
  258. package/tests/http-connectors-parity.test.js +0 -395
  259. package/tests/id-normalization.test.js +0 -444
  260. package/tests/include-depth-validation.test.js +0 -331
  261. package/tests/includes.test.js +0 -427
  262. package/tests/local-storage.test.js +0 -96
  263. package/tests/logical-id-write-validation.test.js +0 -151
  264. package/tests/nested-includes.test.js +0 -617
  265. package/tests/pagination-boundaries.test.js +0 -154
  266. package/tests/pagination-contract.test.js +0 -122
  267. package/tests/pagination-cursor-multifield.test.js +0 -641
  268. package/tests/pagination-enhanced.test.js +0 -563
  269. package/tests/polymorphic-relationships.test.js +0 -347
  270. package/tests/polymorphic-simple.test.js +0 -148
  271. package/tests/positioning-advanced.test.js +0 -692
  272. package/tests/positioning-extended.test.js +0 -915
  273. package/tests/positioning-multifilter.test.js +0 -699
  274. package/tests/positioning.test.js +0 -560
  275. package/tests/post-commit-hooks.test.js +0 -120
  276. package/tests/projected-fields.test.js +0 -319
  277. package/tests/public-exports.test.js +0 -15
  278. package/tests/put-create-parity.test.js +0 -216
  279. package/tests/queries.test.js +0 -521
  280. package/tests/query-limits.test.js +0 -584
  281. package/tests/relationship-endpoints.test.js +0 -620
  282. package/tests/relationship-include-storage-adapter.test.js +0 -56
  283. package/tests/relationships.test.js +0 -681
  284. package/tests/rest-api.test.js +0 -337
  285. package/tests/return-record-settings.test.js +0 -184
  286. package/tests/row-policy.test.js +0 -489
  287. package/tests/s3-storage.test.js +0 -38
  288. package/tests/searchschema-knex-behavior.test.js +0 -187
  289. package/tests/searchschema-merge.test.js +0 -151
  290. package/tests/socketio.test.js +0 -860
  291. package/tests/storage-mapping.test.js +0 -362
  292. package/tests/temporal-boundaries.test.js +0 -208
  293. package/tests/test-development-guide.md +0 -370
  294. 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,148 +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 = `id:${encodeURIComponent(secondPage.data[0].id)}`
131
-
132
- const previousPage = await api.resources.books.query({
133
- queryParams: {
134
- page: {
135
- size: 2,
136
- before: beforeCursor,
137
- },
138
- },
139
- simplified: false,
140
- })
141
-
142
- const secondIds = secondPage.data.map((book) => book.id)
143
- const previousIds = previousPage.data.map((book) => book.id)
144
- assert(!previousIds.some((id) => secondIds.includes(id)), 'Page[before] should return different records')
145
- const firstIds = firstPage.data.map((book) => book.id)
146
- assert(previousIds.every((id) => firstIds.includes(id)), 'Page[before] should return earlier records')
147
- })
148
- })