json-rest-api 1.0.29 → 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 (292) 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 +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} +74 -30
  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 -177
  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 +48 -317
  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 +134 -37
  91. package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
  92. package/plugins/core/lib/querying/sort-helpers.js +13 -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 +179 -194
  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 +100 -290
  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 +93 -38
  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 +128 -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 -2485
  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 -1077
  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 -395
  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 -148
  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 +36 -68
  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 +37 -81
  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 -1247
  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 -148
  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 -155
  251. package/tests/fixtures/api-configs.js +0 -2566
  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-boundaries.test.js +0 -154
  264. package/tests/pagination-contract.test.js +0 -122
  265. package/tests/pagination-cursor-multifield.test.js +0 -641
  266. package/tests/pagination-enhanced.test.js +0 -563
  267. package/tests/polymorphic-relationships.test.js +0 -347
  268. package/tests/polymorphic-simple.test.js +0 -148
  269. package/tests/positioning-advanced.test.js +0 -692
  270. package/tests/positioning-extended.test.js +0 -915
  271. package/tests/positioning-multifilter.test.js +0 -699
  272. package/tests/positioning.test.js +0 -560
  273. package/tests/post-commit-hooks.test.js +0 -120
  274. package/tests/projected-fields.test.js +0 -319
  275. package/tests/public-exports.test.js +0 -15
  276. package/tests/put-create-parity.test.js +0 -216
  277. package/tests/queries.test.js +0 -521
  278. package/tests/query-limits.test.js +0 -584
  279. package/tests/relationship-endpoints.test.js +0 -620
  280. package/tests/relationship-include-storage-adapter.test.js +0 -56
  281. package/tests/relationships.test.js +0 -681
  282. package/tests/rest-api.test.js +0 -337
  283. package/tests/return-record-settings.test.js +0 -184
  284. package/tests/row-policy.test.js +0 -489
  285. package/tests/s3-storage.test.js +0 -38
  286. package/tests/searchschema-knex-behavior.test.js +0 -187
  287. package/tests/searchschema-merge.test.js +0 -151
  288. package/tests/socketio.test.js +0 -860
  289. package/tests/storage-mapping.test.js +0 -362
  290. package/tests/temporal-boundaries.test.js +0 -208
  291. package/tests/test-development-guide.md +0 -370
  292. package/tests/virtual-fields.test.js +0 -404
