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
@@ -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
- }