@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 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 isCasConflict(error) {
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
- try {
430
- return await applyVersionTransition(plan);
431
- } catch (error) {
432
- if (isCasConflict(error)) {
433
- return storageRevisionConflict(plan);
434
- }
435
- return { success: false, error: { type: "storage_error", cause: error } };
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
- if (isCasConflict(error)) {
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 isCasConflict(error) {
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
- try {
413
- return await applyVersionTransition(plan);
414
- } catch (error) {
415
- if (isCasConflict(error)) {
416
- return storageRevisionConflict(plan);
417
- }
418
- return { success: false, error: { type: "storage_error", cause: error } };
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
- if (isCasConflict(error)) {
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.8.0",
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.8.0"
42
+ "@form-engine-ts/core": "2.9.1"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "mongodb": "^6.0.0"