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.
- package/LICENSE +10 -670
- package/LICENSE-GPL-3.0 +674 -0
- package/LICENSE-MIT +21 -0
- package/README.md +39 -30
- package/docs/API.md +744 -1273
- package/docs/GUIDE/01-getting-started.md +338 -0
- package/docs/GUIDE/02-resources-and-relationships.md +33 -0
- package/docs/GUIDE/03-running-example.md +87 -0
- package/docs/GUIDE/04-creating-and-querying.md +181 -0
- package/docs/GUIDE/05-belongs-to.md +177 -0
- package/docs/GUIDE/06-has-many.md +184 -0
- package/docs/GUIDE/07-polymorphic-relationships.md +191 -0
- package/docs/GUIDE/08-many-to-many.md +184 -0
- package/docs/GUIDE/09-pagination-and-sorting.md +149 -0
- package/docs/GUIDE/10-put-and-patch.md +174 -0
- package/docs/GUIDE/11-relationship-endpoints.md +174 -0
- package/docs/GUIDE/12-field-transformations.md +287 -0
- package/docs/GUIDE/13-hooks-and-lifecycle.md +479 -0
- package/docs/GUIDE/14-custom-resource-methods.md +125 -0
- package/docs/GUIDE/15-query-projections.md +266 -0
- package/docs/GUIDE/{GUIDE_X_Autofiltering.md → 16-autofiltering.md} +77 -15
- package/docs/GUIDE/{GUIDE_X_Row_Policies.md → 17-row-policies.md} +137 -44
- package/docs/GUIDE/18-labels.md +86 -0
- package/docs/GUIDE/19-managed-transactions.md +179 -0
- package/docs/GUIDE/20-transaction-outcomes.md +207 -0
- package/docs/GUIDE/21-schema-and-migrations.md +634 -0
- package/docs/GUIDE/22-version-field-migrations.md +208 -0
- package/docs/GUIDE/23-fastify.md +179 -0
- package/docs/GUIDE/24-cors.md +172 -0
- package/docs/GUIDE/{GUIDE_X_URL_Management.md → 25-url-management.md} +7 -1
- package/docs/GUIDE/26-file-uploads.md +278 -0
- package/docs/GUIDE/27-bulk-operations.md +277 -0
- package/docs/GUIDE/28-socketio.md +325 -0
- package/docs/GUIDE/29-writing-plugins.md +278 -0
- package/docs/GUIDE/30-backend-capabilities.md +100 -0
- package/docs/GUIDE/31-positioning.md +181 -0
- package/docs/GUIDE/{GUIDE_Y_Appendices.md → 32-date-and-time.md} +74 -30
- package/docs/GUIDE/33-migrating-to-v2.md +3827 -0
- package/docs/GUIDE/index.md +51 -59
- package/docs/QUICKSTART.md +120 -287
- package/docs/architecture.md +75 -0
- package/docs/contributing.md +178 -0
- package/index.d.ts +36 -0
- package/index.js +1 -22
- package/lib/enhanced-logger.js +91 -85
- package/lib/error-context.js +445 -5
- package/lib/error-formatter.js +219 -59
- package/lib/knex-transaction.js +64 -0
- package/lib/logger-types.d.ts +22 -0
- package/lib/missing-package.js +4 -0
- package/lib/rest-api-errors.js +103 -8
- package/lib/runtime/json-rest-api.js +172 -0
- package/lib/transaction-types.d.ts +59 -0
- package/package.json +97 -27
- package/plugins/core/bulk-operations-plugin.js +191 -184
- package/plugins/core/connectors/express-plugin.js +148 -219
- package/plugins/core/connectors/fastify-plugin.js +125 -82
- package/plugins/core/connectors/lib/busboy-detector.js +74 -156
- package/plugins/core/connectors/lib/connector-core.js +144 -20
- package/plugins/core/connectors/lib/formidable-detector.js +63 -120
- package/plugins/core/connectors/lib/http-validators.js +77 -0
- package/plugins/core/connectors/lib/multipart-helpers.js +18 -0
- package/plugins/core/connectors/lib/request-helpers.js +10 -1
- package/plugins/core/connectors/lib/transport-http-helpers.js +89 -8
- package/plugins/core/connectors/lib/transport-route-schemas.js +7 -4
- package/plugins/core/file-handling-plugin.js +87 -177
- package/plugins/core/lib/anyapi/anyapi-registry.js +213 -164
- package/plugins/core/lib/anyapi/canonical-link-store.js +507 -0
- package/plugins/core/lib/anyapi/canonical-relationship-reader.js +598 -0
- package/plugins/core/lib/anyapi/schema-utils.js +25 -16
- package/plugins/core/lib/anyapi/utils/descriptor-helpers.js +0 -64
- package/plugins/core/lib/dbIntrospection.js +228 -21
- package/plugins/core/lib/dbTablesOperations.js +409 -92
- package/plugins/core/lib/fractional-positioning.js +12 -48
- package/plugins/core/lib/querying/identifier-query.js +19 -0
- package/plugins/core/lib/querying/include-query-helpers.js +364 -0
- package/plugins/core/lib/querying/include-to-many.js +428 -0
- package/plugins/core/lib/querying/include-to-one.js +579 -0
- package/plugins/core/lib/querying/knex-cross-table-search.js +31 -307
- package/plugins/core/lib/querying/knex-json-api-transformers-querying.js +99 -401
- package/plugins/core/lib/querying/knex-pagination-helpers.js +48 -317
- package/plugins/core/lib/querying/knex-process-includes.js +5 -110
- package/plugins/core/lib/querying/knex-query-helpers-base.js +21 -56
- package/plugins/core/lib/querying/knex-query-helpers.js +298 -448
- package/plugins/core/lib/querying/knex-relationship-includes.js +120 -2138
- package/plugins/core/lib/querying/knex-window-queries.js +62 -253
- package/plugins/core/lib/querying/query-builder-utils.js +44 -4
- package/plugins/core/lib/querying/query-constraint.d.ts +12 -0
- package/plugins/core/lib/querying/query-constraint.js +27 -0
- package/plugins/core/lib/querying/query-field-sort-helpers.js +134 -37
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +13 -1
- package/plugins/core/lib/querying/storage-adapter-utils.js +13 -5
- package/plugins/core/lib/querying/url-helpers.js +11 -4
- package/plugins/core/lib/querying-writing/compile-schemas.js +148 -249
- package/plugins/core/lib/querying-writing/connectors-query-parser.js +33 -118
- package/plugins/core/lib/querying-writing/database-capabilities.js +149 -223
- package/plugins/core/lib/querying-writing/database-value-normalizers.js +179 -194
- package/plugins/core/lib/querying-writing/default-data-helpers.js +15 -45
- package/plugins/core/lib/querying-writing/field-selection-types.d.ts +43 -0
- package/plugins/core/lib/querying-writing/field-utils.js +113 -92
- package/plugins/core/lib/querying-writing/knex-constants.d.ts +18 -0
- package/plugins/core/lib/querying-writing/knex-constants.js +2 -26
- package/plugins/core/lib/querying-writing/knex-field-helpers.js +100 -290
- package/plugins/core/lib/querying-writing/knex-json-api-transformers.js +31 -120
- package/plugins/core/lib/querying-writing/query-field-helpers.js +108 -32
- package/plugins/core/lib/querying-writing/query-field-types.d.ts +60 -0
- package/plugins/core/lib/querying-writing/relationship-contracts.js +33 -13
- package/plugins/core/lib/querying-writing/request-contracts.js +93 -38
- package/plugins/core/lib/querying-writing/resource-id-normalization.js +23 -5
- package/plugins/core/lib/querying-writing/response-options.js +35 -0
- package/plugins/core/lib/querying-writing/schema-helpers.js +203 -324
- package/plugins/core/lib/querying-writing/scope-validations.js +92 -290
- package/plugins/core/lib/querying-writing/simplified-helpers.js +113 -712
- package/plugins/core/lib/storage/canonical-data-helpers.js +683 -0
- package/plugins/core/lib/storage/canonical-storage-mapping.js +51 -20
- package/plugins/core/lib/storage/ordinary-data-helpers.js +710 -0
- package/plugins/core/lib/storage/storage-adapter.js +128 -62
- package/plugins/core/lib/storage/storage-mapping.js +78 -28
- package/plugins/core/lib/storage/storage-types.d.ts +502 -0
- package/plugins/core/lib/writing/many-to-many-manipulations.js +114 -284
- package/plugins/core/lib/writing/relationship-processor.js +94 -158
- package/plugins/core/lib/writing/resource-version.js +172 -0
- package/plugins/core/lib/writing/reverse-relationship-manipulations.js +110 -0
- package/plugins/core/rest-api-anyapi-knex-plugin.js +116 -2485
- package/plugins/core/rest-api-autofilter-plugin.js +17 -19
- package/plugins/core/rest-api-cors-plugin.js +52 -205
- package/plugins/core/rest-api-knex-plugin.js +45 -1077
- package/plugins/core/rest-api-label-plugin.js +25 -38
- package/plugins/core/rest-api-plugin-hooks/register-relationship-routes.js +5 -5
- package/plugins/core/rest-api-plugin-hooks/register-scope-routes.js +9 -6
- package/plugins/core/rest-api-plugin-hooks/turn-scope-init-into-vars.js +7 -26
- package/plugins/core/rest-api-plugin-hooks/validate-include-configurations.js +20 -26
- package/plugins/core/rest-api-plugin-methods/add-route.js +4 -6
- package/plugins/core/rest-api-plugin-methods/apply-query-filters.js +42 -13
- package/plugins/core/rest-api-plugin-methods/check-permissions.js +11 -5
- package/plugins/core/rest-api-plugin-methods/common.js +291 -395
- package/plugins/core/rest-api-plugin-methods/delete-relationship.js +49 -47
- package/plugins/core/rest-api-plugin-methods/delete.js +31 -25
- package/plugins/core/rest-api-plugin-methods/enrich-attributes.js +108 -148
- package/plugins/core/rest-api-plugin-methods/get-related.js +113 -305
- package/plugins/core/rest-api-plugin-methods/get-relationship.js +25 -18
- package/plugins/core/rest-api-plugin-methods/get.js +36 -68
- package/plugins/core/rest-api-plugin-methods/lifecycle-types.d.ts +225 -0
- package/plugins/core/rest-api-plugin-methods/patch-relationship.js +28 -25
- package/plugins/core/rest-api-plugin-methods/patch.js +46 -99
- package/plugins/core/rest-api-plugin-methods/post-relationship.js +49 -53
- package/plugins/core/rest-api-plugin-methods/post.js +51 -51
- package/plugins/core/rest-api-plugin-methods/put.js +69 -148
- package/plugins/core/rest-api-plugin-methods/query.js +37 -81
- package/plugins/core/rest-api-plugin-methods/release.js +2 -1
- package/plugins/core/rest-api-plugin.js +32 -46
- package/plugins/core/rest-api-positioning-plugin.js +183 -628
- package/plugins/core/rest-api-query-projections-plugin.js +19 -102
- package/plugins/core/rest-api-row-policy-plugin.js +3 -3
- package/plugins/core/socketio-plugin.js +364 -344
- package/plugins/storage/local-storage.js +40 -36
- package/types/autofilter.d.ts +57 -0
- package/types/bulk-methods.d.ts +63 -0
- package/types/errors.d.ts +118 -0
- package/types/fastify-options.d.ts +19 -0
- package/types/file-detectors.d.ts +33 -0
- package/types/file-storage.d.ts +48 -0
- package/types/hook-context.d.ts +180 -0
- package/types/plugin-options.d.ts +53 -0
- package/types/relationship-methods.d.ts +74 -0
- package/types/representations.d.ts +50 -0
- package/types/resource-methods.d.ts +95 -0
- package/types/resource-schema.d.ts +157 -0
- package/types/row-policy.d.ts +31 -0
- package/types/runtime.d.ts +132 -0
- package/types/transactions.d.ts +21 -0
- package/.claude/agents/library-guide-expert.md +0 -30
- package/.claude/agents/rest-api-internals-expert.md +0 -54
- package/.claude/settings.local.json +0 -70
- package/AGENTS.md +0 -34
- package/CLAUDE.md +0 -160
- package/CRUSH.md +0 -55
- package/JSKIT_BACKEND_MIGRATION_TRACKER.txt +0 -180
- package/SINGLE_VALIDATION_CONTRACT_TODO.txt +0 -172
- package/TODO.todo +0 -13
- package/connectors_ideas/fastify.js +0 -217
- package/connectors_ideas/koa-plugin.js +0 -112
- package/docs/.bundle/config +0 -2
- package/docs/COMPARISON.md +0 -433
- package/docs/GUIDE/GUIDE_1_Initial_Setup.md +0 -1057
- package/docs/GUIDE/GUIDE_2_1_The_Starting_Point.md +0 -88
- package/docs/GUIDE/GUIDE_2_2_Manipulating_And_Searching_Tables.md +0 -808
- package/docs/GUIDE/GUIDE_2_3_BelongsTo_Relationships.md +0 -491
- package/docs/GUIDE/GUIDE_2_4_HasMany_Records.md +0 -398
- package/docs/GUIDE/GUIDE_2_5_HasMany_Polymorphic.md +0 -898
- package/docs/GUIDE/GUIDE_2_6_Many_To_Many.md +0 -712
- package/docs/GUIDE/GUIDE_2_7_Pagination_And_Ordering.md +0 -1247
- package/docs/GUIDE/GUIDE_2_8_Effects_of_PUT_and_PATCH.md +0 -748
- package/docs/GUIDE/GUIDE_2_9_Relationships_Urls.md +0 -517
- package/docs/GUIDE/GUIDE_2_Data_And_Relations.md +0 -66
- package/docs/GUIDE/GUIDE_3_Field_Transformations.md +0 -1252
- package/docs/GUIDE/GUIDE_5_Non-Db_Resources.md +0 -1107
- package/docs/GUIDE/GUIDE_6_Positioning.md +0 -692
- package/docs/GUIDE/GUIDE_7_Hooks_Data_Management_And_Plugins.md +0 -1491
- package/docs/GUIDE/GUIDE_X_Bulk_Operations.md +0 -732
- package/docs/GUIDE/GUIDE_X_Cors.md +0 -1957
- package/docs/GUIDE/GUIDE_X_Fastify.md +0 -78
- package/docs/GUIDE/GUIDE_X_File_Uploads.md +0 -729
- package/docs/GUIDE/GUIDE_X_Knex_Schema_And_Migrations.md +0 -370
- package/docs/GUIDE/GUIDE_X_Query_Projections.md +0 -159
- package/docs/GUIDE/GUIDE_X_SocketIO.md +0 -1040
- package/docs/GUIDE/GUIDE_X_Writing_Plugins.md +0 -201
- package/docs/Gemfile +0 -6
- package/docs/Gemfile.lock +0 -308
- package/docs/ONBOARDING.md +0 -315
- package/docs/_config.yml +0 -36
- package/docs/_layouts/default.html +0 -123
- package/docs/apple-touch-icon.svg +0 -4
- package/docs/assets/css/style.scss +0 -392
- package/docs/favicon.svg +0 -4
- package/docs/index.md +0 -164
- package/docs/mini-guides/file-uploads-complete.md +0 -161
- package/eslint.config.js +0 -13
- package/examples/url-override-example.js +0 -137
- package/fixing_plan.md +0 -112
- package/plugins/core/lib/anyapi/query/anyapi-query-adapter.js +0 -657
- package/plugins/core/lib/querying-writing/return-record-settings.js +0 -47
- package/plugins/core/lib/writing/knex-json-api-transformers-writing.js +0 -110
- package/plugins/storage/s3-storage.js +0 -93
- package/quickTest.js +0 -136
- package/scripts/debug-anyapi-review-filter.mjs +0 -63
- package/scripts/inspect-anyapi-descriptor-country.mjs +0 -19
- package/scripts/inspect-anyapi-descriptor.mjs +0 -19
- package/scripts/run-docs.js +0 -133
- package/test-debug.js +0 -59
- package/tests/README.md +0 -202
- package/tests/TEST_TEMPLATE.test.js +0 -178
- package/tests/anyapi-basic.test.js +0 -105
- package/tests/anyapi-cursor-pagination.test.js +0 -148
- package/tests/anyapi-custom-idproperty.test.js +0 -163
- package/tests/autofilter.test.js +0 -463
- package/tests/bulk-operations.test.js +0 -657
- package/tests/computed-fields.test.js +0 -586
- package/tests/cors.test.js +0 -834
- package/tests/curl.test.js +0 -467
- package/tests/custom-id-mysql-returning-fallback.test.js +0 -128
- package/tests/custom-id-simplified-post.test.js +0 -75
- package/tests/custom-idproperty-relationships.test.js +0 -932
- package/tests/db-introspection.test.js +0 -521
- package/tests/db-table-operations.test.js +0 -798
- package/tests/fastify-plugin.test.js +0 -184
- package/tests/field-getters.test.js +0 -370
- package/tests/field-setters.test.js +0 -306
- package/tests/file-handling.test.js +0 -155
- package/tests/fixtures/api-configs.js +0 -2566
- package/tests/full-jsonapi-belongsto-linkage.test.js +0 -150
- package/tests/helpers/fake-fastify.js +0 -99
- package/tests/helpers/storage-mode.js +0 -65
- package/tests/helpers/test-utils.js +0 -203
- package/tests/http-connectors-parity.test.js +0 -395
- package/tests/id-normalization.test.js +0 -444
- package/tests/include-depth-validation.test.js +0 -331
- package/tests/includes.test.js +0 -427
- package/tests/local-storage.test.js +0 -96
- package/tests/logical-id-write-validation.test.js +0 -151
- package/tests/nested-includes.test.js +0 -617
- package/tests/pagination-boundaries.test.js +0 -154
- package/tests/pagination-contract.test.js +0 -122
- package/tests/pagination-cursor-multifield.test.js +0 -641
- package/tests/pagination-enhanced.test.js +0 -563
- package/tests/polymorphic-relationships.test.js +0 -347
- package/tests/polymorphic-simple.test.js +0 -148
- package/tests/positioning-advanced.test.js +0 -692
- package/tests/positioning-extended.test.js +0 -915
- package/tests/positioning-multifilter.test.js +0 -699
- package/tests/positioning.test.js +0 -560
- package/tests/post-commit-hooks.test.js +0 -120
- package/tests/projected-fields.test.js +0 -319
- package/tests/public-exports.test.js +0 -15
- package/tests/put-create-parity.test.js +0 -216
- package/tests/queries.test.js +0 -521
- package/tests/query-limits.test.js +0 -584
- package/tests/relationship-endpoints.test.js +0 -620
- package/tests/relationship-include-storage-adapter.test.js +0 -56
- package/tests/relationships.test.js +0 -681
- package/tests/rest-api.test.js +0 -337
- package/tests/return-record-settings.test.js +0 -184
- package/tests/row-policy.test.js +0 -489
- package/tests/s3-storage.test.js +0 -38
- package/tests/searchschema-knex-behavior.test.js +0 -187
- package/tests/searchschema-merge.test.js +0 -151
- package/tests/socketio.test.js +0 -860
- package/tests/storage-mapping.test.js +0 -362
- package/tests/temporal-boundaries.test.js +0 -208
- package/tests/test-development-guide.md +0 -370
- 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.
|