@form-engine-ts/storage-mongodb 2.8.0 → 2.9.1
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 +3 -0
- package/dist/index.cjs +43 -18
- package/dist/index.js +43 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -39,3 +39,6 @@ lives in `form_version_states`. `commitVersionTransition(plan)` compares `expect
|
|
|
39
39
|
`revision_conflict` to losing concurrent publishers; on a real MongoDB client, the state and record changes are committed
|
|
40
40
|
in one transaction. Clone, publish, and draft-delete plans persist complete version state, affected records, and audit
|
|
41
41
|
events. State/record/list reads and typed commit errors are exposed through the full `VersionedFormStorageAdapter` API.
|
|
42
|
+
Version commits require transaction support and return `transaction_unsupported` before any write when it is unavailable.
|
|
43
|
+
MongoDB duplicate-key failures are mapped to `draft_already_exists` or `revision_conflict` rather than a generic storage
|
|
44
|
+
error.
|
package/dist/index.cjs
CHANGED
|
@@ -102,15 +102,29 @@ function parseVersionRecordDocument(document) {
|
|
|
102
102
|
}
|
|
103
103
|
return cloneJson(document.record);
|
|
104
104
|
}
|
|
105
|
-
function
|
|
105
|
+
function mongoErrorMessage(error) {
|
|
106
|
+
return error instanceof Error ? error.message : isRecord(error) ? String(error.message ?? "") : "";
|
|
107
|
+
}
|
|
108
|
+
function isDuplicateKeyError(error) {
|
|
109
|
+
return isRecord(error) && error.code === 11e3;
|
|
110
|
+
}
|
|
111
|
+
function duplicateIndexName(error) {
|
|
112
|
+
if (!isDuplicateKeyError(error)) return void 0;
|
|
113
|
+
return /unique_(?:draft|published|version)_per_form/u.exec(mongoErrorMessage(error))?.[0];
|
|
114
|
+
}
|
|
115
|
+
function isRevisionConflict(error) {
|
|
106
116
|
if (!isRecord(error)) return false;
|
|
107
|
-
if (error.code === 11e3)
|
|
108
|
-
const message = error instanceof Error ? error.message : String(error.message ?? "");
|
|
109
|
-
return !/unique_(?:draft|published|version)_per_form/u.test(message);
|
|
110
|
-
}
|
|
117
|
+
if (error.code === 11e3) return duplicateIndexName(error) !== "unique_draft_per_form";
|
|
111
118
|
if (error.code === 112 || error.code === 251) return true;
|
|
112
119
|
return typeof error.hasErrorLabel === "function" && error.hasErrorLabel("TransientTransactionError") === true;
|
|
113
120
|
}
|
|
121
|
+
function isTransactionUnsupported(error) {
|
|
122
|
+
if (!isRecord(error)) return false;
|
|
123
|
+
if (error.code === 20 || error.code === 263 || error.code === 303) return true;
|
|
124
|
+
return /transaction numbers are only allowed|transactions? (?:are|is) not supported|replica set member|mongos/iu.test(
|
|
125
|
+
mongoErrorMessage(error)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
114
128
|
function parseSchemaDocument(document) {
|
|
115
129
|
try {
|
|
116
130
|
(0, import_core.assertValidFormSchema)(document.schema);
|
|
@@ -169,6 +183,18 @@ function createMongoDbStorage(options) {
|
|
|
169
183
|
}
|
|
170
184
|
};
|
|
171
185
|
};
|
|
186
|
+
const storageDraftAlreadyExists = async (plan) => {
|
|
187
|
+
const state = await versionStates.findOne({ _id: plan.formId });
|
|
188
|
+
const record = await versions.findOne({ formId: plan.formId, status: "draft" });
|
|
189
|
+
const currentDraftVersion = record?.version ?? state?.draftVersion ?? plan.draftToCreate?.version;
|
|
190
|
+
return currentDraftVersion === void 0 ? storageRevisionConflict(plan) : { success: false, error: { type: "draft_already_exists", currentDraftVersion } };
|
|
191
|
+
};
|
|
192
|
+
const mapCommitError = async (error, plan) => {
|
|
193
|
+
if (isTransactionUnsupported(error)) return { success: false, error: { type: "transaction_unsupported" } };
|
|
194
|
+
if (duplicateIndexName(error) === "unique_draft_per_form") return storageDraftAlreadyExists(plan);
|
|
195
|
+
if (isRevisionConflict(error)) return storageRevisionConflict(plan);
|
|
196
|
+
return { success: false, error: { type: "storage_error", cause: error } };
|
|
197
|
+
};
|
|
172
198
|
const upsertVersionRecord = async (record, session) => {
|
|
173
199
|
const stored = cloneJson(record);
|
|
174
200
|
await versions.updateOne(
|
|
@@ -426,16 +452,18 @@ function createMongoDbStorage(options) {
|
|
|
426
452
|
}
|
|
427
453
|
const client = options.db.client;
|
|
428
454
|
if (client === void 0 || typeof client.startSession !== "function") {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
455
|
+
return { success: false, error: { type: "transaction_unsupported" } };
|
|
456
|
+
}
|
|
457
|
+
let session;
|
|
458
|
+
try {
|
|
459
|
+
session = client.startSession();
|
|
460
|
+
} catch (error) {
|
|
461
|
+
return isTransactionUnsupported(error) ? { success: false, error: { type: "transaction_unsupported" } } : { success: false, error: { type: "storage_error", cause: error } };
|
|
462
|
+
}
|
|
463
|
+
if (typeof session.withTransaction !== "function") {
|
|
464
|
+
await session.endSession();
|
|
465
|
+
return { success: false, error: { type: "transaction_unsupported" } };
|
|
437
466
|
}
|
|
438
|
-
const session = client.startSession();
|
|
439
467
|
try {
|
|
440
468
|
let result = {
|
|
441
469
|
success: false,
|
|
@@ -446,10 +474,7 @@ function createMongoDbStorage(options) {
|
|
|
446
474
|
});
|
|
447
475
|
return result;
|
|
448
476
|
} catch (error) {
|
|
449
|
-
|
|
450
|
-
return storageRevisionConflict(plan);
|
|
451
|
-
}
|
|
452
|
-
return { success: false, error: { type: "storage_error", cause: error } };
|
|
477
|
+
return mapCommitError(error, plan);
|
|
453
478
|
} finally {
|
|
454
479
|
await session.endSession();
|
|
455
480
|
}
|
package/dist/index.js
CHANGED
|
@@ -85,15 +85,29 @@ function parseVersionRecordDocument(document) {
|
|
|
85
85
|
}
|
|
86
86
|
return cloneJson(document.record);
|
|
87
87
|
}
|
|
88
|
-
function
|
|
88
|
+
function mongoErrorMessage(error) {
|
|
89
|
+
return error instanceof Error ? error.message : isRecord(error) ? String(error.message ?? "") : "";
|
|
90
|
+
}
|
|
91
|
+
function isDuplicateKeyError(error) {
|
|
92
|
+
return isRecord(error) && error.code === 11e3;
|
|
93
|
+
}
|
|
94
|
+
function duplicateIndexName(error) {
|
|
95
|
+
if (!isDuplicateKeyError(error)) return void 0;
|
|
96
|
+
return /unique_(?:draft|published|version)_per_form/u.exec(mongoErrorMessage(error))?.[0];
|
|
97
|
+
}
|
|
98
|
+
function isRevisionConflict(error) {
|
|
89
99
|
if (!isRecord(error)) return false;
|
|
90
|
-
if (error.code === 11e3)
|
|
91
|
-
const message = error instanceof Error ? error.message : String(error.message ?? "");
|
|
92
|
-
return !/unique_(?:draft|published|version)_per_form/u.test(message);
|
|
93
|
-
}
|
|
100
|
+
if (error.code === 11e3) return duplicateIndexName(error) !== "unique_draft_per_form";
|
|
94
101
|
if (error.code === 112 || error.code === 251) return true;
|
|
95
102
|
return typeof error.hasErrorLabel === "function" && error.hasErrorLabel("TransientTransactionError") === true;
|
|
96
103
|
}
|
|
104
|
+
function isTransactionUnsupported(error) {
|
|
105
|
+
if (!isRecord(error)) return false;
|
|
106
|
+
if (error.code === 20 || error.code === 263 || error.code === 303) return true;
|
|
107
|
+
return /transaction numbers are only allowed|transactions? (?:are|is) not supported|replica set member|mongos/iu.test(
|
|
108
|
+
mongoErrorMessage(error)
|
|
109
|
+
);
|
|
110
|
+
}
|
|
97
111
|
function parseSchemaDocument(document) {
|
|
98
112
|
try {
|
|
99
113
|
assertValidFormSchema(document.schema);
|
|
@@ -152,6 +166,18 @@ function createMongoDbStorage(options) {
|
|
|
152
166
|
}
|
|
153
167
|
};
|
|
154
168
|
};
|
|
169
|
+
const storageDraftAlreadyExists = async (plan) => {
|
|
170
|
+
const state = await versionStates.findOne({ _id: plan.formId });
|
|
171
|
+
const record = await versions.findOne({ formId: plan.formId, status: "draft" });
|
|
172
|
+
const currentDraftVersion = record?.version ?? state?.draftVersion ?? plan.draftToCreate?.version;
|
|
173
|
+
return currentDraftVersion === void 0 ? storageRevisionConflict(plan) : { success: false, error: { type: "draft_already_exists", currentDraftVersion } };
|
|
174
|
+
};
|
|
175
|
+
const mapCommitError = async (error, plan) => {
|
|
176
|
+
if (isTransactionUnsupported(error)) return { success: false, error: { type: "transaction_unsupported" } };
|
|
177
|
+
if (duplicateIndexName(error) === "unique_draft_per_form") return storageDraftAlreadyExists(plan);
|
|
178
|
+
if (isRevisionConflict(error)) return storageRevisionConflict(plan);
|
|
179
|
+
return { success: false, error: { type: "storage_error", cause: error } };
|
|
180
|
+
};
|
|
155
181
|
const upsertVersionRecord = async (record, session) => {
|
|
156
182
|
const stored = cloneJson(record);
|
|
157
183
|
await versions.updateOne(
|
|
@@ -409,16 +435,18 @@ function createMongoDbStorage(options) {
|
|
|
409
435
|
}
|
|
410
436
|
const client = options.db.client;
|
|
411
437
|
if (client === void 0 || typeof client.startSession !== "function") {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
438
|
+
return { success: false, error: { type: "transaction_unsupported" } };
|
|
439
|
+
}
|
|
440
|
+
let session;
|
|
441
|
+
try {
|
|
442
|
+
session = client.startSession();
|
|
443
|
+
} catch (error) {
|
|
444
|
+
return isTransactionUnsupported(error) ? { success: false, error: { type: "transaction_unsupported" } } : { success: false, error: { type: "storage_error", cause: error } };
|
|
445
|
+
}
|
|
446
|
+
if (typeof session.withTransaction !== "function") {
|
|
447
|
+
await session.endSession();
|
|
448
|
+
return { success: false, error: { type: "transaction_unsupported" } };
|
|
420
449
|
}
|
|
421
|
-
const session = client.startSession();
|
|
422
450
|
try {
|
|
423
451
|
let result = {
|
|
424
452
|
success: false,
|
|
@@ -429,10 +457,7 @@ function createMongoDbStorage(options) {
|
|
|
429
457
|
});
|
|
430
458
|
return result;
|
|
431
459
|
} catch (error) {
|
|
432
|
-
|
|
433
|
-
return storageRevisionConflict(plan);
|
|
434
|
-
}
|
|
435
|
-
return { success: false, error: { type: "storage_error", cause: error } };
|
|
460
|
+
return mapCommitError(error, plan);
|
|
436
461
|
} finally {
|
|
437
462
|
await session.endSession();
|
|
438
463
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/storage-mongodb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.9.1",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"typescript"
|
|
40
40
|
],
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@form-engine-ts/core": "2.
|
|
42
|
+
"@form-engine-ts/core": "2.9.1"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"mongodb": "^6.0.0"
|