@form-engine-ts/storage-mongodb 2.3.0 → 2.6.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 +9 -0
- package/dist/index.cjs +152 -4
- package/dist/index.d.cts +4 -2
- package/dist/index.d.ts +4 -2
- package/dist/index.js +159 -5
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -26,3 +26,12 @@ const submissions = await storage.listSubmissions(schema.id, schema.version, {
|
|
|
26
26
|
```
|
|
27
27
|
|
|
28
28
|
The caller owns the MongoDB connection lifecycle. Collection names can be customized in the factory options. `createIndexes()` creates named indexes for form/timestamp range scans and locale queries. Range boundaries are inclusive, and MongoDB sorts by submission time and then ID. `clearResponses(formId)` preserves schemas and other forms; `clear()` removes documents only from the configured collections and does not drop collections or indexes.
|
|
29
|
+
|
|
30
|
+
`listSubmissionPage(formId, { version, pageSize, cursor, since, until, locale })` performs bounded reads using the
|
|
31
|
+
compound `submittedAt`/response-ID cursor. Run `createIndexes()` after upgrading to create the matching compound index.
|
|
32
|
+
`metadataFilters` and custom `filter` predicates are applied before page sizing.
|
|
33
|
+
|
|
34
|
+
Version records are stored in `form_versions`, with unique `(formId, version)` and `(formId, status)` indexes. Transition
|
|
35
|
+
state lives in `form_version_states`. `commitVersionTransition(plan)` compares `expectedRevision` atomically and returns
|
|
36
|
+
`revision_conflict` to losing concurrent publishers; on a real MongoDB client, the state and record changes are committed
|
|
37
|
+
in one transaction.
|
package/dist/index.cjs
CHANGED
|
@@ -42,6 +42,33 @@ function parseSubmission(value, location) {
|
|
|
42
42
|
function schemaDocumentId(formId, formVersion) {
|
|
43
43
|
return `schema:${encodeURIComponent(formId)}:${formVersion}`;
|
|
44
44
|
}
|
|
45
|
+
function versionDocumentId(formId, formVersion) {
|
|
46
|
+
return `version:${encodeURIComponent(formId)}:${formVersion}`;
|
|
47
|
+
}
|
|
48
|
+
function assertVersionRecord(record, formId, expectedStatus) {
|
|
49
|
+
if (record.formId !== formId || record.schema.id !== formId || record.schema.version !== record.version || !Number.isSafeInteger(record.version) || record.version < 1 || !Number.isSafeInteger(record.revision) || record.revision < 0 || expectedStatus !== void 0 && record.status !== expectedStatus) {
|
|
50
|
+
throw new TypeError("Version transition record is inconsistent.");
|
|
51
|
+
}
|
|
52
|
+
(0, import_core.assertValidFormSchema)(record.schema);
|
|
53
|
+
}
|
|
54
|
+
function assertTransitionPlan(plan) {
|
|
55
|
+
if (plan.formId.trim().length === 0 || !Number.isSafeInteger(plan.expectedRevision) || plan.expectedRevision < 0 || plan.nextRevision !== plan.expectedRevision + 1 || !Number.isFinite(Date.parse(plan.timestamp))) {
|
|
56
|
+
throw new TypeError("Version transition plan is invalid.");
|
|
57
|
+
}
|
|
58
|
+
if (plan.draftToCreate !== void 0) assertVersionRecord(plan.draftToCreate, plan.formId, "draft");
|
|
59
|
+
if (plan.publishedRecordToSave !== void 0) {
|
|
60
|
+
assertVersionRecord(plan.publishedRecordToSave, plan.formId, "published");
|
|
61
|
+
}
|
|
62
|
+
for (const record of plan.archivedRecordsToSave ?? []) assertVersionRecord(record, plan.formId, "archived");
|
|
63
|
+
if (plan.draftToDeleteVersion !== void 0 && (!Number.isSafeInteger(plan.draftToDeleteVersion) || plan.draftToDeleteVersion < 1)) {
|
|
64
|
+
throw new TypeError("draftToDeleteVersion must be a positive safe integer.");
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function isCasConflict(error) {
|
|
68
|
+
if (!isRecord(error)) return false;
|
|
69
|
+
if (error.code === 11e3 || error.code === 112 || error.code === 251) return true;
|
|
70
|
+
return typeof error.hasErrorLabel === "function" && error.hasErrorLabel("TransientTransactionError") === true;
|
|
71
|
+
}
|
|
45
72
|
function parseSchemaDocument(document) {
|
|
46
73
|
try {
|
|
47
74
|
(0, import_core.assertValidFormSchema)(document.schema);
|
|
@@ -73,8 +100,58 @@ function createMongoDbStorage(options) {
|
|
|
73
100
|
"form_responses",
|
|
74
101
|
"responsesCollectionName"
|
|
75
102
|
);
|
|
103
|
+
const versionsCollectionName = collectionName(
|
|
104
|
+
options.versionsCollectionName,
|
|
105
|
+
"form_versions",
|
|
106
|
+
"versionsCollectionName"
|
|
107
|
+
);
|
|
108
|
+
const versionStatesCollectionName = collectionName(
|
|
109
|
+
options.versionStatesCollectionName,
|
|
110
|
+
"form_version_states",
|
|
111
|
+
"versionStatesCollectionName"
|
|
112
|
+
);
|
|
76
113
|
const schemas = options.db.collection(schemasCollectionName);
|
|
77
114
|
const submissions = options.db.collection(responsesCollectionName);
|
|
115
|
+
const versions = options.db.collection(versionsCollectionName);
|
|
116
|
+
const versionStates = options.db.collection(versionStatesCollectionName);
|
|
117
|
+
const upsertVersionRecord = async (record, session) => {
|
|
118
|
+
const stored = cloneJson(record);
|
|
119
|
+
await versions.updateOne(
|
|
120
|
+
{ _id: versionDocumentId(record.formId, record.version) },
|
|
121
|
+
{ $set: { formId: record.formId, version: record.version, status: record.status, record: stored } },
|
|
122
|
+
{ upsert: true, ...session === void 0 ? {} : { session } }
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
const applyVersionTransition = async (plan, session) => {
|
|
126
|
+
const stateSet = { revision: plan.nextRevision, updatedAt: plan.timestamp };
|
|
127
|
+
if (plan.draftToCreate !== void 0) stateSet.draftVersion = plan.draftToCreate.version;
|
|
128
|
+
if (plan.publishedRecordToSave !== void 0) stateSet.publishedVersion = plan.publishedRecordToSave.version;
|
|
129
|
+
const stateUpdate = {
|
|
130
|
+
$set: stateSet,
|
|
131
|
+
...plan.draftToDeleteVersion === void 0 ? {} : { $unset: { draftVersion: "" } }
|
|
132
|
+
};
|
|
133
|
+
const stateResult = await versionStates.updateOne(
|
|
134
|
+
{ _id: plan.formId, revision: plan.expectedRevision },
|
|
135
|
+
stateUpdate,
|
|
136
|
+
{
|
|
137
|
+
upsert: plan.expectedRevision === 0,
|
|
138
|
+
...session === void 0 ? {} : { session }
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
if (stateResult.matchedCount === 0 && stateResult.upsertedCount === 0) {
|
|
142
|
+
return { success: false, error: "revision_conflict" };
|
|
143
|
+
}
|
|
144
|
+
if (plan.draftToDeleteVersion !== void 0) {
|
|
145
|
+
await versions.deleteOne(
|
|
146
|
+
{ _id: versionDocumentId(plan.formId, plan.draftToDeleteVersion) },
|
|
147
|
+
session === void 0 ? {} : { session }
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (plan.draftToCreate !== void 0) await upsertVersionRecord(plan.draftToCreate, session);
|
|
151
|
+
if (plan.publishedRecordToSave !== void 0) await upsertVersionRecord(plan.publishedRecordToSave, session);
|
|
152
|
+
for (const record of plan.archivedRecordsToSave ?? []) await upsertVersionRecord(record, session);
|
|
153
|
+
return { success: true };
|
|
154
|
+
};
|
|
78
155
|
return {
|
|
79
156
|
async saveSchema(schema) {
|
|
80
157
|
(0, import_core.assertValidFormSchema)(schema);
|
|
@@ -118,6 +195,38 @@ function createMongoDbStorage(options) {
|
|
|
118
195
|
}).sort({ submittedAt: 1, _id: 1 }).toArray();
|
|
119
196
|
return documents.map(parseSubmissionDocument);
|
|
120
197
|
},
|
|
198
|
+
async listSubmissionPage(formId, options2 = {}) {
|
|
199
|
+
const pageSize = (0, import_core.normalizeSubmissionPageSize)(options2.pageSize);
|
|
200
|
+
const cursor = options2.cursor === void 0 ? void 0 : (0, import_core.decodeSubmissionCursor)(options2.cursor);
|
|
201
|
+
const submittedAt = options2.since === void 0 && options2.until === void 0 ? void 0 : {
|
|
202
|
+
...options2.since === void 0 ? {} : { $gte: options2.since },
|
|
203
|
+
...options2.until === void 0 ? {} : { $lte: options2.until }
|
|
204
|
+
};
|
|
205
|
+
const filter = {
|
|
206
|
+
formId,
|
|
207
|
+
...options2.version === void 0 ? {} : { formVersion: options2.version },
|
|
208
|
+
...options2.locale === void 0 ? {} : { "submission.locale": options2.locale },
|
|
209
|
+
...submittedAt === void 0 ? {} : { submittedAt },
|
|
210
|
+
...cursor === void 0 ? {} : {
|
|
211
|
+
$or: [
|
|
212
|
+
{ submittedAt: { $gt: cursor.submittedAt } },
|
|
213
|
+
{ submittedAt: cursor.submittedAt, _id: { $gt: cursor.responseId } }
|
|
214
|
+
]
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
const sorted = submissions.find(filter).sort({ submittedAt: 1, _id: 1 });
|
|
218
|
+
const requiresClientFiltering = options2.filter !== void 0 || options2.metadataFilters !== void 0;
|
|
219
|
+
const documents = requiresClientFiltering ? await sorted.toArray() : await sorted.limit(pageSize + 1).toArray();
|
|
220
|
+
const candidates = documents.map(parseSubmissionDocument).filter((item) => (0, import_core.matchesSubmissionPageFilters)(item, options2));
|
|
221
|
+
const hasMore = candidates.length > pageSize;
|
|
222
|
+
const items = candidates.slice(0, pageSize);
|
|
223
|
+
const last = items.at(-1);
|
|
224
|
+
return {
|
|
225
|
+
items,
|
|
226
|
+
hasMore,
|
|
227
|
+
...hasMore && last !== void 0 ? { nextCursor: (0, import_core.encodeSubmissionCursor)({ submittedAt: last.submittedAt, responseId: last.id }) } : {}
|
|
228
|
+
};
|
|
229
|
+
},
|
|
121
230
|
async deleteSubmission(submissionId) {
|
|
122
231
|
await submissions.deleteOne({ _id: submissionId });
|
|
123
232
|
},
|
|
@@ -125,12 +234,51 @@ function createMongoDbStorage(options) {
|
|
|
125
234
|
await submissions.deleteMany({ formId });
|
|
126
235
|
},
|
|
127
236
|
async clear() {
|
|
128
|
-
await Promise.all([
|
|
237
|
+
await Promise.all([
|
|
238
|
+
schemas.deleteMany({}),
|
|
239
|
+
submissions.deleteMany({}),
|
|
240
|
+
versions.deleteMany({}),
|
|
241
|
+
versionStates.deleteMany({})
|
|
242
|
+
]);
|
|
243
|
+
},
|
|
244
|
+
async commitVersionTransition(plan) {
|
|
245
|
+
assertTransitionPlan(plan);
|
|
246
|
+
const client = options.db.client;
|
|
247
|
+
if (client === void 0 || typeof client.startSession !== "function") {
|
|
248
|
+
try {
|
|
249
|
+
return await applyVersionTransition(plan);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (isCasConflict(error)) return { success: false, error: "revision_conflict" };
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
const session = client.startSession();
|
|
256
|
+
try {
|
|
257
|
+
let result = {
|
|
258
|
+
success: false,
|
|
259
|
+
error: "revision_conflict"
|
|
260
|
+
};
|
|
261
|
+
await session.withTransaction(async () => {
|
|
262
|
+
result = await applyVersionTransition(plan, session);
|
|
263
|
+
});
|
|
264
|
+
return result;
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (isCasConflict(error)) return { success: false, error: "revision_conflict" };
|
|
267
|
+
throw error;
|
|
268
|
+
} finally {
|
|
269
|
+
await session.endSession();
|
|
270
|
+
}
|
|
129
271
|
},
|
|
130
272
|
async createIndexes() {
|
|
131
|
-
await
|
|
132
|
-
|
|
133
|
-
|
|
273
|
+
await Promise.all([
|
|
274
|
+
submissions.createIndexes([
|
|
275
|
+
{ key: { formId: 1, submittedAt: 1, _id: 1 }, name: "form_responses_form_submitted_at_id" },
|
|
276
|
+
{ key: { "submission.locale": 1 }, name: "form_responses_locale" }
|
|
277
|
+
]),
|
|
278
|
+
versions.createIndexes([
|
|
279
|
+
{ key: { formId: 1, version: 1 }, name: "form_versions_form_version", unique: true },
|
|
280
|
+
{ key: { formId: 1, status: 1 }, name: "form_versions_form_status" }
|
|
281
|
+
])
|
|
134
282
|
]);
|
|
135
283
|
}
|
|
136
284
|
};
|
package/dist/index.d.cts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PagedSubmissionStorageAdapter, VersionedFormStorageAdapter } from '@form-engine-ts/core';
|
|
2
2
|
import { Db } from 'mongodb';
|
|
3
3
|
|
|
4
4
|
interface MongoDbStorageOptions {
|
|
5
5
|
readonly db: Db;
|
|
6
6
|
readonly schemasCollectionName?: string;
|
|
7
7
|
readonly responsesCollectionName?: string;
|
|
8
|
+
readonly versionsCollectionName?: string;
|
|
9
|
+
readonly versionStatesCollectionName?: string;
|
|
8
10
|
}
|
|
9
|
-
interface MongoDbStorageAdapter extends
|
|
11
|
+
interface MongoDbStorageAdapter extends PagedSubmissionStorageAdapter, VersionedFormStorageAdapter {
|
|
10
12
|
createIndexes(): Promise<void>;
|
|
11
13
|
}
|
|
12
14
|
declare function createMongoDbStorage(options: MongoDbStorageOptions): MongoDbStorageAdapter;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { PagedSubmissionStorageAdapter, VersionedFormStorageAdapter } from '@form-engine-ts/core';
|
|
2
2
|
import { Db } from 'mongodb';
|
|
3
3
|
|
|
4
4
|
interface MongoDbStorageOptions {
|
|
5
5
|
readonly db: Db;
|
|
6
6
|
readonly schemasCollectionName?: string;
|
|
7
7
|
readonly responsesCollectionName?: string;
|
|
8
|
+
readonly versionsCollectionName?: string;
|
|
9
|
+
readonly versionStatesCollectionName?: string;
|
|
8
10
|
}
|
|
9
|
-
interface MongoDbStorageAdapter extends
|
|
11
|
+
interface MongoDbStorageAdapter extends PagedSubmissionStorageAdapter, VersionedFormStorageAdapter {
|
|
10
12
|
createIndexes(): Promise<void>;
|
|
11
13
|
}
|
|
12
14
|
declare function createMongoDbStorage(options: MongoDbStorageOptions): MongoDbStorageAdapter;
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
assertValidFormSchema,
|
|
4
|
+
decodeSubmissionCursor,
|
|
5
|
+
encodeSubmissionCursor,
|
|
6
|
+
matchesSubmissionPageFilters,
|
|
7
|
+
normalizeSubmissionPageSize
|
|
8
|
+
} from "@form-engine-ts/core";
|
|
3
9
|
function cloneJson(value) {
|
|
4
10
|
return JSON.parse(JSON.stringify(value));
|
|
5
11
|
}
|
|
@@ -18,6 +24,33 @@ function parseSubmission(value, location) {
|
|
|
18
24
|
function schemaDocumentId(formId, formVersion) {
|
|
19
25
|
return `schema:${encodeURIComponent(formId)}:${formVersion}`;
|
|
20
26
|
}
|
|
27
|
+
function versionDocumentId(formId, formVersion) {
|
|
28
|
+
return `version:${encodeURIComponent(formId)}:${formVersion}`;
|
|
29
|
+
}
|
|
30
|
+
function assertVersionRecord(record, formId, expectedStatus) {
|
|
31
|
+
if (record.formId !== formId || record.schema.id !== formId || record.schema.version !== record.version || !Number.isSafeInteger(record.version) || record.version < 1 || !Number.isSafeInteger(record.revision) || record.revision < 0 || expectedStatus !== void 0 && record.status !== expectedStatus) {
|
|
32
|
+
throw new TypeError("Version transition record is inconsistent.");
|
|
33
|
+
}
|
|
34
|
+
assertValidFormSchema(record.schema);
|
|
35
|
+
}
|
|
36
|
+
function assertTransitionPlan(plan) {
|
|
37
|
+
if (plan.formId.trim().length === 0 || !Number.isSafeInteger(plan.expectedRevision) || plan.expectedRevision < 0 || plan.nextRevision !== plan.expectedRevision + 1 || !Number.isFinite(Date.parse(plan.timestamp))) {
|
|
38
|
+
throw new TypeError("Version transition plan is invalid.");
|
|
39
|
+
}
|
|
40
|
+
if (plan.draftToCreate !== void 0) assertVersionRecord(plan.draftToCreate, plan.formId, "draft");
|
|
41
|
+
if (plan.publishedRecordToSave !== void 0) {
|
|
42
|
+
assertVersionRecord(plan.publishedRecordToSave, plan.formId, "published");
|
|
43
|
+
}
|
|
44
|
+
for (const record of plan.archivedRecordsToSave ?? []) assertVersionRecord(record, plan.formId, "archived");
|
|
45
|
+
if (plan.draftToDeleteVersion !== void 0 && (!Number.isSafeInteger(plan.draftToDeleteVersion) || plan.draftToDeleteVersion < 1)) {
|
|
46
|
+
throw new TypeError("draftToDeleteVersion must be a positive safe integer.");
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function isCasConflict(error) {
|
|
50
|
+
if (!isRecord(error)) return false;
|
|
51
|
+
if (error.code === 11e3 || error.code === 112 || error.code === 251) return true;
|
|
52
|
+
return typeof error.hasErrorLabel === "function" && error.hasErrorLabel("TransientTransactionError") === true;
|
|
53
|
+
}
|
|
21
54
|
function parseSchemaDocument(document) {
|
|
22
55
|
try {
|
|
23
56
|
assertValidFormSchema(document.schema);
|
|
@@ -49,8 +82,58 @@ function createMongoDbStorage(options) {
|
|
|
49
82
|
"form_responses",
|
|
50
83
|
"responsesCollectionName"
|
|
51
84
|
);
|
|
85
|
+
const versionsCollectionName = collectionName(
|
|
86
|
+
options.versionsCollectionName,
|
|
87
|
+
"form_versions",
|
|
88
|
+
"versionsCollectionName"
|
|
89
|
+
);
|
|
90
|
+
const versionStatesCollectionName = collectionName(
|
|
91
|
+
options.versionStatesCollectionName,
|
|
92
|
+
"form_version_states",
|
|
93
|
+
"versionStatesCollectionName"
|
|
94
|
+
);
|
|
52
95
|
const schemas = options.db.collection(schemasCollectionName);
|
|
53
96
|
const submissions = options.db.collection(responsesCollectionName);
|
|
97
|
+
const versions = options.db.collection(versionsCollectionName);
|
|
98
|
+
const versionStates = options.db.collection(versionStatesCollectionName);
|
|
99
|
+
const upsertVersionRecord = async (record, session) => {
|
|
100
|
+
const stored = cloneJson(record);
|
|
101
|
+
await versions.updateOne(
|
|
102
|
+
{ _id: versionDocumentId(record.formId, record.version) },
|
|
103
|
+
{ $set: { formId: record.formId, version: record.version, status: record.status, record: stored } },
|
|
104
|
+
{ upsert: true, ...session === void 0 ? {} : { session } }
|
|
105
|
+
);
|
|
106
|
+
};
|
|
107
|
+
const applyVersionTransition = async (plan, session) => {
|
|
108
|
+
const stateSet = { revision: plan.nextRevision, updatedAt: plan.timestamp };
|
|
109
|
+
if (plan.draftToCreate !== void 0) stateSet.draftVersion = plan.draftToCreate.version;
|
|
110
|
+
if (plan.publishedRecordToSave !== void 0) stateSet.publishedVersion = plan.publishedRecordToSave.version;
|
|
111
|
+
const stateUpdate = {
|
|
112
|
+
$set: stateSet,
|
|
113
|
+
...plan.draftToDeleteVersion === void 0 ? {} : { $unset: { draftVersion: "" } }
|
|
114
|
+
};
|
|
115
|
+
const stateResult = await versionStates.updateOne(
|
|
116
|
+
{ _id: plan.formId, revision: plan.expectedRevision },
|
|
117
|
+
stateUpdate,
|
|
118
|
+
{
|
|
119
|
+
upsert: plan.expectedRevision === 0,
|
|
120
|
+
...session === void 0 ? {} : { session }
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
if (stateResult.matchedCount === 0 && stateResult.upsertedCount === 0) {
|
|
124
|
+
return { success: false, error: "revision_conflict" };
|
|
125
|
+
}
|
|
126
|
+
if (plan.draftToDeleteVersion !== void 0) {
|
|
127
|
+
await versions.deleteOne(
|
|
128
|
+
{ _id: versionDocumentId(plan.formId, plan.draftToDeleteVersion) },
|
|
129
|
+
session === void 0 ? {} : { session }
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
if (plan.draftToCreate !== void 0) await upsertVersionRecord(plan.draftToCreate, session);
|
|
133
|
+
if (plan.publishedRecordToSave !== void 0) await upsertVersionRecord(plan.publishedRecordToSave, session);
|
|
134
|
+
for (const record of plan.archivedRecordsToSave ?? []) await upsertVersionRecord(record, session);
|
|
135
|
+
return { success: true };
|
|
136
|
+
};
|
|
54
137
|
return {
|
|
55
138
|
async saveSchema(schema) {
|
|
56
139
|
assertValidFormSchema(schema);
|
|
@@ -94,6 +177,38 @@ function createMongoDbStorage(options) {
|
|
|
94
177
|
}).sort({ submittedAt: 1, _id: 1 }).toArray();
|
|
95
178
|
return documents.map(parseSubmissionDocument);
|
|
96
179
|
},
|
|
180
|
+
async listSubmissionPage(formId, options2 = {}) {
|
|
181
|
+
const pageSize = normalizeSubmissionPageSize(options2.pageSize);
|
|
182
|
+
const cursor = options2.cursor === void 0 ? void 0 : decodeSubmissionCursor(options2.cursor);
|
|
183
|
+
const submittedAt = options2.since === void 0 && options2.until === void 0 ? void 0 : {
|
|
184
|
+
...options2.since === void 0 ? {} : { $gte: options2.since },
|
|
185
|
+
...options2.until === void 0 ? {} : { $lte: options2.until }
|
|
186
|
+
};
|
|
187
|
+
const filter = {
|
|
188
|
+
formId,
|
|
189
|
+
...options2.version === void 0 ? {} : { formVersion: options2.version },
|
|
190
|
+
...options2.locale === void 0 ? {} : { "submission.locale": options2.locale },
|
|
191
|
+
...submittedAt === void 0 ? {} : { submittedAt },
|
|
192
|
+
...cursor === void 0 ? {} : {
|
|
193
|
+
$or: [
|
|
194
|
+
{ submittedAt: { $gt: cursor.submittedAt } },
|
|
195
|
+
{ submittedAt: cursor.submittedAt, _id: { $gt: cursor.responseId } }
|
|
196
|
+
]
|
|
197
|
+
}
|
|
198
|
+
};
|
|
199
|
+
const sorted = submissions.find(filter).sort({ submittedAt: 1, _id: 1 });
|
|
200
|
+
const requiresClientFiltering = options2.filter !== void 0 || options2.metadataFilters !== void 0;
|
|
201
|
+
const documents = requiresClientFiltering ? await sorted.toArray() : await sorted.limit(pageSize + 1).toArray();
|
|
202
|
+
const candidates = documents.map(parseSubmissionDocument).filter((item) => matchesSubmissionPageFilters(item, options2));
|
|
203
|
+
const hasMore = candidates.length > pageSize;
|
|
204
|
+
const items = candidates.slice(0, pageSize);
|
|
205
|
+
const last = items.at(-1);
|
|
206
|
+
return {
|
|
207
|
+
items,
|
|
208
|
+
hasMore,
|
|
209
|
+
...hasMore && last !== void 0 ? { nextCursor: encodeSubmissionCursor({ submittedAt: last.submittedAt, responseId: last.id }) } : {}
|
|
210
|
+
};
|
|
211
|
+
},
|
|
97
212
|
async deleteSubmission(submissionId) {
|
|
98
213
|
await submissions.deleteOne({ _id: submissionId });
|
|
99
214
|
},
|
|
@@ -101,12 +216,51 @@ function createMongoDbStorage(options) {
|
|
|
101
216
|
await submissions.deleteMany({ formId });
|
|
102
217
|
},
|
|
103
218
|
async clear() {
|
|
104
|
-
await Promise.all([
|
|
219
|
+
await Promise.all([
|
|
220
|
+
schemas.deleteMany({}),
|
|
221
|
+
submissions.deleteMany({}),
|
|
222
|
+
versions.deleteMany({}),
|
|
223
|
+
versionStates.deleteMany({})
|
|
224
|
+
]);
|
|
225
|
+
},
|
|
226
|
+
async commitVersionTransition(plan) {
|
|
227
|
+
assertTransitionPlan(plan);
|
|
228
|
+
const client = options.db.client;
|
|
229
|
+
if (client === void 0 || typeof client.startSession !== "function") {
|
|
230
|
+
try {
|
|
231
|
+
return await applyVersionTransition(plan);
|
|
232
|
+
} catch (error) {
|
|
233
|
+
if (isCasConflict(error)) return { success: false, error: "revision_conflict" };
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
const session = client.startSession();
|
|
238
|
+
try {
|
|
239
|
+
let result = {
|
|
240
|
+
success: false,
|
|
241
|
+
error: "revision_conflict"
|
|
242
|
+
};
|
|
243
|
+
await session.withTransaction(async () => {
|
|
244
|
+
result = await applyVersionTransition(plan, session);
|
|
245
|
+
});
|
|
246
|
+
return result;
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if (isCasConflict(error)) return { success: false, error: "revision_conflict" };
|
|
249
|
+
throw error;
|
|
250
|
+
} finally {
|
|
251
|
+
await session.endSession();
|
|
252
|
+
}
|
|
105
253
|
},
|
|
106
254
|
async createIndexes() {
|
|
107
|
-
await
|
|
108
|
-
|
|
109
|
-
|
|
255
|
+
await Promise.all([
|
|
256
|
+
submissions.createIndexes([
|
|
257
|
+
{ key: { formId: 1, submittedAt: 1, _id: 1 }, name: "form_responses_form_submitted_at_id" },
|
|
258
|
+
{ key: { "submission.locale": 1 }, name: "form_responses_locale" }
|
|
259
|
+
]),
|
|
260
|
+
versions.createIndexes([
|
|
261
|
+
{ key: { formId: 1, version: 1 }, name: "form_versions_form_version", unique: true },
|
|
262
|
+
{ key: { formId: 1, status: 1 }, name: "form_versions_form_status" }
|
|
263
|
+
])
|
|
110
264
|
]);
|
|
111
265
|
}
|
|
112
266
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@form-engine-ts/storage-mongodb",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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.6.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"mongodb": "^6.0.0"
|