@simtlix/simfinity-js 3.14.0 → 3.15.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/README.md +21 -0
- package/package.json +3 -3
- package/src/index.js +1 -0
- package/src/mongo/adapter.js +6 -3
- package/src/mongo/integrity.js +2 -2
- package/src/mongo/transactions.js +9 -2
- package/types/index.d.ts +10 -2
package/README.md
CHANGED
|
@@ -95,3 +95,24 @@ For tooling that locates the package, use `require.resolve('@simtlix/simfinity-j
|
|
|
95
95
|
- [MCP integration](https://simtlix.github.io/simfinity.js/guide/mcp.html)
|
|
96
96
|
- [Complete library documentation](https://github.com/simtlix/simfinity.js#readme)
|
|
97
97
|
- [Aggregation examples](./AGGREGATION_EXAMPLE.md)
|
|
98
|
+
|
|
99
|
+
Controller `onSaveCommitted`, `onUpdateCommitted`, `onDeleteCommitted` and `onTransitionCommitted` callbacks run serially after the owning transaction confirms commit and attempts cleanup. `configureAfterCommit({ onError })` configures reporting per runtime; callback failures do not fail the committed write. Borrowed lifecycle writes join an adapter-owned attempt; externally owned native sessions do not provide observed commit notifications. Existing hooks remain transactional. See [the contract, payloads and outbox requirements](https://simtlix.github.io/simfinity.js/guide/controllers.html#after-commit-callbacks).
|
|
100
|
+
|
|
101
|
+
## Client metadata
|
|
102
|
+
|
|
103
|
+
Since 3.15.0, install the optional `@simtlix/simfinity-descriptor` package,
|
|
104
|
+
aligned with your Simfinity version. It exports `buildClientDescriptor(schema)`
|
|
105
|
+
and `createClientDescriptorHandler(schema, { authorize: 'public' })`. The builder
|
|
106
|
+
describes the exact `createSchema()` result; the explicitly mounted handler can
|
|
107
|
+
instead use an application access function. These functions are not facade/core
|
|
108
|
+
exports. The descriptor does not evaluate dynamic permissions or serialize
|
|
109
|
+
arbitrary validators.
|
|
110
|
+
|
|
111
|
+
Legacy introspection stays enabled by default in 3.x. To opt out, import
|
|
112
|
+
`configureIntrospection` from `@simtlix/simfinity-core/introspection` in a separate
|
|
113
|
+
bootstrap module and call it with `{ fieldExtensions: false }` **before importing
|
|
114
|
+
the runtime or facades**. That direct import requires core as a direct dependency
|
|
115
|
+
pinned to the facade's exact version, including under pnpm or Yarn PnP. A late
|
|
116
|
+
call fails without deleting installed metadata. Migrate clients selecting
|
|
117
|
+
`__Field.extensions` before opting out. See the [descriptor reference](https://simtlix.github.io/simfinity.js/reference/client-descriptor.html)
|
|
118
|
+
for the format, protected HTTP access and composition limits.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@simtlix/simfinity-js",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.15.0",
|
|
4
4
|
"description": "MongoDB/Mongoose adapter and compatibility facade for Simfinity GraphQL APIs",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"types": "./types/index.d.ts",
|
|
@@ -29,8 +29,8 @@
|
|
|
29
29
|
"owner": "simtlix"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@simtlix/simfinity-core": "3.
|
|
33
|
-
"@simtlix/simfinity-mcp": "3.
|
|
32
|
+
"@simtlix/simfinity-core": "3.15.0",
|
|
33
|
+
"@simtlix/simfinity-mcp": "3.15.0"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
36
|
"src",
|
package/src/index.js
CHANGED
|
@@ -15,6 +15,7 @@ const runtime = createRuntime(mongoAdapter);
|
|
|
15
15
|
|
|
16
16
|
export const configureQueryLimits = runtime.configureQueryLimits;
|
|
17
17
|
export const configureMutationLimits = runtime.configureMutationLimits;
|
|
18
|
+
export const configureAfterCommit = runtime.configureAfterCommit;
|
|
18
19
|
export const connect = runtime.connect;
|
|
19
20
|
export const addNoEndpointType = runtime.addNoEndpointType;
|
|
20
21
|
export const createSchema = runtime.createSchema;
|
package/src/mongo/adapter.js
CHANGED
|
@@ -2,6 +2,7 @@ import mongoose from 'mongoose';
|
|
|
2
2
|
import { SimfinityError } from '@simtlix/simfinity-core';
|
|
3
3
|
import { MAX_RECORD_VERSION, isProtectedType, isVersionField, readStoredVersion, retainStoredVersion } from '@simtlix/simfinity-core/internal/record-version';
|
|
4
4
|
import { getListShape, normalizeConnectionField } from '@simtlix/simfinity-core/internal/relation-storage';
|
|
5
|
+
import { createTransactionCallbacks } from '@simtlix/simfinity-core/internal/transaction-callbacks';
|
|
5
6
|
|
|
6
7
|
import { createMongoModel } from './models.js';
|
|
7
8
|
import { MONGO_QUERY_OPERATORS, createMongoQueries } from './queries.js';
|
|
@@ -255,7 +256,8 @@ const nestedPathOf = (nested) => {
|
|
|
255
256
|
};
|
|
256
257
|
|
|
257
258
|
export const createMongoAdapter = (options) => {
|
|
258
|
-
const
|
|
259
|
+
const callbacks = createTransactionCallbacks();
|
|
260
|
+
const integrity = createMongoIntegrity(options, callbacks);
|
|
259
261
|
const collectionBatching = readCollectionBatching(options);
|
|
260
262
|
let queries;
|
|
261
263
|
let getRegistrations = () => [];
|
|
@@ -303,8 +305,8 @@ export const createMongoAdapter = (options) => {
|
|
|
303
305
|
|
|
304
306
|
// A supplied session or model is used as is; only an owned session without a model looks up
|
|
305
307
|
// the registrations.
|
|
306
|
-
const withOwnedTransaction = (session, body, Model,
|
|
307
|
-
session, body, (Model || session) ? Model : sessionModel(),
|
|
308
|
+
const withOwnedTransaction = (session, body, Model, transactionOptions) => withMongoTransaction(
|
|
309
|
+
session, body, (Model || session) ? Model : sessionModel(), transactionOptions, callbacks,
|
|
308
310
|
);
|
|
309
311
|
|
|
310
312
|
const versionedRead = (Model, query, many = false) => getRegistrations().some(
|
|
@@ -364,6 +366,7 @@ export const createMongoAdapter = (options) => {
|
|
|
364
366
|
castId: castObjectId,
|
|
365
367
|
initialize: integrity.initialize,
|
|
366
368
|
withTransaction: integrity.enabled ? integrity.withTransaction : withOwnedTransaction,
|
|
369
|
+
enqueueAfterCommit: callbacks.enqueue,
|
|
367
370
|
newRecord(Model, data, session) {
|
|
368
371
|
const record = new Model(data);
|
|
369
372
|
record.$session(session);
|
package/src/mongo/integrity.js
CHANGED
|
@@ -59,7 +59,7 @@ const valuesAt = (value, path) => {
|
|
|
59
59
|
// createMongoAdapter's options: the adapter reads the collection batching ones.
|
|
60
60
|
const ADAPTER_OPTIONS = ['referentialIntegrity', 'batchedCollections', 'collectionBatchSize'];
|
|
61
61
|
|
|
62
|
-
export const createMongoIntegrity = (options = {}) => {
|
|
62
|
+
export const createMongoIntegrity = (options = {}, callbacks) => {
|
|
63
63
|
if (!options || typeof options !== 'object' || Array.isArray(options)
|
|
64
64
|
|| Object.keys(options).some((key) => !ADAPTER_OPTIONS.includes(key))) {
|
|
65
65
|
throw invalid('Expected Mongo adapter options among referentialIntegrity, batchedCollections and collectionBatchSize');
|
|
@@ -316,7 +316,7 @@ export const createMongoIntegrity = (options = {}) => {
|
|
|
316
316
|
withTransaction(session, body, Model) {
|
|
317
317
|
assertReady();
|
|
318
318
|
if (session) assertSession(session);
|
|
319
|
-
return withMongoTransaction(session, body, Model || models.values().next().value, transactionOptions);
|
|
319
|
+
return withMongoTransaction(session, body, Model || models.values().next().value, transactionOptions, callbacks);
|
|
320
320
|
},
|
|
321
321
|
async saveRecord(Model, record, session) {
|
|
322
322
|
assertWrite(Model, session);
|
|
@@ -46,7 +46,7 @@ const endOwnedSession = async (session, failed) => {
|
|
|
46
46
|
}
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
export const withMongoTransaction = async (session, body, model, transactionOptions) => {
|
|
49
|
+
export const withMongoTransaction = async (session, body, model, transactionOptions, callbacks) => {
|
|
50
50
|
const connection = model?.db || mongoose.connection;
|
|
51
51
|
if (session) {
|
|
52
52
|
if (!session.inTransaction()) {
|
|
@@ -61,15 +61,21 @@ export const withMongoTransaction = async (session, body, model, transactionOpti
|
|
|
61
61
|
const mySession = connection === mongoose.connection
|
|
62
62
|
? await mongoose.startSession() : await connection.startSession();
|
|
63
63
|
let failed = false;
|
|
64
|
+
let confirmed;
|
|
64
65
|
try {
|
|
65
66
|
for (let attempt = 0; ; attempt++) {
|
|
66
67
|
if (transactionOptions) await mySession.startTransaction(transactionOptions);
|
|
67
68
|
else await mySession.startTransaction();
|
|
69
|
+
const effects = callbacks?.begin(mySession);
|
|
68
70
|
try {
|
|
69
71
|
const result = await body(mySession);
|
|
72
|
+
effects?.seal();
|
|
70
73
|
await commitWithRetry(mySession);
|
|
74
|
+
effects?.confirm();
|
|
75
|
+
confirmed = effects;
|
|
71
76
|
return result;
|
|
72
77
|
} catch (error) {
|
|
78
|
+
effects?.discard();
|
|
73
79
|
if (error?.errorLabels?.includes('UnknownTransactionCommitResult')) {
|
|
74
80
|
throw error;
|
|
75
81
|
}
|
|
@@ -96,6 +102,7 @@ export const withMongoTransaction = async (session, body, model, transactionOpti
|
|
|
96
102
|
failed = true;
|
|
97
103
|
throw error;
|
|
98
104
|
} finally {
|
|
99
|
-
|
|
105
|
+
// Cleanup can reject after commit. The confirmed effects still run, outside every retry catch.
|
|
106
|
+
try { await endOwnedSession(mySession, failed); } finally { await confirmed?.drain(); }
|
|
100
107
|
}
|
|
101
108
|
};
|
package/types/index.d.ts
CHANGED
|
@@ -13,6 +13,8 @@ import type {
|
|
|
13
13
|
GraphQLOutputType,
|
|
14
14
|
} from 'graphql';
|
|
15
15
|
import type {
|
|
16
|
+
AfterCommitOptions,
|
|
17
|
+
CommittedControllerCallbacks,
|
|
16
18
|
CreateSchemaOptions,
|
|
17
19
|
DatabaseAdapter,
|
|
18
20
|
MutationLimitsOptions,
|
|
@@ -36,6 +38,9 @@ export {
|
|
|
36
38
|
validators,
|
|
37
39
|
} from '@simtlix/simfinity-core';
|
|
38
40
|
export type {
|
|
41
|
+
AfterCommitErrorDetails,
|
|
42
|
+
AfterCommitOptions,
|
|
43
|
+
CommittedControllerCallbacks,
|
|
39
44
|
AuthPluginOptions,
|
|
40
45
|
AuthRule,
|
|
41
46
|
AuthRuleFunction,
|
|
@@ -155,8 +160,8 @@ export function getRegistrations(): RuntimeRegistration[];
|
|
|
155
160
|
* Core schema-building API (src/index.js)
|
|
156
161
|
* ========================================================================== */
|
|
157
162
|
|
|
158
|
-
/** Lifecycle hooks run before commit
|
|
159
|
-
export interface EntityController {
|
|
163
|
+
/** Lifecycle hooks run before commit; the inherited committed callbacks run after confirmed commit. */
|
|
164
|
+
export interface EntityController extends CommittedControllerCallbacks {
|
|
160
165
|
onSaving?(doc: any, args: any, session: any, context: any): void | Promise<void>;
|
|
161
166
|
onSaved?(result: any, args: any, session: any, context: any): void | Promise<void>;
|
|
162
167
|
onUpdating?(id: any, args: any, session: any, context: any): void | Promise<void>;
|
|
@@ -298,6 +303,9 @@ export function preventCreatingCollection(prevent: boolean): void;
|
|
|
298
303
|
*/
|
|
299
304
|
export function configureQueryLimits(options?: QueryLimitsOptions): void;
|
|
300
305
|
|
|
306
|
+
/** Configure the singleton runtime's best-effort reporting of after-commit callback failures. */
|
|
307
|
+
export function configureAfterCommit(options?: AfterCommitOptions): void;
|
|
308
|
+
|
|
301
309
|
/**
|
|
302
310
|
* Process-wide limit on nested added/updated/deleted entries per generated add, update or
|
|
303
311
|
* state-action mutation; unlimited by default, restored by calling it without options.
|