@mongoosejs/studio 0.5.13 → 0.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/AGENTS.md +74 -0
- package/README.md +27 -0
- package/backend/actions/ChatMessage/executeScript.js +15 -5
- package/backend/actions/ChatThread/createChatMessage.js +7 -2
- package/backend/actions/ChatThread/getChatThread.js +8 -2
- package/backend/actions/ChatThread/listChatThreads.js +6 -1
- package/backend/actions/ChatThread/shareChatThread.js +8 -2
- package/backend/actions/ChatThread/streamChatMessage.js +7 -2
- package/backend/actions/ChatThread/toggleAgentMode.js +8 -2
- package/backend/actions/Dashboard/createDashboard.js +3 -0
- package/backend/actions/Dashboard/deleteDashboard.js +13 -3
- package/backend/actions/Dashboard/getDashboard.js +33 -10
- package/backend/actions/Dashboard/getDashboards.js +9 -1
- package/backend/actions/Dashboard/updateDashboard.js +9 -2
- package/backend/actions/Model/addField.js +9 -2
- package/backend/actions/Model/analyzeSchema.js +16 -4
- package/backend/actions/Model/createChatMessage.js +3 -0
- package/backend/actions/Model/createDocument.js +3 -0
- package/backend/actions/Model/deleteDocument.js +9 -3
- package/backend/actions/Model/deleteDocuments.js +9 -1
- package/backend/actions/Model/dropCollection.js +3 -0
- package/backend/actions/Model/dropIndex.js +3 -0
- package/backend/actions/Model/executeDocumentScript.js +33 -26
- package/backend/actions/Model/exportQueryResults.js +13 -1
- package/backend/actions/Model/getCollectionInfo.js +15 -3
- package/backend/actions/Model/getDocument.js +13 -2
- package/backend/actions/Model/getDocuments.js +25 -5
- package/backend/actions/Model/getDocumentsStream.js +38 -18
- package/backend/actions/Model/getEstimatedDocumentCounts.js +15 -2
- package/backend/actions/Model/getIndexes.js +3 -0
- package/backend/actions/Model/getSuggestedProjection.js +3 -0
- package/backend/actions/Model/listModels.js +3 -0
- package/backend/actions/Model/streamChatMessage.js +3 -0
- package/backend/actions/Model/streamDocumentChanges.js +3 -0
- package/backend/actions/Model/updateDocument.js +10 -2
- package/backend/actions/Model/updateDocuments.js +10 -2
- package/backend/actions/Model/validateDocument.js +15 -2
- package/backend/actions/Script/createScript.js +36 -0
- package/backend/actions/Script/executeScript.js +89 -0
- package/backend/actions/Script/getScript.js +43 -0
- package/backend/actions/Script/index.js +5 -0
- package/backend/actions/Task/cancelTask.js +9 -3
- package/backend/actions/Task/getTaskOverview.js +7 -3
- package/backend/actions/Task/getTasks.js +7 -3
- package/backend/actions/Task/getTasksOverTime.js +7 -3
- package/backend/actions/Task/rescheduleTask.js +9 -3
- package/backend/actions/Task/runTask.js +9 -3
- package/backend/actions/index.js +1 -0
- package/backend/authorize.js +6 -1
- package/backend/db/scriptSchema.js +29 -0
- package/backend/helpers/omitNullish.js +5 -0
- package/backend/helpers/operationOptions.js +26 -0
- package/backend/helpers/readOperationOptions.js +31 -0
- package/backend/index.js +6 -4
- package/backend/integrations/mothership.js +28 -0
- package/backend/mcp.js +140 -0
- package/backend/netlify.js +5 -3
- package/backend/next.js +5 -3
- package/backend/sandbox/createSandbox.js +2 -2
- package/backend/sandbox/createScriptDb.js +60 -20
- package/backend/util/archetypeToZodSchema.js +74 -0
- package/docs/mcp-oauth.md +106 -0
- package/eslint.config.js +1 -0
- package/eval/agent/run.js +1 -1
- package/express.js +183 -41
- package/frontend/index.js +3 -0
- package/frontend/public/app.js +740 -124
- package/frontend/public/tw.css +76 -0
- package/frontend/src/api.js +27 -1
- package/frontend/src/index.js +9 -1
- package/frontend/src/mcp/mcp.html +55 -0
- package/frontend/src/mcp/mcp.js +27 -0
- package/frontend/src/models/models.html +29 -8
- package/frontend/src/models/models.js +30 -2
- package/frontend/src/navbar/navbar.html +8 -0
- package/frontend/src/navbar/navbar.js +3 -0
- package/frontend/src/routes.js +16 -0
- package/frontend/src/script/script.html +105 -0
- package/frontend/src/script/script.js +98 -0
- package/frontend/src/tooltip/tooltip.html +28 -0
- package/frontend/src/tooltip/tooltip.js +94 -0
- package/index.d.ts +10 -1
- package/local.js +2 -1
- package/nest.d.ts +4 -0
- package/package.json +5 -3
- package/vercel.d.ts +4 -0
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const { Buffer } = require('buffer');
|
|
6
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
6
7
|
const validateDocumentWithTimeout = require('../../helpers/validateDocumentWithTimeout');
|
|
7
8
|
|
|
8
9
|
const SAMPLE_SIZE = 1000;
|
|
@@ -15,10 +16,17 @@ const AnalyzeSchemaParams = new Archetype({
|
|
|
15
16
|
},
|
|
16
17
|
roles: {
|
|
17
18
|
$type: ['string']
|
|
19
|
+
},
|
|
20
|
+
maxTimeMS: {
|
|
21
|
+
$type: 'number'
|
|
22
|
+
},
|
|
23
|
+
readPreference: {
|
|
24
|
+
$type: 'string',
|
|
25
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
18
26
|
}
|
|
19
27
|
}).compile('AnalyzeSchemaParams');
|
|
20
28
|
|
|
21
|
-
module.exports = ({ db }) => async function analyzeSchema(params) {
|
|
29
|
+
module.exports = ({ db, options }) => async function analyzeSchema(params) {
|
|
22
30
|
const { model, roles } = new AnalyzeSchemaParams(params);
|
|
23
31
|
|
|
24
32
|
await authorize('Model.analyzeSchema', roles);
|
|
@@ -28,10 +36,11 @@ module.exports = ({ db }) => async function analyzeSchema(params) {
|
|
|
28
36
|
throw new Error(`Model ${model} not found`);
|
|
29
37
|
}
|
|
30
38
|
|
|
31
|
-
const
|
|
39
|
+
const dbOptions = readOperationOptions(options, params);
|
|
40
|
+
const documentCount = await Model.collection.countDocuments({}, dbOptions);
|
|
32
41
|
const rawDocs = documentCount > SAMPLE_SIZE ?
|
|
33
|
-
await Model.collection.aggregate([{ $sample: { size: SAMPLE_SIZE } }]).toArray() :
|
|
34
|
-
await Model.collection.find({}).toArray();
|
|
42
|
+
await Model.collection.aggregate([{ $sample: { size: SAMPLE_SIZE } }], dbOptions).toArray() :
|
|
43
|
+
await Model.collection.find({}, dbOptions).toArray();
|
|
35
44
|
|
|
36
45
|
const paths = getSchemaPaths(Model.schema);
|
|
37
46
|
const pathTypeCounts = {};
|
|
@@ -200,3 +209,6 @@ function getType(value) {
|
|
|
200
209
|
}
|
|
201
210
|
return typeof value;
|
|
202
211
|
}
|
|
212
|
+
|
|
213
|
+
module.exports.paramsType = AnalyzeSchemaParams;
|
|
214
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const DeleteDocumentParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -14,10 +15,13 @@ const DeleteDocumentParams = new Archetype({
|
|
|
14
15
|
},
|
|
15
16
|
roles: {
|
|
16
17
|
$type: ['string']
|
|
18
|
+
},
|
|
19
|
+
maxTimeMS: {
|
|
20
|
+
$type: 'number'
|
|
17
21
|
}
|
|
18
22
|
}).compile('DeleteDocumentParams');
|
|
19
23
|
|
|
20
|
-
module.exports = ({ db }) => async function DeleteDocument(params) {
|
|
24
|
+
module.exports = ({ db, options }) => async function DeleteDocument(params) {
|
|
21
25
|
const { model, documentId, roles } = new DeleteDocumentParams(params);
|
|
22
26
|
|
|
23
27
|
const Model = db.models[model];
|
|
@@ -30,9 +34,11 @@ module.exports = ({ db }) => async function DeleteDocument(params) {
|
|
|
30
34
|
|
|
31
35
|
const doc = await Model.
|
|
32
36
|
deleteOne({ _id: documentId }).
|
|
33
|
-
setOptions({ sanitizeFilter: true }).
|
|
37
|
+
setOptions(operationOptions(options, params, { sanitizeFilter: true })).
|
|
34
38
|
orFail();
|
|
35
|
-
console.log('what is doc', doc);
|
|
36
39
|
|
|
37
40
|
return { doc };
|
|
38
41
|
};
|
|
42
|
+
|
|
43
|
+
module.exports.paramsType = DeleteDocumentParams;
|
|
44
|
+
module.exports.tags = ['destructive'];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const DeleteDocumentsParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -14,10 +15,13 @@ const DeleteDocumentsParams = new Archetype({
|
|
|
14
15
|
},
|
|
15
16
|
roles: {
|
|
16
17
|
$type: ['string']
|
|
18
|
+
},
|
|
19
|
+
maxTimeMS: {
|
|
20
|
+
$type: 'number'
|
|
17
21
|
}
|
|
18
22
|
}).compile('DeleteDocumentsParams');
|
|
19
23
|
|
|
20
|
-
module.exports = ({ db }) => async function DeleteDocuments(params) {
|
|
24
|
+
module.exports = ({ db, options }) => async function DeleteDocuments(params) {
|
|
21
25
|
const { model, documentIds, roles } = new DeleteDocumentsParams(params);
|
|
22
26
|
|
|
23
27
|
const Model = db.models[model];
|
|
@@ -30,7 +34,11 @@ module.exports = ({ db }) => async function DeleteDocuments(params) {
|
|
|
30
34
|
|
|
31
35
|
await Model.
|
|
32
36
|
deleteMany({ _id: { $in: documentIds } }).
|
|
37
|
+
setOptions(operationOptions(options, params)).
|
|
33
38
|
orFail();
|
|
34
39
|
|
|
35
40
|
return { };
|
|
36
41
|
};
|
|
42
|
+
|
|
43
|
+
module.exports.paramsType = DeleteDocumentsParams;
|
|
44
|
+
module.exports.tags = ['destructive'];
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
5
|
+
const createSandbox = require('../../sandbox/createSandbox');
|
|
6
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
7
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
8
8
|
|
|
9
9
|
const ExecuteDocumentScriptParams = new Archetype({
|
|
10
10
|
model: {
|
|
@@ -21,41 +21,48 @@ const ExecuteDocumentScriptParams = new Archetype({
|
|
|
21
21
|
},
|
|
22
22
|
roles: {
|
|
23
23
|
$type: ['string']
|
|
24
|
+
},
|
|
25
|
+
maxTimeMS: {
|
|
26
|
+
$type: 'number'
|
|
27
|
+
},
|
|
28
|
+
readPreference: {
|
|
29
|
+
$type: 'string',
|
|
30
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
24
31
|
}
|
|
25
32
|
}).compile('ExecuteDocumentScriptParams');
|
|
26
33
|
|
|
27
|
-
module.exports = ({ db }) => async function executeDocumentScript(params) {
|
|
34
|
+
module.exports = ({ db, options }) => async function executeDocumentScript(params) {
|
|
28
35
|
const { model, documentId, script, roles } = new ExecuteDocumentScriptParams(params);
|
|
29
36
|
|
|
30
37
|
await authorize('Model.executeDocumentScript', roles);
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
if (Model == null) {
|
|
39
|
+
if (db.models[model] == null) {
|
|
34
40
|
throw new Error(`Model ${model} not found`);
|
|
35
41
|
}
|
|
36
42
|
|
|
37
|
-
const
|
|
43
|
+
const sandbox = createSandbox(db, { ...readOperationOptions(options, params) });
|
|
38
44
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
sandbox.console.log = function() {
|
|
46
|
-
const args = Array.from(arguments);
|
|
47
|
-
logs.push(args.map(arg => typeof arg === 'object' ? util.inspect(arg) : arg).join(' '));
|
|
48
|
-
};
|
|
45
|
+
try {
|
|
46
|
+
const Model = sandbox.db.models[model];
|
|
47
|
+
const doc = await Model.findById(documentId).
|
|
48
|
+
setOptions(operationOptions(options, params, { sanitizeFilter: true })).
|
|
49
|
+
orFail();
|
|
50
|
+
sandbox.context.doc = doc;
|
|
49
51
|
|
|
50
|
-
|
|
51
|
-
const result = await vm.runInContext(wrappedScript(script), context);
|
|
52
|
+
const result = await sandbox.runScript({ script });
|
|
52
53
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
return {
|
|
55
|
+
result,
|
|
56
|
+
logs: sandbox.getLogs()
|
|
57
|
+
};
|
|
58
|
+
} finally {
|
|
59
|
+
try {
|
|
60
|
+
await sandbox.close();
|
|
61
|
+
} catch (_) {
|
|
62
|
+
// Ignore sandbox cleanup errors so they do not mask the primary result.
|
|
63
|
+
}
|
|
64
|
+
}
|
|
57
65
|
};
|
|
58
66
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
})()`;
|
|
67
|
+
module.exports.paramsType = ExecuteDocumentScriptParams;
|
|
68
|
+
module.exports.tags = [];
|
|
@@ -5,6 +5,7 @@ const mongoose = require('mongoose');
|
|
|
5
5
|
const { stringify } = require('csv-stringify/sync');
|
|
6
6
|
const authorize = require('../../authorize');
|
|
7
7
|
const evaluateFilter = require('../../helpers/evaluateFilter');
|
|
8
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
8
9
|
|
|
9
10
|
const GetDocumentsParams = new Archetype({
|
|
10
11
|
model: {
|
|
@@ -26,10 +27,17 @@ const GetDocumentsParams = new Archetype({
|
|
|
26
27
|
},
|
|
27
28
|
roles: {
|
|
28
29
|
$type: ['string']
|
|
30
|
+
},
|
|
31
|
+
maxTimeMS: {
|
|
32
|
+
$type: 'number'
|
|
33
|
+
},
|
|
34
|
+
readPreference: {
|
|
35
|
+
$type: 'string',
|
|
36
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
29
37
|
}
|
|
30
38
|
}).compile('GetDocumentsParams');
|
|
31
39
|
|
|
32
|
-
module.exports = ({ db }) => async function exportQueryResults(params, req, res) {
|
|
40
|
+
module.exports = ({ db, options }) => async function exportQueryResults(params, req, res) {
|
|
33
41
|
params = new GetDocumentsParams(params);
|
|
34
42
|
const { model, propertiesToInclude, roles, searchText } = params;
|
|
35
43
|
|
|
@@ -45,6 +53,7 @@ module.exports = ({ db }) => async function exportQueryResults(params, req, res)
|
|
|
45
53
|
|
|
46
54
|
const docs = await Model.
|
|
47
55
|
find(filter).
|
|
56
|
+
setOptions(readOperationOptions(options, params)).
|
|
48
57
|
sort({ _id: -1 });
|
|
49
58
|
|
|
50
59
|
const rows = [propertiesToInclude];
|
|
@@ -66,3 +75,6 @@ module.exports = ({ db }) => async function exportQueryResults(params, req, res)
|
|
|
66
75
|
res.setHeader('Content-Disposition', `attachment; filename="${model.toLowerCase()}-export.csv"`);
|
|
67
76
|
return csv;
|
|
68
77
|
};
|
|
78
|
+
|
|
79
|
+
module.exports.paramsType = GetDocumentsParams;
|
|
80
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
5
6
|
|
|
6
7
|
const GetCollectionInfoParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -10,10 +11,17 @@ const GetCollectionInfoParams = new Archetype({
|
|
|
10
11
|
},
|
|
11
12
|
roles: {
|
|
12
13
|
$type: ['string']
|
|
14
|
+
},
|
|
15
|
+
maxTimeMS: {
|
|
16
|
+
$type: 'number'
|
|
17
|
+
},
|
|
18
|
+
readPreference: {
|
|
19
|
+
$type: 'string',
|
|
20
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
13
21
|
}
|
|
14
22
|
}).compile('GetCollectionInfoParams');
|
|
15
23
|
|
|
16
|
-
module.exports = ({ db }) => async function getCollectionInfo(params) {
|
|
24
|
+
module.exports = ({ db, options }) => async function getCollectionInfo(params) {
|
|
17
25
|
const { model, roles } = new GetCollectionInfoParams(params);
|
|
18
26
|
|
|
19
27
|
await authorize('Model.getCollectionInfo', roles);
|
|
@@ -23,8 +31,9 @@ module.exports = ({ db }) => async function getCollectionInfo(params) {
|
|
|
23
31
|
throw new Error(`Model ${model} not found`);
|
|
24
32
|
}
|
|
25
33
|
|
|
34
|
+
const dbOptions = readOperationOptions(options, params);
|
|
26
35
|
const [collectionOptions, stats] = await Promise.all([
|
|
27
|
-
Model.collection.options(),
|
|
36
|
+
Model.collection.options(dbOptions),
|
|
28
37
|
Model.aggregate([
|
|
29
38
|
{
|
|
30
39
|
$collStats: {
|
|
@@ -32,7 +41,7 @@ module.exports = ({ db }) => async function getCollectionInfo(params) {
|
|
|
32
41
|
count: {}
|
|
33
42
|
}
|
|
34
43
|
}
|
|
35
|
-
]).then(res => res[0] ?? {})
|
|
44
|
+
]).option(dbOptions).then(res => res[0] ?? {})
|
|
36
45
|
]);
|
|
37
46
|
|
|
38
47
|
return {
|
|
@@ -47,3 +56,6 @@ module.exports = ({ db }) => async function getCollectionInfo(params) {
|
|
|
47
56
|
}
|
|
48
57
|
};
|
|
49
58
|
};
|
|
59
|
+
|
|
60
|
+
module.exports.paramsType = GetCollectionInfoParams;
|
|
61
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -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 readOperationOptions = require('../../helpers/readOperationOptions');
|
|
7
8
|
|
|
8
9
|
const GetDocumentParams = new Archetype({
|
|
9
10
|
model: {
|
|
@@ -16,10 +17,17 @@ const GetDocumentParams = new Archetype({
|
|
|
16
17
|
},
|
|
17
18
|
roles: {
|
|
18
19
|
$type: ['string']
|
|
20
|
+
},
|
|
21
|
+
maxTimeMS: {
|
|
22
|
+
$type: 'number'
|
|
23
|
+
},
|
|
24
|
+
readPreference: {
|
|
25
|
+
$type: 'string',
|
|
26
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
19
27
|
}
|
|
20
28
|
}).compile('GetDocumentParams');
|
|
21
29
|
|
|
22
|
-
module.exports = ({ db }) => async function getDocument(params) {
|
|
30
|
+
module.exports = ({ db, options }) => async function getDocument(params) {
|
|
23
31
|
const { model, documentId, roles } = new GetDocumentParams(params);
|
|
24
32
|
|
|
25
33
|
await authorize('Model.getDocument', roles);
|
|
@@ -31,7 +39,7 @@ module.exports = ({ db }) => async function getDocument(params) {
|
|
|
31
39
|
|
|
32
40
|
const doc = await Model.
|
|
33
41
|
findById(documentId).
|
|
34
|
-
setOptions({ sanitizeFilter: true }).
|
|
42
|
+
setOptions(readOperationOptions(options, { sanitizeFilter: true })).
|
|
35
43
|
orFail();
|
|
36
44
|
const schemaPaths = {};
|
|
37
45
|
for (const path of Object.keys(Model.schema.paths)) {
|
|
@@ -49,3 +57,6 @@ module.exports = ({ db }) => async function getDocument(params) {
|
|
|
49
57
|
|
|
50
58
|
return { doc: doc.toJSON({ virtuals: true, getters: false, transform: false }), schemaPaths, virtualPaths };
|
|
51
59
|
};
|
|
60
|
+
|
|
61
|
+
module.exports.paramsType = GetDocumentParams;
|
|
62
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -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 readOperationOptions = require('../../helpers/readOperationOptions');
|
|
10
11
|
|
|
11
12
|
const GetDocumentsParams = new Archetype({
|
|
12
13
|
model: {
|
|
@@ -37,10 +38,17 @@ const GetDocumentsParams = new Archetype({
|
|
|
37
38
|
},
|
|
38
39
|
roles: {
|
|
39
40
|
$type: ['string']
|
|
41
|
+
},
|
|
42
|
+
maxTimeMS: {
|
|
43
|
+
$type: 'number'
|
|
44
|
+
},
|
|
45
|
+
readPreference: {
|
|
46
|
+
$type: 'string',
|
|
47
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
40
48
|
}
|
|
41
49
|
}).compile('GetDocumentsParams');
|
|
42
50
|
|
|
43
|
-
module.exports = ({ db }) => async function getDocuments(params) {
|
|
51
|
+
module.exports = ({ db, options }) => async function getDocuments(params) {
|
|
44
52
|
params = new GetDocumentsParams(params);
|
|
45
53
|
const { roles } = params;
|
|
46
54
|
await authorize('Model.getDocuments', roles);
|
|
@@ -75,6 +83,7 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
75
83
|
if (projection != null) {
|
|
76
84
|
query = query.select(projection);
|
|
77
85
|
}
|
|
86
|
+
query.setOptions(readOperationOptions(options, params));
|
|
78
87
|
const cursor = await query.cursor();
|
|
79
88
|
const docs = [];
|
|
80
89
|
for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
|
|
@@ -106,9 +115,16 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
106
115
|
}
|
|
107
116
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
108
117
|
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
118
|
+
const countQuery = parsedFilter == null ?
|
|
119
|
+
Model.estimatedDocumentCount() :
|
|
120
|
+
Model.countDocuments(filter);
|
|
121
|
+
let numDocuments = null;
|
|
122
|
+
let numDocsError = null;
|
|
123
|
+
try {
|
|
124
|
+
numDocuments = await countQuery.setOptions(readOperationOptions(options, params));
|
|
125
|
+
} catch (err) {
|
|
126
|
+
numDocsError = err?.message;
|
|
127
|
+
}
|
|
112
128
|
|
|
113
129
|
const suggestedFields = getSuggestedProjection(Model);
|
|
114
130
|
|
|
@@ -116,6 +132,10 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
116
132
|
docs: docs.map(doc => doc.toJSON({ virtuals: false, getters: false, transform: false })),
|
|
117
133
|
schemaPaths,
|
|
118
134
|
suggestedFields,
|
|
119
|
-
numDocs: numDocuments
|
|
135
|
+
numDocs: numDocuments,
|
|
136
|
+
numDocsError
|
|
120
137
|
};
|
|
121
138
|
};
|
|
139
|
+
|
|
140
|
+
module.exports.paramsType = GetDocumentsParams;
|
|
141
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -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 readOperationOptions = require('../../helpers/readOperationOptions');
|
|
10
11
|
|
|
11
12
|
const GetDocumentsParams = new Archetype({
|
|
12
13
|
model: {
|
|
@@ -37,10 +38,17 @@ const GetDocumentsParams = new Archetype({
|
|
|
37
38
|
},
|
|
38
39
|
roles: {
|
|
39
40
|
$type: ['string']
|
|
41
|
+
},
|
|
42
|
+
maxTimeMS: {
|
|
43
|
+
$type: 'number'
|
|
44
|
+
},
|
|
45
|
+
readPreference: {
|
|
46
|
+
$type: 'string',
|
|
47
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
40
48
|
}
|
|
41
49
|
}).compile('GetDocumentsParams');
|
|
42
50
|
|
|
43
|
-
module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
51
|
+
module.exports = ({ db, options }) => async function* getDocumentsStream(params) {
|
|
44
52
|
params = new GetDocumentsParams(params);
|
|
45
53
|
const { roles } = params;
|
|
46
54
|
await authorize('Model.getDocumentsStream', roles);
|
|
@@ -75,6 +83,7 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
75
83
|
if (projection != null) {
|
|
76
84
|
query = query.select(projection);
|
|
77
85
|
}
|
|
86
|
+
query.setOptions(readOperationOptions(options, params));
|
|
78
87
|
|
|
79
88
|
const schemaPaths = {};
|
|
80
89
|
for (const path of Object.keys(Model.schema.paths)) {
|
|
@@ -106,32 +115,36 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
106
115
|
yield { schemaPaths, suggestedFields };
|
|
107
116
|
|
|
108
117
|
// Start counting documents in parallel with streaming documents
|
|
109
|
-
const
|
|
110
|
-
? Model.estimatedDocumentCount()
|
|
111
|
-
: Model.countDocuments(filter)
|
|
112
|
-
|
|
113
|
-
|
|
118
|
+
const countQuery = (parsedFilter == null)
|
|
119
|
+
? Model.estimatedDocumentCount()
|
|
120
|
+
: Model.countDocuments(filter);
|
|
121
|
+
const numDocsPromise = countQuery.
|
|
122
|
+
setOptions(readOperationOptions(options, params)).
|
|
123
|
+
exec();
|
|
114
124
|
|
|
115
125
|
let numDocsYielded = false;
|
|
116
126
|
let numDocumentsPromiseResolved = false;
|
|
117
127
|
let numDocumentsValue;
|
|
118
128
|
let numDocumentsError;
|
|
119
129
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
130
|
+
// Attach both handlers immediately so a fast count failure cannot become
|
|
131
|
+
// an unhandled rejection while the document cursor is still opening.
|
|
132
|
+
numDocsPromise.then(num => {
|
|
133
|
+
numDocumentsPromiseResolved = true;
|
|
134
|
+
numDocumentsValue = num;
|
|
135
|
+
}).catch(err => {
|
|
136
|
+
numDocumentsPromiseResolved = true;
|
|
137
|
+
numDocumentsError = err;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
const cursor = await query.cursor();
|
|
129
141
|
|
|
142
|
+
try {
|
|
130
143
|
for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
|
|
131
144
|
// If numDocsPromise has resolved and not yet yielded, yield it first
|
|
132
145
|
if (numDocumentsPromiseResolved && !numDocsYielded) {
|
|
133
146
|
if (numDocumentsError) {
|
|
134
|
-
yield {
|
|
147
|
+
yield { numDocsError: numDocumentsError.message };
|
|
135
148
|
} else {
|
|
136
149
|
yield { numDocs: numDocumentsValue };
|
|
137
150
|
}
|
|
@@ -142,10 +155,17 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
142
155
|
|
|
143
156
|
// If numDocsPromise hasn't resolved yet, wait for it and yield
|
|
144
157
|
if (!numDocsYielded) {
|
|
145
|
-
|
|
146
|
-
|
|
158
|
+
try {
|
|
159
|
+
const numDocuments = await numDocsPromise;
|
|
160
|
+
yield { numDocs: numDocuments };
|
|
161
|
+
} catch (err) {
|
|
162
|
+
yield { numDocsError: err.message };
|
|
163
|
+
}
|
|
147
164
|
}
|
|
148
165
|
} finally {
|
|
149
166
|
await cursor.close();
|
|
150
167
|
}
|
|
151
168
|
};
|
|
169
|
+
|
|
170
|
+
module.exports.paramsType = GetDocumentsParams;
|
|
171
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,14 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
5
6
|
|
|
6
7
|
const GetEstimatedDocumentCountsParams = new Archetype({
|
|
7
8
|
roles: {
|
|
8
9
|
$type: ['string']
|
|
10
|
+
},
|
|
11
|
+
maxTimeMS: {
|
|
12
|
+
$type: 'number'
|
|
13
|
+
},
|
|
14
|
+
readPreference: {
|
|
15
|
+
$type: 'string',
|
|
16
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
9
17
|
}
|
|
10
18
|
}).compile('GetEstimatedDocumentCountsParams');
|
|
11
19
|
|
|
12
|
-
module.exports = ({ db }) => async function getEstimatedDocumentCounts(params) {
|
|
20
|
+
module.exports = ({ db, options }) => async function getEstimatedDocumentCounts(params) {
|
|
13
21
|
const { roles } = new GetEstimatedDocumentCountsParams(params);
|
|
14
22
|
await authorize('Model.getEstimatedDocumentCounts', roles);
|
|
15
23
|
|
|
@@ -20,7 +28,9 @@ module.exports = ({ db }) => async function getEstimatedDocumentCounts(params) {
|
|
|
20
28
|
const results = await Promise.allSettled(
|
|
21
29
|
modelNames.map(name => {
|
|
22
30
|
const Model = db.models[name];
|
|
23
|
-
return Model.estimatedDocumentCount().
|
|
31
|
+
return Model.estimatedDocumentCount().
|
|
32
|
+
setOptions(readOperationOptions(options, params)).
|
|
33
|
+
exec();
|
|
24
34
|
})
|
|
25
35
|
);
|
|
26
36
|
|
|
@@ -36,3 +46,6 @@ module.exports = ({ db }) => async function getEstimatedDocumentCounts(params) {
|
|
|
36
46
|
|
|
37
47
|
return { counts };
|
|
38
48
|
};
|
|
49
|
+
|
|
50
|
+
module.exports.paramsType = GetEstimatedDocumentCountsParams;
|
|
51
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
5
6
|
|
|
6
7
|
const UpdateDocumentsParams = new Archetype({
|
|
7
8
|
model: {
|
|
@@ -21,10 +22,13 @@ const UpdateDocumentsParams = new Archetype({
|
|
|
21
22
|
},
|
|
22
23
|
roles: {
|
|
23
24
|
$type: ['string']
|
|
25
|
+
},
|
|
26
|
+
maxTimeMS: {
|
|
27
|
+
$type: 'number'
|
|
24
28
|
}
|
|
25
29
|
}).compile('UpdateDocumentsParams');
|
|
26
30
|
|
|
27
|
-
module.exports = ({ db }) => async function updateDocument(params) {
|
|
31
|
+
module.exports = ({ db, options }) => async function updateDocument(params) {
|
|
28
32
|
const { model, _id, update, unset, roles } = new UpdateDocumentsParams(params);
|
|
29
33
|
|
|
30
34
|
await authorize('Model.updateDocument', roles);
|
|
@@ -58,6 +62,10 @@ module.exports = ({ db }) => async function updateDocument(params) {
|
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
const doc = await Model.
|
|
61
|
-
findByIdAndUpdate(_id, updateOperation, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true, runValidators: false })
|
|
65
|
+
findByIdAndUpdate(_id, updateOperation, { sanitizeFilter: true, returnDocument: 'after', overwriteImmutable: true, runValidators: false }).
|
|
66
|
+
setOptions(operationOptions(options, params));
|
|
62
67
|
return { doc };
|
|
63
68
|
};
|
|
69
|
+
|
|
70
|
+
module.exports.paramsType = UpdateDocumentsParams;
|
|
71
|
+
module.exports.tags = [];
|