@mongoosejs/studio 0.3.3 → 0.3.4
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/backend/actions/Model/getDocuments.js +16 -7
- package/backend/actions/Model/getDocumentsStream.js +16 -9
- package/backend/actions/Model/getSuggestedProjection.js +33 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/authorize.js +1 -0
- package/backend/helpers/getSuggestedProjection.js +37 -0
- package/backend/helpers/parseFieldsParam.js +36 -0
- package/frontend/public/app.js +690 -172
- package/frontend/public/dark-theme.css +166 -0
- package/frontend/public/theme-variables.css +30 -0
- package/frontend/public/tw.css +110 -46
- package/frontend/src/api.js +7 -1
- package/frontend/src/dashboard/dashboard.js +1 -1
- package/frontend/src/list-mixed/list-mixed.html +0 -1
- package/frontend/src/list-string/list-string.html +0 -1
- package/frontend/src/models/models.css +25 -90
- package/frontend/src/models/models.html +213 -53
- package/frontend/src/models/models.js +611 -132
- package/frontend/src/navbar/navbar.js +1 -1
- package/frontend/src/task-by-name/task-by-name.html +11 -11
- package/frontend/src/task-single/task-single.html +4 -4
- package/frontend/src/tasks/task-details/task-details.html +6 -6
- package/package.json +1 -1
|
@@ -4,6 +4,8 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const removeSpecifiedPaths = require('../../helpers/removeSpecifiedPaths');
|
|
5
5
|
const evaluateFilter = require('../../helpers/evaluateFilter');
|
|
6
6
|
const getRefFromSchemaType = require('../../helpers/getRefFromSchemaType');
|
|
7
|
+
const getSuggestedProjection = require('../../helpers/getSuggestedProjection');
|
|
8
|
+
const parseFieldsParam = require('../../helpers/parseFieldsParam');
|
|
7
9
|
const authorize = require('../../authorize');
|
|
8
10
|
|
|
9
11
|
const GetDocumentsParams = new Archetype({
|
|
@@ -30,6 +32,9 @@ const GetDocumentsParams = new Archetype({
|
|
|
30
32
|
sortDirection: {
|
|
31
33
|
$type: 'number'
|
|
32
34
|
},
|
|
35
|
+
fields: {
|
|
36
|
+
$type: 'string'
|
|
37
|
+
},
|
|
33
38
|
roles: {
|
|
34
39
|
$type: ['string']
|
|
35
40
|
}
|
|
@@ -40,7 +45,7 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
40
45
|
const { roles } = params;
|
|
41
46
|
await authorize('Model.getDocuments', roles);
|
|
42
47
|
|
|
43
|
-
const { model, limit, skip, sortKey, sortDirection, searchText } = params;
|
|
48
|
+
const { model, limit, skip, sortKey, sortDirection, searchText, fields } = params;
|
|
44
49
|
|
|
45
50
|
const Model = db.models[model];
|
|
46
51
|
if (Model == null) {
|
|
@@ -61,12 +66,13 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
61
66
|
if (!sortObj.hasOwnProperty('_id')) {
|
|
62
67
|
sortObj._id = -1;
|
|
63
68
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
|
|
70
|
+
let query = Model.find(filter).limit(limit).skip(skip).sort(sortObj);
|
|
71
|
+
const projection = parseFieldsParam(fields);
|
|
72
|
+
if (projection != null) {
|
|
73
|
+
query = query.select(projection);
|
|
74
|
+
}
|
|
75
|
+
const cursor = await query.cursor();
|
|
70
76
|
const docs = [];
|
|
71
77
|
for (let doc = await cursor.next(); doc != null; doc = await cursor.next()) {
|
|
72
78
|
docs.push(doc);
|
|
@@ -101,9 +107,12 @@ module.exports = ({ db }) => async function getDocuments(params) {
|
|
|
101
107
|
await Model.estimatedDocumentCount() :
|
|
102
108
|
await Model.countDocuments(filter);
|
|
103
109
|
|
|
110
|
+
const suggestedFields = getSuggestedProjection(Model);
|
|
111
|
+
|
|
104
112
|
return {
|
|
105
113
|
docs: docs.map(doc => doc.toJSON({ virtuals: false, getters: false, transform: false })),
|
|
106
114
|
schemaPaths,
|
|
115
|
+
suggestedFields,
|
|
107
116
|
numDocs: numDocuments
|
|
108
117
|
};
|
|
109
118
|
};
|
|
@@ -4,6 +4,8 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const removeSpecifiedPaths = require('../../helpers/removeSpecifiedPaths');
|
|
5
5
|
const evaluateFilter = require('../../helpers/evaluateFilter');
|
|
6
6
|
const getRefFromSchemaType = require('../../helpers/getRefFromSchemaType');
|
|
7
|
+
const getSuggestedProjection = require('../../helpers/getSuggestedProjection');
|
|
8
|
+
const parseFieldsParam = require('../../helpers/parseFieldsParam');
|
|
7
9
|
const authorize = require('../../authorize');
|
|
8
10
|
|
|
9
11
|
const GetDocumentsParams = new Archetype({
|
|
@@ -30,6 +32,9 @@ const GetDocumentsParams = new Archetype({
|
|
|
30
32
|
sortDirection: {
|
|
31
33
|
$type: 'number'
|
|
32
34
|
},
|
|
35
|
+
fields: {
|
|
36
|
+
$type: 'string'
|
|
37
|
+
},
|
|
33
38
|
roles: {
|
|
34
39
|
$type: ['string']
|
|
35
40
|
}
|
|
@@ -40,7 +45,7 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
40
45
|
const { roles } = params;
|
|
41
46
|
await authorize('Model.getDocumentsStream', roles);
|
|
42
47
|
|
|
43
|
-
const { model, limit, skip, sortKey, sortDirection, searchText } = params;
|
|
48
|
+
const { model, limit, skip, sortKey, sortDirection, searchText, fields } = params;
|
|
44
49
|
|
|
45
50
|
const Model = db.models[model];
|
|
46
51
|
if (Model == null) {
|
|
@@ -62,6 +67,12 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
62
67
|
sortObj._id = -1;
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
let query = Model.find(filter).limit(limit).skip(skip).sort(sortObj).batchSize(1);
|
|
71
|
+
const projection = parseFieldsParam(fields);
|
|
72
|
+
if (projection != null) {
|
|
73
|
+
query = query.select(projection);
|
|
74
|
+
}
|
|
75
|
+
|
|
65
76
|
const schemaPaths = {};
|
|
66
77
|
for (const path of Object.keys(Model.schema.paths)) {
|
|
67
78
|
const schemaType = Model.schema.paths[path];
|
|
@@ -87,20 +98,16 @@ module.exports = ({ db }) => async function* getDocumentsStream(params) {
|
|
|
87
98
|
}
|
|
88
99
|
removeSpecifiedPaths(schemaPaths, '.$*');
|
|
89
100
|
|
|
90
|
-
|
|
101
|
+
const suggestedFields = getSuggestedProjection(Model);
|
|
102
|
+
|
|
103
|
+
yield { schemaPaths, suggestedFields };
|
|
91
104
|
|
|
92
105
|
// Start counting documents in parallel with streaming documents
|
|
93
106
|
const numDocsPromise = (parsedFilter == null)
|
|
94
107
|
? Model.estimatedDocumentCount().exec()
|
|
95
108
|
: Model.countDocuments(filter).exec();
|
|
96
109
|
|
|
97
|
-
const cursor = await
|
|
98
|
-
find(filter).
|
|
99
|
-
limit(limit).
|
|
100
|
-
skip(skip).
|
|
101
|
-
sort(sortObj).
|
|
102
|
-
batchSize(1).
|
|
103
|
-
cursor();
|
|
110
|
+
const cursor = await query.cursor();
|
|
104
111
|
|
|
105
112
|
let numDocsYielded = false;
|
|
106
113
|
let numDocumentsPromiseResolved = false;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const getSuggestedProjection = require('../../helpers/getSuggestedProjection');
|
|
5
|
+
const authorize = require('../../authorize');
|
|
6
|
+
|
|
7
|
+
const GetSuggestedProjectionParams = new Archetype({
|
|
8
|
+
model: {
|
|
9
|
+
$type: 'string',
|
|
10
|
+
$required: true
|
|
11
|
+
},
|
|
12
|
+
roles: {
|
|
13
|
+
$type: ['string']
|
|
14
|
+
}
|
|
15
|
+
}).compile('GetSuggestedProjectionParams');
|
|
16
|
+
|
|
17
|
+
module.exports = ({ db }) => async function getSuggestedProjectionAction(params) {
|
|
18
|
+
params = new GetSuggestedProjectionParams(params);
|
|
19
|
+
const { roles } = params;
|
|
20
|
+
await authorize('Model.getSuggestedProjection', roles);
|
|
21
|
+
|
|
22
|
+
const { model } = params;
|
|
23
|
+
|
|
24
|
+
const Model = db.models[model];
|
|
25
|
+
if (Model == null) {
|
|
26
|
+
throw new Error(`Model ${model} not found`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Default columns: first N schema paths (no scoring).
|
|
30
|
+
const suggestedFields = getSuggestedProjection(Model);
|
|
31
|
+
|
|
32
|
+
return { suggestedFields };
|
|
33
|
+
};
|
|
@@ -11,6 +11,7 @@ exports.exportQueryResults = require('./exportQueryResults');
|
|
|
11
11
|
exports.getDocument = require('./getDocument');
|
|
12
12
|
exports.getDocuments = require('./getDocuments');
|
|
13
13
|
exports.getDocumentsStream = require('./getDocumentsStream');
|
|
14
|
+
exports.getSuggestedProjection = require('./getSuggestedProjection');
|
|
14
15
|
exports.getCollectionInfo = require('./getCollectionInfo');
|
|
15
16
|
exports.getIndexes = require('./getIndexes');
|
|
16
17
|
exports.getEstimatedDocumentCounts = require('./getEstimatedDocumentCounts');
|
package/backend/authorize.js
CHANGED
|
@@ -22,6 +22,7 @@ const actionsToRequiredRoles = {
|
|
|
22
22
|
'Model.getDocument': ['owner', 'admin', 'member', 'readonly'],
|
|
23
23
|
'Model.getDocuments': ['owner', 'admin', 'member', 'readonly'],
|
|
24
24
|
'Model.getDocumentsStream': ['owner', 'admin', 'member', 'readonly'],
|
|
25
|
+
'Model.getSuggestedProjection': ['owner', 'admin', 'member', 'readonly'],
|
|
25
26
|
'Model.getEstimatedDocumentCounts': ['owner', 'admin', 'member', 'readonly'],
|
|
26
27
|
'Model.getIndexes': ['owner', 'admin', 'member', 'readonly'],
|
|
27
28
|
'Model.listModels': ['owner', 'admin', 'member', 'readonly'],
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/** Max number of paths to use for the default table projection. */
|
|
4
|
+
const DEFAULT_SUGGESTED_LIMIT = 6;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Default projection for the models table: the first N schema paths (definition order),
|
|
8
|
+
* excluding Mongoose internals. No scoring — stable and predictable.
|
|
9
|
+
*
|
|
10
|
+
* @param {import('mongoose').Model} Model - Mongoose model
|
|
11
|
+
* @param {{ limit?: number }} options - max paths returned
|
|
12
|
+
* @returns {string[]} Path names in schema order
|
|
13
|
+
*/
|
|
14
|
+
function getSuggestedProjection(Model, options = {}) {
|
|
15
|
+
const limit = typeof options.limit === 'number' && options.limit > 0
|
|
16
|
+
? options.limit
|
|
17
|
+
: DEFAULT_SUGGESTED_LIMIT;
|
|
18
|
+
|
|
19
|
+
const pathNames = Object.keys(Model.schema.paths).filter(key =>
|
|
20
|
+
!key.includes('.$*') &&
|
|
21
|
+
key !== '__v'
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
pathNames.sort((k1, k2) => {
|
|
25
|
+
if (k1 === '_id' && k2 !== '_id') {
|
|
26
|
+
return -1;
|
|
27
|
+
}
|
|
28
|
+
if (k1 !== '_id' && k2 === '_id') {
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
return 0;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
return pathNames.slice(0, limit);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = getSuggestedProjection;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parse the `fields` request param for Model.getDocuments / getDocumentsStream.
|
|
5
|
+
* Expects JSON: either `["a","b"]` (inclusion list) or `{"a":1,"b":1}` (Mongo projection).
|
|
6
|
+
*
|
|
7
|
+
* @param {string|undefined} fields
|
|
8
|
+
* @returns {string|object|null} Argument suitable for Query.select(), or null when unset/invalid.
|
|
9
|
+
*/
|
|
10
|
+
function parseFieldsParam(fields) {
|
|
11
|
+
if (fields == null || typeof fields !== 'string') {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
const trimmed = fields.trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(trimmed);
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (Array.isArray(parsed)) {
|
|
27
|
+
const list = parsed.map(x => String(x).trim()).filter(Boolean);
|
|
28
|
+
return list.length > 0 ? list.join(' ') : null;
|
|
29
|
+
}
|
|
30
|
+
if (parsed != null && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
31
|
+
return Object.keys(parsed).length > 0 ? parsed : null;
|
|
32
|
+
}
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
module.exports = parseFieldsParam;
|