@form-engine-ts/storage-mongodb 2.7.0 → 2.9.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 +6 -2
- package/dist/index.cjs +88 -39
- package/dist/index.js +88 -39
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -33,8 +33,12 @@ Metadata filters and the generic submission-filter AST are translated to MongoDB
|
|
|
33
33
|
Legacy predicate filters remain supported and are applied client-side. `listTextAnswerPage` returns stable cursor pages
|
|
34
34
|
of individual text/textarea answers without loading every answer body at once.
|
|
35
35
|
|
|
36
|
-
Version records are stored in `form_versions`, with unique `(formId, version)`
|
|
37
|
-
|
|
36
|
+
Version records are stored in `form_versions`, with a unique `(formId, version)` index. Partial unique indexes allow at
|
|
37
|
+
most one Draft and one Published record per form, while any number of Archived records remain available. Transition state
|
|
38
|
+
lives in `form_version_states`. `commitVersionTransition(plan)` compares `expectedRevision` atomically and returns
|
|
38
39
|
`revision_conflict` to losing concurrent publishers; on a real MongoDB client, the state and record changes are committed
|
|
39
40
|
in one transaction. Clone, publish, and draft-delete plans persist complete version state, affected records, and audit
|
|
40
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,11 +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
|
|
117
|
+
if (error.code === 11e3) return duplicateIndexName(error) !== "unique_draft_per_form";
|
|
118
|
+
if (error.code === 112 || error.code === 251) return true;
|
|
108
119
|
return typeof error.hasErrorLabel === "function" && error.hasErrorLabel("TransientTransactionError") === true;
|
|
109
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
|
+
}
|
|
110
128
|
function parseSchemaDocument(document) {
|
|
111
129
|
try {
|
|
112
130
|
(0, import_core.assertValidFormSchema)(document.schema);
|
|
@@ -165,6 +183,18 @@ function createMongoDbStorage(options) {
|
|
|
165
183
|
}
|
|
166
184
|
};
|
|
167
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
|
+
};
|
|
168
198
|
const upsertVersionRecord = async (record, session) => {
|
|
169
199
|
const stored = cloneJson(record);
|
|
170
200
|
await versions.updateOne(
|
|
@@ -217,8 +247,8 @@ function createMongoDbStorage(options) {
|
|
|
217
247
|
);
|
|
218
248
|
}
|
|
219
249
|
if (plan.draftToCreate !== void 0) await upsertVersionRecord(plan.draftToCreate, session);
|
|
220
|
-
if (plan.publishedRecordToSave !== void 0) await upsertVersionRecord(plan.publishedRecordToSave, session);
|
|
221
250
|
for (const record of plan.archivedRecordsToSave ?? []) await upsertVersionRecord(record, session);
|
|
251
|
+
if (plan.publishedRecordToSave !== void 0) await upsertVersionRecord(plan.publishedRecordToSave, session);
|
|
222
252
|
for (const [eventIndex, event] of plan.events.entries()) {
|
|
223
253
|
await versionEvents.updateOne(
|
|
224
254
|
{ _id: versionEventDocumentId(plan.formId, plan.expectedRevision, eventIndex) },
|
|
@@ -311,12 +341,16 @@ function createMongoDbStorage(options) {
|
|
|
311
341
|
...hasMore && last !== void 0 ? { nextCursor: (0, import_core.encodeSubmissionCursor)({ submittedAt: last.submittedAt, responseId: last.id }) } : {}
|
|
312
342
|
};
|
|
313
343
|
},
|
|
314
|
-
async listTextAnswerPage(formId,
|
|
315
|
-
|
|
344
|
+
async listTextAnswerPage(formId, fieldIdOrOptions, providedOptions) {
|
|
345
|
+
const options2 = typeof fieldIdOrOptions === "string" ? providedOptions ?? {} : fieldIdOrOptions ?? {};
|
|
346
|
+
const requestedFieldIds = typeof fieldIdOrOptions === "string" ? [fieldIdOrOptions] : options2.fieldIds === void 0 ? void 0 : [...new Set(options2.fieldIds)];
|
|
347
|
+
if (requestedFieldIds?.some((fieldId) => fieldId.trim().length === 0)) {
|
|
348
|
+
throw new TypeError("fieldIds must not contain empty values.");
|
|
349
|
+
}
|
|
316
350
|
const pageSize = (0, import_core.normalizeSubmissionPageSize)(options2.pageSize);
|
|
317
351
|
const cursor = options2.cursor === void 0 ? void 0 : (0, import_core.decodeTextAnswerCursor)(options2.cursor);
|
|
318
|
-
if (cursor !== void 0 &&
|
|
319
|
-
throw new TypeError("Text answer cursor fieldId does not match the query.");
|
|
352
|
+
if (cursor !== void 0 && requestedFieldIds !== void 0 && !requestedFieldIds.includes(cursor.fieldId)) {
|
|
353
|
+
throw new TypeError("Text answer cursor fieldId does not match the query fields.");
|
|
320
354
|
}
|
|
321
355
|
const submittedAt = options2.since === void 0 && options2.until === void 0 ? void 0 : {
|
|
322
356
|
...options2.since === void 0 ? {} : { $gte: options2.since },
|
|
@@ -327,8 +361,8 @@ function createMongoDbStorage(options) {
|
|
|
327
361
|
...options2.version === void 0 ? {} : { formVersion: options2.version },
|
|
328
362
|
...options2.locale === void 0 ? {} : { "submission.locale": options2.locale },
|
|
329
363
|
...submittedAt === void 0 ? {} : { submittedAt },
|
|
330
|
-
...cursor === void 0 ? {} : { _id: { $
|
|
331
|
-
[`submission.values.${
|
|
364
|
+
...cursor === void 0 ? {} : { _id: { $gte: cursor.responseId } },
|
|
365
|
+
...requestedFieldIds?.length === 1 ? { [`submission.values.${requestedFieldIds[0]}`]: { $type: "string" } } : {}
|
|
332
366
|
};
|
|
333
367
|
const serverFilters = [baseFilter];
|
|
334
368
|
if (options2.filter !== void 0 && typeof options2.filter !== "function") {
|
|
@@ -339,22 +373,27 @@ function createMongoDbStorage(options) {
|
|
|
339
373
|
}
|
|
340
374
|
const filter = serverFilters.length === 1 ? baseFilter : { $and: serverFilters };
|
|
341
375
|
const sorted = submissions.find(filter).sort({ _id: 1 });
|
|
342
|
-
const documents =
|
|
376
|
+
const documents = await sorted.toArray();
|
|
343
377
|
const candidates = documents.map(parseSubmissionDocument).filter((submission) => (0, import_core.matchesSubmissionPageFilters)(submission, options2)).flatMap((submission) => {
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
{
|
|
348
|
-
|
|
349
|
-
formId: submission.formId,
|
|
350
|
-
formVersion: submission.formVersion,
|
|
351
|
-
fieldId,
|
|
352
|
-
text,
|
|
353
|
-
locale: submission.locale,
|
|
354
|
-
submittedAt: submission.submittedAt,
|
|
355
|
-
...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
|
|
378
|
+
const entries = requestedFieldIds === void 0 ? Object.entries(submission.values) : requestedFieldIds.map((fieldId) => [fieldId, submission.values[fieldId]]);
|
|
379
|
+
return entries.flatMap(([fieldId, text]) => {
|
|
380
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
381
|
+
if (cursor !== void 0 && (submission.id < cursor.responseId || submission.id === cursor.responseId && fieldId <= cursor.fieldId)) {
|
|
382
|
+
return [];
|
|
356
383
|
}
|
|
357
|
-
|
|
384
|
+
return [
|
|
385
|
+
{
|
|
386
|
+
responseId: submission.id,
|
|
387
|
+
formId: submission.formId,
|
|
388
|
+
formVersion: submission.formVersion,
|
|
389
|
+
fieldId,
|
|
390
|
+
text,
|
|
391
|
+
locale: submission.locale,
|
|
392
|
+
submittedAt: submission.submittedAt,
|
|
393
|
+
...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
|
|
394
|
+
}
|
|
395
|
+
];
|
|
396
|
+
});
|
|
358
397
|
});
|
|
359
398
|
const hasMore = candidates.length > pageSize;
|
|
360
399
|
const items = candidates.slice(0, pageSize);
|
|
@@ -362,7 +401,7 @@ function createMongoDbStorage(options) {
|
|
|
362
401
|
return {
|
|
363
402
|
items,
|
|
364
403
|
hasMore,
|
|
365
|
-
...hasMore && last !== void 0 ? { nextCursor: (0, import_core.encodeTextAnswerCursor)({ responseId: last.responseId, fieldId }) } : {}
|
|
404
|
+
...hasMore && last !== void 0 ? { nextCursor: (0, import_core.encodeTextAnswerCursor)({ responseId: last.responseId, fieldId: last.fieldId }) } : {}
|
|
366
405
|
};
|
|
367
406
|
},
|
|
368
407
|
async deleteSubmission(submissionId) {
|
|
@@ -413,16 +452,18 @@ function createMongoDbStorage(options) {
|
|
|
413
452
|
}
|
|
414
453
|
const client = options.db.client;
|
|
415
454
|
if (client === void 0 || typeof client.startSession !== "function") {
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
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" } };
|
|
424
466
|
}
|
|
425
|
-
const session = client.startSession();
|
|
426
467
|
try {
|
|
427
468
|
let result = {
|
|
428
469
|
success: false,
|
|
@@ -433,10 +474,7 @@ function createMongoDbStorage(options) {
|
|
|
433
474
|
});
|
|
434
475
|
return result;
|
|
435
476
|
} catch (error) {
|
|
436
|
-
|
|
437
|
-
return storageRevisionConflict(plan);
|
|
438
|
-
}
|
|
439
|
-
return { success: false, error: { type: "storage_error", cause: error } };
|
|
477
|
+
return mapCommitError(error, plan);
|
|
440
478
|
} finally {
|
|
441
479
|
await session.endSession();
|
|
442
480
|
}
|
|
@@ -448,8 +486,19 @@ function createMongoDbStorage(options) {
|
|
|
448
486
|
{ key: { "submission.locale": 1 }, name: "form_responses_locale" }
|
|
449
487
|
]),
|
|
450
488
|
versions.createIndexes([
|
|
451
|
-
{ key: { formId: 1, version: 1 }, name: "
|
|
452
|
-
{
|
|
489
|
+
{ key: { formId: 1, version: 1 }, name: "unique_version_per_form", unique: true },
|
|
490
|
+
{
|
|
491
|
+
key: { formId: 1 },
|
|
492
|
+
name: "unique_draft_per_form",
|
|
493
|
+
unique: true,
|
|
494
|
+
partialFilterExpression: { status: "draft" }
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
key: { formId: 1 },
|
|
498
|
+
name: "unique_published_per_form",
|
|
499
|
+
unique: true,
|
|
500
|
+
partialFilterExpression: { status: "published" }
|
|
501
|
+
}
|
|
453
502
|
]),
|
|
454
503
|
versionEvents.createIndexes([
|
|
455
504
|
{ key: { formId: 1, fromRevision: 1, eventIndex: 1 }, name: "form_version_events_revision" }
|
package/dist/index.js
CHANGED
|
@@ -85,11 +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
|
|
100
|
+
if (error.code === 11e3) return duplicateIndexName(error) !== "unique_draft_per_form";
|
|
101
|
+
if (error.code === 112 || error.code === 251) return true;
|
|
91
102
|
return typeof error.hasErrorLabel === "function" && error.hasErrorLabel("TransientTransactionError") === true;
|
|
92
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
|
+
}
|
|
93
111
|
function parseSchemaDocument(document) {
|
|
94
112
|
try {
|
|
95
113
|
assertValidFormSchema(document.schema);
|
|
@@ -148,6 +166,18 @@ function createMongoDbStorage(options) {
|
|
|
148
166
|
}
|
|
149
167
|
};
|
|
150
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
|
+
};
|
|
151
181
|
const upsertVersionRecord = async (record, session) => {
|
|
152
182
|
const stored = cloneJson(record);
|
|
153
183
|
await versions.updateOne(
|
|
@@ -200,8 +230,8 @@ function createMongoDbStorage(options) {
|
|
|
200
230
|
);
|
|
201
231
|
}
|
|
202
232
|
if (plan.draftToCreate !== void 0) await upsertVersionRecord(plan.draftToCreate, session);
|
|
203
|
-
if (plan.publishedRecordToSave !== void 0) await upsertVersionRecord(plan.publishedRecordToSave, session);
|
|
204
233
|
for (const record of plan.archivedRecordsToSave ?? []) await upsertVersionRecord(record, session);
|
|
234
|
+
if (plan.publishedRecordToSave !== void 0) await upsertVersionRecord(plan.publishedRecordToSave, session);
|
|
205
235
|
for (const [eventIndex, event] of plan.events.entries()) {
|
|
206
236
|
await versionEvents.updateOne(
|
|
207
237
|
{ _id: versionEventDocumentId(plan.formId, plan.expectedRevision, eventIndex) },
|
|
@@ -294,12 +324,16 @@ function createMongoDbStorage(options) {
|
|
|
294
324
|
...hasMore && last !== void 0 ? { nextCursor: encodeSubmissionCursor({ submittedAt: last.submittedAt, responseId: last.id }) } : {}
|
|
295
325
|
};
|
|
296
326
|
},
|
|
297
|
-
async listTextAnswerPage(formId,
|
|
298
|
-
|
|
327
|
+
async listTextAnswerPage(formId, fieldIdOrOptions, providedOptions) {
|
|
328
|
+
const options2 = typeof fieldIdOrOptions === "string" ? providedOptions ?? {} : fieldIdOrOptions ?? {};
|
|
329
|
+
const requestedFieldIds = typeof fieldIdOrOptions === "string" ? [fieldIdOrOptions] : options2.fieldIds === void 0 ? void 0 : [...new Set(options2.fieldIds)];
|
|
330
|
+
if (requestedFieldIds?.some((fieldId) => fieldId.trim().length === 0)) {
|
|
331
|
+
throw new TypeError("fieldIds must not contain empty values.");
|
|
332
|
+
}
|
|
299
333
|
const pageSize = normalizeSubmissionPageSize(options2.pageSize);
|
|
300
334
|
const cursor = options2.cursor === void 0 ? void 0 : decodeTextAnswerCursor(options2.cursor);
|
|
301
|
-
if (cursor !== void 0 &&
|
|
302
|
-
throw new TypeError("Text answer cursor fieldId does not match the query.");
|
|
335
|
+
if (cursor !== void 0 && requestedFieldIds !== void 0 && !requestedFieldIds.includes(cursor.fieldId)) {
|
|
336
|
+
throw new TypeError("Text answer cursor fieldId does not match the query fields.");
|
|
303
337
|
}
|
|
304
338
|
const submittedAt = options2.since === void 0 && options2.until === void 0 ? void 0 : {
|
|
305
339
|
...options2.since === void 0 ? {} : { $gte: options2.since },
|
|
@@ -310,8 +344,8 @@ function createMongoDbStorage(options) {
|
|
|
310
344
|
...options2.version === void 0 ? {} : { formVersion: options2.version },
|
|
311
345
|
...options2.locale === void 0 ? {} : { "submission.locale": options2.locale },
|
|
312
346
|
...submittedAt === void 0 ? {} : { submittedAt },
|
|
313
|
-
...cursor === void 0 ? {} : { _id: { $
|
|
314
|
-
[`submission.values.${
|
|
347
|
+
...cursor === void 0 ? {} : { _id: { $gte: cursor.responseId } },
|
|
348
|
+
...requestedFieldIds?.length === 1 ? { [`submission.values.${requestedFieldIds[0]}`]: { $type: "string" } } : {}
|
|
315
349
|
};
|
|
316
350
|
const serverFilters = [baseFilter];
|
|
317
351
|
if (options2.filter !== void 0 && typeof options2.filter !== "function") {
|
|
@@ -322,22 +356,27 @@ function createMongoDbStorage(options) {
|
|
|
322
356
|
}
|
|
323
357
|
const filter = serverFilters.length === 1 ? baseFilter : { $and: serverFilters };
|
|
324
358
|
const sorted = submissions.find(filter).sort({ _id: 1 });
|
|
325
|
-
const documents =
|
|
359
|
+
const documents = await sorted.toArray();
|
|
326
360
|
const candidates = documents.map(parseSubmissionDocument).filter((submission) => matchesSubmissionPageFilters(submission, options2)).flatMap((submission) => {
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
{
|
|
331
|
-
|
|
332
|
-
formId: submission.formId,
|
|
333
|
-
formVersion: submission.formVersion,
|
|
334
|
-
fieldId,
|
|
335
|
-
text,
|
|
336
|
-
locale: submission.locale,
|
|
337
|
-
submittedAt: submission.submittedAt,
|
|
338
|
-
...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
|
|
361
|
+
const entries = requestedFieldIds === void 0 ? Object.entries(submission.values) : requestedFieldIds.map((fieldId) => [fieldId, submission.values[fieldId]]);
|
|
362
|
+
return entries.flatMap(([fieldId, text]) => {
|
|
363
|
+
if (typeof text !== "string" || text.length === 0) return [];
|
|
364
|
+
if (cursor !== void 0 && (submission.id < cursor.responseId || submission.id === cursor.responseId && fieldId <= cursor.fieldId)) {
|
|
365
|
+
return [];
|
|
339
366
|
}
|
|
340
|
-
|
|
367
|
+
return [
|
|
368
|
+
{
|
|
369
|
+
responseId: submission.id,
|
|
370
|
+
formId: submission.formId,
|
|
371
|
+
formVersion: submission.formVersion,
|
|
372
|
+
fieldId,
|
|
373
|
+
text,
|
|
374
|
+
locale: submission.locale,
|
|
375
|
+
submittedAt: submission.submittedAt,
|
|
376
|
+
...submission.metadata === void 0 ? {} : { metadata: submission.metadata }
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
});
|
|
341
380
|
});
|
|
342
381
|
const hasMore = candidates.length > pageSize;
|
|
343
382
|
const items = candidates.slice(0, pageSize);
|
|
@@ -345,7 +384,7 @@ function createMongoDbStorage(options) {
|
|
|
345
384
|
return {
|
|
346
385
|
items,
|
|
347
386
|
hasMore,
|
|
348
|
-
...hasMore && last !== void 0 ? { nextCursor: encodeTextAnswerCursor({ responseId: last.responseId, fieldId }) } : {}
|
|
387
|
+
...hasMore && last !== void 0 ? { nextCursor: encodeTextAnswerCursor({ responseId: last.responseId, fieldId: last.fieldId }) } : {}
|
|
349
388
|
};
|
|
350
389
|
},
|
|
351
390
|
async deleteSubmission(submissionId) {
|
|
@@ -396,16 +435,18 @@ function createMongoDbStorage(options) {
|
|
|
396
435
|
}
|
|
397
436
|
const client = options.db.client;
|
|
398
437
|
if (client === void 0 || typeof client.startSession !== "function") {
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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" } };
|
|
407
449
|
}
|
|
408
|
-
const session = client.startSession();
|
|
409
450
|
try {
|
|
410
451
|
let result = {
|
|
411
452
|
success: false,
|
|
@@ -416,10 +457,7 @@ function createMongoDbStorage(options) {
|
|
|
416
457
|
});
|
|
417
458
|
return result;
|
|
418
459
|
} catch (error) {
|
|
419
|
-
|
|
420
|
-
return storageRevisionConflict(plan);
|
|
421
|
-
}
|
|
422
|
-
return { success: false, error: { type: "storage_error", cause: error } };
|
|
460
|
+
return mapCommitError(error, plan);
|
|
423
461
|
} finally {
|
|
424
462
|
await session.endSession();
|
|
425
463
|
}
|
|
@@ -431,8 +469,19 @@ function createMongoDbStorage(options) {
|
|
|
431
469
|
{ key: { "submission.locale": 1 }, name: "form_responses_locale" }
|
|
432
470
|
]),
|
|
433
471
|
versions.createIndexes([
|
|
434
|
-
{ key: { formId: 1, version: 1 }, name: "
|
|
435
|
-
{
|
|
472
|
+
{ key: { formId: 1, version: 1 }, name: "unique_version_per_form", unique: true },
|
|
473
|
+
{
|
|
474
|
+
key: { formId: 1 },
|
|
475
|
+
name: "unique_draft_per_form",
|
|
476
|
+
unique: true,
|
|
477
|
+
partialFilterExpression: { status: "draft" }
|
|
478
|
+
},
|
|
479
|
+
{
|
|
480
|
+
key: { formId: 1 },
|
|
481
|
+
name: "unique_published_per_form",
|
|
482
|
+
unique: true,
|
|
483
|
+
partialFilterExpression: { status: "published" }
|
|
484
|
+
}
|
|
436
485
|
]),
|
|
437
486
|
versionEvents.createIndexes([
|
|
438
487
|
{ key: { formId: 1, fromRevision: 1, eventIndex: 1 }, name: "form_version_events_revision" }
|
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.0",
|
|
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.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"mongodb": "^6.0.0"
|