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
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Labels"
|
|
3
|
+
chapter: 18
|
|
4
|
+
chapter_label: "18"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 18. Labels
|
|
8
|
+
|
|
9
|
+
Install `LabelPlugin` after `RestApiPlugin` and before adding resources:
|
|
10
|
+
|
|
11
|
+
```js
|
|
12
|
+
import { LabelPlugin } from 'json-rest-api'
|
|
13
|
+
|
|
14
|
+
await api.use(LabelPlugin, { preferNameFields: ['name', 'title'] })
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
It adds a computed string `label` to resources that do not already declare one.
|
|
18
|
+
The label is derived during reads and is not a stored column. GET, query, full
|
|
19
|
+
write responses and included resources use the same computation in both plain
|
|
20
|
+
and JSON:API formats.
|
|
21
|
+
|
|
22
|
+
Candidates are derived during schema compilation, after attribute and search
|
|
23
|
+
enrichment. Canonical `addKnexFields` rebuilds them from the new configuration,
|
|
24
|
+
so a field addition retains the label and an added preferred/global-search
|
|
25
|
+
source becomes eligible. Reads and descriptor-only refreshes reuse the compiled
|
|
26
|
+
candidates. Ordinary Knex field-addition helpers perform DDL only; changing
|
|
27
|
+
their runtime declarations still requires resource initialization.
|
|
28
|
+
|
|
29
|
+
The plugin considers, in order:
|
|
30
|
+
|
|
31
|
+
1. The first global-search field that resolves to a public stored field on this
|
|
32
|
+
resource. A search alias uses its `actualField`; a joined search path is not
|
|
33
|
+
a local label source.
|
|
34
|
+
2. String fields listed in `preferNameFields`, in the configured order.
|
|
35
|
+
3. The first public stored string field in the schema.
|
|
36
|
+
4. The resource's logical ID.
|
|
37
|
+
|
|
38
|
+
Null and undefined values fall through to the next candidate. Empty strings,
|
|
39
|
+
zero and false are retained and converted to strings. Getters run before label
|
|
40
|
+
computation. Hidden, normally hidden and virtual fields, belongs-to foreign keys
|
|
41
|
+
and polymorphic backing fields are not automatic label sources. This keeps the default label from exposing private values or depending
|
|
42
|
+
on a transient write input.
|
|
43
|
+
|
|
44
|
+
Sparse fieldsets can select just the label:
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
await api.resources.books.query({
|
|
48
|
+
format: 'jsonapi',
|
|
49
|
+
queryParams: { fields: { books: 'label' } }
|
|
50
|
+
})
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
The existing computed-field dependency mechanism fetches the candidate values
|
|
54
|
+
and removes unrequested dependencies from the result. Null fallbacks therefore
|
|
55
|
+
work with sparse fieldsets too. Pass `{ disable: true }` when installing the
|
|
56
|
+
plugin to disable automatic labels.
|
|
57
|
+
|
|
58
|
+
## Authored labels and record identity
|
|
59
|
+
|
|
60
|
+
An explicitly declared stored or computed `label` remains authoritative. The
|
|
61
|
+
plugin does not remove its schema definition or replace its computation. For
|
|
62
|
+
example, a resource may declare:
|
|
63
|
+
|
|
64
|
+
```js
|
|
65
|
+
label: {
|
|
66
|
+
type: 'string',
|
|
67
|
+
computed: true,
|
|
68
|
+
dependencies: ['title'],
|
|
69
|
+
compute: ({ id, attributes }) => `${id}: ${attributes.title ?? ''}`
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Computed callbacks receive the current resource's public logical `id` separately
|
|
74
|
+
from `attributes`. This also works for custom `idProperty` names, collections
|
|
75
|
+
and included resources; the ID is not taken from the parent request. Direct
|
|
76
|
+
callers of `enrichAttributes` can supply its `id` argument explicitly.
|
|
77
|
+
|
|
78
|
+
Computed fields honor `hidden` and `normallyHidden` like stored fields: hidden
|
|
79
|
+
fields are omitted, and normally hidden fields appear only when requested.
|
|
80
|
+
Authors who intentionally derive a public value from normally hidden dependencies should
|
|
81
|
+
declare that computed field explicitly and review the information it returns.
|
|
82
|
+
|
|
83
|
+
The shared label suite (source checkout: `tests/conformance-labels.test.js`) checks these
|
|
84
|
+
contracts on regular and canonical storage. The
|
|
85
|
+
[migration guide](33-migrating-to-v2.md#generated-labels-and-computed-visibility)
|
|
86
|
+
describes corrections from the earlier behavior.
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Managed transactions"
|
|
3
|
+
chapter: 19
|
|
4
|
+
chapter_label: "19"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 19. Managed transactions
|
|
8
|
+
|
|
9
|
+
Use `api.transaction()` to group resource operations and raw SQL in one explicit
|
|
10
|
+
unit. The helper owns commit, rollback and completion hooks. Pass its transaction
|
|
11
|
+
to every participating operation; the library does not infer ambient ownership.
|
|
12
|
+
|
|
13
|
+
## Callback and result
|
|
14
|
+
|
|
15
|
+
```js
|
|
16
|
+
const result = await api.transaction(async transaction => {
|
|
17
|
+
const book = await api.resources.books.post({
|
|
18
|
+
data: { title: 'New book' },
|
|
19
|
+
format: 'plain',
|
|
20
|
+
transaction
|
|
21
|
+
})
|
|
22
|
+
await transaction('audit_entries').insert({ book_id: book.id })
|
|
23
|
+
return book
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`api.transaction(callback, context = {})` acquires one top-level transaction,
|
|
28
|
+
awaits the callback, completes the transaction, awaits its completion work and
|
|
29
|
+
returns the callback's value unchanged. The second argument is mutable diagnostic
|
|
30
|
+
context, consistent with other API methods; it is not a second options parser.
|
|
31
|
+
Invalid callbacks fail before acquisition. The callback receives the actual
|
|
32
|
+
Knex transaction, so existing first-argument `transaction` forwarding and raw SQL
|
|
33
|
+
remain straightforward. No transaction is inferred from process-global or async
|
|
34
|
+
ambient state. Callers must await all work before the callback returns.
|
|
35
|
+
Individual resource calls inside the callback resolve while the unit is still
|
|
36
|
+
pending; only the outer helper waits for commit and completion hooks. Both
|
|
37
|
+
synchronous callback values and awaited promises are supported, including
|
|
38
|
+
undefined. The helper does not clone or serialize the callback result.
|
|
39
|
+
|
|
40
|
+
The helper owns completion. The callback must not invoke commit/rollback,
|
|
41
|
+
transaction-control SQL or DDL that implicitly completes its transaction. A
|
|
42
|
+
handle completed outside the helper is rejected with an unknown outcome unless
|
|
43
|
+
the library has actual completion evidence; it must not trigger speculative
|
|
44
|
+
rollback cleanup or commit notifications. SQL/connection error evidence also
|
|
45
|
+
cannot be replaced by a later successful empty COMMIT.
|
|
46
|
+
|
|
47
|
+
## Participation and failure
|
|
48
|
+
|
|
49
|
+
Ordinary resource and relationship calls without a transaction use the same
|
|
50
|
+
completion mechanism for their own transaction. Atomic bulk uses it for the
|
|
51
|
+
whole batch. Calls receiving its transaction participate and never complete it
|
|
52
|
+
individually. Non-atomic bulk with an outer transaction continues to reject.
|
|
53
|
+
|
|
54
|
+
The final contract requires library writes to use a library-owned transaction.
|
|
55
|
+
Standalone transactions created with `knex.transaction()` remain usable for raw
|
|
56
|
+
SQL, but passing them into library writes will reject before partial work.
|
|
57
|
+
Migrate those units to `api.transaction`; its callback still receives a real
|
|
58
|
+
Knex handle. This replaces the previous ambiguous borrowed-write mode in which
|
|
59
|
+
notifications/uploads can wait forever for an unobserved owner. Read-only/raw
|
|
60
|
+
storage use is not a competing event-delivery mode.
|
|
61
|
+
|
|
62
|
+
Canonical `addKnexFields()` batches use this helper too. Direct registry writes
|
|
63
|
+
that receive a transaction require a managed handle. Their caller still
|
|
64
|
+
invalidates the registry descriptor cache after confirmed commit; this does not
|
|
65
|
+
publish a new compiled resource schema. Raw descriptor reads and schema/data
|
|
66
|
+
migration helpers retain their separate documented transaction contracts.
|
|
67
|
+
|
|
68
|
+
Any rejected participating write marks the unit failed, even if the callback
|
|
69
|
+
catches the error and returns normally. Observed SQL failures also prevent a
|
|
70
|
+
later commit from masking aborted work. A rejected callback or failed unit rolls
|
|
71
|
+
back; no automatic retries or savepoints restore it. A failure before acquisition
|
|
72
|
+
has outcome none. Participant errors retain their pending/none snapshot, and an
|
|
73
|
+
outer error records the owner's eventual outcome without rewriting its cause.
|
|
74
|
+
If the callback rejects, its rejection is primary. If it returns after catching
|
|
75
|
+
failures, the first recorded participant/SQL failure is primary instead.
|
|
76
|
+
|
|
77
|
+
A native lock timeout, deadlock or stale SQLite snapshot is reported to the
|
|
78
|
+
caller. The library does not replay the callback, field setters or hooks.
|
|
79
|
+
Effects performed outside the database before the conflict can therefore remain
|
|
80
|
+
once even though the database write rolls back. Retry only a new complete unit
|
|
81
|
+
whose external effects your application can safely repeat or reconcile; never
|
|
82
|
+
retry just the failed statement inside an already failed unit.
|
|
83
|
+
|
|
84
|
+
## Completion work
|
|
85
|
+
|
|
86
|
+
Each accepted resource/relationship operation enlists its existing completion
|
|
87
|
+
hook chain once. This includes nested resource calls made by relationship
|
|
88
|
+
implementation; helper-internal SQL does not create synthetic operations. Bulk
|
|
89
|
+
or transaction owners do not also run a duplicate resource hook chain.
|
|
90
|
+
|
|
91
|
+
After confirmed commit, invoke enlisted `afterCommit` chains in enlistment
|
|
92
|
+
order. After confirmed rollback, invoke `afterRollback` chains in reverse order.
|
|
93
|
+
A chain retains its normal local stop-on-error behavior; failure in one chain
|
|
94
|
+
does not prevent other enlisted operations' completion work from being attempted.
|
|
95
|
+
Collect secondary failures in owner diagnostics. Retain the original callback/
|
|
96
|
+
write failure on rollback; after commit, report the first completion failure as
|
|
97
|
+
the primary error with outcome committed. Unconfirmed completion runs neither
|
|
98
|
+
set of outcome-dependent hooks.
|
|
99
|
+
|
|
100
|
+
Enlistment happens when the library accepts an operation into the transaction.
|
|
101
|
+
Overlapping operations can finish their writes in another order; that does not
|
|
102
|
+
change completion-hook order. Internal finalizers, currently bulk diagnostic
|
|
103
|
+
collection, run after those chains in registration order even when a chain or
|
|
104
|
+
an earlier finalizer fails. They also collect diagnostics after unknown
|
|
105
|
+
completion; they must not infer commit or run outcome-dependent side effects.
|
|
106
|
+
The first post-commit failure remains primary across hooks and finalizers.
|
|
107
|
+
|
|
108
|
+
Use the existing diagnostic shape on the owner's context: `error` is the primary
|
|
109
|
+
failure and `cleanupErrors` is an ordered array of `{ phase, error }` entries.
|
|
110
|
+
Completion-chain entries additionally identify `operationIndex`, `scopeName`
|
|
111
|
+
and `method`; phases distinguish `rollback`, `afterRollback` and `afterCommit`.
|
|
112
|
+
Secondary finalizer failures use phase `finalization` and `finalizerIndex`.
|
|
113
|
+
The error carrier retains its cause even when any of these values are frozen or
|
|
114
|
+
non-Error throws. Do not serialize transaction handles or raw cleanup objects
|
|
115
|
+
into HTTP errors.
|
|
116
|
+
|
|
117
|
+
An operation's mutable context remains in use until its owner completes. Use a
|
|
118
|
+
separate context object per operation; reject reuse through library calls while
|
|
119
|
+
that context is still enlisted. Do not copy arbitrary contexts, replay mutations
|
|
120
|
+
into reused objects, or rebuild scope-aware hook dispatch in the transaction
|
|
121
|
+
helper. Completion hooks receive their operation's context, with the observed
|
|
122
|
+
owner outcome added at completion. Previously thrown errors remain immutable
|
|
123
|
+
snapshots. Application code must not mutate an enlisted context concurrently.
|
|
124
|
+
The private owner record controls commit and rollback. Changing the diagnostic
|
|
125
|
+
`shouldCommit` flag cannot transfer ownership to a participant or stop an
|
|
126
|
+
implicit owner from completing. During rollback, a failed operation retains its
|
|
127
|
+
original error; an operation that succeeded before the unit failed receives the
|
|
128
|
+
owner's error in its rollback context.
|
|
129
|
+
|
|
130
|
+
File cleanup/tracking and Socket.IO delivery use these existing hooks. Uploaded
|
|
131
|
+
files are cleaned only after confirmed rollback, and notifications run only
|
|
132
|
+
after confirmed outer commit. Bulk diagnostics aggregate unresolved child state
|
|
133
|
+
after finalization; the parent must not delete a child's files a second time.
|
|
134
|
+
Tests cover repeated changes to the same resource, multiple uploads,
|
|
135
|
+
callback/SQL failures and failing completion hooks. File-specific failures and
|
|
136
|
+
unresolved upload tracking remain on each operation's context; rejected hook
|
|
137
|
+
chains are reported on the owner. After confirmed commit, stored objects are
|
|
138
|
+
application-owned. Replacement and deletion preserve previously committed files:
|
|
139
|
+
opaque URLs may be shared by other records or external consumers. Rollback
|
|
140
|
+
removes only new uploads belonging to the failed unit. See the
|
|
141
|
+
[file ownership contract](26-file-uploads.md#ownership-after-commit)
|
|
142
|
+
for application-managed garbage collection.
|
|
143
|
+
|
|
144
|
+
Socket.IO notices are per-operation invalidations, without coalescing: a
|
|
145
|
+
sequential create/update/delete can emit all three after commit. Callback
|
|
146
|
+
rollback discards the queue. The selected tests check pending silence, ordered
|
|
147
|
+
notices, real committed/rolled-back rows, visibility filtering and a later
|
|
148
|
+
chain flushing once after the first chain fails. These are awaited in-memory
|
|
149
|
+
notifications, not durable delivery or an exactly-once guarantee.
|
|
150
|
+
|
|
151
|
+
Notification capture order is the order in which operations reach the Socket.IO
|
|
152
|
+
finish hook's queue. For overlapping writes it can differ from enlistment order.
|
|
153
|
+
The first successful delivery chain drains that queue without reordering it.
|
|
154
|
+
Real polling/WebSocket tests deliberately pause the first enlisted write so the
|
|
155
|
+
second finishes first, and verify both distinct orders.
|
|
156
|
+
|
|
157
|
+
## Nesting and application ownership
|
|
158
|
+
|
|
159
|
+
An application-created Knex transaction is unmanaged from this library's point
|
|
160
|
+
of view: the application decides when it finally commits or rolls back. That
|
|
161
|
+
final outcome controls after-commit hooks and rollback cleanup, so passing an
|
|
162
|
+
arbitrary Knex transaction into resource writes is rejected. Use
|
|
163
|
+
`api.transaction` to group resource operations and raw SQL under one owner.
|
|
164
|
+
|
|
165
|
+
A savepoint is a rollback checkpoint inside an outer transaction. Releasing it
|
|
166
|
+
does not commit that outer transaction. Resource writes therefore cannot treat
|
|
167
|
+
savepoint success as permission to send notifications or run after-commit hooks.
|
|
168
|
+
|
|
169
|
+
Every `api.transaction` call starts an independent top-level unit. There is no
|
|
170
|
+
implicit joining or nested callback overload. To compose work within one unit,
|
|
171
|
+
pass its transaction to ordinary functions and resource methods. Managed library
|
|
172
|
+
writes do not support child savepoints; reject them explicitly instead of
|
|
173
|
+
treating savepoint release as outer commit. The current owned-completion guard
|
|
174
|
+
already rejects a custom factory's savepoint; its caller-owned parent survives.
|
|
175
|
+
|
|
176
|
+
When migrating an application's transaction wrapper, delegate to `api.transaction`
|
|
177
|
+
and forward the received handle. Port raw transaction creation, context reuse and
|
|
178
|
+
error handling together. Preserve domain scope resolution in application code;
|
|
179
|
+
keep completion ownership in one place.
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Transaction outcomes"
|
|
3
|
+
chapter: 20
|
|
4
|
+
chapter_label: "20"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 20. Transaction outcomes
|
|
8
|
+
|
|
9
|
+
Resource, relationship, bulk and canonical registry write errors carry the
|
|
10
|
+
outcome of the transaction they represent. HTTP and non-atomic bulk errors
|
|
11
|
+
preserve that outcome. The states below describe the evidence available when
|
|
12
|
+
the failure is reported.
|
|
13
|
+
|
|
14
|
+
[Managed transactions](19-managed-transactions.md) explains ownership and
|
|
15
|
+
completion ordering. These guarantees cannot recover an error swallowed by
|
|
16
|
+
an application extension.
|
|
17
|
+
|
|
18
|
+
## The five states
|
|
19
|
+
|
|
20
|
+
| State | Exact meaning |
|
|
21
|
+
| --- | --- |
|
|
22
|
+
| `none` | No transaction was acquired or accepted for this operation. This includes rejection before transaction setup. It does not certify that external or nontransactional work had no effects. |
|
|
23
|
+
| `pending` | The operation participated in a transaction whose owner has not completed it. The owner still must decide its outcome. A database error may have made further SQL unusable; pending does not mean the owner can safely continue writing. |
|
|
24
|
+
| `committed` | The backend confirmed commit of the represented outer transaction. Later hook, file, notification or logging failure does not change this outcome. Releasing a savepoint does not establish outer commit. |
|
|
25
|
+
| `rolledBack` | The backend confirmed rollback of the represented transaction. Later cleanup failure does not undo that confirmation. This says nothing about external effects performed before rollback. |
|
|
26
|
+
| `unknown` | A transaction was acquired or accepted, but the available evidence cannot establish its outcome or leave it confidently pending. Examples include lost completion acknowledgements, failed completion/rollback, and an accepted handle completed outside its owner without observed outcome. |
|
|
27
|
+
|
|
28
|
+
Use these values consistently. Do not add
|
|
29
|
+
synonyms for driver states or infer them from exception messages. Ownership is
|
|
30
|
+
separate from outcome: an individual resource operation borrows its caller's
|
|
31
|
+
transaction, including a transaction managed by an outer library operation.
|
|
32
|
+
|
|
33
|
+
## Evidence and transitions
|
|
34
|
+
|
|
35
|
+
Transaction setup starts with `none`. Successful acquisition or acceptance of
|
|
36
|
+
an unfinished transaction moves to `pending`. An operation that merely borrows
|
|
37
|
+
that transaction leaves it pending; its own success or rejection does not commit
|
|
38
|
+
or roll back the outer unit. A rejected statement may require the owner to roll
|
|
39
|
+
back even if the driver's handle remains usable-looking.
|
|
40
|
+
|
|
41
|
+
The private transaction owner controls completion; a hook changing the mutable
|
|
42
|
+
`shouldCommit` diagnostic flag neither takes ownership nor suppresses an implicit
|
|
43
|
+
owner's commit or rollback. Participating operation errors stay pending snapshots
|
|
44
|
+
even when the outer helper later reports rolledBack.
|
|
45
|
+
|
|
46
|
+
Confirmed commit or rollback establishes `committed` or `rolledBack`. A failed
|
|
47
|
+
completion attempt without confirmation establishes `unknown`. If a subsequent
|
|
48
|
+
rollback is actually confirmed, the reported outcome can be `rolledBack`; a
|
|
49
|
+
cleanup attempt or a resolved wrapper alone is insufficient. Once commit is
|
|
50
|
+
confirmed, later failure remains committed and must not trigger rollback.
|
|
51
|
+
|
|
52
|
+
Knex 3.1.0 exposes a specific trap: `commit()` and `rollback()` can resolve even
|
|
53
|
+
when their control query failed, while `executionPromise` rejects. Its
|
|
54
|
+
`isCompleted()` flag is set when completion starts and after failures; the flag
|
|
55
|
+
alone proves neither commit nor rollback. The current shared completion helpers
|
|
56
|
+
now await `executionPromise` before reporting successful completion to hooks or
|
|
57
|
+
the registry. Driver-path regressions cover rejected SQL and injected lost
|
|
58
|
+
acknowledgements after real execution.
|
|
59
|
+
|
|
60
|
+
The shared helper also rejects a PostgreSQL COMMIT response reporting ROLLBACK.
|
|
61
|
+
Both Knex promises can resolve in this case, but the backend has confirmed that
|
|
62
|
+
the writes were rolled back. The existing failure path consumes that confirmation
|
|
63
|
+
once, without sending another ROLLBACK. Resource rollback hooks can then clean
|
|
64
|
+
uploaded files; commit hooks and registry cache publication do not run.
|
|
65
|
+
|
|
66
|
+
Owned completion now requires a top-level transaction. A custom transaction
|
|
67
|
+
factory returning a savepoint is rejected before release; the existing failure
|
|
68
|
+
path rolls back that savepoint and leaves its caller-owned parent active.
|
|
69
|
+
No commit hook or committed registry cache publication follows that rejection.
|
|
70
|
+
The managed implementation rejects raw transactions and savepoints supplied to
|
|
71
|
+
library writes before acceptance, with outcome none. A managed handle completed
|
|
72
|
+
outside its owner after acceptance instead has outcome unknown. Connection
|
|
73
|
+
failure during commit must remain unknown unless available evidence establishes
|
|
74
|
+
the outcome. The error remains a snapshot even if the application later
|
|
75
|
+
reconciles the database state.
|
|
76
|
+
|
|
77
|
+
## When commit acknowledgement is lost
|
|
78
|
+
|
|
79
|
+
A connection can fail before COMMIT reaches the database or after the database
|
|
80
|
+
commits but before the caller receives confirmation. A rejected promise or HTTP
|
|
81
|
+
500 alone cannot distinguish those cases. `unknown` deliberately preserves that
|
|
82
|
+
uncertainty; it is not a synonym for rolledBack.
|
|
83
|
+
|
|
84
|
+
| Observed evidence | Reported outcome and behavior |
|
|
85
|
+
| --- | --- |
|
|
86
|
+
| Commit and driver completion acknowledged, then an after-commit hook fails | `committed`; retain the failure, never attempt rollback of the committed write. |
|
|
87
|
+
| Commit control statement or its completion promise rejects without acknowledged outcome | `unknown`; no speculative commit/rollback hooks. A driver's completed flag alone does not establish success. |
|
|
88
|
+
| PostgreSQL acknowledges COMMIT as ROLLBACK | `rolledBack`; run rollback completion work without issuing a second rollback. |
|
|
89
|
+
| Rollback is acknowledged, then cleanup fails | `rolledBack`; retain the primary write error and secondary cleanup diagnostics. |
|
|
90
|
+
| Rollback itself cannot be acknowledged | `unknown`; keep the original failure and rollback diagnostic. |
|
|
91
|
+
|
|
92
|
+
The Knex storage plugins and owned AnyAPI registry operations retain the
|
|
93
|
+
physical connection lease until the
|
|
94
|
+
transaction completion promise settles. A failed completion marks that connection
|
|
95
|
+
disposed before releasing it, so even a request already waiting for the pool
|
|
96
|
+
cannot borrow its unfinished transaction. A failed BEGIN also rejects before the
|
|
97
|
+
application callback runs. Acknowledged completion keeps normal connection reuse.
|
|
98
|
+
|
|
99
|
+
Owned PostgreSQL `alterKnexFields` also uses the managed completion runner.
|
|
100
|
+
A deep-helper call given an existing transaction uses a savepoint and leaves
|
|
101
|
+
completion to that caller. SQLite table rebuilds are Knex-managed internally;
|
|
102
|
+
the library holds their connection lease through error cleanup, rolling back a
|
|
103
|
+
transaction the driver reports still active and restoring foreign-key enforcement
|
|
104
|
+
before release. If cleanup cannot establish a reusable connection, it is disposed.
|
|
105
|
+
See the [schema guide](21-schema-and-migrations.md) for DDL-specific
|
|
106
|
+
ownership, cleanup errors and dialect restrictions.
|
|
107
|
+
|
|
108
|
+
If a completion method rejects without settling its driver completion promise,
|
|
109
|
+
an owner finishing with outcome `unknown` also discards its unreleased connection
|
|
110
|
+
lease. A later driver promise settlement cannot release the lease twice. The raw
|
|
111
|
+
driver promise may remain unsettled; application completion follows the library
|
|
112
|
+
call and its outcome, rather than waiting on that internal promise.
|
|
113
|
+
|
|
114
|
+
Connection release is cleanup, separate from the driver's SQL-completion promise.
|
|
115
|
+
A release error after acknowledged COMMIT keeps outcome `committed`; it becomes a
|
|
116
|
+
post-commit error and later completion hooks/finalizers are still attempted. A
|
|
117
|
+
release error after confirmed rollback keeps `rolledBack`, preserves the original
|
|
118
|
+
failure, and adds a `connectionRelease` diagnostic to the owner's cleanup errors.
|
|
119
|
+
Failed BEGIN also retains its original cause if releasing its lease fails.
|
|
120
|
+
|
|
121
|
+
Disposal does not establish whether a write committed, and does not replay it.
|
|
122
|
+
Knex destroys the disposed connection during pool validation or idle cleanup;
|
|
123
|
+
this is not a guarantee of immediate server-side lock release. SQLite `:memory:`
|
|
124
|
+
data belongs to its connection and is lost when that connection is discarded;
|
|
125
|
+
use a file-backed database when data must survive connection replacement.
|
|
126
|
+
|
|
127
|
+
After `unknown`, reconcile using application-owned operation identity and
|
|
128
|
+
persisted state before deciding whether to issue a new write. Do not treat a
|
|
129
|
+
previously captured participant error as the owner's final outcome. Do not infer
|
|
130
|
+
that files in upload tracking are safe to delete: they may belong to committed
|
|
131
|
+
rows. Outcome-dependent hooks are skipped when the outcome is unknown, so
|
|
132
|
+
notifications and other in-memory completion work may require reconciliation as
|
|
133
|
+
well. There is no durable outbox, automatic retry or exactly-once delivery.
|
|
134
|
+
|
|
135
|
+
Database rollback does not undo email, remote calls or other effects performed
|
|
136
|
+
outside that database. Even `rolledBack` permits a safe retry only when the
|
|
137
|
+
application can repeat or reconcile those effects. See the
|
|
138
|
+
[file ownership contract](26-file-uploads.md#ownership-after-commit).
|
|
139
|
+
|
|
140
|
+
The completion tests inject failures at the actual driver query boundary before
|
|
141
|
+
and after executing COMMIT/ROLLBACK, inspect persisted rows afterward, and
|
|
142
|
+
assert no outcome hooks run for unknown completion. They cover real supported
|
|
143
|
+
databases, but do not simulate every network partition, server failover or
|
|
144
|
+
physical connection-loss sequence. This contract reports available evidence;
|
|
145
|
+
it does not promise to resolve an acknowledgement the client never received.
|
|
146
|
+
|
|
147
|
+
## Selected error carrier
|
|
148
|
+
|
|
149
|
+
Writes reject with `RestApiWriteError`, exported from the package root. Its
|
|
150
|
+
read-only `transactionOutcome` is one of the five strings above. `cause` retains
|
|
151
|
+
the original thrown value, including a frozen error, null or undefined; a fresh
|
|
152
|
+
wrapper is created for each failed operation. Read errors retain their existing
|
|
153
|
+
contract. The runtime propagates null and undefined throws. Errors already lost
|
|
154
|
+
inside an extension's own catch cannot be restored by the write boundary.
|
|
155
|
+
|
|
156
|
+
The wrapper retains ordinary data fields used for classification: `code`,
|
|
157
|
+
`type`, `subtype`, `details`, `statusCode`, `status`, `path`, `parameter` and
|
|
158
|
+
`validation`, including inherited status fields from HTTP error classes. It does
|
|
159
|
+
not execute diagnostic getters. Without an original code,
|
|
160
|
+
its code is `REST_API_WRITE`. The original error class is available through
|
|
161
|
+
`cause`; the wrapper itself has class `RestApiWriteError`. Its JSON representation
|
|
162
|
+
omits the cause, stack and transaction handle while retaining diagnostic fields.
|
|
163
|
+
|
|
164
|
+
An atomic child's wrapper can be the outer wrapper's cause. Both snapshots are
|
|
165
|
+
retained: for example, child pending and outer rolledBack. Existing code/subtype
|
|
166
|
+
classification can usually remain at the outer error; code that checks original
|
|
167
|
+
error identity or `instanceof` needs migration. HTTP mapping preserves status
|
|
168
|
+
classification and adds `meta.transactionOutcome` to each JSON:API error.
|
|
169
|
+
Non-atomic bulk entries expose `error.transactionOutcome` individually.
|
|
170
|
+
|
|
171
|
+
## Errors, batches and later observations
|
|
172
|
+
|
|
173
|
+
Outcome evidence must survive a secondary cleanup error without replacing the
|
|
174
|
+
primary failure or its cause. The selected wrapper carries frozen errors and
|
|
175
|
+
non-Error throws without mutating the original value.
|
|
176
|
+
Completion hooks and internal diagnostic finalizers are attempted in their
|
|
177
|
+
documented order despite earlier completion failures. After commit, the first
|
|
178
|
+
such failure remains primary. Finalizer errors after rollback or unknown
|
|
179
|
+
completion remain secondary; they do not change the observed outcome or cause
|
|
180
|
+
speculative outcome hooks to run. See the
|
|
181
|
+
[managed completion contract](19-managed-transactions.md#completion-work).
|
|
182
|
+
Only safe, serializable outcome fields belong in transport errors. Transaction,
|
|
183
|
+
connection, storage and raw cleanup objects remain internal diagnostics.
|
|
184
|
+
|
|
185
|
+
An outcome attached to an error is a snapshot, not a live view. A borrowed
|
|
186
|
+
operation can report pending and its owner later report rolledBack. Later
|
|
187
|
+
confirmation belongs to the owner's completion result; do not mutate previously
|
|
188
|
+
reported failures or their causes to rewrite that history.
|
|
189
|
+
|
|
190
|
+
An atomic batch has one transaction outcome. A non-atomic batch has separate
|
|
191
|
+
outcomes for its entries; it must not claim one shared committed/rolledBack
|
|
192
|
+
outcome from aggregate success counts. Its `meta.failed` counts rejected calls,
|
|
193
|
+
including possible post-commit failures. Each rejected entry carries its own
|
|
194
|
+
outcome; mixed results do not introduce a sixth transaction state.
|
|
195
|
+
|
|
196
|
+
None of these states is a general retry instruction. Repeating a committed or
|
|
197
|
+
unknown write can duplicate effects. Pending work belongs to its owner. Even a
|
|
198
|
+
confirmed rollback cannot undo an email, upload, external API call or other
|
|
199
|
+
effect outside the database transaction. No automatic replay of arbitrary hooks
|
|
200
|
+
or setters is part of this contract.
|
|
201
|
+
|
|
202
|
+
### Dispatcher failures
|
|
203
|
+
|
|
204
|
+
Resource dispatch and hook execution preserve the original thrown value, including
|
|
205
|
+
null and undefined. The runtime does not add automatic failure logging or wrap plugin
|
|
206
|
+
installation errors. The transaction layer attaches write outcomes and records failed
|
|
207
|
+
cleanup diagnostics without replacing the primary cause.
|