@mongoosejs/studio 0.5.13 → 0.5.14
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/AGENTS.md +7 -0
- package/CLAUDE.md +7 -0
- package/README.md +3 -0
- package/backend/actions/ChatMessage/executeScript.js +7 -5
- package/backend/actions/ChatThread/createChatMessage.js +4 -2
- package/backend/actions/ChatThread/getChatThread.js +5 -2
- package/backend/actions/ChatThread/listChatThreads.js +3 -1
- package/backend/actions/ChatThread/shareChatThread.js +5 -2
- package/backend/actions/ChatThread/streamChatMessage.js +4 -2
- package/backend/actions/ChatThread/toggleAgentMode.js +5 -2
- package/backend/actions/Dashboard/deleteDashboard.js +7 -3
- package/backend/actions/Dashboard/getDashboard.js +21 -10
- package/backend/actions/Dashboard/getDashboards.js +3 -1
- package/backend/actions/Dashboard/updateDashboard.js +3 -2
- package/backend/actions/Model/addField.js +3 -2
- package/backend/actions/Model/analyzeSchema.js +6 -4
- package/backend/actions/Model/deleteDocument.js +3 -3
- package/backend/actions/Model/deleteDocuments.js +3 -1
- package/backend/actions/Model/executeDocumentScript.js +25 -29
- package/backend/actions/Model/exportQueryResults.js +3 -1
- package/backend/actions/Model/getCollectionInfo.js +5 -3
- package/backend/actions/Model/getDocument.js +3 -2
- package/backend/actions/Model/getDocuments.js +15 -5
- package/backend/actions/Model/getDocumentsStream.js +28 -18
- package/backend/actions/Model/getEstimatedDocumentCounts.js +5 -2
- package/backend/actions/Model/updateDocument.js +4 -2
- package/backend/actions/Model/updateDocuments.js +4 -2
- package/backend/actions/Model/validateDocument.js +5 -2
- package/backend/actions/Task/cancelTask.js +6 -3
- package/backend/actions/Task/getTaskOverview.js +3 -2
- package/backend/actions/Task/getTasks.js +3 -2
- package/backend/actions/Task/getTasksOverTime.js +3 -2
- package/backend/actions/Task/rescheduleTask.js +6 -3
- package/backend/actions/Task/runTask.js +6 -3
- package/backend/helpers/omitNullish.js +5 -0
- package/backend/index.js +4 -4
- package/backend/sandbox/createSandbox.js +2 -2
- package/backend/sandbox/createScriptDb.js +42 -19
- package/frontend/public/app.js +319 -96
- package/frontend/public/tw.css +68 -0
- package/frontend/src/api.js +5 -1
- package/frontend/src/models/models.html +29 -8
- package/frontend/src/models/models.js +30 -2
- package/frontend/src/tooltip/tooltip.html +28 -0
- package/frontend/src/tooltip/tooltip.js +94 -0
- package/index.d.ts +1 -1
- package/local.js +2 -1
- package/nest.d.ts +1 -0
- package/package.json +1 -1
- package/vercel.d.ts +2 -0
|
@@ -4,6 +4,7 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const getRefFromSchemaType = require('../../helpers/getRefFromSchemaType');
|
|
5
5
|
const removeSpecifiedPaths = require('../../helpers/removeSpecifiedPaths');
|
|
6
6
|
const authorize = require('../../authorize');
|
|
7
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
7
8
|
|
|
8
9
|
const GetDocumentParams = new Archetype({
|
|
9
10
|
model: {
|
|
@@ -19,7 +20,7 @@ const GetDocumentParams = new Archetype({
|
|
|
19
20
|
}
|
|
20
21
|
}).compile('GetDocumentParams');
|
|
21
22
|
|
|
22
|
-
module.exports = ({ db }) => async function getDocument(params) {
|
|
23
|
+
module.exports = ({ db, options }) => async function getDocument(params) {
|
|
23
24
|
const { model, documentId, roles } = new GetDocumentParams(params);
|
|
24
25
|
|
|
25
26
|
await authorize('Model.getDocument', roles);
|
|
@@ -31,7 +32,7 @@ module.exports = ({ db }) => async function getDocument(params) {
|
|
|
31
32
|
|
|
32
33
|
const doc = await Model.
|
|
33
34
|
findById(documentId).
|
|
34
|
-
setOptions({ sanitizeFilter: true }).
|
|
35
|
+
setOptions(omitNullish({ sanitizeFilter: true, maxTimeMS: options?.maxTimeMS })).
|
|
35
36
|
orFail();
|
|
36
37
|
const schemaPaths = {};
|
|
37
38
|
for (const path of Object.keys(Model.schema.paths)) {
|
|
@@ -7,6 +7,7 @@ const getRefFromSchemaType = require('../../helpers/getRefFromSchemaType');
|
|
|
7
7
|
const getSuggestedProjection = require('../../helpers/getSuggestedProjection');
|
|
8
8
|
const parseProjectionParam = require('../../helpers/parseProjectionParam');
|
|
9
9
|
const authorize = require('../../authorize');
|
|
10
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
10
11
|
|
|
11
12
|
const GetDocumentsParams = new Archetype({
|
|
12
13
|
model: {
|
|
@@ -40,7 +41,7 @@ const GetDocumentsParams = new Archetype({
|
|
|
40
41
|
}
|
|
41
42
|
}).compile('GetDocumentsParams');
|
|
42
43
|
|
|
43
|
-
module.exports = ({ db }) => async function getDocuments(params) {
|
|
44
|
+
module.exports = ({ db, options }) => async function getDocuments(params) {
|
|
44
45
|
params = new GetDocumentsParams(params);
|
|
45
46
|
const { roles } = params;
|
|
46
47
|
await authorize('Model.getDocuments', roles);
|
|
@@ -75,6 +76,7 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
75
76
|
if (projection != null) {
|
|
76
77
|
query = query.select(projection);
|
|
77
78
|
}
|
|
79
|
+
query.setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
78
80
|
const cursor = await query.cursor();
|
|
79
81
|
const docs = [];
|
|
80
82
|
for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
|
|
@@ -106,9 +108,16 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
106
108
|
}
|
|
107
109
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
108
110
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
const countQuery = parsedFilter == null ?
|
|
112
|
+
Model.estimatedDocumentCount() :
|
|
113
|
+
Model.countDocuments(filter);
|
|
114
|
+
let numDocuments = null;
|
|
115
|
+
let numDocsError = null;
|
|
116
|
+
try {
|
|
117
|
+
numDocuments = await countQuery.setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
118
|
+
} catch (err) {
|
|
119
|
+
numDocsError = err?.message;
|
|
120
|
+
}
|
|
112
121
|
|
|
113
122
|
const suggestedFields = getSuggestedProjection(Model);
|
|
114
123
|
|
|
@@ -116,6 +125,7 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
116
125
|
docs: docs.map(doc => doc.toJSON({ virtuals: false, getters: false, transform: false })),
|
|
117
126
|
schemaPaths,
|
|
118
127
|
suggestedFields,
|
|
119
|
-
numDocs: numDocuments
|
|
128
|
+
numDocs: numDocuments,
|
|
129
|
+
numDocsError
|
|
120
130
|
};
|
|
121
131
|
};
|
|
@@ -7,6 +7,7 @@ const getRefFromSchemaType = require('../../helpers/getRefFromSchemaType');
|
|
|
7
7
|
const getSuggestedProjection = require('../../helpers/getSuggestedProjection');
|
|
8
8
|
const parseProjectionParam = require('../../helpers/parseProjectionParam');
|
|
9
9
|
const authorize = require('../../authorize');
|
|
10
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
10
11
|
|
|
11
12
|
const GetDocumentsParams = new Archetype({
|
|
12
13
|
model: {
|
|
@@ -40,7 +41,7 @@ const GetDocumentsParams = new Archetype({
|
|
|
40
41
|
}
|
|
41
42
|
}).compile('GetDocumentsParams');
|
|
42
43
|
|
|
43
|
-
module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
44
|
+
module.exports = ({ db, options }) => async function* getDocumentsStream(params) {
|
|
44
45
|
params = new GetDocumentsParams(params);
|
|
45
46
|
const { roles } = params;
|
|
46
47
|
await authorize('Model.getDocumentsStream', roles);
|
|
@@ -75,6 +76,7 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
75
76
|
if (projection != null) {
|
|
76
77
|
query = query.select(projection);
|
|
77
78
|
}
|
|
79
|
+
query.setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
78
80
|
|
|
79
81
|
const schemaPaths = {};
|
|
80
82
|
for (const path of Object.keys(Model.schema.paths)) {
|
|
@@ -106,32 +108,36 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
106
108
|
yield { schemaPaths, suggestedFields };
|
|
107
109
|
|
|
108
110
|
// Start counting documents in parallel with streaming documents
|
|
109
|
-
const
|
|
110
|
-
? Model.estimatedDocumentCount()
|
|
111
|
-
: Model.countDocuments(filter)
|
|
112
|
-
|
|
113
|
-
|
|
111
|
+
const countQuery = (parsedFilter == null)
|
|
112
|
+
? Model.estimatedDocumentCount()
|
|
113
|
+
: Model.countDocuments(filter);
|
|
114
|
+
const numDocsPromise = countQuery.
|
|
115
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS })).
|
|
116
|
+
exec();
|
|
114
117
|
|
|
115
118
|
let numDocsYielded = false;
|
|
116
119
|
let numDocumentsPromiseResolved = false;
|
|
117
120
|
let numDocumentsValue;
|
|
118
121
|
let numDocumentsError;
|
|
119
122
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
123
|
+
// Attach both handlers immediately so a fast count failure cannot become
|
|
124
|
+
// an unhandled rejection while the document cursor is still opening.
|
|
125
|
+
numDocsPromise.then(num => {
|
|
126
|
+
numDocumentsPromiseResolved = true;
|
|
127
|
+
numDocumentsValue = num;
|
|
128
|
+
}).catch(err => {
|
|
129
|
+
numDocumentsPromiseResolved = true;
|
|
130
|
+
numDocumentsError = err;
|
|
131
|
+
});
|
|
129
132
|
|
|
133
|
+
const cursor = await query.cursor();
|
|
134
|
+
|
|
135
|
+
try {
|
|
130
136
|
for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
|
|
131
137
|
// If numDocsPromise has resolved and not yet yielded, yield it first
|
|
132
138
|
if (numDocumentsPromiseResolved && !numDocsYielded) {
|
|
133
139
|
if (numDocumentsError) {
|
|
134
|
-
yield {
|
|
140
|
+
yield { numDocsError: numDocumentsError.message };
|
|
135
141
|
} else {
|
|
136
142
|
yield { numDocs: numDocumentsValue };
|
|
137
143
|
}
|
|
@@ -142,8 +148,12 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
142
148
|
|
|
143
149
|
// If numDocsPromise hasn't resolved yet, wait for it and yield
|
|
144
150
|
if (!numDocsYielded) {
|
|
145
|
-
|
|
146
|
-
|
|
151
|
+
try {
|
|
152
|
+
const numDocuments = await numDocsPromise;
|
|
153
|
+
yield { numDocs: numDocuments };
|
|
154
|
+
} catch (err) {
|
|
155
|
+
yield { numDocsError: err.message };
|
|
156
|
+
}
|
|
147
157
|
}
|
|
148
158
|
} finally {
|
|
149
159
|
await cursor.close();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const GetEstimatedDocumentCountsParams = new Archetype({
|
|
7
8
|
roles: {
|
|
@@ -9,7 +10,7 @@ const GetEstimatedDocumentCountsParams = new Archetype({
|
|
|
9
10
|
}
|
|
10
11
|
}).compile('GetEstimatedDocumentCountsParams');
|
|
11
12
|
|
|
12
|
-
module.exports = ({ db }) => async function getEstimatedDocumentCounts(params) {
|
|
13
|
+
module.exports = ({ db, options }) => async function getEstimatedDocumentCounts(params) {
|
|
13
14
|
const { roles } = new GetEstimatedDocumentCountsParams(params);
|
|
14
15
|
await authorize('Model.getEstimatedDocumentCounts', roles);
|
|
15
16
|
|
|
@@ -20,7 +21,9 @@ module.exports = ({ db }) => async function getEstimatedDocumentCounts(params) {
|
|
|
20
21
|
const results = await Promise.allSettled(
|
|
21
22
|
modelNames.map(name => {
|
|
22
23
|
const Model = db.models[name];
|
|
23
|
-
return Model.estimatedDocumentCount().
|
|
24
|
+
return Model.estimatedDocumentCount().
|
|
25
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS })).
|
|
26
|
+
exec();
|
|
24
27
|
})
|
|
25
28
|
);
|
|
26
29
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const UpdateDocumentsParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -24,7 +25,7 @@ const UpdateDocumentsParams = new Archetype({
|
|
|
24
25
|
}
|
|
25
26
|
}).compile('UpdateDocumentsParams');
|
|
26
27
|
|
|
27
|
-
module.exports = ({ db }) => async function updateDocument(params) {
|
|
28
|
+
module.exports = ({ db, options }) => async function updateDocument(params) {
|
|
28
29
|
const { model, _id, update, unset, roles } = new UpdateDocumentsParams(params);
|
|
29
30
|
|
|
30
31
|
await authorize('Model.updateDocument', roles);
|
|
@@ -58,6 +59,7 @@ module.exports = ({ db }) => async function updateDocument(params) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
const doc = await Model.
|
|
61
|
-
findByIdAndUpdate(_id, updateOperation, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true, runValidators: false })
|
|
62
|
+
findByIdAndUpdate(_id, updateOperation, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true, runValidators: false }).
|
|
63
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
62
64
|
return { doc };
|
|
63
65
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const UpdateDocumentsParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -21,7 +22,7 @@ const UpdateDocumentsParams = new Archetype({
|
|
|
21
22
|
}
|
|
22
23
|
}).compile('UpdateDocumentsParams');
|
|
23
24
|
|
|
24
|
-
module.exports = ({ db }) => async function updateDocuments(params) {
|
|
25
|
+
module.exports = ({ db, options }) => async function updateDocuments(params) {
|
|
25
26
|
const { model, _id, update, roles } = new UpdateDocumentsParams(params);
|
|
26
27
|
|
|
27
28
|
await authorize('Model.updateDocuments', roles);
|
|
@@ -60,7 +61,8 @@ module.exports = ({ db }) => async function updateDocuments(params) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const result = await Model.
|
|
63
|
-
updateMany({ _id: { $in: _id } }, updateOperation, { overwriteImmutable: true, runValidators: false })
|
|
64
|
+
updateMany({ _id: { $in: _id } }, updateOperation, { overwriteImmutable: true, runValidators: false }).
|
|
65
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
64
66
|
|
|
65
67
|
return { result };
|
|
66
68
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
const validateDocumentWithTimeout = require('../../helpers/validateDocumentWithTimeout');
|
|
6
7
|
|
|
7
8
|
const ValidateDocumentParams = new Archetype({
|
|
@@ -18,7 +19,7 @@ const ValidateDocumentParams = new Archetype({
|
|
|
18
19
|
}
|
|
19
20
|
}).compile('ValidateDocumentParams');
|
|
20
21
|
|
|
21
|
-
module.exports = ({ db }) => async function validateDocument(params) {
|
|
22
|
+
module.exports = ({ db, options }) => async function validateDocument(params) {
|
|
22
23
|
const { model, documentId, roles } = new ValidateDocumentParams(params);
|
|
23
24
|
|
|
24
25
|
await authorize('Model.validateDocument', roles);
|
|
@@ -28,7 +29,9 @@ module.exports = ({ db }) => async function validateDocument(params) {
|
|
|
28
29
|
throw new Error(`Model ${model} not found`);
|
|
29
30
|
}
|
|
30
31
|
|
|
31
|
-
const doc = await Model.findById(documentId).
|
|
32
|
+
const doc = await Model.findById(documentId).
|
|
33
|
+
setOptions(omitNullish({ sanitizeFilter: true, maxTimeMS: options?.maxTimeMS })).
|
|
34
|
+
orFail();
|
|
32
35
|
return {
|
|
33
36
|
result: await validateDocumentWithTimeout(doc)
|
|
34
37
|
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const mongoose = require('mongoose');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const CancelTaskParams = new Archetype({
|
|
7
8
|
taskId: {
|
|
@@ -10,15 +11,17 @@ const CancelTaskParams = new Archetype({
|
|
|
10
11
|
}
|
|
11
12
|
}).compile('CancelTaskParams');
|
|
12
13
|
|
|
13
|
-
module.exports = ({ db }) => async function cancelTask(params) {
|
|
14
|
+
module.exports = ({ db, options }) => async function cancelTask(params) {
|
|
14
15
|
params = new CancelTaskParams(params);
|
|
15
16
|
const { taskId } = params;
|
|
16
17
|
const { Task } = db.models;
|
|
17
18
|
|
|
18
|
-
const task = await Task.findOne({ _id: taskId }).
|
|
19
|
+
const task = await Task.findOne({ _id: taskId }).
|
|
20
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS })).
|
|
21
|
+
orFail();
|
|
19
22
|
|
|
20
23
|
const cancelledTask = await Task.cancelTask({ _id: taskId });
|
|
21
24
|
return {
|
|
22
25
|
task: cancelledTask
|
|
23
26
|
};
|
|
24
|
-
};
|
|
27
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const escape = require('regexp.escape');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const GetTaskOverviewParams = new Archetype({
|
|
7
8
|
start: { $type: Date },
|
|
@@ -34,7 +35,7 @@ function buildMatch(params) {
|
|
|
34
35
|
return match;
|
|
35
36
|
}
|
|
36
37
|
|
|
37
|
-
module.exports = ({ db }) => async function getTaskOverview(params) {
|
|
38
|
+
module.exports = ({ db, options }) => async function getTaskOverview(params) {
|
|
38
39
|
params = new GetTaskOverviewParams(params);
|
|
39
40
|
if (typeof params.status === 'string') params.status = params.status.trim();
|
|
40
41
|
if (typeof params.name === 'string') params.name = params.name.trim();
|
|
@@ -93,7 +94,7 @@ module.exports = ({ db }) => async function getTaskOverview(params) {
|
|
|
93
94
|
}
|
|
94
95
|
];
|
|
95
96
|
|
|
96
|
-
const [result] = await Task.aggregate(pipeline);
|
|
97
|
+
const [result] = await Task.aggregate(pipeline).option(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
97
98
|
|
|
98
99
|
return {
|
|
99
100
|
statusCounts: result.statusCounts?.[0] ?? {},
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const escape = require('regexp.escape');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const GetTasksParams = new Archetype({
|
|
7
8
|
start: { $type: Date },
|
|
@@ -55,7 +56,7 @@ const TASK_PROJECT_STAGE = {
|
|
|
55
56
|
parameters: '$payload'
|
|
56
57
|
};
|
|
57
58
|
|
|
58
|
-
module.exports = ({ db }) => async function getTasks(params) {
|
|
59
|
+
module.exports = ({ db, options }) => async function getTasks(params) {
|
|
59
60
|
params = new GetTasksParams(params);
|
|
60
61
|
if (typeof params.status === 'string') params.status = params.status.trim();
|
|
61
62
|
if (typeof params.name === 'string') params.name = params.name.trim();
|
|
@@ -96,7 +97,7 @@ module.exports = ({ db }) => async function getTasks(params) {
|
|
|
96
97
|
}
|
|
97
98
|
];
|
|
98
99
|
|
|
99
|
-
const [result] = await Task.aggregate(pipeline);
|
|
100
|
+
const [result] = await Task.aggregate(pipeline).option(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
100
101
|
const tasks = result.tasks || [];
|
|
101
102
|
const numDocs = (result.count && result.count[0] && result.count[0].total) || 0;
|
|
102
103
|
const statusCounts = result.statusCounts?.[0] ?? {};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
4
5
|
|
|
5
6
|
const GetTasksOverTimeParams = new Archetype({
|
|
6
7
|
start: { $type: Date },
|
|
@@ -10,7 +11,7 @@ const GetTasksOverTimeParams = new Archetype({
|
|
|
10
11
|
|
|
11
12
|
const TRACKED_STATUSES = ['succeeded', 'failed', 'cancelled'];
|
|
12
13
|
|
|
13
|
-
module.exports = ({ db }) => async function getTasksOverTime(params) {
|
|
14
|
+
module.exports = ({ db, options }) => async function getTasksOverTime(params) {
|
|
14
15
|
params = new GetTasksOverTimeParams(params);
|
|
15
16
|
const { Task } = db.models;
|
|
16
17
|
const { start, end, bucketSizeMs } = params;
|
|
@@ -54,7 +55,7 @@ module.exports = ({ db }) => async function getTasksOverTime(params) {
|
|
|
54
55
|
{ $sort: { _id: 1 } }
|
|
55
56
|
];
|
|
56
57
|
|
|
57
|
-
const results = await Task.aggregate(pipeline);
|
|
58
|
+
const results = await Task.aggregate(pipeline).option(omitNullish({ maxTimeMS: options?.maxTimeMS }));
|
|
58
59
|
|
|
59
60
|
return results.map(r => {
|
|
60
61
|
const bucket = { timestamp: r._id, succeeded: 0, failed: 0, cancelled: 0 };
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const mongoose = require('mongoose');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const RescheduleTaskParams = new Archetype({
|
|
7
8
|
taskId: {
|
|
@@ -14,12 +15,14 @@ const RescheduleTaskParams = new Archetype({
|
|
|
14
15
|
}
|
|
15
16
|
}).compile('RescheduleTaskParams');
|
|
16
17
|
|
|
17
|
-
module.exports = ({ db }) => async function rescheduleTask(params) {
|
|
18
|
+
module.exports = ({ db, options }) => async function rescheduleTask(params) {
|
|
18
19
|
params = new RescheduleTaskParams(params);
|
|
19
20
|
const { taskId, scheduledAt } = params;
|
|
20
21
|
const { Task } = db.models;
|
|
21
22
|
|
|
22
|
-
const task = await Task.findOne({ _id: taskId }).
|
|
23
|
+
const task = await Task.findOne({ _id: taskId }).
|
|
24
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS })).
|
|
25
|
+
orFail();
|
|
23
26
|
|
|
24
27
|
if (scheduledAt < Date.now()) {
|
|
25
28
|
throw new Error('Cannot reschedule a task for the past');
|
|
@@ -36,4 +39,4 @@ module.exports = ({ db }) => async function rescheduleTask(params) {
|
|
|
36
39
|
return {
|
|
37
40
|
task
|
|
38
41
|
};
|
|
39
|
-
};
|
|
42
|
+
};
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const mongoose = require('mongoose');
|
|
5
|
+
const omitNullish = require('../../helpers/omitNullish');
|
|
5
6
|
|
|
6
7
|
const RunTaskParams = new Archetype({
|
|
7
8
|
taskId: {
|
|
@@ -10,16 +11,18 @@ const RunTaskParams = new Archetype({
|
|
|
10
11
|
}
|
|
11
12
|
}).compile('RunTaskParams');
|
|
12
13
|
|
|
13
|
-
module.exports = ({ db }) => async function runTask(params) {
|
|
14
|
+
module.exports = ({ db, options }) => async function runTask(params) {
|
|
14
15
|
params = new RunTaskParams(params);
|
|
15
16
|
const { taskId } = params;
|
|
16
17
|
const { Task } = db.models;
|
|
17
18
|
|
|
18
|
-
const task = await Task.findOne({ _id: taskId }).
|
|
19
|
+
const task = await Task.findOne({ _id: taskId }).
|
|
20
|
+
setOptions(omitNullish({ maxTimeMS: options?.maxTimeMS })).
|
|
21
|
+
orFail();
|
|
19
22
|
|
|
20
23
|
const executedTask = await Task.execute(task);
|
|
21
24
|
|
|
22
25
|
return {
|
|
23
26
|
task: executedTask
|
|
24
27
|
};
|
|
25
|
-
};
|
|
28
|
+
};
|
package/backend/index.js
CHANGED
|
@@ -16,10 +16,10 @@ module.exports = function backend(db, studioConnection, options) {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
studioConnection = studioConnection ?? db;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
studioConnection.model('__Studio_Dashboard', dashboardSchema, 'studio__dashboards');
|
|
20
|
+
studioConnection.model('__Studio_DashboardResult', dashboardResultSchema, 'studio__dashboardResults');
|
|
21
|
+
studioConnection.model('__Studio_ChatMessage', chatMessageSchema, 'studio__chatMessages');
|
|
22
|
+
studioConnection.model('__Studio_ChatThread', chatThreadSchema, 'studio__chatThreads');
|
|
23
23
|
|
|
24
24
|
let changeStream = null;
|
|
25
25
|
if (options?.changeStream) {
|
|
@@ -5,7 +5,7 @@ const mongoose = require('mongoose');
|
|
|
5
5
|
const vm = require('vm');
|
|
6
6
|
const { createScriptDb } = require('./createScriptDb');
|
|
7
7
|
|
|
8
|
-
module.exports = function createSandbox({ db }) {
|
|
8
|
+
module.exports = function createSandbox({ db, maxTimeMS }) {
|
|
9
9
|
const logs = [];
|
|
10
10
|
|
|
11
11
|
// db must be a connection, not a Mongoose instance
|
|
@@ -13,7 +13,7 @@ module.exports = function createSandbox({ db }) {
|
|
|
13
13
|
db = db.connection;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
const scriptDb = createScriptDb(db);
|
|
16
|
+
const scriptDb = createScriptDb(db, { maxTimeMS });
|
|
17
17
|
if (!scriptDb.db.Types) {
|
|
18
18
|
scriptDb.db.Types = mongoose.Types;
|
|
19
19
|
}
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const omitNullish = require('../helpers/omitNullish');
|
|
4
|
+
|
|
3
5
|
const wrappedCollections = new WeakSet();
|
|
4
6
|
|
|
5
7
|
const collectionMethodOptionsIndex = new Map([
|
|
8
|
+
['aggregate', 1],
|
|
6
9
|
['bulkWrite', 1],
|
|
7
10
|
['countDocuments', 1],
|
|
8
11
|
['createIndex', 1],
|
|
@@ -21,26 +24,34 @@ const collectionMethodOptionsIndex = new Map([
|
|
|
21
24
|
['findOneAndUpdate', 2],
|
|
22
25
|
['insertMany', 1],
|
|
23
26
|
['insertOne', 1],
|
|
27
|
+
['listIndexes', 0],
|
|
24
28
|
['rename', 1],
|
|
25
29
|
['replaceOne', 2],
|
|
26
30
|
['updateMany', 2],
|
|
27
31
|
['updateOne', 2]
|
|
28
32
|
]);
|
|
29
33
|
|
|
30
|
-
function createScriptDb(db) {
|
|
34
|
+
function createScriptDb(db, options = {}) {
|
|
31
35
|
const sourceConnection = db;
|
|
32
36
|
|
|
33
37
|
const scriptConnection = sourceConnection.useDb(sourceConnection.name, { useCache: false });
|
|
38
|
+
// Mongoose useDb() shares the source connection's options object. Copy it
|
|
39
|
+
// before setting sandbox-only options so the application connection remains untouched.
|
|
40
|
+
scriptConnection.options = { ...scriptConnection.options };
|
|
34
41
|
scriptConnection.config = {
|
|
35
42
|
...scriptConnection.config,
|
|
36
43
|
autoCreate: false,
|
|
37
44
|
autoIndex: false
|
|
38
45
|
};
|
|
46
|
+
if (options.maxTimeMS != null) {
|
|
47
|
+
scriptConnection.set('maxTimeMS', options.maxTimeMS);
|
|
48
|
+
}
|
|
39
49
|
|
|
40
50
|
let dryRunSession = null;
|
|
41
51
|
const getSession = () => dryRunSession;
|
|
42
|
-
|
|
43
|
-
|
|
52
|
+
const getMaxTimeMS = () => options.maxTimeMS;
|
|
53
|
+
cloneModels(sourceConnection, scriptConnection, getSession, getMaxTimeMS);
|
|
54
|
+
wrapCollectionAccessors(scriptConnection, getSession, getMaxTimeMS);
|
|
44
55
|
|
|
45
56
|
const originalDebug = sourceConnection.options?.debug;
|
|
46
57
|
scriptConnection.set('debug', function() {
|
|
@@ -68,14 +79,14 @@ function createScriptDb(db) {
|
|
|
68
79
|
};
|
|
69
80
|
}
|
|
70
81
|
|
|
71
|
-
function cloneModels(sourceConnection, scriptConnection, getSession) {
|
|
82
|
+
function cloneModels(sourceConnection, scriptConnection, getSession, getMaxTimeMS) {
|
|
72
83
|
for (const Model of Object.values(sourceConnection.models ?? {})) {
|
|
73
84
|
if (Model?.schema == null || typeof scriptConnection.model !== 'function') {
|
|
74
85
|
continue;
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
const ClonedModel = scriptConnection.model(Model.modelName, Model.schema, getCollectionName(Model));
|
|
78
|
-
wrapModelCollections(ClonedModel, getSession);
|
|
89
|
+
wrapModelCollections(ClonedModel, getSession, getMaxTimeMS);
|
|
79
90
|
}
|
|
80
91
|
}
|
|
81
92
|
|
|
@@ -83,13 +94,13 @@ function getCollectionName(Model) {
|
|
|
83
94
|
return Model.collection?.collectionName ?? Model.collection?.name ?? Model.$__collection?.collectionName;
|
|
84
95
|
}
|
|
85
96
|
|
|
86
|
-
function wrapModelCollections(Model, getSession) {
|
|
97
|
+
function wrapModelCollections(Model, getSession, getMaxTimeMS) {
|
|
87
98
|
const collection = Model?.collection ?? Model?.$__collection;
|
|
88
99
|
if (collection == null) {
|
|
89
100
|
return;
|
|
90
101
|
}
|
|
91
102
|
|
|
92
|
-
const wrappedCollection = wrapCollection(collection, getSession);
|
|
103
|
+
const wrappedCollection = wrapCollection(collection, getSession, getMaxTimeMS);
|
|
93
104
|
Model.collection = wrappedCollection;
|
|
94
105
|
Model.$__collection = wrappedCollection;
|
|
95
106
|
|
|
@@ -108,7 +119,7 @@ function setModelCollectionSymbols(target, collection) {
|
|
|
108
119
|
}
|
|
109
120
|
}
|
|
110
121
|
|
|
111
|
-
function wrapCollectionAccessors(scriptConnection, getSession) {
|
|
122
|
+
function wrapCollectionAccessors(scriptConnection, getSession, getMaxTimeMS) {
|
|
112
123
|
const wrappedCollectionCache = new WeakMap();
|
|
113
124
|
|
|
114
125
|
const nativeDb = scriptConnection.db;
|
|
@@ -125,19 +136,19 @@ function wrapCollectionAccessors(scriptConnection, getSession) {
|
|
|
125
136
|
collection.collection = originalDbCollection.call(nativeDb, collection.name);
|
|
126
137
|
}
|
|
127
138
|
}
|
|
128
|
-
return getOrCreateWrappedCollection(wrappedCollectionCache, collection, getSession);
|
|
139
|
+
return getOrCreateWrappedCollection(wrappedCollectionCache, collection, getSession, getMaxTimeMS);
|
|
129
140
|
};
|
|
130
141
|
}
|
|
131
142
|
|
|
132
143
|
if (originalDbCollection != null) {
|
|
133
144
|
nativeDb.collection = function() {
|
|
134
145
|
const collection = originalDbCollection.apply(this, arguments);
|
|
135
|
-
return getOrCreateWrappedCollection(wrappedCollectionCache, collection, getSession);
|
|
146
|
+
return getOrCreateWrappedCollection(wrappedCollectionCache, collection, getSession, getMaxTimeMS);
|
|
136
147
|
};
|
|
137
148
|
}
|
|
138
149
|
}
|
|
139
150
|
|
|
140
|
-
function getOrCreateWrappedCollection(cache, collection, getSession) {
|
|
151
|
+
function getOrCreateWrappedCollection(cache, collection, getSession, getMaxTimeMS) {
|
|
141
152
|
if (collection == null || (typeof collection !== 'object' && typeof collection !== 'function')) {
|
|
142
153
|
return collection;
|
|
143
154
|
}
|
|
@@ -147,12 +158,12 @@ function getOrCreateWrappedCollection(cache, collection, getSession) {
|
|
|
147
158
|
if (cache.has(collection)) {
|
|
148
159
|
return cache.get(collection);
|
|
149
160
|
}
|
|
150
|
-
const wrapped = wrapCollection(collection, getSession);
|
|
161
|
+
const wrapped = wrapCollection(collection, getSession, getMaxTimeMS);
|
|
151
162
|
cache.set(collection, wrapped);
|
|
152
163
|
return wrapped;
|
|
153
164
|
}
|
|
154
165
|
|
|
155
|
-
function wrapCollection(collection, getSession) {
|
|
166
|
+
function wrapCollection(collection, getSession, getMaxTimeMS) {
|
|
156
167
|
if (wrappedCollections.has(collection)) {
|
|
157
168
|
return collection;
|
|
158
169
|
}
|
|
@@ -165,15 +176,20 @@ function wrapCollection(collection, getSession) {
|
|
|
165
176
|
}
|
|
166
177
|
|
|
167
178
|
wrapped[methodName] = function() {
|
|
168
|
-
const args =
|
|
179
|
+
const args = addOperationOptions(
|
|
180
|
+
Array.from(arguments),
|
|
181
|
+
optionsIndex,
|
|
182
|
+
getSession(),
|
|
183
|
+
getMaxTimeMS()
|
|
184
|
+
);
|
|
169
185
|
return method.apply(collection, args);
|
|
170
186
|
};
|
|
171
187
|
}
|
|
172
188
|
return wrapped;
|
|
173
189
|
}
|
|
174
190
|
|
|
175
|
-
function
|
|
176
|
-
if (session == null) {
|
|
191
|
+
function addOperationOptions(args, optionsIndex, session, maxTimeMS) {
|
|
192
|
+
if (session == null && maxTimeMS == null) {
|
|
177
193
|
return args;
|
|
178
194
|
}
|
|
179
195
|
|
|
@@ -184,14 +200,21 @@ function addSessionOption(args, optionsIndex, session) {
|
|
|
184
200
|
const options = args[optionsIndex];
|
|
185
201
|
|
|
186
202
|
if (options != null && typeof options !== 'object') {
|
|
187
|
-
|
|
203
|
+
if (session != null) {
|
|
204
|
+
throw new Error('Cannot run dry run on script where options arg is a non-object');
|
|
205
|
+
}
|
|
206
|
+
throw new Error('Cannot apply maxTimeMS to script where options arg is a non-object');
|
|
188
207
|
}
|
|
189
208
|
|
|
190
|
-
if (options?.session != null) {
|
|
209
|
+
if (session != null && options?.session != null) {
|
|
191
210
|
throw new Error('Cannot run dry run on script that uses sessions');
|
|
192
211
|
}
|
|
193
212
|
|
|
194
|
-
args[optionsIndex] =
|
|
213
|
+
args[optionsIndex] = omitNullish({
|
|
214
|
+
...(options ?? {}),
|
|
215
|
+
maxTimeMS: maxTimeMS ?? options?.maxTimeMS,
|
|
216
|
+
session: session ?? options?.session
|
|
217
|
+
});
|
|
195
218
|
return args;
|
|
196
219
|
}
|
|
197
220
|
|