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
@@ -1,150 +0,0 @@
1
- import { after, before, beforeEach, describe, it } from 'node:test'
2
- import assert from 'node:assert/strict'
3
- import knexLib from 'knex'
4
- import { createCamelCaseBelongsToApi } from './fixtures/api-configs.js'
5
- import {
6
- createJsonApiDocument,
7
- createRelationship,
8
- resourceIdentifier,
9
- assertResourceRelationship
10
- } from './helpers/test-utils.js'
11
-
12
- const knex = knexLib({
13
- client: 'better-sqlite3',
14
- connection: {
15
- filename: ':memory:'
16
- },
17
- useNullAsDefault: true
18
- })
19
-
20
- let api
21
-
22
- describe('Full JSON:API belongsTo linkage', () => {
23
- before(async () => {
24
- api = await createCamelCaseBelongsToApi(knex)
25
- })
26
-
27
- after(async () => {
28
- await knex.destroy()
29
- })
30
-
31
- beforeEach(async () => {
32
- await knex('camel_fk_publishers').delete()
33
- await knex('camel_fk_countries').delete()
34
- })
35
-
36
- const createCountry = async () => {
37
- return api.resources.countries.post({
38
- inputRecord: createJsonApiDocument('countries', {
39
- name: 'United States'
40
- }),
41
- simplified: false
42
- })
43
- }
44
-
45
- const createPublisher = async (countryId = null) => {
46
- return api.resources.publishers.post({
47
- inputRecord: createJsonApiDocument(
48
- 'publishers',
49
- { name: 'Mapped Publisher' },
50
- {
51
- country: createRelationship(
52
- countryId == null ? null : resourceIdentifier('countries', countryId)
53
- )
54
- }
55
- ),
56
- simplified: false
57
- })
58
- }
59
-
60
- it('emits belongsTo linkage on primary resources without include for single and collection responses', async () => {
61
- const country = await createCountry()
62
- const publisher = await createPublisher(country.data.id)
63
-
64
- assertResourceRelationship(
65
- publisher.data,
66
- 'country',
67
- resourceIdentifier('countries', country.data.id)
68
- )
69
- assert.equal(Object.hasOwn(publisher.data.attributes, 'countryId'), false)
70
-
71
- const single = await api.resources.publishers.get({
72
- id: publisher.data.id,
73
- simplified: false
74
- })
75
-
76
- assertResourceRelationship(
77
- single.data,
78
- 'country',
79
- resourceIdentifier('countries', country.data.id)
80
- )
81
- assert.equal(Object.hasOwn(single.data.attributes, 'countryId'), false)
82
-
83
- const collection = await api.resources.publishers.query({
84
- simplified: false
85
- })
86
-
87
- assert.equal(collection.data.length, 1)
88
- assertResourceRelationship(
89
- collection.data[0],
90
- 'country',
91
- resourceIdentifier('countries', country.data.id)
92
- )
93
- assert.equal(Object.hasOwn(collection.data[0].attributes, 'countryId'), false)
94
- })
95
-
96
- it('keeps primary belongsTo linkage when include is requested and adds included resources', async () => {
97
- const country = await createCountry()
98
- const publisher = await createPublisher(country.data.id)
99
-
100
- const result = await api.resources.publishers.get({
101
- id: publisher.data.id,
102
- queryParams: {
103
- include: ['country']
104
- },
105
- simplified: false,
106
- returnFullRecord: 'full'
107
- })
108
-
109
- assertResourceRelationship(
110
- result.data,
111
- 'country',
112
- resourceIdentifier('countries', country.data.id)
113
- )
114
- assert.equal(Object.hasOwn(result.data.attributes, 'countryId'), false)
115
- assert.equal(result.included?.length, 1)
116
- assert.equal(result.included?.[0]?.type, 'countries')
117
- assert.equal(result.included?.[0]?.id, country.data.id)
118
- })
119
-
120
- it('emits explicit null belongsTo linkage without exposing the foreign key attribute', async () => {
121
- const country = await createCountry()
122
- const publisher = await createPublisher(country.data.id)
123
-
124
- const patched = await api.resources.publishers.patch({
125
- id: publisher.data.id,
126
- inputRecord: {
127
- data: {
128
- type: 'publishers',
129
- id: publisher.data.id,
130
- relationships: {
131
- country: { data: null }
132
- }
133
- }
134
- },
135
- simplified: false,
136
- returnFullRecord: 'full'
137
- })
138
-
139
- assert.equal(patched.data.relationships?.country?.data, null)
140
- assert.equal(Object.hasOwn(patched.data.attributes, 'countryId'), false)
141
-
142
- const single = await api.resources.publishers.get({
143
- id: publisher.data.id,
144
- simplified: false
145
- })
146
-
147
- assert.equal(single.data.relationships?.country?.data, null)
148
- assert.equal(Object.hasOwn(single.data.attributes, 'countryId'), false)
149
- })
150
- })
@@ -1,99 +0,0 @@
1
- export class FakeFastifyApp {
2
- constructor () {
3
- this.routes = []
4
- this.parsers = []
5
- }
6
-
7
- route (definition) {
8
- this.routes.push(definition)
9
- }
10
-
11
- addContentTypeParser (contentType, options, parser) {
12
- this.parsers.push({ contentType, options, parser })
13
- }
14
-
15
- hasContentTypeParser (contentType) {
16
- return this.parsers.some((entry) => entry.contentType === contentType)
17
- }
18
- }
19
-
20
- export class FakeReply {
21
- constructor () {
22
- this.statusCode = null
23
- this.contentType = null
24
- this.headers = {}
25
- this.payload = undefined
26
- }
27
-
28
- code (statusCode) {
29
- this.statusCode = statusCode
30
- return this
31
- }
32
-
33
- type (contentType) {
34
- this.contentType = contentType
35
- return this
36
- }
37
-
38
- header (name, value) {
39
- this.headers[name] = value
40
- return this
41
- }
42
-
43
- send (payload) {
44
- this.payload = payload
45
- return this
46
- }
47
- }
48
-
49
- export function findFastifyRoute (app, method, url) {
50
- return app.routes.find((route) => route.method === method && route.url === url)
51
- }
52
-
53
- export async function invokeFastifyRoute (
54
- app,
55
- {
56
- method,
57
- routeUrl,
58
- requestUrl = routeUrl,
59
- headers = {},
60
- body,
61
- params = {},
62
- query = {}
63
- }
64
- ) {
65
- const route = findFastifyRoute(app, method, routeUrl)
66
- if (!route) {
67
- throw new Error(`Fastify route not found: ${method} ${routeUrl}`)
68
- }
69
-
70
- const reply = new FakeReply()
71
- const request = {
72
- method,
73
- url: requestUrl,
74
- raw: { url: requestUrl },
75
- headers,
76
- body,
77
- params,
78
- query
79
- }
80
-
81
- if (typeof route.preValidation === 'function') {
82
- await route.preValidation(request, reply)
83
- if (reply.statusCode !== null || reply.payload !== undefined) {
84
- return { route, request, reply }
85
- }
86
- }
87
-
88
- try {
89
- await route.handler(request, reply)
90
- } catch (error) {
91
- if (typeof route.errorHandler === 'function') {
92
- await route.errorHandler(error, request, reply)
93
- } else {
94
- throw error
95
- }
96
- }
97
-
98
- return { route, request, reply }
99
- }
@@ -1,65 +0,0 @@
1
- const STORAGE_MODE = process.env.JSON_REST_API_STORAGE === 'anyapi' ? 'anyapi' : 'knex'
2
- const DEFAULT_TENANT = 'default'
3
-
4
- const tableMetadata = new Map()
5
- let resetMetadataFn = null
6
- const linkMappings = new Map()
7
- let currentTenant = DEFAULT_TENANT
8
-
9
- export const storageMode = {
10
- mode: STORAGE_MODE,
11
- isAnyApi () {
12
- return STORAGE_MODE === 'anyapi'
13
- },
14
- defaultTenant: DEFAULT_TENANT,
15
- get currentTenant () {
16
- return currentTenant
17
- },
18
- registerReset (fn) {
19
- resetMetadataFn = fn
20
- },
21
- setCurrentTenant (tenantId) {
22
- currentTenant = tenantId || DEFAULT_TENANT
23
- },
24
- registerTable (tableName, resourceName, tenantId = currentTenant || DEFAULT_TENANT) {
25
- if (!tableName || !resourceName) return
26
- tableMetadata.set(tableName, {
27
- resource: resourceName,
28
- tenantId: tenantId || DEFAULT_TENANT,
29
- })
30
- },
31
- registerLink (
32
- tableName,
33
- ownerResource,
34
- relationshipName,
35
- relationshipKey,
36
- inverseRelationshipKey,
37
- tenantId = currentTenant || DEFAULT_TENANT
38
- ) {
39
- if (!tableName || !ownerResource || !relationshipName) return
40
- linkMappings.set(tableName, {
41
- ownerResource,
42
- relationshipName,
43
- relationshipKey,
44
- inverseRelationshipKey,
45
- tenantId: tenantId || DEFAULT_TENANT,
46
- })
47
- },
48
- getResourceForTable (tableName) {
49
- return tableMetadata.get(tableName)?.resource
50
- },
51
- getTenantForTable (tableName) {
52
- return tableMetadata.get(tableName)?.tenantId || DEFAULT_TENANT
53
- },
54
- getLinkInfo (tableName) {
55
- return linkMappings.get(tableName)
56
- },
57
- clearRegistry () {
58
- tableMetadata.clear()
59
- linkMappings.clear()
60
- currentTenant = DEFAULT_TENANT
61
- if (resetMetadataFn) {
62
- resetMetadataFn()
63
- }
64
- },
65
- }
@@ -1,203 +0,0 @@
1
- import assert from 'node:assert/strict'
2
- import { storageMode } from './storage-mode.js'
3
-
4
- /**
5
- * Validates that a response follows JSON:API structure
6
- */
7
- export function validateJsonApiStructure (response, isCollection = false) {
8
- assert(response, 'Response should exist')
9
-
10
- if (isCollection) {
11
- assert(Array.isArray(response.data), 'Response data should be an array for collections')
12
- } else {
13
- assert(response.data && typeof response.data === 'object', 'Response data should be an object')
14
- assert(!Array.isArray(response.data), 'Response data should not be an array for single resources')
15
- }
16
-
17
- // Validate each resource object
18
- const resources = isCollection ? response.data : [response.data]
19
- resources.forEach(resource => {
20
- assert(resource.type, 'Resource should have a type')
21
- assert(resource.id, 'Resource should have an id')
22
- assert(resource.attributes && typeof resource.attributes === 'object', 'Resource should have attributes object')
23
-
24
- if (resource.relationships) {
25
- assert(typeof resource.relationships === 'object', 'Relationships should be an object')
26
- Object.values(resource.relationships).forEach(rel => {
27
- assert(rel.data !== undefined || rel.links !== undefined || rel.meta !== undefined,
28
- 'Relationship should have data, links, or meta')
29
- })
30
- }
31
- })
32
-
33
- if (response.included) {
34
- assert(Array.isArray(response.included), 'Included should be an array')
35
- response.included.forEach(resource => {
36
- assert(resource.type && resource.id, 'Included resources should have type and id')
37
- })
38
- }
39
- }
40
-
41
- /**
42
- * Creates a resource identifier for JSON:API relationships
43
- */
44
- export function resourceIdentifier (type, id) {
45
- return { type, id: String(id) }
46
- }
47
-
48
- /**
49
- * Cleans all records from the given tables
50
- */
51
- export async function cleanTables (knex, tableNames) {
52
- if (storageMode.isAnyApi()) {
53
- const resourceGroups = new Map()
54
-
55
- for (const table of tableNames) {
56
- const resource = storageMode.getResourceForTable(table)
57
- if (!resource) continue
58
- const tenantId = storageMode.getTenantForTable(table)
59
- const key = `${tenantId}::${resource}`
60
- if (!resourceGroups.has(key)) {
61
- resourceGroups.set(key, { resource, tenantId })
62
- }
63
- }
64
-
65
- for (const { resource, tenantId } of resourceGroups.values()) {
66
- await knex('any_records')
67
- .where({ tenant_id: tenantId, resource })
68
- .delete()
69
- }
70
-
71
- for (const table of tableNames) {
72
- const linkInfo = storageMode.getLinkInfo(table)
73
- if (linkInfo) {
74
- const tenantId = linkInfo.tenantId || storageMode.defaultTenant
75
- const relationshipKey = linkInfo.relationshipKey ||
76
- `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`
77
- await knex('any_links')
78
- .where({ tenant_id: tenantId })
79
- .andWhere((builder) => {
80
- builder.where('relationship', relationshipKey)
81
- .orWhere('inverse_relationship', relationshipKey)
82
- if (linkInfo.inverseRelationshipKey) {
83
- builder.orWhere('relationship', linkInfo.inverseRelationshipKey)
84
- .orWhere('inverse_relationship', linkInfo.inverseRelationshipKey)
85
- }
86
- })
87
- .delete()
88
- .catch(() => {})
89
- }
90
- try {
91
- await knex(table).delete()
92
- } catch (error) {
93
- // Ignore missing tables in anyapi mode
94
- }
95
- }
96
- } else {
97
- for (const table of tableNames) {
98
- await knex(table).delete()
99
- }
100
- }
101
- }
102
-
103
- /**
104
- * Counts records in a table
105
- */
106
- export async function countRecords (knex, tableName) {
107
- if (storageMode.isAnyApi()) {
108
- const resource = storageMode.getResourceForTable(tableName)
109
- const linkInfo = storageMode.getLinkInfo(tableName)
110
- const tenantId = storageMode.getTenantForTable(tableName)
111
- if (linkInfo) {
112
- const relationshipKey = linkInfo.relationshipKey ||
113
- `${tenantId}:${linkInfo.ownerResource}:${linkInfo.relationshipName}`
114
- const result = await knex('any_links')
115
- .where({ tenant_id: tenantId })
116
- .andWhere((builder) => {
117
- builder.where('relationship', relationshipKey)
118
- .orWhere('inverse_relationship', relationshipKey)
119
- })
120
- .count('* as count')
121
- .first()
122
- return parseInt(result?.count ?? 0, 10)
123
- }
124
- if (resource) {
125
- const result = await knex('any_records')
126
- .where({
127
- tenant_id: tenantId,
128
- resource,
129
- })
130
- .count('* as count')
131
- .first()
132
- return parseInt(result.count)
133
- }
134
- try {
135
- const result = await knex(tableName).count('* as count').first()
136
- return parseInt(result?.count || 0)
137
- } catch (error) {
138
- return 0
139
- }
140
- }
141
-
142
- const result = await knex(tableName).count('* as count').first()
143
- return parseInt(result.count)
144
- }
145
-
146
- /**
147
- * Helper to create a JSON:API document for POST/PUT/PATCH
148
- */
149
- export function createJsonApiDocument (type, attributes, relationships = {}) {
150
- const doc = {
151
- data: {
152
- type,
153
- attributes
154
- }
155
- }
156
-
157
- if (Object.keys(relationships).length > 0) {
158
- doc.data.relationships = relationships
159
- }
160
-
161
- return doc
162
- }
163
-
164
- /**
165
- * Helper to create a relationship object
166
- */
167
- export function createRelationship (resourceIdentifier) {
168
- return {
169
- data: resourceIdentifier
170
- }
171
- }
172
-
173
- /**
174
- * Helper to create a to-many relationship object
175
- */
176
- export function createToManyRelationship (resourceIdentifiers) {
177
- return {
178
- data: resourceIdentifiers
179
- }
180
- }
181
-
182
- /**
183
- * Asserts that a resource has specific attributes
184
- */
185
- export function assertResourceAttributes (resource, expectedAttributes) {
186
- Object.entries(expectedAttributes).forEach(([key, value]) => {
187
- assert.deepEqual(resource.attributes[key], value,
188
- `Attribute ${key} should equal ${JSON.stringify(value)}`)
189
- })
190
- }
191
-
192
- /**
193
- * Asserts that a resource has a specific relationship
194
- */
195
- export function assertResourceRelationship (resource, relationshipName, expectedIdentifier) {
196
- assert(resource.relationships, 'Resource should have relationships')
197
- assert(resource.relationships[relationshipName], `Resource should have ${relationshipName} relationship`)
198
- assert.deepEqual(
199
- resource.relationships[relationshipName].data,
200
- expectedIdentifier,
201
- `Relationship ${relationshipName} should match expected identifier`
202
- )
203
- }