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,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.