@mongoosejs/studio 0.4.0 → 0.5.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/backend/actions/ChatMessage/executeScript.js +17 -25
- package/backend/actions/Model/getDocuments.js +7 -4
- package/backend/actions/Model/getDocumentsStream.js +7 -4
- package/backend/actions/getCapabilities.js +42 -0
- package/backend/actions/index.js +1 -1
- package/backend/db/chatMessageSchema.js +2 -1
- package/backend/helpers/isBindIPConnection.js +8 -0
- package/backend/helpers/isLocalhostConnection.js +8 -0
- package/backend/helpers/normalizeBindIPOption.js +16 -0
- package/backend/helpers/parseProjectionParam.js +180 -0
- package/backend/sandbox/createSandbox.js +83 -0
- package/backend/sandbox/createScriptDb.js +201 -0
- package/express.js +16 -0
- package/frontend/public/app.js +845 -114
- package/frontend/public/tw.css +4 -0
- package/frontend/src/api.js +6 -0
- package/frontend/src/chat/chat-message/chat-message.html +1 -0
- package/frontend/src/chat/chat-message/chat-message.js +4 -3
- package/frontend/src/chat/chat-message-script/chat-message-script.html +98 -16
- package/frontend/src/chat/chat-message-script/chat-message-script.js +77 -17
- package/frontend/src/chat/chat.html +5 -1
- package/frontend/src/chat/chat.js +11 -2
- package/frontend/src/detail-objectid/detail-objectid.html +1 -0
- package/frontend/src/detail-objectid/detail-objectid.js +67 -0
- package/frontend/src/document-details/document-property/document-property.html +8 -3
- package/frontend/src/document-details/document-property/document-property.js +7 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.html +12 -0
- package/frontend/src/document-details/objectid-view-mode-picker/objectid-view-mode-picker.js +19 -0
- package/frontend/src/modal/modal.css +0 -1
- package/frontend/src/models/document-search/document-search.js +6 -0
- package/frontend/src/models/models.html +6 -8
- package/frontend/src/models/models.js +257 -85
- package/frontend/src/models/projection-search/projection-search.html +27 -0
- package/frontend/src/models/projection-search/projection-search.js +226 -0
- package/index.d.ts +1 -1
- package/package.json +2 -2
- package/backend/actions/Script/execute.js +0 -20
- package/backend/actions/Script/index.js +0 -3
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const wrappedCollections = new WeakSet();
|
|
4
|
+
|
|
5
|
+
const collectionMethodOptionsIndex = new Map([
|
|
6
|
+
['bulkWrite', 1],
|
|
7
|
+
['countDocuments', 1],
|
|
8
|
+
['createIndex', 1],
|
|
9
|
+
['createIndexes', 1],
|
|
10
|
+
['deleteMany', 1],
|
|
11
|
+
['deleteOne', 1],
|
|
12
|
+
['distinct', 2],
|
|
13
|
+
['drop', 0],
|
|
14
|
+
['dropIndex', 1],
|
|
15
|
+
['dropIndexes', 0],
|
|
16
|
+
['estimatedDocumentCount', 0],
|
|
17
|
+
['find', 1],
|
|
18
|
+
['findOne', 1],
|
|
19
|
+
['findOneAndDelete', 1],
|
|
20
|
+
['findOneAndReplace', 2],
|
|
21
|
+
['findOneAndUpdate', 2],
|
|
22
|
+
['insertMany', 1],
|
|
23
|
+
['insertOne', 1],
|
|
24
|
+
['rename', 1],
|
|
25
|
+
['replaceOne', 2],
|
|
26
|
+
['updateMany', 2],
|
|
27
|
+
['updateOne', 2]
|
|
28
|
+
]);
|
|
29
|
+
|
|
30
|
+
function createScriptDb(db) {
|
|
31
|
+
const sourceConnection = db;
|
|
32
|
+
|
|
33
|
+
const scriptConnection = sourceConnection.useDb(sourceConnection.name, { useCache: false });
|
|
34
|
+
scriptConnection.config = {
|
|
35
|
+
...scriptConnection.config,
|
|
36
|
+
autoCreate: false,
|
|
37
|
+
autoIndex: false
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
let dryRunSession = null;
|
|
41
|
+
const getSession = () => dryRunSession;
|
|
42
|
+
cloneModels(sourceConnection, scriptConnection, getSession);
|
|
43
|
+
wrapCollectionAccessors(scriptConnection, getSession);
|
|
44
|
+
|
|
45
|
+
const originalDebug = sourceConnection.options?.debug;
|
|
46
|
+
scriptConnection.set('debug', function() {
|
|
47
|
+
if (typeof originalDebug === 'function') {
|
|
48
|
+
return originalDebug.apply(this, arguments);
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
db: scriptConnection,
|
|
54
|
+
setDryRunSession(session) {
|
|
55
|
+
dryRunSession = session;
|
|
56
|
+
},
|
|
57
|
+
close() {
|
|
58
|
+
if (Array.isArray(sourceConnection.otherDbs)) {
|
|
59
|
+
sourceConnection.otherDbs = sourceConnection.otherDbs.filter(db => db !== scriptConnection);
|
|
60
|
+
}
|
|
61
|
+
if (Array.isArray(scriptConnection.otherDbs)) {
|
|
62
|
+
scriptConnection.otherDbs = [];
|
|
63
|
+
}
|
|
64
|
+
if (sourceConnection.relatedDbs?.[scriptConnection.name] === scriptConnection) {
|
|
65
|
+
delete sourceConnection.relatedDbs[scriptConnection.name];
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function cloneModels(sourceConnection, scriptConnection, getSession) {
|
|
72
|
+
for (const Model of Object.values(sourceConnection.models ?? {})) {
|
|
73
|
+
if (Model?.schema == null || typeof scriptConnection.model !== 'function') {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const ClonedModel = scriptConnection.model(Model.modelName, Model.schema, getCollectionName(Model));
|
|
78
|
+
wrapModelCollections(ClonedModel, getSession);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function getCollectionName(Model) {
|
|
83
|
+
return Model.collection?.collectionName ?? Model.collection?.name ?? Model.$__collection?.collectionName;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function wrapModelCollections(Model, getSession) {
|
|
87
|
+
const collection = Model?.collection ?? Model?.$__collection;
|
|
88
|
+
if (collection == null) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const wrappedCollection = wrapCollection(collection, getSession);
|
|
93
|
+
Model.collection = wrappedCollection;
|
|
94
|
+
Model.$__collection = wrappedCollection;
|
|
95
|
+
|
|
96
|
+
if (Model.prototype != null) {
|
|
97
|
+
Model.prototype.collection = wrappedCollection;
|
|
98
|
+
Model.prototype.$collection = wrappedCollection;
|
|
99
|
+
setModelCollectionSymbols(Model.prototype, wrappedCollection);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function setModelCollectionSymbols(target, collection) {
|
|
104
|
+
for (const symbol of Object.getOwnPropertySymbols(target)) {
|
|
105
|
+
if (symbol.description === 'mongoose#Model#collection') {
|
|
106
|
+
target[symbol] = collection;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function wrapCollectionAccessors(scriptConnection, getSession) {
|
|
112
|
+
const wrappedCollectionCache = new WeakMap();
|
|
113
|
+
|
|
114
|
+
const nativeDb = scriptConnection.db;
|
|
115
|
+
const originalDbCollection = nativeDb != null && typeof nativeDb.collection === 'function'
|
|
116
|
+
? nativeDb.collection
|
|
117
|
+
: null;
|
|
118
|
+
|
|
119
|
+
if (typeof scriptConnection.collection === 'function') {
|
|
120
|
+
const originalConnectionCollection = scriptConnection.collection;
|
|
121
|
+
scriptConnection.collection = function() {
|
|
122
|
+
const collection = originalConnectionCollection.apply(this, arguments);
|
|
123
|
+
if (collection != null && originalDbCollection != null && collection.name != null) {
|
|
124
|
+
if (collection.collection == null || wrappedCollections.has(collection.collection)) {
|
|
125
|
+
collection.collection = originalDbCollection.call(nativeDb, collection.name);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return getOrCreateWrappedCollection(wrappedCollectionCache, collection, getSession);
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (originalDbCollection != null) {
|
|
133
|
+
nativeDb.collection = function() {
|
|
134
|
+
const collection = originalDbCollection.apply(this, arguments);
|
|
135
|
+
return getOrCreateWrappedCollection(wrappedCollectionCache, collection, getSession);
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function getOrCreateWrappedCollection(cache, collection, getSession) {
|
|
141
|
+
if (collection == null || (typeof collection !== 'object' && typeof collection !== 'function')) {
|
|
142
|
+
return collection;
|
|
143
|
+
}
|
|
144
|
+
if (wrappedCollections.has(collection)) {
|
|
145
|
+
return collection;
|
|
146
|
+
}
|
|
147
|
+
if (cache.has(collection)) {
|
|
148
|
+
return cache.get(collection);
|
|
149
|
+
}
|
|
150
|
+
const wrapped = wrapCollection(collection, getSession);
|
|
151
|
+
cache.set(collection, wrapped);
|
|
152
|
+
return wrapped;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function wrapCollection(collection, getSession) {
|
|
156
|
+
if (wrappedCollections.has(collection)) {
|
|
157
|
+
return collection;
|
|
158
|
+
}
|
|
159
|
+
const wrapped = Object.create(collection);
|
|
160
|
+
wrappedCollections.add(wrapped);
|
|
161
|
+
for (const [methodName, optionsIndex] of collectionMethodOptionsIndex) {
|
|
162
|
+
const method = collection[methodName];
|
|
163
|
+
if (typeof method !== 'function') {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
wrapped[methodName] = function() {
|
|
168
|
+
const args = addSessionOption(Array.from(arguments), optionsIndex, getSession());
|
|
169
|
+
return method.apply(collection, args);
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
return wrapped;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function addSessionOption(args, optionsIndex, session) {
|
|
176
|
+
if (session == null) {
|
|
177
|
+
return args;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
while (args.length < optionsIndex) {
|
|
181
|
+
args.push(undefined);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const options = args[optionsIndex];
|
|
185
|
+
|
|
186
|
+
if (options != null && typeof options !== 'object') {
|
|
187
|
+
throw new Error('Cannot run dry run on script where options arg is a non-object');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (options?.session != null) {
|
|
191
|
+
throw new Error('Cannot run dry run on script that uses sessions');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
args[optionsIndex] = { ...(options ?? {}), session };
|
|
195
|
+
return args;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
module.exports = {
|
|
199
|
+
createScriptDb,
|
|
200
|
+
collectionMethodOptionsIndex
|
|
201
|
+
};
|
package/express.js
CHANGED
|
@@ -3,12 +3,17 @@
|
|
|
3
3
|
const Backend = require('./backend');
|
|
4
4
|
const express = require('express');
|
|
5
5
|
const frontend = require('./frontend');
|
|
6
|
+
const isBindIPConnection = require('./backend/helpers/isBindIPConnection');
|
|
7
|
+
const isLocalhostConnection = require('./backend/helpers/isLocalhostConnection');
|
|
8
|
+
const normalizeBindIPOption = require('./backend/helpers/normalizeBindIPOption');
|
|
6
9
|
const { toRoute, objectRouter } = require('extrovert');
|
|
7
10
|
const { defaultMothershipURL } = require('./constants');
|
|
8
11
|
|
|
9
12
|
module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options) {
|
|
10
13
|
const router = express.Router();
|
|
11
14
|
options = options ? { changeStream: true, ...options } : { changeStream: true };
|
|
15
|
+
const hasBindIpOption = Object.prototype.hasOwnProperty.call(options, 'bindIp');
|
|
16
|
+
const bindIp = normalizeBindIPOption(options.bindIp);
|
|
12
17
|
|
|
13
18
|
const mothershipUrl = options._mothershipUrl || defaultMothershipURL;
|
|
14
19
|
let workspace = null;
|
|
@@ -32,6 +37,17 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
|
|
|
32
37
|
.then(res => res.json()));
|
|
33
38
|
}
|
|
34
39
|
|
|
40
|
+
if (!workspace && bindIp !== null) {
|
|
41
|
+
router.use((req, res, next) => {
|
|
42
|
+
const allowed = hasBindIpOption ? isBindIPConnection(req, bindIp) : isLocalhostConnection(req);
|
|
43
|
+
if (!allowed) {
|
|
44
|
+
return res.status(403).json({ message: 'Mongoose Studio without an API key only accepts localhost or configured bindIp connections' });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
next();
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
35
51
|
apiUrl = apiUrl || 'api';
|
|
36
52
|
const backend = Backend(conn, options.studioConnection, options);
|
|
37
53
|
delete backend.services;
|