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,732 +0,0 @@
1
- # Bulk Operations Guide
2
-
3
- The **Bulk Operations Plugin** enables efficient processing of multiple records in a single request, supporting atomic transactions, batch processing, and error handling. This guide demonstrates how to create, update, and delete multiple records efficiently.
4
-
5
- ## Overview
6
-
7
- Bulk operations are essential for:
8
- - **Data Import/Export**: Processing large datasets efficiently
9
- - **Batch Updates**: Modifying multiple records with consistent rules
10
- - **Transactional Safety**: Ensuring all-or-nothing operations
11
- - **Performance**: Reducing network overhead and database round-trips
12
-
13
- The plugin provides three main operations:
14
- - **bulkPost**: Create multiple records
15
- - **bulkPatch**: Update multiple records
16
- - **bulkDelete**: Delete multiple records
17
-
18
- ## Installation and Setup
19
-
20
- First, install the Bulk Operations plugin alongside the standard REST API plugins:
21
-
22
- ```javascript
23
- import { RestApiPlugin, RestApiKnexPlugin } from 'json-rest-api';
24
- import { BulkOperationsPlugin } from 'json-rest-api/plugins/core/bulk-operations-plugin.js';
25
- import { Api } from 'hooked-api';
26
- import knexLib from 'knex';
27
- import util from 'util';
28
-
29
- // Utility for displaying results
30
- const inspect = (obj) => util.inspect(obj, { depth: 5 });
31
-
32
- // Create database connection
33
- const knex = knexLib({
34
- client: 'sqlite3',
35
- connection: { filename: ':memory:' },
36
- useNullAsDefault: true
37
- });
38
-
39
- // Create API instance
40
- const api = new Api({
41
- name: 'book-catalog-api',
42
- });
43
-
44
- // Install plugins in order
45
- await api.use(RestApiPlugin);
46
- await api.use(RestApiKnexPlugin, { knex });
47
-
48
- // Install Bulk Operations plugin with configuration
49
- await api.use(BulkOperationsPlugin, {
50
- 'bulk-operations': {
51
- maxBulkOperations: 100, // Maximum records per request
52
- defaultAtomic: true, // Default transaction mode
53
- batchSize: 10, // Internal batch processing size
54
- enableOptimizations: true // Enable database-specific optimizations
55
- }
56
- });
57
- ```
58
-
59
- ## Configuration Options
60
-
61
- The Bulk Operations plugin supports several configuration options:
62
-
63
- | Option | Type | Default | Description |
64
- |--------|------|---------|-------------|
65
- | `maxBulkOperations` | number | 100 | Maximum number of records that can be processed in a single request |
66
- | `defaultAtomic` | boolean | true | Whether operations are atomic (all-or-nothing) by default |
67
- | `batchSize` | number | 100 | Number of records to process in each internal batch |
68
- | `enableOptimizations` | boolean | true | Enable database-specific bulk optimizations when available |
69
-
70
- ## Using the Book Catalog Schema
71
-
72
- Let's use the standard book catalog schema for all examples:
73
-
74
- ```javascript
75
- // Define the book catalog schema
76
- await api.addResource('countries', {
77
- schema: {
78
- id: { type: 'id' },
79
- name: { type: 'string', required: true, max: 100 },
80
- code: { type: 'string', max: 2, unique: true }
81
- },
82
- relationships: {
83
- publishers: { type: 'hasMany', target: 'publishers', foreignKey: 'country_id' },
84
- books: { type: 'hasMany', target: 'books', foreignKey: 'country_id' }
85
- }
86
- });
87
-
88
- await api.addResource('publishers', {
89
- schema: {
90
- id: { type: 'id' },
91
- name: { type: 'string', required: true, max: 200 },
92
- country_id: { type: 'number', nullable: true, belongsTo: 'countries', as: 'country' }
93
- },
94
- relationships: {
95
- books: { type: 'hasMany', target: 'books', foreignKey: 'publisher_id' }
96
- }
97
- });
98
-
99
- await api.addResource('authors', {
100
- schema: {
101
- id: { type: 'id' },
102
- name: { type: 'string', required: true, max: 200 }
103
- },
104
- relationships: {
105
- books: {
106
- type: 'manyToMany',
107
- through: 'book_authors',
108
- foreignKey: 'author_id',
109
- otherKey: 'book_id'
110
- }
111
- }
112
- });
113
-
114
- await api.addResource('books', {
115
- schema: {
116
- id: { type: 'id' },
117
- title: { type: 'string', required: true, max: 300 },
118
- country_id: { type: 'number', required: true, belongsTo: 'countries', as: 'country' },
119
- publisher_id: { type: 'number', nullable: true, belongsTo: 'publishers', as: 'publisher' }
120
- },
121
- relationships: {
122
- authors: {
123
- type: 'manyToMany',
124
- through: 'book_authors',
125
- foreignKey: 'book_id',
126
- otherKey: 'author_id'
127
- }
128
- }
129
- });
130
-
131
- await api.addResource('book_authors', {
132
- schema: {
133
- id: { type: 'id' },
134
- book_id: { type: 'number', required: true, belongsTo: 'books', as: 'book' },
135
- author_id: { type: 'number', required: true, belongsTo: 'authors', as: 'author' }
136
- }
137
- });
138
-
139
- // Create tables
140
- await api.resources.countries.createKnexTable();
141
- await api.resources.publishers.createKnexTable();
142
- await api.resources.authors.createKnexTable();
143
- await api.resources.books.createKnexTable();
144
- await api.resources.book_authors.createKnexTable();
145
- ```
146
-
147
- ## Bulk Create (bulkPost)
148
-
149
- Create multiple records in a single operation. The `bulkPost` method accepts an array of JSON:API documents.
150
-
151
- ### Basic Bulk Create
152
-
153
- ```javascript
154
- // Create multiple authors at once
155
- const bulkCreateResult = await api.scopes.authors.bulkPost({
156
- inputRecords: [
157
- { type: 'authors', attributes: { name: 'J.K. Rowling' } },
158
- { type: 'authors', attributes: { name: 'George R.R. Martin' } },
159
- { type: 'authors', attributes: { name: 'Brandon Sanderson' } }
160
- ],
161
- atomic: true // All succeed or all fail
162
- });
163
-
164
- console.log(inspect(bulkCreateResult));
165
- // Output:
166
- // {
167
- // data: [
168
- // { type: 'authors', id: '1', attributes: { name: 'J.K. Rowling' } },
169
- // { type: 'authors', id: '2', attributes: { name: 'George R.R. Martin' } },
170
- // { type: 'authors', id: '3', attributes: { name: 'Brandon Sanderson' } }
171
- // ],
172
- // meta: {
173
- // total: 3,
174
- // succeeded: 3,
175
- // failed: 0,
176
- // atomic: true
177
- // }
178
- // }
179
- ```
180
-
181
- ### Bulk Create with Relationships
182
-
183
- Create records with relationships to existing data:
184
-
185
- ```javascript
186
- // First, create some countries and publishers
187
- await api.resources.countries.post({
188
- inputRecord: { type: 'countries', attributes: { name: 'United States', code: 'US' } }
189
- });
190
- await api.resources.countries.post({
191
- inputRecord: { type: 'countries', attributes: { name: 'United Kingdom', code: 'UK' } }
192
- });
193
-
194
- // Create publishers with country relationships
195
- const publisherResult = await api.scopes.publishers.bulkPost({
196
- inputRecords: [
197
- {
198
- type: 'publishers',
199
- attributes: { name: 'Penguin Random House' },
200
- relationships: {
201
- country: { data: { type: 'countries', id: '1' } } // US
202
- }
203
- },
204
- {
205
- type: 'publishers',
206
- attributes: { name: 'Bloomsbury Publishing' },
207
- relationships: {
208
- country: { data: { type: 'countries', id: '2' } } // UK
209
- }
210
- }
211
- ]
212
- });
213
-
214
- console.log('Created publishers:', publisherResult.meta.succeeded);
215
- ```
216
-
217
- ### Non-Atomic Mode (Partial Success)
218
-
219
- Allow some records to fail while others succeed:
220
-
221
- ```javascript
222
- const partialResult = await api.scopes.authors.bulkPost({
223
- inputRecords: [
224
- { type: 'authors', attributes: { name: 'Valid Author' } },
225
- { type: 'authors', attributes: {} }, // Invalid - missing required name
226
- { type: 'authors', attributes: { name: 'Another Valid Author' } }
227
- ],
228
- atomic: false // Allow partial success
229
- });
230
-
231
- console.log(inspect(partialResult));
232
- // Output:
233
- // {
234
- // data: [
235
- // { type: 'authors', id: '4', attributes: { name: 'Valid Author' } },
236
- // { type: 'authors', id: '5', attributes: { name: 'Another Valid Author' } }
237
- // ],
238
- // errors: [{
239
- // index: 1,
240
- // status: 'error',
241
- // error: {
242
- // code: 'REST_API_VALIDATION',
243
- // message: 'Schema validation failed for resource attributes',
244
- // details: { fields: ['data.attributes.name'], violations: [...] }
245
- // }
246
- // }],
247
- // meta: {
248
- // total: 3,
249
- // succeeded: 2,
250
- // failed: 1,
251
- // atomic: false
252
- // }
253
- // }
254
- ```
255
-
256
- ## Bulk Update (bulkPatch)
257
-
258
- Update multiple records with different values in a single operation.
259
-
260
- ### Basic Bulk Update
261
-
262
- ```javascript
263
- // Update multiple authors
264
- const bulkUpdateResult = await api.scopes.authors.bulkPatch({
265
- operations: [
266
- {
267
- id: '1',
268
- data: {
269
- type: 'authors',
270
- id: '1',
271
- attributes: { name: 'J.K. Rowling (Harry Potter)' }
272
- }
273
- },
274
- {
275
- id: '2',
276
- data: {
277
- type: 'authors',
278
- id: '2',
279
- attributes: { name: 'George R.R. Martin (Game of Thrones)' }
280
- }
281
- }
282
- ],
283
- atomic: true
284
- });
285
-
286
- console.log('Updated authors:', bulkUpdateResult.meta.succeeded);
287
- ```
288
-
289
- ### Updating Relationships
290
-
291
- Bulk update relationships between resources:
292
-
293
- ```javascript
294
- // Create some books first
295
- const bookResults = await api.scopes.books.bulkPost({
296
- inputRecords: [
297
- {
298
- type: 'books',
299
- attributes: { title: 'Harry Potter and the Philosopher\'s Stone' },
300
- relationships: {
301
- country: { data: { type: 'countries', id: '2' } } // UK
302
- }
303
- },
304
- {
305
- type: 'books',
306
- attributes: { title: 'A Game of Thrones' },
307
- relationships: {
308
- country: { data: { type: 'countries', id: '1' } } // US
309
- }
310
- }
311
- ]
312
- });
313
-
314
- // Now update the books to assign publishers
315
- const bookIds = bookResults.data.map(book => book.id);
316
- const updateOps = await api.scopes.books.bulkPatch({
317
- operations: [
318
- {
319
- id: bookIds[0],
320
- data: {
321
- type: 'books',
322
- id: bookIds[0],
323
- attributes: {},
324
- relationships: {
325
- publisher: { data: { type: 'publishers', id: '2' } } // Bloomsbury
326
- }
327
- }
328
- },
329
- {
330
- id: bookIds[1],
331
- data: {
332
- type: 'books',
333
- id: bookIds[1],
334
- attributes: {},
335
- relationships: {
336
- publisher: { data: { type: 'publishers', id: '1' } } // Penguin
337
- }
338
- }
339
- }
340
- ]
341
- });
342
-
343
- console.log('Updated book relationships:', updateOps.meta.succeeded);
344
- ```
345
-
346
- ### Handling Update Errors
347
-
348
- When updating non-existent records or with invalid data:
349
-
350
- ```javascript
351
- const errorResult = await api.scopes.authors.bulkPatch({
352
- operations: [
353
- { id: '1', data: { type: 'authors', id: '1', attributes: { name: 'Updated Name' } } },
354
- { id: '999', data: { type: 'authors', id: '999', attributes: { name: 'Non-existent' } } },
355
- { id: '2', data: { type: 'authors', id: '2', attributes: { name: '' } } } // Empty name
356
- ],
357
- atomic: false // Allow partial success
358
- });
359
-
360
- console.log(inspect(errorResult));
361
- // Shows successful updates and errors for failed operations
362
- ```
363
-
364
- ## Bulk Delete (bulkDelete)
365
-
366
- Delete multiple records by their IDs.
367
-
368
- ### Basic Bulk Delete
369
-
370
- ```javascript
371
- // Delete multiple authors
372
- const bulkDeleteResult = await api.scopes.authors.bulkDelete({
373
- ids: ['4', '5', '6'],
374
- atomic: true
375
- });
376
-
377
- console.log(inspect(bulkDeleteResult));
378
- // Output:
379
- // {
380
- // meta: {
381
- // total: 3,
382
- // succeeded: 3,
383
- // failed: 0,
384
- // deleted: ['4', '5', '6'],
385
- // atomic: true
386
- // }
387
- // }
388
- ```
389
-
390
- ### Handling Referential Integrity
391
-
392
- When deleting records with relationships:
393
-
394
- ```javascript
395
- // Try to delete a country that has books
396
- try {
397
- await api.scopes.countries.bulkDelete({
398
- ids: ['1', '2'], // Countries with related books
399
- atomic: true
400
- });
401
- } catch (error) {
402
- console.log('Cannot delete:', error.message);
403
- // Will fail due to foreign key constraints
404
- }
405
-
406
- // First delete the related records
407
- await api.scopes.books.bulkDelete({
408
- ids: bookIds, // Delete books first
409
- atomic: true
410
- });
411
-
412
- // Now can delete the countries
413
- await api.scopes.countries.bulkDelete({
414
- ids: ['1', '2'],
415
- atomic: true
416
- });
417
- ```
418
-
419
- ### Mixed Success Scenarios
420
-
421
- Handle cases where some deletes succeed and others fail:
422
-
423
- ```javascript
424
- const mixedResult = await api.scopes.authors.bulkDelete({
425
- ids: ['1', '999', '2', '888'], // Mix of valid and invalid IDs
426
- atomic: false // Allow partial success
427
- });
428
-
429
- console.log(inspect(mixedResult));
430
- // Output:
431
- // {
432
- // meta: {
433
- // total: 4,
434
- // succeeded: 2,
435
- // failed: 2,
436
- // deleted: ['1', '2'],
437
- // atomic: false
438
- // },
439
- // errors: [
440
- // { index: 1, id: '999', status: 'error', error: { code: 'REST_API_RESOURCE', message: 'Resource not found' } },
441
- // { index: 3, id: '888', status: 'error', error: { code: 'REST_API_RESOURCE', message: 'Resource not found' } }
442
- // ]
443
- // }
444
- ```
445
-
446
- ## HTTP API Usage
447
-
448
- When using the Express plugin, bulk operations are available via HTTP endpoints:
449
-
450
- ```javascript
451
- import { ExpressPlugin } from 'json-rest-api/plugins/core/connectors/express-plugin.js';
452
- import express from 'express';
453
-
454
- // Add Express plugin
455
- await api.use(ExpressPlugin);
456
-
457
- // Create and mount Express app
458
- const app = express();
459
- app.use(express.json());
460
- api.http.express.mount(app);
461
-
462
- app.listen(3000, () => {
463
- console.log('API with bulk operations running on http://localhost:3000');
464
- }).on('error', (err) => {
465
- console.error('Failed to start server:', err);
466
- process.exit(1)
467
- });
468
- ```
469
-
470
- ### HTTP Bulk Create
471
-
472
- ```bash
473
- POST /api/authors/bulk
474
- Content-Type: application/json
475
-
476
- {
477
- "data": [
478
- { "type": "authors", "attributes": { "name": "Author One" } },
479
- { "type": "authors", "attributes": { "name": "Author Two" } }
480
- ]
481
- }
482
-
483
- # With query parameter for non-atomic mode
484
- POST /api/authors/bulk?atomic=false
485
- ```
486
-
487
- ### HTTP Bulk Update
488
-
489
- ```bash
490
- PATCH /api/authors/bulk
491
- Content-Type: application/json
492
-
493
- {
494
- "operations": [
495
- { "id": "1", "data": { "type": "authors", "id": "1", "attributes": { "name": "Updated Name" } } },
496
- { "id": "2", "data": { "type": "authors", "id": "2", "attributes": { "name": "Another Update" } } }
497
- ]
498
- }
499
- ```
500
-
501
- ### HTTP Bulk Delete
502
-
503
- ```bash
504
- DELETE /api/authors/bulk
505
- Content-Type: application/json
506
-
507
- {
508
- "data": ["1", "2", "3"]
509
- }
510
-
511
- # Alternative format
512
- {
513
- "ids": ["1", "2", "3"]
514
- }
515
- ```
516
-
517
- ## Advanced Features
518
-
519
- ### Batch Processing
520
-
521
- The plugin processes records in configurable batches to manage memory usage:
522
-
523
- ```javascript
524
- // Configure smaller batches for memory-constrained environments
525
- await api.use(BulkOperationsPlugin, {
526
- 'bulk-operations': {
527
- batchSize: 5, // Process 5 records at a time internally
528
- maxBulkOperations: 1000 // But allow up to 1000 total
529
- }
530
- });
531
-
532
- // Create 100 records - processed in batches of 5
533
- const largeDataset = Array.from({ length: 100 }, (_, i) => ({
534
- type: 'authors',
535
- attributes: { name: `Author ${i + 1}` }
536
- }));
537
-
538
- const result = await api.scopes.authors.bulkPost({
539
- inputRecords: largeDataset,
540
- atomic: true
541
- });
542
-
543
- console.log(`Created ${result.meta.succeeded} authors in batches`);
544
- ```
545
-
546
- ### Transaction Context
547
-
548
- Bulk operations provide context information to hooks and plugins:
549
-
550
- ```javascript
551
- // Add a hook that runs for each bulk operation
552
- api.addHook('beforePost', 'bulkTracking', {}, async ({ context, params }) => {
553
- if (context.bulkOperation) {
554
- console.log(`Processing bulk item ${context.bulkIndex + 1}`);
555
- }
556
- });
557
- ```
558
-
559
- ### Error Handling Patterns
560
-
561
- Implement robust error handling for bulk operations:
562
-
563
- ```javascript
564
- async function importAuthors(authorData) {
565
- try {
566
- const result = await api.scopes.authors.bulkPost({
567
- inputRecords: authorData,
568
- atomic: false // Continue on errors
569
- });
570
-
571
- // Log successful imports
572
- console.log(`Imported ${result.meta.succeeded} of ${result.meta.total} authors`);
573
-
574
- // Handle errors if any
575
- if (result.errors && result.errors.length > 0) {
576
- console.error('Import errors:');
577
- result.errors.forEach(error => {
578
- console.error(` Row ${error.index}: ${error.error.message}`);
579
- });
580
-
581
- // Return failed records for retry
582
- return authorData.filter((_, index) =>
583
- result.errors.some(e => e.index === index)
584
- );
585
- }
586
-
587
- return []; // All succeeded
588
- } catch (error) {
589
- // Handle complete failure (e.g., database connection error)
590
- console.error('Bulk import failed completely:', error.message);
591
- throw error;
592
- }
593
- }
594
- ```
595
-
596
- ### Performance Considerations
597
-
598
- 1. **Use Atomic Mode Wisely**: Atomic operations provide consistency but may be slower for large datasets
599
- 2. **Adjust Batch Sizes**: Larger batches improve performance but use more memory
600
- 3. **Enable Optimizations**: The plugin uses database-specific bulk insert optimizations when available
601
- 4. **Monitor Limits**: Set appropriate `maxBulkOperations` to prevent resource exhaustion
602
-
603
- ## Complete Example: Book Import System
604
-
605
- Here's a complete example showing how to import a book catalog with all relationships:
606
-
607
- ```javascript
608
- async function importBookCatalog(catalogData) {
609
- // Step 1: Import countries
610
- console.log('Importing countries...');
611
- const countryResult = await api.scopes.countries.bulkPost({
612
- inputRecords: catalogData.countries,
613
- atomic: true
614
- });
615
-
616
- // Step 2: Import publishers with country relationships
617
- console.log('Importing publishers...');
618
- const publisherResult = await api.scopes.publishers.bulkPost({
619
- inputRecords: catalogData.publishers,
620
- atomic: true
621
- });
622
-
623
- // Step 3: Import authors
624
- console.log('Importing authors...');
625
- const authorResult = await api.scopes.authors.bulkPost({
626
- inputRecords: catalogData.authors,
627
- atomic: true
628
- });
629
-
630
- // Step 4: Import books with country and publisher relationships
631
- console.log('Importing books...');
632
- const bookResult = await api.scopes.books.bulkPost({
633
- inputRecords: catalogData.books,
634
- atomic: false // Allow partial success for books
635
- });
636
-
637
- // Step 5: Create author-book relationships
638
- console.log('Creating author-book relationships...');
639
- const relationshipData = [];
640
-
641
- for (const book of bookResult.data) {
642
- const bookAuthors = catalogData.bookAuthors[book.attributes.title] || [];
643
- for (const authorName of bookAuthors) {
644
- const author = authorResult.data.find(a => a.attributes.name === authorName);
645
- if (author) {
646
- relationshipData.push({
647
- type: 'book_authors',
648
- attributes: {
649
- book_id: parseInt(book.id),
650
- author_id: parseInt(author.id)
651
- }
652
- });
653
- }
654
- }
655
- }
656
-
657
- const relationshipResult = await api.scopes.book_authors.bulkPost({
658
- inputRecords: relationshipData,
659
- atomic: false
660
- });
661
-
662
- // Summary
663
- console.log('\nImport Summary:');
664
- console.log(`- Countries: ${countryResult.meta.succeeded}`);
665
- console.log(`- Publishers: ${publisherResult.meta.succeeded}`);
666
- console.log(`- Authors: ${authorResult.meta.succeeded}`);
667
- console.log(`- Books: ${bookResult.meta.succeeded} (${bookResult.meta.failed} failed)`);
668
- console.log(`- Relationships: ${relationshipResult.meta.succeeded}`);
669
-
670
- return {
671
- countries: countryResult.meta.succeeded,
672
- publishers: publisherResult.meta.succeeded,
673
- authors: authorResult.meta.succeeded,
674
- books: bookResult.meta.succeeded,
675
- relationships: relationshipResult.meta.succeeded,
676
- errors: bookResult.errors || []
677
- };
678
- }
679
-
680
- // Example usage
681
- const catalogData = {
682
- countries: [
683
- { type: 'countries', attributes: { name: 'United States', code: 'US' } },
684
- { type: 'countries', attributes: { name: 'United Kingdom', code: 'UK' } }
685
- ],
686
- publishers: [
687
- {
688
- type: 'publishers',
689
- attributes: { name: 'Penguin Random House' },
690
- relationships: { country: { data: { type: 'countries', id: '1' } } }
691
- }
692
- ],
693
- authors: [
694
- { type: 'authors', attributes: { name: 'Stephen King' } },
695
- { type: 'authors', attributes: { name: 'J.K. Rowling' } }
696
- ],
697
- books: [
698
- {
699
- type: 'books',
700
- attributes: { title: 'The Shining' },
701
- relationships: {
702
- country: { data: { type: 'countries', id: '1' } },
703
- publisher: { data: { type: 'publishers', id: '1' } }
704
- }
705
- }
706
- ],
707
- bookAuthors: {
708
- 'The Shining': ['Stephen King']
709
- }
710
- };
711
-
712
- const importResults = await importBookCatalog(catalogData);
713
- ```
714
-
715
- ## Summary
716
-
717
- The Bulk Operations plugin provides powerful capabilities for processing multiple records efficiently:
718
-
719
- - **Three Core Operations**: bulkPost, bulkPatch, and bulkDelete
720
- - **Atomic Transactions**: All-or-nothing processing for data consistency
721
- - **Partial Success Mode**: Continue processing despite individual failures
722
- - **Batch Processing**: Efficient handling of large datasets
723
- - **Full JSON:API Support**: Maintains compatibility with standard format
724
- - **HTTP Endpoints**: RESTful API for bulk operations
725
-
726
- Use bulk operations when you need to:
727
- - Import or export large datasets
728
- - Apply consistent updates across multiple records
729
- - Delete multiple records safely
730
- - Optimize performance by reducing API calls
731
-
732
- Remember to consider transaction modes, error handling, and performance implications when designing your bulk operation workflows.