@mongoosejs/studio 0.5.1 → 0.5.2
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/Dashboard/getDashboard.js +29 -2
- package/backend/actions/Model/dropCollection.js +29 -0
- package/backend/actions/Model/index.js +1 -0
- package/backend/authorize.js +1 -0
- package/backend/db/dashboardSchema.js +0 -32
- package/backend/index.js +1 -1
- package/backend/integrations/callLLM.js +5 -20
- package/backend/integrations/streamLLM.js +5 -24
- package/backend/sandbox/createSandbox.js +6 -0
- package/backend/vercel.js +3 -0
- package/frontend/index.js +2 -1
- package/frontend/public/app.js +237 -196
- package/frontend/public/tw.css +29 -0
- package/frontend/src/api.js +96 -164
- package/frontend/src/chat/chat.html +1 -1
- package/frontend/src/document/confirm-changes/confirm-changes.js +20 -0
- package/frontend/src/index.js +5 -0
- package/frontend/src/json-node/json-node.html +41 -39
- package/frontend/src/json-node/json-node.js +38 -0
- package/frontend/src/list-json/list-json.html +3 -2
- package/frontend/src/list-json/list-json.js +5 -0
- package/frontend/src/models/models.html +45 -5
- package/frontend/src/models/models.js +26 -0
- package/frontend/src/navbar/navbar.html +19 -3
- package/frontend/src/navbar/navbar.js +6 -0
- package/package.json +1 -1
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
+
const createSandbox = require('../../sandbox/createSandbox');
|
|
5
6
|
const mongoose = require('mongoose');
|
|
6
7
|
const { defaultMothershipURL } = require('../../../constants');
|
|
7
8
|
|
|
@@ -27,7 +28,7 @@ const GetDashboardParams = new Archetype({
|
|
|
27
28
|
}
|
|
28
29
|
}).compile('GetDashboardParams');
|
|
29
30
|
|
|
30
|
-
module.exports = ({ studioConnection, options }) => async function getDashboard(params) {
|
|
31
|
+
module.exports = ({ db, studioConnection, options }) => async function getDashboard(params) {
|
|
31
32
|
const { $workspaceId, authorization, userId, dashboardId, evaluate, roles } = new GetDashboardParams(params);
|
|
32
33
|
const Dashboard = studioConnection.model('__Studio_Dashboard');
|
|
33
34
|
const DashboardResult = studioConnection.model('__Studio_DashboardResult');
|
|
@@ -38,10 +39,12 @@ module.exports = ({ studioConnection, options }) => async function getDashboard(
|
|
|
38
39
|
const dashboard = await Dashboard.findOne({ _id: dashboardId });
|
|
39
40
|
if (evaluate) {
|
|
40
41
|
let result = null;
|
|
42
|
+
const sandbox = createSandbox({ db });
|
|
41
43
|
const startExec = startDashboardEvaluate(DashboardResult, dashboardId, $workspaceId, userId);
|
|
42
44
|
startExec.catch(() => {}); // Avoid unhandled promise rejections - we will handle this error later.
|
|
43
45
|
try {
|
|
44
|
-
result = await dashboard.
|
|
46
|
+
result = await sandbox.runScript({ script: dashboard.code });
|
|
47
|
+
addDocumentSchemaPaths(result);
|
|
45
48
|
} catch (error) {
|
|
46
49
|
const { dashboardResult } = await startExec.then(({ dashboardResult }) => {
|
|
47
50
|
if (!dashboardResult) {
|
|
@@ -56,6 +59,12 @@ module.exports = ({ studioConnection, options }) => async function getDashboard(
|
|
|
56
59
|
);
|
|
57
60
|
});
|
|
58
61
|
return { dashboard, dashboardResult, error: { message: error.message } };
|
|
62
|
+
} finally {
|
|
63
|
+
try {
|
|
64
|
+
await sandbox.close();
|
|
65
|
+
} catch (_) {
|
|
66
|
+
// Ignore sandbox cleanup errors so they do not mask the primary result.
|
|
67
|
+
}
|
|
59
68
|
}
|
|
60
69
|
|
|
61
70
|
try {
|
|
@@ -170,3 +179,21 @@ function getSortTime(result) {
|
|
|
170
179
|
const time = new Date(candidate).getTime();
|
|
171
180
|
return Number.isNaN(time) ? 0 : time;
|
|
172
181
|
}
|
|
182
|
+
|
|
183
|
+
function addDocumentSchemaPaths(result) {
|
|
184
|
+
const Model = result?.$document?.constructor;
|
|
185
|
+
if (!Model?.modelName || !Model.schema?.paths) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const schemaPaths = {};
|
|
190
|
+
for (const path of Object.keys(Model.schema.paths)) {
|
|
191
|
+
schemaPaths[path] = {
|
|
192
|
+
instance: Model.schema.paths[path].instance,
|
|
193
|
+
path,
|
|
194
|
+
ref: Model.schema.paths[path].options?.ref,
|
|
195
|
+
required: Model.schema.paths[path].options?.required
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
result.$document.schemaPaths = schemaPaths;
|
|
199
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const Archetype = require('archetype');
|
|
4
|
+
const authorize = require('../../authorize');
|
|
5
|
+
|
|
6
|
+
const DropCollectionParams = new Archetype({
|
|
7
|
+
model: {
|
|
8
|
+
$type: 'string',
|
|
9
|
+
$required: true
|
|
10
|
+
},
|
|
11
|
+
roles: {
|
|
12
|
+
$type: ['string']
|
|
13
|
+
}
|
|
14
|
+
}).compile('DropCollectionParams');
|
|
15
|
+
|
|
16
|
+
module.exports = ({ db }) => async function dropCollection(params) {
|
|
17
|
+
const { model, roles } = new DropCollectionParams(params);
|
|
18
|
+
|
|
19
|
+
await authorize('Model.dropCollection', roles);
|
|
20
|
+
|
|
21
|
+
const Model = db.models[model];
|
|
22
|
+
if (Model == null) {
|
|
23
|
+
throw new Error(`Model ${model} not found`);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
await Model.collection.drop();
|
|
27
|
+
|
|
28
|
+
return { ok: true };
|
|
29
|
+
};
|
|
@@ -5,6 +5,7 @@ exports.createChatMessage = require('./createChatMessage');
|
|
|
5
5
|
exports.createDocument = require('./createDocument');
|
|
6
6
|
exports.deleteDocument = require('./deleteDocument');
|
|
7
7
|
exports.deleteDocuments = require('./deleteDocuments');
|
|
8
|
+
exports.dropCollection = require('./dropCollection');
|
|
8
9
|
exports.dropIndex = require('./dropIndex');
|
|
9
10
|
exports.executeDocumentScript = require('./executeDocumentScript');
|
|
10
11
|
exports.exportQueryResults = require('./exportQueryResults');
|
package/backend/authorize.js
CHANGED
|
@@ -17,6 +17,7 @@ const actionsToRequiredRoles = {
|
|
|
17
17
|
'Model.updateDocument': ['owner', 'admin', 'member'],
|
|
18
18
|
'Model.deleteDocument': ['owner', 'admin', 'member'],
|
|
19
19
|
'Model.deleteDocuments': ['owner', 'admin', 'member'],
|
|
20
|
+
'Model.dropCollection': ['owner', 'admin'],
|
|
20
21
|
'Model.dropIndex': ['owner', 'admin'],
|
|
21
22
|
'Model.executeDocumentScript': ['owner', 'admin', 'member'],
|
|
22
23
|
'Model.exportQueryResults': ['owner', 'admin', 'member', 'readonly'],
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const MongooseStudioChartColors = require('../constants/mongooseStudioChartColors');
|
|
4
3
|
const mongoose = require('mongoose');
|
|
5
|
-
const vm = require('vm');
|
|
6
4
|
|
|
7
5
|
const dashboardSchema = new mongoose.Schema({
|
|
8
6
|
title: {
|
|
@@ -18,34 +16,4 @@ const dashboardSchema = new mongoose.Schema({
|
|
|
18
16
|
}
|
|
19
17
|
});
|
|
20
18
|
|
|
21
|
-
dashboardSchema.methods.evaluate = async function evaluate() {
|
|
22
|
-
const context = vm.createContext({
|
|
23
|
-
db: this.constructor.db,
|
|
24
|
-
setTimeout,
|
|
25
|
-
ObjectId: mongoose.Types.ObjectId,
|
|
26
|
-
MongooseStudioChartColors
|
|
27
|
-
});
|
|
28
|
-
let result = null;
|
|
29
|
-
result = await vm.runInContext(formatFunction(this.code), context);
|
|
30
|
-
if (result.$document?.constructor?.modelName) {
|
|
31
|
-
const schemaPaths = {};
|
|
32
|
-
const Model = this.constructor.db.model(result.$document?.constructor?.modelName);
|
|
33
|
-
for (const path of Object.keys(Model.schema.paths)) {
|
|
34
|
-
schemaPaths[path] = {
|
|
35
|
-
instance: Model.schema.paths[path].instance,
|
|
36
|
-
path,
|
|
37
|
-
ref: Model.schema.paths[path].options?.ref,
|
|
38
|
-
required: Model.schema.paths[path].options?.required
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
result.$document.schemaPaths = schemaPaths;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
return result;
|
|
45
|
-
};
|
|
46
|
-
|
|
47
19
|
module.exports = dashboardSchema;
|
|
48
|
-
|
|
49
|
-
const formatFunction = code => `(async function() {
|
|
50
|
-
${code}
|
|
51
|
-
})();`;
|
package/backend/index.js
CHANGED
|
@@ -20,7 +20,7 @@ module.exports = function backend(db, studioConnection, options) {
|
|
|
20
20
|
|
|
21
21
|
let changeStream = null;
|
|
22
22
|
if (options?.changeStream) {
|
|
23
|
-
const conn = db
|
|
23
|
+
const conn = db.connection ? db.connection : db;
|
|
24
24
|
if (conn.readyState !== mongoose.Connection.STATES.connected) {
|
|
25
25
|
conn._waitForConnect().then(() => {
|
|
26
26
|
changeStream = conn.watch();
|
|
@@ -4,7 +4,6 @@ const { createAnthropic } = require('@ai-sdk/anthropic');
|
|
|
4
4
|
const { createGoogleGenerativeAI } = require('@ai-sdk/google');
|
|
5
5
|
const { createOpenAI } = require('@ai-sdk/openai');
|
|
6
6
|
const { generateText, stepCountIs } = require('ai');
|
|
7
|
-
const { defaultMothershipURL } = require('../../constants');
|
|
8
7
|
|
|
9
8
|
module.exports = async function callLLM(messages, system, options) {
|
|
10
9
|
let provider = null;
|
|
@@ -13,6 +12,7 @@ module.exports = async function callLLM(messages, system, options) {
|
|
|
13
12
|
|
|
14
13
|
if (options?.openAIAPIKey) {
|
|
15
14
|
providers.push({
|
|
15
|
+
name: 'OpenAI',
|
|
16
16
|
provider: createOpenAI({ apiKey: options.openAIAPIKey }),
|
|
17
17
|
model: options?.model ?? 'gpt-4o-mini'
|
|
18
18
|
});
|
|
@@ -20,6 +20,7 @@ module.exports = async function callLLM(messages, system, options) {
|
|
|
20
20
|
|
|
21
21
|
if (options?.anthropicAPIKey) {
|
|
22
22
|
providers.push({
|
|
23
|
+
name: 'Anthropic',
|
|
23
24
|
provider: createAnthropic({ apiKey: options.anthropicAPIKey }),
|
|
24
25
|
model: options?.model ?? 'claude-haiku-4-5-20251001'
|
|
25
26
|
});
|
|
@@ -27,13 +28,14 @@ module.exports = async function callLLM(messages, system, options) {
|
|
|
27
28
|
|
|
28
29
|
if (options?.googleGeminiAPIKey) {
|
|
29
30
|
providers.push({
|
|
31
|
+
name: 'Gemini',
|
|
30
32
|
provider: createGoogleGenerativeAI({ apiKey: options.googleGeminiAPIKey }),
|
|
31
33
|
model: options?.model ?? 'gemini-2.5-flash'
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
if (providers.length > 1) {
|
|
36
|
-
throw new Error(
|
|
38
|
+
throw new Error(`Cannot set multiple LLM API keys, found ${providers.map(p => p.name).join(', ')}`);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
if (providers.length > 0) {
|
|
@@ -52,22 +54,5 @@ module.exports = async function callLLM(messages, system, options) {
|
|
|
52
54
|
});
|
|
53
55
|
}
|
|
54
56
|
|
|
55
|
-
|
|
56
|
-
const response = await fetch(`${defaultMothershipURL}/getChatCompletion`, {
|
|
57
|
-
method: 'POST',
|
|
58
|
-
headers,
|
|
59
|
-
body: JSON.stringify({
|
|
60
|
-
messages: [{ role: 'system', content: { type: 'text', text: system } }, ...messages],
|
|
61
|
-
model: options?.model
|
|
62
|
-
})
|
|
63
|
-
}).then(response => {
|
|
64
|
-
if (!response.ok) {
|
|
65
|
-
return response.json().then(data => {
|
|
66
|
-
throw new Error(`Mongoose Studio chat completion error: ${data.message}`);
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
return response;
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
return await response.json().then(res => ({ text: res.response }));
|
|
57
|
+
throw new Error('No LLM API key configured. Set one of anthropicAPIKey, googleGeminiAPIKey, or openAIAPIKey.');
|
|
73
58
|
};
|
|
@@ -4,7 +4,6 @@ const { createAnthropic } = require('@ai-sdk/anthropic');
|
|
|
4
4
|
const { createGoogleGenerativeAI } = require('@ai-sdk/google');
|
|
5
5
|
const { createOpenAI } = require('@ai-sdk/openai');
|
|
6
6
|
const { streamText, stepCountIs } = require('ai');
|
|
7
|
-
const { defaultMothershipURL } = require('../../constants');
|
|
8
7
|
|
|
9
8
|
module.exports = async function* streamLLM(messages, system, options) {
|
|
10
9
|
let provider = null;
|
|
@@ -13,6 +12,7 @@ module.exports = async function* streamLLM(messages, system, options) {
|
|
|
13
12
|
|
|
14
13
|
if (options?.openAIAPIKey) {
|
|
15
14
|
providers.push({
|
|
15
|
+
name: 'OpenAI',
|
|
16
16
|
provider: createOpenAI({ apiKey: options.openAIAPIKey }),
|
|
17
17
|
model: options?.model ?? 'gpt-4o-mini'
|
|
18
18
|
});
|
|
@@ -20,6 +20,7 @@ module.exports = async function* streamLLM(messages, system, options) {
|
|
|
20
20
|
|
|
21
21
|
if (options?.anthropicAPIKey) {
|
|
22
22
|
providers.push({
|
|
23
|
+
name: 'Anthropic',
|
|
23
24
|
provider: createAnthropic({ apiKey: options.anthropicAPIKey }),
|
|
24
25
|
model: options?.model ?? 'claude-haiku-4-5-20251001'
|
|
25
26
|
});
|
|
@@ -27,13 +28,14 @@ module.exports = async function* streamLLM(messages, system, options) {
|
|
|
27
28
|
|
|
28
29
|
if (options?.googleGeminiAPIKey) {
|
|
29
30
|
providers.push({
|
|
31
|
+
name: 'Gemini',
|
|
30
32
|
provider: createGoogleGenerativeAI({ apiKey: options.googleGeminiAPIKey }),
|
|
31
33
|
model: options?.model ?? 'gemini-2.5-flash'
|
|
32
34
|
});
|
|
33
35
|
}
|
|
34
36
|
|
|
35
37
|
if (providers.length > 1) {
|
|
36
|
-
throw new Error(
|
|
38
|
+
throw new Error(`Cannot set multiple LLM API keys, found ${providers.map(p => p.name).join(', ')}`);
|
|
37
39
|
}
|
|
38
40
|
|
|
39
41
|
if (providers.length > 0) {
|
|
@@ -69,26 +71,5 @@ module.exports = async function* streamLLM(messages, system, options) {
|
|
|
69
71
|
return;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
|
|
73
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
74
|
-
const response = await fetch(`${defaultMothershipURL}/getChatCompletion`, {
|
|
75
|
-
method: 'POST',
|
|
76
|
-
headers,
|
|
77
|
-
body: JSON.stringify({
|
|
78
|
-
messages: [{ role: 'system', content: { type: 'text', text: system } }, ...messages],
|
|
79
|
-
model: options?.model
|
|
80
|
-
})
|
|
81
|
-
}).then(response => {
|
|
82
|
-
if (!response.ok) {
|
|
83
|
-
return response.json().then(data => {
|
|
84
|
-
throw new Error(`Mongoose Studio chat completion error: ${data.message}`);
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
return response;
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
const data = await response.json();
|
|
91
|
-
|
|
92
|
-
// Simulate streaming by yielding the whole response at once as a single chunk
|
|
93
|
-
yield data.response;
|
|
74
|
+
throw new Error('No LLM API key configured. Set one of anthropicAPIKey, googleGeminiAPIKey, or openAIAPIKey.');
|
|
94
75
|
};
|
|
@@ -7,6 +7,12 @@ const { createScriptDb } = require('./createScriptDb');
|
|
|
7
7
|
|
|
8
8
|
module.exports = function createSandbox({ db }) {
|
|
9
9
|
const logs = [];
|
|
10
|
+
|
|
11
|
+
// db must be a connection, not a Mongoose instance
|
|
12
|
+
if (db.connection && db.connections) {
|
|
13
|
+
db = db.connection;
|
|
14
|
+
}
|
|
15
|
+
|
|
10
16
|
const scriptDb = createScriptDb(db);
|
|
11
17
|
if (!scriptDb.db.Types) {
|
|
12
18
|
scriptDb.db.Types = mongoose.Types;
|
package/frontend/index.js
CHANGED
|
@@ -35,7 +35,8 @@ module.exports = async function frontend(apiUrl, isLambda, options, workspace) {
|
|
|
35
35
|
baseURL: apiUrl,
|
|
36
36
|
isLambda,
|
|
37
37
|
mothershipUrl: apiKey ? mothershipUrl : null,
|
|
38
|
-
workspace: workspaceData
|
|
38
|
+
workspace: workspaceData,
|
|
39
|
+
hasLLMAPIKey: [options?.anthropicAPIKey, options?.googleGeminiAPIKey, options?.openAIAPIKey].some(v => v != null)
|
|
39
40
|
};
|
|
40
41
|
|
|
41
42
|
if (isLambda) {
|