json-rest-api 1.0.28 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +746 -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} +90 -31
- 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 -174
- 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 +67 -330
- 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 +205 -30
- package/plugins/core/lib/querying/relationship-identifiers.js +208 -0
- package/plugins/core/lib/querying/sort-helpers.js +20 -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 +238 -187
- 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 +111 -287
- 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 +109 -44
- 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 +142 -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 -2509
- 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 -1069
- 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 -385
- 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 -145
- 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 +38 -69
- 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 +39 -82
- 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 -1238
- 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 -150
- 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 -118
- package/tests/fixtures/api-configs.js +0 -2509
- 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-cursor-multifield.test.js +0 -643
- 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/test-development-guide.md +0 -370
- package/tests/virtual-fields.test.js +0 -404
|
@@ -1,560 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import { cleanTables } from './helpers/test-utils.js'
|
|
5
|
-
import { createPositioningApi } from './fixtures/api-configs.js'
|
|
6
|
-
import { PositioningPlugin } from '../plugins/core/rest-api-positioning-plugin.js'
|
|
7
|
-
|
|
8
|
-
// Create Knex instance for tests - always use SQLite in-memory
|
|
9
|
-
const knex = knexLib({
|
|
10
|
-
client: 'better-sqlite3',
|
|
11
|
-
connection: {
|
|
12
|
-
filename: ':memory:'
|
|
13
|
-
},
|
|
14
|
-
useNullAsDefault: true
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
// API instance that persists across tests
|
|
18
|
-
let api
|
|
19
|
-
|
|
20
|
-
describe('Positioning Plugin Tests', { timeout: 30000 }, () => {
|
|
21
|
-
// IMPORTANT: before() runs ONCE for the entire test suite
|
|
22
|
-
before(async () => {
|
|
23
|
-
// Initialize API once
|
|
24
|
-
api = await createPositioningApi(knex)
|
|
25
|
-
|
|
26
|
-
// Install positioning plugin with default configuration
|
|
27
|
-
await api.use(PositioningPlugin, {
|
|
28
|
-
field: 'position',
|
|
29
|
-
filters: ['category'], // Tasks will be grouped by category
|
|
30
|
-
defaultPosition: 'last',
|
|
31
|
-
autoIndex: true
|
|
32
|
-
})
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
// IMPORTANT: after() cleans up resources
|
|
36
|
-
after(async () => {
|
|
37
|
-
// Always destroy knex connection to allow tests to exit
|
|
38
|
-
await knex.destroy()
|
|
39
|
-
})
|
|
40
|
-
|
|
41
|
-
// IMPORTANT: beforeEach() cleans data but does NOT recreate API
|
|
42
|
-
beforeEach(async () => {
|
|
43
|
-
// Clean all tables - list ALL tables your tests use
|
|
44
|
-
await cleanTables(knex, [
|
|
45
|
-
'positioning_categories',
|
|
46
|
-
'positioning_tasks',
|
|
47
|
-
'positioning_projects',
|
|
48
|
-
'positioning_items'
|
|
49
|
-
])
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
describe('Basic Positioning', () => {
|
|
53
|
-
it('should add position field to first item automatically', async () => {
|
|
54
|
-
// Create a category first
|
|
55
|
-
const category = await api.resources.categories.post({
|
|
56
|
-
name: 'Work Tasks'
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
// Create first task
|
|
60
|
-
const result = await api.resources.tasks.post({
|
|
61
|
-
title: 'First Task',
|
|
62
|
-
category: category.id
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
assert.equal(result.title, 'First Task')
|
|
66
|
-
// Should have position assigned
|
|
67
|
-
assert(result.position, 'Position should be assigned')
|
|
68
|
-
assert.equal(typeof result.position, 'string')
|
|
69
|
-
})
|
|
70
|
-
|
|
71
|
-
it('should position items at the end by default', async () => {
|
|
72
|
-
// Create a category
|
|
73
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
74
|
-
|
|
75
|
-
// Create multiple tasks
|
|
76
|
-
const task1 = await api.resources.tasks.post({
|
|
77
|
-
title: 'Task 1',
|
|
78
|
-
category: category.id
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
const task2 = await api.resources.tasks.post({
|
|
82
|
-
title: 'Task 2',
|
|
83
|
-
category: category.id
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
const task3 = await api.resources.tasks.post({
|
|
87
|
-
title: 'Task 3',
|
|
88
|
-
category: category.id
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
// Verify positions are in order
|
|
92
|
-
const pos1 = task1.position
|
|
93
|
-
const pos2 = task2.position
|
|
94
|
-
const pos3 = task3.position
|
|
95
|
-
|
|
96
|
-
assert(pos1 < pos2, 'Task 1 should be before Task 2')
|
|
97
|
-
assert(pos2 < pos3, 'Task 2 should be before Task 3')
|
|
98
|
-
})
|
|
99
|
-
|
|
100
|
-
it('should maintain separate position sequences per filter group', async () => {
|
|
101
|
-
// Create two categories
|
|
102
|
-
const category1 = await api.resources.categories.post({ name: 'Category 1' })
|
|
103
|
-
const category2 = await api.resources.categories.post({ name: 'Category 2' })
|
|
104
|
-
|
|
105
|
-
// Create tasks in different categories
|
|
106
|
-
const task1Cat1 = await api.resources.tasks.post({
|
|
107
|
-
title: 'Cat1 Task 1',
|
|
108
|
-
category: category1.id
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
const task1Cat2 = await api.resources.tasks.post({
|
|
112
|
-
title: 'Cat2 Task 1',
|
|
113
|
-
category: category2.id
|
|
114
|
-
})
|
|
115
|
-
|
|
116
|
-
const task2Cat1 = await api.resources.tasks.post({
|
|
117
|
-
title: 'Cat1 Task 2',
|
|
118
|
-
category: category1.id
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
// Positions should be independent per category
|
|
122
|
-
assert(task1Cat1.position)
|
|
123
|
-
assert(task1Cat2.position)
|
|
124
|
-
assert(task2Cat1.position)
|
|
125
|
-
|
|
126
|
-
// Task 2 in category 1 should be after task 1 in category 1
|
|
127
|
-
assert(task1Cat1.position < task2Cat1.position)
|
|
128
|
-
|
|
129
|
-
// But positions in different categories can be similar
|
|
130
|
-
// (they're independent sequences)
|
|
131
|
-
})
|
|
132
|
-
})
|
|
133
|
-
|
|
134
|
-
describe('Positioning with beforeId', () => {
|
|
135
|
-
it('should position item before specified id', async () => {
|
|
136
|
-
// Create category
|
|
137
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
138
|
-
|
|
139
|
-
// Create initial tasks
|
|
140
|
-
const task1 = await api.resources.tasks.post({
|
|
141
|
-
title: 'Task 1',
|
|
142
|
-
category: category.id
|
|
143
|
-
})
|
|
144
|
-
|
|
145
|
-
const task2 = await api.resources.tasks.post({
|
|
146
|
-
title: 'Task 2',
|
|
147
|
-
category: category.id
|
|
148
|
-
})
|
|
149
|
-
|
|
150
|
-
// Create task that should go between 1 and 2
|
|
151
|
-
const taskMiddle = await api.resources.tasks.post({
|
|
152
|
-
title: 'Middle Task',
|
|
153
|
-
category: category.id,
|
|
154
|
-
beforeId: task2.id
|
|
155
|
-
})
|
|
156
|
-
|
|
157
|
-
// Verify positioning
|
|
158
|
-
const pos1 = task1.position
|
|
159
|
-
const posMiddle = taskMiddle.position
|
|
160
|
-
const pos2 = task2.position
|
|
161
|
-
|
|
162
|
-
assert(pos1 < posMiddle, 'Task 1 should be before Middle Task')
|
|
163
|
-
assert(posMiddle < pos2, 'Middle Task should be before Task 2')
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('should position at beginning when beforeId is first item', async () => {
|
|
167
|
-
// Create category
|
|
168
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
169
|
-
|
|
170
|
-
// Create initial tasks
|
|
171
|
-
const task1 = await api.resources.tasks.post({
|
|
172
|
-
title: 'Task 1',
|
|
173
|
-
category: category.id
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
const task2 = await api.resources.tasks.post({
|
|
177
|
-
title: 'Task 2',
|
|
178
|
-
category: category.id
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
// Create task at beginning
|
|
182
|
-
const taskFirst = await api.resources.tasks.post({
|
|
183
|
-
title: 'First Task',
|
|
184
|
-
category: category.id,
|
|
185
|
-
beforeId: task1.id
|
|
186
|
-
})
|
|
187
|
-
|
|
188
|
-
// Verify it's first
|
|
189
|
-
const posFirst = taskFirst.position
|
|
190
|
-
const pos1 = task1.position
|
|
191
|
-
const pos2 = task2.position
|
|
192
|
-
|
|
193
|
-
assert(posFirst < pos1, 'First Task should be before Task 1')
|
|
194
|
-
assert(pos1 < pos2, 'Task 1 should be before Task 2')
|
|
195
|
-
})
|
|
196
|
-
})
|
|
197
|
-
|
|
198
|
-
describe('Position Updates', () => {
|
|
199
|
-
it('should reposition item when updated with beforeId', async () => {
|
|
200
|
-
// Create category
|
|
201
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
202
|
-
|
|
203
|
-
// Create three tasks
|
|
204
|
-
const task1 = await api.resources.tasks.post({
|
|
205
|
-
title: 'Task 1',
|
|
206
|
-
category: category.id
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
const task2 = await api.resources.tasks.post({
|
|
210
|
-
title: 'Task 2',
|
|
211
|
-
category: category.id
|
|
212
|
-
})
|
|
213
|
-
|
|
214
|
-
const task3 = await api.resources.tasks.post({
|
|
215
|
-
title: 'Task 3',
|
|
216
|
-
category: category.id
|
|
217
|
-
})
|
|
218
|
-
|
|
219
|
-
// Move task 3 before task 2
|
|
220
|
-
const updated = await api.resources.tasks.patch({
|
|
221
|
-
id: task3.id,
|
|
222
|
-
beforeId: task2.id
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
// Get fresh data
|
|
226
|
-
const { data: allTasks } = await api.resources.tasks.query({
|
|
227
|
-
queryParams: {
|
|
228
|
-
filters: { category: category.id },
|
|
229
|
-
sort: ['position']
|
|
230
|
-
}
|
|
231
|
-
})
|
|
232
|
-
|
|
233
|
-
// Verify order is now: Task 1, Task 3, Task 2
|
|
234
|
-
assert.equal(allTasks[0].title, 'Task 1')
|
|
235
|
-
assert.equal(allTasks[1].title, 'Task 3')
|
|
236
|
-
assert.equal(allTasks[2].title, 'Task 2')
|
|
237
|
-
})
|
|
238
|
-
|
|
239
|
-
it('should maintain position when updating without beforeId', async () => {
|
|
240
|
-
// Create category
|
|
241
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
242
|
-
|
|
243
|
-
// Create task
|
|
244
|
-
const task = await api.resources.tasks.post({
|
|
245
|
-
title: 'Original Title',
|
|
246
|
-
category: category.id
|
|
247
|
-
})
|
|
248
|
-
|
|
249
|
-
const originalPosition = task.position
|
|
250
|
-
|
|
251
|
-
// Update title without beforeId
|
|
252
|
-
const updated = await api.resources.tasks.patch({
|
|
253
|
-
id: task.id,
|
|
254
|
-
title: 'Updated Title'
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
// Position should remain the same
|
|
258
|
-
assert.equal(updated.position, originalPosition)
|
|
259
|
-
assert.equal(updated.title, 'Updated Title')
|
|
260
|
-
})
|
|
261
|
-
})
|
|
262
|
-
|
|
263
|
-
describe('Manual Position Override', () => {
|
|
264
|
-
it('should ignore manual position setting', async () => {
|
|
265
|
-
// Create category
|
|
266
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
267
|
-
|
|
268
|
-
// Create task with manual position attempt
|
|
269
|
-
const task = await api.resources.tasks.post({
|
|
270
|
-
title: 'Manual Position Task',
|
|
271
|
-
category: category.id,
|
|
272
|
-
position: 'zzz' // Manual position should be ignored
|
|
273
|
-
})
|
|
274
|
-
|
|
275
|
-
// Position should be automatically assigned, not 'zzz'
|
|
276
|
-
assert.notEqual(task.position, 'zzz')
|
|
277
|
-
assert(task.position) // Should have a position
|
|
278
|
-
assert.equal(task.position, 'a0') // First item gets 'a0'
|
|
279
|
-
})
|
|
280
|
-
|
|
281
|
-
it('should ignore manual position and use beforeId', async () => {
|
|
282
|
-
// Create category
|
|
283
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
284
|
-
|
|
285
|
-
// Create initial task
|
|
286
|
-
const task1 = await api.resources.tasks.post({
|
|
287
|
-
title: 'Task 1',
|
|
288
|
-
category: category.id
|
|
289
|
-
})
|
|
290
|
-
|
|
291
|
-
// Create task with both manual position and beforeId
|
|
292
|
-
const task2 = await api.resources.tasks.post({
|
|
293
|
-
title: 'Task 2',
|
|
294
|
-
category: category.id,
|
|
295
|
-
position: 'zzz', // Should be ignored
|
|
296
|
-
beforeId: task1.id // Should be used
|
|
297
|
-
})
|
|
298
|
-
|
|
299
|
-
// Manual position should be ignored, beforeId should work
|
|
300
|
-
assert.notEqual(task2.position, 'zzz')
|
|
301
|
-
assert(task2.position < task1.position) // Should be positioned before task1
|
|
302
|
-
})
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
describe('Position Configuration', () => {
|
|
306
|
-
it('should handle different field name configuration', async () => {
|
|
307
|
-
// Create a new API with different position field name
|
|
308
|
-
const customApi = await createPositioningApi(knex, {
|
|
309
|
-
apiName: 'positioning-custom-field-test',
|
|
310
|
-
tablePrefix: 'pos_custom'
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
await customApi.use(PositioningPlugin, {
|
|
314
|
-
field: 'sort_order', // Different field name
|
|
315
|
-
filters: ['project'],
|
|
316
|
-
defaultPosition: 'last'
|
|
317
|
-
})
|
|
318
|
-
|
|
319
|
-
// Create project
|
|
320
|
-
const project = await customApi.resources.projects.post({ name: 'Test Project' })
|
|
321
|
-
|
|
322
|
-
// Create item
|
|
323
|
-
const item = await customApi.resources.items.post({
|
|
324
|
-
name: 'Test Item',
|
|
325
|
-
project: project.id
|
|
326
|
-
})
|
|
327
|
-
|
|
328
|
-
// Should have sort_order field, not position
|
|
329
|
-
assert(item.sort_order, 'Should have sort_order field')
|
|
330
|
-
assert(!item.position, 'Should not have position field')
|
|
331
|
-
})
|
|
332
|
-
|
|
333
|
-
it('should handle multiple filter fields', async () => {
|
|
334
|
-
// Create a new API with multiple filters
|
|
335
|
-
const multiFilterApi = await createPositioningApi(knex, {
|
|
336
|
-
apiName: 'positioning-multi-filter-test',
|
|
337
|
-
tablePrefix: 'pos_multi'
|
|
338
|
-
})
|
|
339
|
-
|
|
340
|
-
await multiFilterApi.use(PositioningPlugin, {
|
|
341
|
-
field: 'position',
|
|
342
|
-
filters: ['project', 'status'], // Multiple filters
|
|
343
|
-
defaultPosition: 'last'
|
|
344
|
-
})
|
|
345
|
-
|
|
346
|
-
// Create project
|
|
347
|
-
const project = await multiFilterApi.resources.projects.post({ name: 'Multi Filter Project' })
|
|
348
|
-
|
|
349
|
-
// Create items with different statuses
|
|
350
|
-
const item1Active = await multiFilterApi.resources.items.post({
|
|
351
|
-
name: 'Active Item 1',
|
|
352
|
-
project: project.id,
|
|
353
|
-
status: 'active'
|
|
354
|
-
})
|
|
355
|
-
|
|
356
|
-
const item2Active = await multiFilterApi.resources.items.post({
|
|
357
|
-
name: 'Active Item 2',
|
|
358
|
-
project: project.id,
|
|
359
|
-
status: 'active'
|
|
360
|
-
})
|
|
361
|
-
|
|
362
|
-
const item1Archived = await multiFilterApi.resources.items.post({
|
|
363
|
-
name: 'Archived Item 1',
|
|
364
|
-
project: project.id,
|
|
365
|
-
status: 'archived'
|
|
366
|
-
})
|
|
367
|
-
|
|
368
|
-
// Active items should be in sequence
|
|
369
|
-
assert(item1Active.position < item2Active.position)
|
|
370
|
-
|
|
371
|
-
// Archived item should have its own sequence
|
|
372
|
-
// (its position might be similar to active item 1, but they're in different groups)
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
it('should handle defaultPosition first configuration', async () => {
|
|
376
|
-
// Create API with defaultPosition: 'first'
|
|
377
|
-
const firstApi = await createPositioningApi(knex, {
|
|
378
|
-
apiName: 'positioning-first-test',
|
|
379
|
-
tablePrefix: 'pos_first'
|
|
380
|
-
})
|
|
381
|
-
|
|
382
|
-
await firstApi.use(PositioningPlugin, {
|
|
383
|
-
field: 'position',
|
|
384
|
-
filters: ['category'],
|
|
385
|
-
defaultPosition: 'first' // Items go to beginning by default
|
|
386
|
-
})
|
|
387
|
-
|
|
388
|
-
// Create category
|
|
389
|
-
const category = await firstApi.resources.categories.post({ name: 'First Default Category' })
|
|
390
|
-
|
|
391
|
-
// Create tasks - they should be added at the beginning
|
|
392
|
-
const task1 = await firstApi.resources.tasks.post({
|
|
393
|
-
title: 'Task 1',
|
|
394
|
-
category: category.id
|
|
395
|
-
})
|
|
396
|
-
|
|
397
|
-
const task2 = await firstApi.resources.tasks.post({
|
|
398
|
-
title: 'Task 2',
|
|
399
|
-
category: category.id
|
|
400
|
-
})
|
|
401
|
-
|
|
402
|
-
const task3 = await firstApi.resources.tasks.post({
|
|
403
|
-
title: 'Task 3',
|
|
404
|
-
category: category.id
|
|
405
|
-
})
|
|
406
|
-
|
|
407
|
-
// With defaultPosition: 'first', the order should be reversed
|
|
408
|
-
// Task 3 should be first, then Task 2, then Task 1
|
|
409
|
-
assert(task3.position < task2.position)
|
|
410
|
-
assert(task2.position < task1.position)
|
|
411
|
-
})
|
|
412
|
-
})
|
|
413
|
-
|
|
414
|
-
describe('Error Handling', () => {
|
|
415
|
-
it('should handle invalid beforeId gracefully', async () => {
|
|
416
|
-
// Create category
|
|
417
|
-
const category = await api.resources.categories.post({ name: 'Test Category' })
|
|
418
|
-
|
|
419
|
-
// Create task with non-existent beforeId
|
|
420
|
-
const task = await api.resources.tasks.post({
|
|
421
|
-
title: 'Task with Invalid BeforeId',
|
|
422
|
-
category: category.id,
|
|
423
|
-
beforeId: '99999' // Non-existent ID
|
|
424
|
-
})
|
|
425
|
-
|
|
426
|
-
// Should still create the task (positioned at end)
|
|
427
|
-
assert(task.position)
|
|
428
|
-
assert.equal(task.title, 'Task with Invalid BeforeId')
|
|
429
|
-
})
|
|
430
|
-
|
|
431
|
-
it('should handle null filter values', async () => {
|
|
432
|
-
// Create task without category (null filter)
|
|
433
|
-
const task = await api.resources.tasks.post({
|
|
434
|
-
title: 'Task without Category',
|
|
435
|
-
category: null
|
|
436
|
-
})
|
|
437
|
-
|
|
438
|
-
// Should still get position
|
|
439
|
-
assert(task.position)
|
|
440
|
-
|
|
441
|
-
// Create another task without category
|
|
442
|
-
const task2 = await api.resources.tasks.post({
|
|
443
|
-
title: 'Another Task without Category',
|
|
444
|
-
category: null
|
|
445
|
-
})
|
|
446
|
-
|
|
447
|
-
// They should be in sequence
|
|
448
|
-
assert(task.position < task2.position)
|
|
449
|
-
})
|
|
450
|
-
})
|
|
451
|
-
|
|
452
|
-
describe('Excluded Resources', () => {
|
|
453
|
-
it('should not add positioning to excluded resources', async () => {
|
|
454
|
-
// Create API with excluded resources
|
|
455
|
-
const excludeApi = await createPositioningApi(knex, {
|
|
456
|
-
apiName: 'positioning-exclude-test',
|
|
457
|
-
tablePrefix: 'pos_exclude'
|
|
458
|
-
})
|
|
459
|
-
|
|
460
|
-
await excludeApi.use(PositioningPlugin, {
|
|
461
|
-
field: 'position',
|
|
462
|
-
filters: [],
|
|
463
|
-
excludeResources: ['categories'] // Exclude categories from positioning
|
|
464
|
-
})
|
|
465
|
-
|
|
466
|
-
// Create category - should not have position
|
|
467
|
-
const category = await excludeApi.resources.categories.post({ name: 'Excluded Category' })
|
|
468
|
-
|
|
469
|
-
assert(!category.position, 'Category should not have position')
|
|
470
|
-
|
|
471
|
-
// Create task - should have position
|
|
472
|
-
const task = await excludeApi.resources.tasks.post({
|
|
473
|
-
title: 'Task with Position',
|
|
474
|
-
category: category.id
|
|
475
|
-
})
|
|
476
|
-
|
|
477
|
-
assert(task.position, 'Task should have position')
|
|
478
|
-
})
|
|
479
|
-
})
|
|
480
|
-
|
|
481
|
-
describe('Simplified API Mode', () => {
|
|
482
|
-
it('should handle positioning in simplified mode', async () => {
|
|
483
|
-
// We're already using simplified mode throughout these tests!
|
|
484
|
-
// This test just verifies it works as expected
|
|
485
|
-
|
|
486
|
-
// Create category
|
|
487
|
-
const category = await api.resources.categories.post({ name: 'Simplified Category' })
|
|
488
|
-
|
|
489
|
-
// Create tasks in simplified mode
|
|
490
|
-
const task1 = await api.resources.tasks.post({
|
|
491
|
-
title: 'Simplified Task 1',
|
|
492
|
-
category: category.id
|
|
493
|
-
})
|
|
494
|
-
|
|
495
|
-
const task2 = await api.resources.tasks.post({
|
|
496
|
-
title: 'Simplified Task 2',
|
|
497
|
-
category: category.id,
|
|
498
|
-
beforeId: task1.id
|
|
499
|
-
})
|
|
500
|
-
|
|
501
|
-
// Verify positioning
|
|
502
|
-
assert(task1.position, 'Task 1 should have position')
|
|
503
|
-
assert(task2.position, 'Task 2 should have position')
|
|
504
|
-
assert(task2.position < task1.position, 'Task 2 should be before Task 1')
|
|
505
|
-
})
|
|
506
|
-
})
|
|
507
|
-
|
|
508
|
-
describe('Query Ordering', () => {
|
|
509
|
-
it('should allow sorting by position field', async () => {
|
|
510
|
-
// Create category
|
|
511
|
-
const category = await api.resources.categories.post({ name: 'Sort Test Category' })
|
|
512
|
-
|
|
513
|
-
// Create tasks in specific order
|
|
514
|
-
const taskC = await api.resources.tasks.post({
|
|
515
|
-
title: 'C Task',
|
|
516
|
-
category: category.id
|
|
517
|
-
})
|
|
518
|
-
|
|
519
|
-
const taskA = await api.resources.tasks.post({
|
|
520
|
-
title: 'A Task',
|
|
521
|
-
category: category.id,
|
|
522
|
-
beforeId: taskC.id
|
|
523
|
-
})
|
|
524
|
-
|
|
525
|
-
const taskB = await api.resources.tasks.post({
|
|
526
|
-
title: 'B Task',
|
|
527
|
-
category: category.id,
|
|
528
|
-
beforeId: taskC.id
|
|
529
|
-
})
|
|
530
|
-
|
|
531
|
-
// Query with position sort
|
|
532
|
-
const { data: sorted } = await api.resources.tasks.query({
|
|
533
|
-
queryParams: {
|
|
534
|
-
filters: { category: category.id },
|
|
535
|
-
sort: ['position']
|
|
536
|
-
}
|
|
537
|
-
})
|
|
538
|
-
|
|
539
|
-
// Should be in position order: A, B, C
|
|
540
|
-
// Note: When both A and B are positioned before C, they maintain creation order
|
|
541
|
-
assert.equal(sorted.length, 3)
|
|
542
|
-
assert.equal(sorted[0].title, 'A Task')
|
|
543
|
-
assert.equal(sorted[1].title, 'B Task')
|
|
544
|
-
assert.equal(sorted[2].title, 'C Task')
|
|
545
|
-
|
|
546
|
-
// Query with reverse position sort
|
|
547
|
-
const { data: reverseSorted } = await api.resources.tasks.query({
|
|
548
|
-
queryParams: {
|
|
549
|
-
filters: { category: category.id },
|
|
550
|
-
sort: ['-position']
|
|
551
|
-
}
|
|
552
|
-
})
|
|
553
|
-
|
|
554
|
-
// Should be in reverse order: C, B, A
|
|
555
|
-
assert.equal(reverseSorted[0].title, 'C Task')
|
|
556
|
-
assert.equal(reverseSorted[1].title, 'B Task')
|
|
557
|
-
assert.equal(reverseSorted[2].title, 'A Task')
|
|
558
|
-
})
|
|
559
|
-
})
|
|
560
|
-
})
|
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after, beforeEach } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import knexLib from 'knex'
|
|
4
|
-
import {
|
|
5
|
-
cleanTables,
|
|
6
|
-
countRecords,
|
|
7
|
-
createJsonApiDocument,
|
|
8
|
-
createRelationship,
|
|
9
|
-
resourceIdentifier
|
|
10
|
-
} from './helpers/test-utils.js'
|
|
11
|
-
import { createBasicApi } from './fixtures/api-configs.js'
|
|
12
|
-
|
|
13
|
-
const knex = knexLib({
|
|
14
|
-
client: 'better-sqlite3',
|
|
15
|
-
connection: {
|
|
16
|
-
filename: ':memory:'
|
|
17
|
-
},
|
|
18
|
-
useNullAsDefault: true
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
let api
|
|
22
|
-
let afterCommitFailureMethod = null
|
|
23
|
-
const rollbackEvents = []
|
|
24
|
-
|
|
25
|
-
describe('Post-commit hook failure semantics', () => {
|
|
26
|
-
before(async () => {
|
|
27
|
-
api = await createBasicApi(knex, {
|
|
28
|
-
apiName: 'post-commit-hook-test',
|
|
29
|
-
tablePrefix: 'post_commit'
|
|
30
|
-
})
|
|
31
|
-
|
|
32
|
-
await api.customize({
|
|
33
|
-
hooks: {
|
|
34
|
-
afterCommit: {
|
|
35
|
-
functionName: 'post-commit-failure-test',
|
|
36
|
-
handler: async ({ context }) => {
|
|
37
|
-
if (context.method === afterCommitFailureMethod) {
|
|
38
|
-
throw new Error(`afterCommit failed for ${context.method}`)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
},
|
|
42
|
-
afterRollback: {
|
|
43
|
-
functionName: 'post-commit-rollback-tracker',
|
|
44
|
-
handler: async ({ context }) => {
|
|
45
|
-
rollbackEvents.push(context.method)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
})
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
after(async () => {
|
|
53
|
-
await knex.destroy()
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
beforeEach(async () => {
|
|
57
|
-
afterCommitFailureMethod = null
|
|
58
|
-
rollbackEvents.length = 0
|
|
59
|
-
|
|
60
|
-
await cleanTables(knex, [
|
|
61
|
-
'post_commit_countries',
|
|
62
|
-
'post_commit_publishers',
|
|
63
|
-
'post_commit_authors',
|
|
64
|
-
'post_commit_books',
|
|
65
|
-
'post_commit_book_authors'
|
|
66
|
-
])
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
it('surfaces afterCommit errors without rolling back committed resource writes', async () => {
|
|
70
|
-
afterCommitFailureMethod = 'post'
|
|
71
|
-
|
|
72
|
-
await assert.rejects(
|
|
73
|
-
api.resources.countries.post({
|
|
74
|
-
inputRecord: createJsonApiDocument('countries', {
|
|
75
|
-
name: 'Committed Country',
|
|
76
|
-
code: 'CC'
|
|
77
|
-
}),
|
|
78
|
-
simplified: false
|
|
79
|
-
}),
|
|
80
|
-
/afterCommit failed for post/
|
|
81
|
-
)
|
|
82
|
-
|
|
83
|
-
assert.equal(await countRecords(knex, 'post_commit_countries'), 1)
|
|
84
|
-
assert.deepEqual(rollbackEvents, [])
|
|
85
|
-
})
|
|
86
|
-
|
|
87
|
-
it('surfaces afterCommit errors without rolling back committed relationship writes', async () => {
|
|
88
|
-
const country = await api.resources.countries.post({
|
|
89
|
-
inputRecord: createJsonApiDocument('countries', { name: 'USA', code: 'US' }),
|
|
90
|
-
simplified: false
|
|
91
|
-
})
|
|
92
|
-
const book = await api.resources.books.post({
|
|
93
|
-
inputRecord: createJsonApiDocument(
|
|
94
|
-
'books',
|
|
95
|
-
{ title: 'Committed Book' },
|
|
96
|
-
{ country: createRelationship(resourceIdentifier('countries', country.data.id)) }
|
|
97
|
-
),
|
|
98
|
-
simplified: false
|
|
99
|
-
})
|
|
100
|
-
const author = await api.resources.authors.post({
|
|
101
|
-
inputRecord: createJsonApiDocument('authors', { name: 'Committed Author' }),
|
|
102
|
-
simplified: false
|
|
103
|
-
})
|
|
104
|
-
|
|
105
|
-
rollbackEvents.length = 0
|
|
106
|
-
afterCommitFailureMethod = 'postRelationship'
|
|
107
|
-
|
|
108
|
-
await assert.rejects(
|
|
109
|
-
api.resources.books.postRelationship({
|
|
110
|
-
id: book.data.id,
|
|
111
|
-
relationshipName: 'authors',
|
|
112
|
-
relationshipData: [resourceIdentifier('authors', author.data.id)]
|
|
113
|
-
}),
|
|
114
|
-
/afterCommit failed for postRelationship/
|
|
115
|
-
)
|
|
116
|
-
|
|
117
|
-
assert.equal(await countRecords(knex, 'post_commit_book_authors'), 1)
|
|
118
|
-
assert.deepEqual(rollbackEvents, [])
|
|
119
|
-
})
|
|
120
|
-
})
|