@@ -1,110 +0,0 @@
1
- import { getSchemaStructure } from '../querying-writing/knex-constants.js'
2
-
3
- /**
4
- * Processes belongsTo relationships from JSON:API input to foreign key updates
5
- *
6
- * @param {Object} scope - The scope object containing schema info
7
- * @param {Object} deps - Dependencies object containing context with inputRecord
8
- * @returns {Object} Object mapping foreign key fields to their values
9
- *
10
- * @example
11
- * // Input: JSON:API document with relationships
12
- * const inputRecord = {
13
- * data: {
14
- * type: 'articles',
15
- * attributes: { title: 'My Article' },
16
- * relationships: {
17
- * author: {
18
- * data: { type: 'users', id: '123' }
19
- * },
20
- * category: {
21
- * data: { type: 'categories', id: '456' }
22
- * }
23
- * }
24
- * }
25
- * };
26
- *
27
- * // Schema has: author_id field with belongsTo: 'users', as: 'author'
28
- * // Schema has: category_id field with belongsTo: 'categories', as: 'category'
29
- *
30
- * const updates = processBelongsToRelationships(scope, { context: { inputRecord } });
31
- *
32
- * // Output: Foreign key values extracted
33
- * // {
34
- * // author_id: '123',
35
- * // category_id: '456'
36
- * // }
37
- *
38
- * @example
39
- * // Input: Removing a relationship (setting to null)
40
- * const inputRecord = {
41
- * data: {
42
- * type: 'articles',
43
- * id: '1',
44
- * relationships: {
45
- * author: {
46
- * data: null // Remove author
47
- * }
48
- * }
49
- * }
50
- * };
51
- *
52
- * const updates = processBelongsToRelationships(scope, { context: { inputRecord } });
53
- *
54
- * // Output: Foreign key set to null
55
- * // {
56
- * // author_id: null
57
- * // }
58
- *
59
- * @description
60
- * Used by:
61
- * - rest-api-knex-plugin's dataPut and dataPatch methods
62
- * - Called before saving to extract foreign keys from JSON:API relationships
63
- * - Works in conjunction with relationship-processor for complete handling
64
- *
65
- * Purpose:
66
- * - Converts JSON:API relationship format to database foreign keys
67
- * - Maps from relationship names (e.g., 'author') to field names (e.g., 'author_id')
68
- * - Handles both setting relationships (with id) and removing them (null)
69
- * - Only processes belongsTo relationships, not hasMany
70
- *
71
- * Data flow:
72
- * 1. Receives JSON:API document with relationships section
73
- * 2. Looks up schema to find foreign key fields
74
- * 3. For each belongsTo field, checks if relationship exists in input
75
- * 4. Extracts id from relationship data or sets null
76
- * 5. Returns object ready to merge with attributes for database update
77
- */
78
- export const processBelongsToRelationships = (scope, deps) => {
79
- const foreignKeyUpdates = {}
80
-
81
- // Extract values from deps
82
- const { context } = deps
83
- const inputRecord = context.inputRecord
84
-
85
- if (!inputRecord.data.relationships) {
86
- return foreignKeyUpdates
87
- }
88
-
89
- // Extract schema from scope
90
- const {
91
- vars: {
92
- schemaInfo: { schema }
93
- }
94
- } = scope
95
-
96
- // Schema might be a Schema object or plain object
97
- const schemaStructure = getSchemaStructure(schema)
98
-
99
- for (const [fieldName, fieldDef] of Object.entries(schemaStructure)) {
100
- if (fieldDef.belongsTo && fieldDef.as) {
101
- const relName = fieldDef.as
102
- if (inputRecord.data.relationships[relName] !== undefined) {
103
- const relData = inputRecord.data.relationships[relName]
104
- foreignKeyUpdates[fieldName] = relData.data?.id || null
105
- }
106
- }
107
- }
108
-
109
- return foreignKeyUpdates
110
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * S3 Storage Adapter (mock/demo)
3
- *
4
- * Generates S3-style URLs without uploading files to Amazon S3.
5
- *
6
- * Usage:
7
- * ```javascript
8
- * import { S3Storage } from 'json-rest-api';
9
- *
10
- * const storage = new S3Storage({
11
- * bucket: 'my-uploads',
12
- * region: 'us-east-1',
13
- * prefix: 'uploads/',
14
- * mockMode: true
15
- * });
16
- *
17
- * const schema = {
18
- * image: { type: 'file', storage }
19
- * };
20
- * ```
21
- */
22
-
23
- import crypto from 'crypto'
24
- import path from 'path'
25
-
26
- const REAL_S3_NOT_IMPLEMENTED_MESSAGE = 'S3Storage real S3 mode is not implemented. The included adapter only supports mockMode: true; provide a production storage adapter or add an AWS SDK-backed implementation.'
27
-
28
- function createRealS3NotImplementedError () {
29
- return new Error(REAL_S3_NOT_IMPLEMENTED_MESSAGE)
30
- }
31
-
32
- export class S3Storage {
33
- constructor (options = {}) {
34
- this.bucket = options.bucket
35
- this.region = options.region || 'us-east-1'
36
- this.prefix = options.prefix || ''
37
- this.acl = options.acl || 'public-read'
38
-
39
- if (!this.bucket) {
40
- throw new Error('S3Storage requires a bucket name')
41
- }
42
-
43
- this.mockMode = options.mockMode !== false // Default to mock mode
44
-
45
- if (!this.mockMode) {
46
- throw createRealS3NotImplementedError()
47
- }
48
- }
49
-
50
- /**
51
- * Upload a file to mock S3 storage.
52
- * @param {Object} file - File object from detector
53
- * @returns {Promise<string>} URL of the uploaded file
54
- */
55
- async upload (file) {
56
- // Generate S3 key
57
- const ext = path.extname(file.filename)
58
- const hash = crypto.randomBytes(16).toString('hex')
59
- const key = `${this.prefix}${hash}${ext}`
60
-
61
- if (this.mockMode) {
62
- // Mock mode - just return a fake URL
63
- const url = `https://${this.bucket}.s3.${this.region}.amazonaws.com/${key}`
64
-
65
- // Simulate upload delay
66
- await new Promise(resolve => setTimeout(resolve, 100))
67
-
68
- return url
69
- }
70
-
71
- throw createRealS3NotImplementedError()
72
- }
73
-
74
- /**
75
- * Delete a file from mock S3 storage.
76
- * @param {string} url - URL returned by upload()
77
- * @returns {Promise<void>}
78
- */
79
- async delete (url) {
80
- if (!this.mockMode) {
81
- throw createRealS3NotImplementedError()
82
- }
83
-
84
- const urlObj = new URL(url)
85
-
86
- if (urlObj.pathname.length === 0) {
87
- throw new Error('Invalid S3 object URL')
88
- }
89
-
90
- // Mock mode - just simulate deletion
91
- await new Promise(resolve => setTimeout(resolve, 50))
92
- }
93
- }
package/quickTest.js DELETED
@@ -1,136 +0,0 @@
1
- //
2
- // index.js
3
- //
4
- import { RestApiPlugin, RestApiKnexPlugin, ExpressPlugin } from './index.js' // Added: ExpressPlugin
5
- import { Api } from 'hooked-api'
6
- import knexLib from 'knex'
7
- import util from 'util'
8
- import express from 'express' // Added: Express
9
-
10
- // Utility used throughout this guide
11
- const inspect = (obj) => util.inspect(obj, { depth: 8 })
12
-
13
- // Create a Knex instance connected to SQLite in-memory database
14
- const knex = knexLib({
15
- client: 'better-sqlite3',
16
- connection: {
17
- filename: ':memory:'
18
- },
19
- useNullAsDefault: true
20
- })
21
-
22
- // Create API instance
23
- const api = new Api({ name: 'book-catalog-api', logLevel: 'trace' })
24
-
25
- // Install plugins
26
- await api.use(RestApiPlugin, { returnBasePath: '/api' })
27
- await api.use(RestApiKnexPlugin, { knex })
28
- await api.use(ExpressPlugin, { mountPath: '/api' }) // Added: Express Plugin
29
-
30
- // Define publishers resource
31
- await api.addResource('publishers', {
32
- schema: {
33
- name: { type: 'string', required: true, max: 255, search: true, indexed: true },
34
- },
35
- relationships: {
36
- // A publisher has many authors
37
- authors: { hasMany: 'authors', foreignKey: 'publisher_id' },
38
- },
39
- searchSchema: { // Adding search schema for publishers
40
- name: { type: 'string', filterOperator: 'like' }
41
- }
42
- })
43
- await api.resources.publishers.createKnexTable()
44
-
45
- // Define authors resource, which belongs to a publisher
46
- await api.addResource('authors', {
47
- schema: {
48
- name: { type: 'string', required: true, max: 100, search: true },
49
- surname: { type: 'string', required: true, max: 100, search: true },
50
- publisher_id: { type: 'id', belongsTo: 'publishers', as: 'publisher', nullable: true }
51
- },
52
- searchSchema: { // Adding search schema for authors
53
- name: { type: 'string', filterOperator: 'like' },
54
- surname: { type: 'string', filterOperator: 'like' },
55
- publisher: { type: 'id', actualField: 'publisher_id', nullable: true },
56
- publisherName: { type: 'string', actualField: 'publishers.name', filterOperator: 'like' } // Cross-table search
57
- }
58
- })
59
- await api.resources.authors.createKnexTable()
60
-
61
- // Method 1: Simplified mode without inputRecord (most concise)
62
- const penguinResult = await api.resources.publishers.post({
63
- name: 'Penguin Random House'
64
- })
65
- console.log('Created publisher:', inspect(penguinResult))
66
-
67
- // Method 2: Simplified mode with inputRecord (explicit)
68
- const harperResult = await api.resources.publishers.post({
69
- inputRecord: {
70
- name: 'HarperCollins'
71
- }
72
- })
73
- console.log('Created second publisher:', inspect(harperResult))
74
-
75
- // Method 3: Full JSON:API mode (standards compliant)
76
- const oxfordResult = await api.resources.publishers.post({
77
- inputRecord: {
78
- data: {
79
- type: 'publishers',
80
- attributes: {
81
- name: 'Oxford University Press'
82
- }
83
- }
84
- },
85
- simplified: false
86
- })
87
- console.log('JSON:API response:', inspect(oxfordResult))
88
-
89
- // Create an author linked to the first publisher (simplified)
90
- const authorResult = await api.resources.authors.post({
91
- name: 'George',
92
- surname: 'Orwell',
93
- publisher_id: penguinResult.id
94
- })
95
- console.log('Created author:', inspect(authorResult))
96
-
97
- // Get all publishers
98
- const allPublishers = await api.resources.publishers.query({})
99
- console.log('All publishers:', inspect(allPublishers))
100
-
101
- // Get publisher with included authors
102
- const publisherWithAuthors = await api.resources.publishers.get({
103
- id: penguinResult.id,
104
- include: ['authors']
105
- })
106
- console.log('Publisher with authors:', inspect(publisherWithAuthors))
107
-
108
- // Search authors by name
109
- const searchResult = await api.resources.authors.query({
110
- filter: { name: 'George' }
111
- })
112
- console.log('Search results:', inspect(searchResult))
113
-
114
- // Update an author
115
- const updateResult = await api.resources.authors.patch({
116
- id: authorResult.id,
117
- surname: 'Orwell (Eric Blair)'
118
- })
119
- console.log('Updated author:', inspect(updateResult))
120
-
121
- /// *** ...programmatic calls here... ***
122
-
123
- // Create the express server and add the API's routes
124
- const app = express()
125
- app.use(api.http.express.router)
126
- app.use(api.http.express.notFoundRouter)
127
-
128
- app.listen(3000, () => {
129
- console.log('Express server started on port 3000. API available at http://localhost:3000/api')
130
- }).on('error', (err) => {
131
- console.error('Failed to start server:', err)
132
- process.exit(1)
133
- }).on('error', (err) => {
134
- console.error('Failed to start server:', err)
135
- process.exit(1)
136
- })
@@ -1,63 +0,0 @@
1
- import knexLib from 'knex'
2
- import { createCustomIdPropertyApi } from '../tests/fixtures/api-configs.js'
3
- import { createJsonApiDocument } from '../tests/helpers/test-utils.js'
4
-
5
- async function run () {
6
- const knex = knexLib({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
7
- const api = await createCustomIdPropertyApi(knex)
8
-
9
- const country = await api.resources.countries.post({
10
- inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' })
11
- })
12
-
13
- const book = await api.resources.books.post({
14
- inputRecord: createJsonApiDocument('books',
15
- { title: 'Test Book' },
16
- { country: { data: { type: 'countries', id: country.data.id } } }
17
- )
18
- })
19
-
20
- const author = await api.resources.authors.post({
21
- inputRecord: createJsonApiDocument('authors', { name: 'Author' })
22
- })
23
-
24
- await api.resources.reviews.post({
25
- inputRecord: createJsonApiDocument('reviews', {
26
- rating: 5,
27
- title: 'Book review',
28
- content: 'Great book',
29
- reviewer_name: 'Reader',
30
- reviewable_type: 'books',
31
- reviewable_id: book.data.id
32
- })
33
- })
34
-
35
- await api.resources.reviews.post({
36
- inputRecord: createJsonApiDocument('reviews', {
37
- rating: 4,
38
- title: 'Author review',
39
- content: 'Great author',
40
- reviewer_name: 'Fan',
41
- reviewable_type: 'authors',
42
- reviewable_id: author.data.id
43
- })
44
- })
45
-
46
- try {
47
- const res = await api.resources.reviews.query({
48
- queryParams: { filters: { reviewable_type: 'books' } }
49
- })
50
- console.log('Query succeeded with rows:', res.data.length)
51
- } catch (error) {
52
- console.error('Query failed:', error)
53
- if (error?.sql) {
54
- console.error('SQL:', error.sql)
55
- }
56
- }
57
-
58
- await knex.destroy()
59
- }
60
-
61
- run().catch((err) => {
62
- console.error('Unexpected failure:', err)
63
- })
@@ -1,19 +0,0 @@
1
- import knexLib from 'knex'
2
- import { createCustomIdPropertyApi } from '../tests/fixtures/api-configs.js'
3
-
4
- async function run () {
5
- const knex = knexLib({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
6
- const api = await createCustomIdPropertyApi(knex)
7
- const descriptor = await api.anyapi.registry.getDescriptor('default', 'countries')
8
- console.log(JSON.stringify({
9
- idProperty: descriptor.idProperty,
10
- fields: descriptor.fields,
11
- canonicalFieldMap: descriptor.canonicalFieldMap,
12
- reverseAttributes: descriptor.reverseAttributes,
13
- }, null, 2))
14
- await knex.destroy()
15
- }
16
-
17
- run().catch((err) => {
18
- console.error('Failed to inspect descriptor', err)
19
- })
@@ -1,19 +0,0 @@
1
- import knexLib from 'knex'
2
- import { createCustomIdPropertyApi } from '../tests/fixtures/api-configs.js'
3
-
4
- async function run () {
5
- const knex = knexLib({ client: 'better-sqlite3', connection: { filename: ':memory:' }, useNullAsDefault: true })
6
- const api = await createCustomIdPropertyApi(knex)
7
- const descriptor = await api.anyapi.registry.getDescriptor('default', 'reviews')
8
- console.log(JSON.stringify({
9
- idProperty: descriptor.idProperty,
10
- fields: descriptor.fields,
11
- canonicalFieldMap: descriptor.canonicalFieldMap,
12
- reverseAttributes: descriptor.reverseAttributes,
13
- }, null, 2))
14
- await knex.destroy()
15
- }
16
-
17
- run().catch((err) => {
18
- console.error('Failed to inspect descriptor', err)
19
- })
@@ -1,133 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- import { spawn } from 'child_process'
4
- import path from 'path'
5
- import { fileURLToPath } from 'url'
6
-
7
- const __filename = fileURLToPath(import.meta.url)
8
- const __dirname = path.dirname(__filename)
9
- const projectRoot = path.join(__dirname, '..')
10
- const docsRoot = path.join(projectRoot, 'docs')
11
- const LOCAL_URL = 'http://localhost:4000/json-rest-api/'
12
- const BROWSER_DELAY_MS = 3000
13
-
14
- function parseMode (argv = []) {
15
- if (argv.includes('--serve') || argv.includes('--dev')) {
16
- return 'serve'
17
- }
18
-
19
- return 'build'
20
- }
21
-
22
- async function runCommand (command, args, options = {}) {
23
- return new Promise((resolve, reject) => {
24
- const proc = spawn(command, args, {
25
- stdio: 'inherit',
26
- shell: true,
27
- ...options
28
- })
29
-
30
- proc.on('error', reject)
31
- proc.on('exit', (code) => {
32
- if (code === 0) {
33
- resolve()
34
- return
35
- }
36
-
37
- reject(new Error(`Command failed with code ${code}`))
38
- })
39
- })
40
- }
41
-
42
- async function ensureBundlerDependencies () {
43
- try {
44
- await runCommand('bundle', ['check'], { cwd: docsRoot })
45
- } catch {
46
- console.log('Installing Jekyll dependencies...')
47
- await runCommand('bundle', ['install'], { cwd: docsRoot })
48
- }
49
- }
50
-
51
- async function openBrowser (url) {
52
- const platform = process.platform
53
- const command = platform === 'darwin'
54
- ? 'open'
55
- : platform === 'win32'
56
- ? 'start'
57
- : 'xdg-open'
58
-
59
- try {
60
- await runCommand(command, [url])
61
- console.log(`Opened browser at ${url}`)
62
- } catch {
63
- console.log(`Visit ${url} in your browser`)
64
- }
65
- }
66
-
67
- async function buildDocs () {
68
- console.log('Building documentation site...')
69
- await runCommand('bundle', ['exec', 'jekyll', 'build'], { cwd: docsRoot })
70
- }
71
-
72
- async function serveDocs () {
73
- console.log('Starting documentation server...')
74
-
75
- const jekyll = spawn('bundle', ['exec', 'jekyll', 'serve', '--watch'], {
76
- cwd: docsRoot,
77
- stdio: 'pipe',
78
- shell: true
79
- })
80
-
81
- jekyll.stdout.on('data', (data) => {
82
- const output = data.toString()
83
- process.stdout.write(output)
84
-
85
- if (output.includes('Server running') || output.includes('Server address:')) {
86
- setTimeout(() => openBrowser(LOCAL_URL), 1000)
87
- }
88
- })
89
-
90
- jekyll.stderr.on('data', (data) => {
91
- process.stderr.write(data)
92
- })
93
-
94
- process.on('SIGINT', () => {
95
- console.log('\nShutting down Jekyll server...')
96
- jekyll.kill()
97
- process.exit(0)
98
- })
99
-
100
- jekyll.on('error', (err) => {
101
- console.error('Jekyll server error:', err)
102
- process.exit(1)
103
- })
104
-
105
- jekyll.on('exit', (code) => {
106
- if (code !== 0 && code !== null) {
107
- console.error(`Jekyll server exited with code ${code}`)
108
- process.exit(code)
109
- }
110
- })
111
-
112
- setTimeout(() => openBrowser(LOCAL_URL), BROWSER_DELAY_MS)
113
- }
114
-
115
- async function main () {
116
- const mode = parseMode(process.argv.slice(2))
117
-
118
- try {
119
- await ensureBundlerDependencies()
120
-
121
- if (mode === 'serve') {
122
- await serveDocs()
123
- return
124
- }
125
-
126
- await buildDocs()
127
- } catch (error) {
128
- console.error('Error:', error.message)
129
- process.exit(1)
130
- }
131
- }
132
-
133
- main()
package/test-debug.js DELETED
@@ -1,59 +0,0 @@
1
- import { createBasicApi } from './tests/fixtures/api-configs.js'
2
- import { cleanTables, createJsonApiDocument } from './tests/helpers/test-utils.js'
3
- import knex from 'knex'
4
-
5
- const dbConfig = {
6
- client: 'better-sqlite3',
7
- connection: { filename: ':memory:' },
8
- useNullAsDefault: true
9
- }
10
-
11
- const db = knex(dbConfig)
12
-
13
- async function test () {
14
- const api = await createBasicApi(db)
15
-
16
- // Create test data
17
- await cleanTables(db, ['basic_countries', 'basic_publishers', 'basic_authors', 'basic_books', 'basic_book_authors'])
18
-
19
- // Create a country first
20
- const countryDoc = createJsonApiDocument('countries', { name: 'Test Country', code: 'TC' })
21
- const country = await api.resources.countries.post({ inputRecord: countryDoc })
22
-
23
- // Create an author
24
- const authorDoc = createJsonApiDocument('authors', { name: 'Test Author' })
25
- const author = await api.resources.authors.post({ inputRecord: authorDoc })
26
-
27
- // Create a book with proper relationships
28
- const bookDoc = {
29
- data: {
30
- type: 'books',
31
- attributes: { title: 'Test Book' },
32
- relationships: {
33
- country: { data: { type: 'countries', id: String(country.data.id) } }
34
- }
35
- }
36
- }
37
- const book = await api.resources.books.post({ inputRecord: bookDoc })
38
-
39
- // Create the many-to-many relationship
40
- await db('basic_book_authors').insert({
41
- author_id: author.data.id,
42
- book_id: book.data.id
43
- })
44
-
45
- // Now try to query authors with books included
46
- console.log('Querying authors with books included...')
47
- try {
48
- const result = await api.resources.authors.query({
49
- queryParams: { include: ['books'] }
50
- })
51
- console.log('Success!', JSON.stringify(result, null, 2))
52
- } catch (error) {
53
- console.error('Error:', error.message)
54
- }
55
-
56
- await db.destroy()
57
- }
58
-
59
- test().catch(console.error)