@mongoosejs/studio 0.5.14 → 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 +69 -2
- package/README.md +24 -0
- package/backend/actions/ChatMessage/executeScript.js +14 -6
- package/backend/actions/ChatThread/createChatMessage.js +7 -4
- package/backend/actions/ChatThread/getChatThread.js +7 -4
- package/backend/actions/ChatThread/listChatThreads.js +5 -2
- package/backend/actions/ChatThread/shareChatThread.js +5 -2
- package/backend/actions/ChatThread/streamChatMessage.js +7 -4
- package/backend/actions/ChatThread/toggleAgentMode.js +5 -2
- package/backend/actions/Dashboard/createDashboard.js +3 -0
- package/backend/actions/Dashboard/deleteDashboard.js +9 -3
- package/backend/actions/Dashboard/getDashboard.js +23 -11
- package/backend/actions/Dashboard/getDashboards.js +8 -2
- package/backend/actions/Dashboard/updateDashboard.js +8 -2
- package/backend/actions/Model/addField.js +8 -2
- package/backend/actions/Model/analyzeSchema.js +15 -5
- package/backend/actions/Model/createChatMessage.js +3 -0
- package/backend/actions/Model/createDocument.js +3 -0
- package/backend/actions/Model/deleteDocument.js +8 -2
- package/backend/actions/Model/deleteDocuments.js +8 -2
- package/backend/actions/Model/dropCollection.js +3 -0
- package/backend/actions/Model/dropIndex.js +3 -0
- package/backend/actions/Model/executeDocumentScript.js +14 -3
- package/backend/actions/Model/exportQueryResults.js +12 -2
- package/backend/actions/Model/getCollectionInfo.js +14 -4
- package/backend/actions/Model/getDocument.js +12 -2
- package/backend/actions/Model/getDocuments.js +13 -3
- package/backend/actions/Model/getDocumentsStream.js +13 -3
- package/backend/actions/Model/getEstimatedDocumentCounts.js +12 -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 +8 -2
- package/backend/actions/Model/updateDocuments.js +8 -2
- package/backend/actions/Model/validateDocument.js +12 -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 +5 -2
- package/backend/actions/Task/getTaskOverview.js +6 -3
- package/backend/actions/Task/getTasks.js +6 -3
- package/backend/actions/Task/getTasksOverTime.js +6 -3
- package/backend/actions/Task/rescheduleTask.js +5 -2
- package/backend/actions/Task/runTask.js +5 -2
- package/backend/actions/index.js +1 -0
- package/backend/authorize.js +6 -1
- package/backend/db/scriptSchema.js +29 -0
- package/backend/helpers/operationOptions.js +26 -0
- package/backend/helpers/readOperationOptions.js +31 -0
- package/backend/index.js +2 -0
- 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 +40 -23
- 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 +441 -48
- package/frontend/public/tw.css +8 -0
- package/frontend/src/api.js +22 -0
- 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/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/index.d.ts +10 -1
- package/nest.d.ts +3 -0
- package/package.json +5 -3
- package/vercel.d.ts +2 -0
- package/CLAUDE.md +0 -7
package/AGENTS.md
CHANGED
|
@@ -1,7 +1,74 @@
|
|
|
1
1
|
# Repository guidance
|
|
2
2
|
|
|
3
|
-
The Mongoose
|
|
3
|
+
The project's name is Mongoose Studio. Do not refer to the project as "Studio" - the repository is named "studio" to maintain parity between the npm package name `@mongoosejs/studio` and the GitHub repository `mongoosejs/studio`.
|
|
4
|
+
|
|
5
|
+
The Mongoose connection passed to Mongoose Studio is owned by the host application.
|
|
4
6
|
Do not change connection-level settings, options, configuration, models, middleware, plugins, collection objects, or methods on the application connection to implement Studio behavior.
|
|
5
|
-
Apply Studio-specific behavior to individual operations whenever possible.
|
|
7
|
+
Apply Mongoose-Studio-specific behavior to individual operations whenever possible.
|
|
6
8
|
When isolation requires a derived connection, copy any Mongoose state that `useDb()` shares with its source before changing the derived connection.
|
|
7
9
|
Sandbox wrappers must remain confined to sandbox-owned models and collections.
|
|
10
|
+
|
|
11
|
+
# Frontend Guidelines
|
|
12
|
+
|
|
13
|
+
Conceptually, the frontend's job is to render the data and provide a user interface for accessing API calls.
|
|
14
|
+
The frontend should minimize transformations and renaming of data provided by the backend: default to displaying the raw data as-is.
|
|
15
|
+
|
|
16
|
+
## API Client
|
|
17
|
+
|
|
18
|
+
`src/client/api.js` exposes one wrapper function per backend action; every wrapper POSTs to `${baseUrl}/<endpoint>` and resolves with the response payload directly (`res.data`), not the axios response. The endpoint list lives in `src/endpoints.js`, which `build.js` generates from the files in `backend/actions/` — do not edit it by hand. Flat action files become flat wrappers like `api.addToCart`, while capitalized `Model/action.js` directories become namespaced wrappers like `api.quote.createQuote`, so adding an action file automatically makes its wrapper available on the next build.
|
|
19
|
+
|
|
20
|
+
## Vue User Input
|
|
21
|
+
|
|
22
|
+
Prefer explicit DOM event handlers such as `@change`, `@input`, and `@update:modelValue` over `watch` for reacting to user input. Use `watch` only when the behavior is truly derived from state changes that do not have a single clear DOM event source.
|
|
23
|
+
|
|
24
|
+
## Vue Templates
|
|
25
|
+
|
|
26
|
+
Prefer safe property access directly in HTML over computed properties or methods for simple display values. For example, use `{{ user?.name ?? '-' }}` instead of creating a `userName` computed property. Keep display behavior local unless the logic is reused or too complex to read inline.
|
|
27
|
+
|
|
28
|
+
Prefer using mustache interpolation (`{{ expr }}`) over `v-text` for rendering text values in Vue templates.
|
|
29
|
+
|
|
30
|
+
## Component API Ownership
|
|
31
|
+
|
|
32
|
+
Components that present an async action must own the API request for that action. Do not emit an event to a parent solely so the parent can make the HTTP request. Emit events after the component finishes the request when the parent needs to close a modal, refresh a list, or update surrounding state.
|
|
33
|
+
|
|
34
|
+
## Async Buttons
|
|
35
|
+
|
|
36
|
+
For a button that triggers an async action, use the `<async-button>` component instead of tracking a `saveStatus`/`loading` flag by hand. It manages the in-progress state, disables itself while running, and shows a spinner. Do not add a component-level status property, a `try/finally` to reset it, or `.finally()` on the API call just to toggle a button label.
|
|
37
|
+
|
|
38
|
+
```html
|
|
39
|
+
<!-- ❌ Incorrect: hand-rolled status + disabled + label swap -->
|
|
40
|
+
<button :disabled="saveStatus === 'saving'" @click="save">
|
|
41
|
+
<span v-if="saveStatus === 'saving'">Saving...</span>
|
|
42
|
+
<span v-else>Save</span>
|
|
43
|
+
</button>
|
|
44
|
+
|
|
45
|
+
<!-- ✅ Correct -->
|
|
46
|
+
<async-button @click="save">Save</async-button>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The click handler is just an `async` method that does the work; let errors propagate to the global handler. Keep any real `:disabled` condition (e.g. invalid input) — `async-button` ORs it with its own in-progress state.
|
|
50
|
+
|
|
51
|
+
## Avoid trivial helpers
|
|
52
|
+
|
|
53
|
+
Minimize scattered helpers on the frontend - functions and computed properties carry real cognitive overhead and need to earn their existence.
|
|
54
|
+
Optimize for locality and terseness over abstraction.
|
|
55
|
+
|
|
56
|
+
For example, instead of:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
computed: {
|
|
60
|
+
displayName() {
|
|
61
|
+
return `${user.firstName} ${user.lastName}`;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
and:
|
|
67
|
+
|
|
68
|
+
<div>{{ displayName }}</div>
|
|
69
|
+
|
|
70
|
+
Write:
|
|
71
|
+
|
|
72
|
+
<div>{{ user.firstName }} {{ user.lastName }}</div>
|
|
73
|
+
|
|
74
|
+
No single-expression helper functions or computed properties unless there's heavy repetition (25+ instances).
|
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ opts.anthropicAPIKey = process.env.ANTHROPIC_API_KEY;
|
|
|
41
41
|
opts.googleGeminiAPIKey = process.env.GOOGLE_GEMINI_API_KEY;
|
|
42
42
|
// Apply a maximum execution time to all read operations, including reads in scripts.
|
|
43
43
|
// MongoDB does not support maxTimeMS for inserts and index operations.
|
|
44
|
+
// This is also a ceiling: a request may ask for a lower limit, never a higher one.
|
|
44
45
|
opts.maxTimeMS = 10000;
|
|
45
46
|
|
|
46
47
|
// Mount Mongoose Studio on '/studio'
|
|
@@ -55,6 +56,29 @@ app.use('/studio', await studio('/studio/api', mongoose, {
|
|
|
55
56
|
}));
|
|
56
57
|
```
|
|
57
58
|
|
|
59
|
+
The Express integration also exposes a Streamable HTTP MCP endpoint at `/studio/mcp`.
|
|
60
|
+
Set `mcp: false` to turn it off, along with the OAuth protected resource metadata that advertises it.
|
|
61
|
+
It provides authorized Dashboard and Model actions, plus `Script.createScript`, as MCP tools.
|
|
62
|
+
With a Pro API key, pass the logged-in Studio access token in the `Authorization` header (either directly or as a bearer token); tools receive that user's roles and permissions.
|
|
63
|
+
Without workspace authentication, the MCP endpoint uses the same localhost and `bindIp` restrictions as the rest of Studio.
|
|
64
|
+
|
|
65
|
+
#### Connecting ChatGPT or Claude with OAuth
|
|
66
|
+
|
|
67
|
+
With a Pro API key, `/studio/mcp` is also an OAuth 2.1 protected resource, so MCP clients such as ChatGPT and Claude can connect to it directly.
|
|
68
|
+
Point the client at your `/studio/mcp` URL and it will discover the Mongoose Studio authorization server, walk the user through signing in and approving access, and receive a short-lived access token.
|
|
69
|
+
Users review and revoke these connections from their Mongoose Studio account page, and the access an AI client gets can never exceed the access the user who approved it already has.
|
|
70
|
+
|
|
71
|
+
Mongoose Studio advertises itself to MCP clients using its externally reachable URL.
|
|
72
|
+
When MCP and a Pro API key are enabled, set `publicUrl` to the URL where Mongoose Studio is mounted:
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
opts.publicUrl = 'https://app.example.com/studio';
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Mongoose Studio finds the authorization server through the mothership it is already configured to use, so it needs no separate authorization server configuration.
|
|
79
|
+
|
|
80
|
+
See `docs/mcp-oauth.md` for how the flow and the delegated access policy work.
|
|
81
|
+
|
|
58
82
|
### Next.js
|
|
59
83
|
|
|
60
84
|
First, add `withMongooseStudio` to your `next.config.js` file:
|
|
@@ -4,7 +4,8 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const createSandbox = require('../../sandbox/createSandbox');
|
|
6
6
|
const mongoose = require('mongoose');
|
|
7
|
-
const
|
|
7
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
8
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
8
9
|
|
|
9
10
|
const ExecuteScriptParams = new Archetype({
|
|
10
11
|
initiatedById: {
|
|
@@ -22,6 +23,13 @@ const ExecuteScriptParams = new Archetype({
|
|
|
22
23
|
},
|
|
23
24
|
roles: {
|
|
24
25
|
$type: ['string']
|
|
26
|
+
},
|
|
27
|
+
maxTimeMS: {
|
|
28
|
+
$type: 'number'
|
|
29
|
+
},
|
|
30
|
+
readPreference: {
|
|
31
|
+
$type: 'string',
|
|
32
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
25
33
|
}
|
|
26
34
|
}).compile('ExecuteScriptParams');
|
|
27
35
|
|
|
@@ -32,18 +40,18 @@ module.exports = ({ db, studioConnection, options }) => async function executeSc
|
|
|
32
40
|
|
|
33
41
|
await authorize('ChatMessage.executeScript', roles);
|
|
34
42
|
|
|
35
|
-
const
|
|
36
|
-
const chatMessage = await ChatMessage.findById(chatMessageId).setOptions(
|
|
43
|
+
const dbOptions = operationOptions(options, params);
|
|
44
|
+
const chatMessage = await ChatMessage.findById(chatMessageId).setOptions(dbOptions);
|
|
37
45
|
if (!chatMessage) {
|
|
38
46
|
throw new Error('Chat message not found');
|
|
39
47
|
}
|
|
40
|
-
const chatThread = await ChatThread.findById(chatMessage.chatThreadId).setOptions(
|
|
48
|
+
const chatThread = await ChatThread.findById(chatMessage.chatThreadId).setOptions(dbOptions).orFail();
|
|
41
49
|
|
|
42
50
|
if (initiatedById && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
43
51
|
throw new Error('Unauthorized');
|
|
44
52
|
}
|
|
45
53
|
|
|
46
|
-
const sandbox = createSandbox(
|
|
54
|
+
const sandbox = createSandbox(db, { ...readOperationOptions(options, params) });
|
|
47
55
|
|
|
48
56
|
let output;
|
|
49
57
|
let error;
|
|
@@ -76,7 +84,7 @@ module.exports = ({ db, studioConnection, options }) => async function executeSc
|
|
|
76
84
|
dryRun: !!dryRun
|
|
77
85
|
}
|
|
78
86
|
}
|
|
79
|
-
).setOptions(
|
|
87
|
+
).setOptions(operationOptions(options, params));
|
|
80
88
|
|
|
81
89
|
throw new Error(`Script execution failed: ${error}`);
|
|
82
90
|
} finally {
|
|
@@ -8,7 +8,7 @@ const callLLM = require('../../integrations/callLLM');
|
|
|
8
8
|
const getAgentTools = require('../../chatAgent/getAgentTools');
|
|
9
9
|
const getModelDescriptions = require('../../helpers/getModelDescriptions');
|
|
10
10
|
const mongoose = require('mongoose');
|
|
11
|
-
const
|
|
11
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
12
12
|
|
|
13
13
|
const CreateChatMessageParams = new Archetype({
|
|
14
14
|
chatThreadId: {
|
|
@@ -26,6 +26,9 @@ const CreateChatMessageParams = new Archetype({
|
|
|
26
26
|
},
|
|
27
27
|
roles: {
|
|
28
28
|
$type: ['string']
|
|
29
|
+
},
|
|
30
|
+
maxTimeMS: {
|
|
31
|
+
$type: 'number'
|
|
29
32
|
}
|
|
30
33
|
}).compile('CreateChatMessageParams');
|
|
31
34
|
|
|
@@ -37,8 +40,8 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
37
40
|
await authorize('ChatThread.createChatMessage', roles);
|
|
38
41
|
|
|
39
42
|
// Check that the user owns the thread
|
|
40
|
-
const
|
|
41
|
-
const chatThread = await ChatThread.findOne({ _id: chatThreadId }).setOptions(
|
|
43
|
+
const dbOptions = operationOptions(options, params);
|
|
44
|
+
const chatThread = await ChatThread.findOne({ _id: chatThreadId }).setOptions(dbOptions);
|
|
42
45
|
if (!chatThread) {
|
|
43
46
|
throw new Error('Chat thread not found');
|
|
44
47
|
}
|
|
@@ -46,7 +49,7 @@ module.exports = ({ db, studioConnection, options }) => async function createCha
|
|
|
46
49
|
throw new Error('Not authorized');
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
const messages = await ChatMessage.find({ chatThreadId }).setOptions(
|
|
52
|
+
const messages = await ChatMessage.find({ chatThreadId }).setOptions(dbOptions).sort({ createdAt: 1 });
|
|
50
53
|
const llmMessages = messages.map(m => ({
|
|
51
54
|
role: m.role,
|
|
52
55
|
content: [{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
-
const
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
7
7
|
|
|
8
8
|
const GetChatThreadParams = new Archetype({
|
|
9
9
|
chatThreadId: {
|
|
@@ -17,6 +17,9 @@ const GetChatThreadParams = new Archetype({
|
|
|
17
17
|
},
|
|
18
18
|
$workspaceId: {
|
|
19
19
|
$type: mongoose.Types.ObjectId
|
|
20
|
+
},
|
|
21
|
+
maxTimeMS: {
|
|
22
|
+
$type: 'number'
|
|
20
23
|
}
|
|
21
24
|
}).compile('GetChatThreadParams');
|
|
22
25
|
|
|
@@ -27,8 +30,8 @@ module.exports = ({ db, studioConnection, options }) => async function getChatTh
|
|
|
27
30
|
|
|
28
31
|
await authorize('ChatThread.getChatThread', roles);
|
|
29
32
|
|
|
30
|
-
const
|
|
31
|
-
const chatThread = await ChatThread.findById(chatThreadId).setOptions(
|
|
33
|
+
const dbOptions = operationOptions(options, params);
|
|
34
|
+
const chatThread = await ChatThread.findById(chatThreadId).setOptions(dbOptions);
|
|
32
35
|
|
|
33
36
|
if (!chatThread) {
|
|
34
37
|
throw new Error('Chat thread not found');
|
|
@@ -41,7 +44,7 @@ module.exports = ({ db, studioConnection, options }) => async function getChatTh
|
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
const chatMessages = await ChatMessage.find({ chatThreadId })
|
|
44
|
-
.setOptions(
|
|
47
|
+
.setOptions(dbOptions)
|
|
45
48
|
.sort({ createdAt: -1 });
|
|
46
49
|
|
|
47
50
|
return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
-
const
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
7
7
|
|
|
8
8
|
const ListChatThreadsParams = new Archetype({
|
|
9
9
|
initiatedById: {
|
|
@@ -11,6 +11,9 @@ const ListChatThreadsParams = new Archetype({
|
|
|
11
11
|
},
|
|
12
12
|
roles: {
|
|
13
13
|
$type: ['string']
|
|
14
|
+
},
|
|
15
|
+
maxTimeMS: {
|
|
16
|
+
$type: 'number'
|
|
14
17
|
}
|
|
15
18
|
}).compile('ListChatThreadsParams');
|
|
16
19
|
|
|
@@ -25,7 +28,7 @@ module.exports = ({ db, studioConnection, options }) => async function listChatT
|
|
|
25
28
|
|
|
26
29
|
// Get all chat threads
|
|
27
30
|
const chatThreads = await ChatThread.find(query)
|
|
28
|
-
.setOptions(
|
|
31
|
+
.setOptions(operationOptions(options, params))
|
|
29
32
|
.sort({ updatedAt: -1 }); // Sort by most recently updated
|
|
30
33
|
|
|
31
34
|
return {
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
-
const
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
7
7
|
|
|
8
8
|
const ShareChatThreadParams = new Archetype({
|
|
9
9
|
chatThreadId: {
|
|
@@ -20,6 +20,9 @@ const ShareChatThreadParams = new Archetype({
|
|
|
20
20
|
},
|
|
21
21
|
$workspaceId: {
|
|
22
22
|
$type: mongoose.Types.ObjectId
|
|
23
|
+
},
|
|
24
|
+
maxTimeMS: {
|
|
25
|
+
$type: 'number'
|
|
23
26
|
}
|
|
24
27
|
}).compile('ShareChatThreadParams');
|
|
25
28
|
|
|
@@ -30,7 +33,7 @@ module.exports = ({ studioConnection, options }) => async function shareChatThre
|
|
|
30
33
|
await authorize('ChatThread.shareChatThread', roles);
|
|
31
34
|
|
|
32
35
|
const chatThread = await ChatThread.findById(chatThreadId).
|
|
33
|
-
setOptions(
|
|
36
|
+
setOptions(operationOptions(options, params)).
|
|
34
37
|
orFail();
|
|
35
38
|
if (initiatedById != null && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
36
39
|
throw new Error('Not authorized');
|
|
@@ -8,7 +8,7 @@ const runChatAgent = require('../../chatAgent/runChatAgent');
|
|
|
8
8
|
const streamLLM = require('../../integrations/streamLLM');
|
|
9
9
|
const getModelDescriptions = require('../../helpers/getModelDescriptions');
|
|
10
10
|
const mongoose = require('mongoose');
|
|
11
|
-
const
|
|
11
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
12
12
|
|
|
13
13
|
const CreateChatMessageParams = new Archetype({
|
|
14
14
|
chatThreadId: {
|
|
@@ -26,6 +26,9 @@ const CreateChatMessageParams = new Archetype({
|
|
|
26
26
|
},
|
|
27
27
|
roles: {
|
|
28
28
|
$type: ['string']
|
|
29
|
+
},
|
|
30
|
+
maxTimeMS: {
|
|
31
|
+
$type: 'number'
|
|
29
32
|
}
|
|
30
33
|
}).compile('CreateChatMessageParams');
|
|
31
34
|
|
|
@@ -37,8 +40,8 @@ module.exports = ({ db, studioConnection, options }) => async function* streamCh
|
|
|
37
40
|
await authorize('ChatThread.createChatMessage', roles);
|
|
38
41
|
|
|
39
42
|
// Check that the user owns the thread
|
|
40
|
-
const
|
|
41
|
-
const chatThread = await ChatThread.findOne({ _id: chatThreadId }).setOptions(
|
|
43
|
+
const dbOptions = operationOptions(options, params);
|
|
44
|
+
const chatThread = await ChatThread.findOne({ _id: chatThreadId }).setOptions(dbOptions);
|
|
42
45
|
if (!chatThread) {
|
|
43
46
|
throw new Error('Chat thread not found');
|
|
44
47
|
}
|
|
@@ -46,7 +49,7 @@ module.exports = ({ db, studioConnection, options }) => async function* streamCh
|
|
|
46
49
|
throw new Error('Not authorized');
|
|
47
50
|
}
|
|
48
51
|
|
|
49
|
-
const messages = await ChatMessage.find({ chatThreadId }).setOptions(
|
|
52
|
+
const messages = await ChatMessage.find({ chatThreadId }).setOptions(dbOptions).sort({ createdAt: 1 });
|
|
50
53
|
const llmMessages = messages.map(m => ({
|
|
51
54
|
role: m.role,
|
|
52
55
|
content: [{
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const mongoose = require('mongoose');
|
|
6
|
-
const
|
|
6
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
7
7
|
|
|
8
8
|
const ToggleAgentModeParams = new Archetype({
|
|
9
9
|
chatThreadId: {
|
|
@@ -17,6 +17,9 @@ const ToggleAgentModeParams = new Archetype({
|
|
|
17
17
|
},
|
|
18
18
|
roles: {
|
|
19
19
|
$type: ['string']
|
|
20
|
+
},
|
|
21
|
+
maxTimeMS: {
|
|
22
|
+
$type: 'number'
|
|
20
23
|
}
|
|
21
24
|
}).compile('ToggleAgentModeParams');
|
|
22
25
|
|
|
@@ -27,7 +30,7 @@ module.exports = ({ studioConnection, options }) => async function toggleAgentMo
|
|
|
27
30
|
await authorize('ChatThread.toggleAgentMode', roles);
|
|
28
31
|
|
|
29
32
|
const chatThread = await ChatThread.findById(chatThreadId).
|
|
30
|
-
setOptions(
|
|
33
|
+
setOptions(operationOptions(options, params)).
|
|
31
34
|
orFail();
|
|
32
35
|
if (initiatedById != null && chatThread.userId?.toString() !== initiatedById.toString()) {
|
|
33
36
|
throw new Error('Not authorized');
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
-
const
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
6
|
|
|
7
7
|
const DeleteDashboardParams = new Archetype({
|
|
8
8
|
dashboardId: {
|
|
@@ -11,6 +11,9 @@ const DeleteDashboardParams = new Archetype({
|
|
|
11
11
|
},
|
|
12
12
|
roles: {
|
|
13
13
|
$type: ['string']
|
|
14
|
+
},
|
|
15
|
+
maxTimeMS: {
|
|
16
|
+
$type: 'number'
|
|
14
17
|
}
|
|
15
18
|
}).compile('DeleteDashboardParams');
|
|
16
19
|
|
|
@@ -22,9 +25,12 @@ module.exports = ({ studioConnection, options }) => async function deleteDashboa
|
|
|
22
25
|
await authorize('Dashboard.deleteDashboard', roles);
|
|
23
26
|
|
|
24
27
|
const result = await Dashboard.deleteOne({ _id: dashboardId }).
|
|
25
|
-
setOptions(
|
|
28
|
+
setOptions(operationOptions(options, params)).
|
|
26
29
|
orFail();
|
|
27
30
|
await DashboardResult.deleteMany({ dashboardId }).
|
|
28
|
-
setOptions(
|
|
31
|
+
setOptions(operationOptions(options, params));
|
|
29
32
|
return { result };
|
|
30
33
|
};
|
|
34
|
+
|
|
35
|
+
module.exports.paramsType = DeleteDashboardParams;
|
|
36
|
+
module.exports.tags = ['destructive'];
|
|
@@ -4,7 +4,8 @@ const Archetype = require('archetype');
|
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const createSandbox = require('../../sandbox/createSandbox');
|
|
6
6
|
const mongoose = require('mongoose');
|
|
7
|
-
const
|
|
7
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
8
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
8
9
|
const { defaultMothershipURL } = require('../../../constants');
|
|
9
10
|
|
|
10
11
|
const GetDashboardParams = new Archetype({
|
|
@@ -26,6 +27,13 @@ const GetDashboardParams = 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('GetDashboardParams');
|
|
31
39
|
|
|
@@ -37,11 +45,12 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
37
45
|
|
|
38
46
|
await authorize('Dashboard.getDashboard', roles);
|
|
39
47
|
|
|
48
|
+
const dashboardOptions = operationOptions(options, params);
|
|
40
49
|
const dashboard = await Dashboard.findOne({ _id: dashboardId }).
|
|
41
|
-
setOptions(
|
|
50
|
+
setOptions(dashboardOptions);
|
|
42
51
|
if (evaluate) {
|
|
43
52
|
let result = null;
|
|
44
|
-
const sandbox = createSandbox(
|
|
53
|
+
const sandbox = createSandbox(db, { ...readOperationOptions(options, params) });
|
|
45
54
|
const startExec = startDashboardEvaluate(DashboardResult, dashboardId, $workspaceId, userId);
|
|
46
55
|
startExec.catch(() => {}); // Avoid unhandled promise rejections - we will handle this error later.
|
|
47
56
|
try {
|
|
@@ -59,7 +68,7 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
59
68
|
null,
|
|
60
69
|
{ message: error.message },
|
|
61
70
|
'failed',
|
|
62
|
-
|
|
71
|
+
dashboardOptions
|
|
63
72
|
);
|
|
64
73
|
});
|
|
65
74
|
return { dashboard, dashboardResult, error: { message: error.message } };
|
|
@@ -83,7 +92,7 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
83
92
|
result,
|
|
84
93
|
undefined,
|
|
85
94
|
'completed',
|
|
86
|
-
|
|
95
|
+
dashboardOptions
|
|
87
96
|
);
|
|
88
97
|
});
|
|
89
98
|
|
|
@@ -98,15 +107,15 @@ module.exports = ({ db, studioConnection, options }) => async function getDashbo
|
|
|
98
107
|
$workspaceId,
|
|
99
108
|
authorization,
|
|
100
109
|
mothershipUrl,
|
|
101
|
-
|
|
110
|
+
dashboardOptions
|
|
102
111
|
);
|
|
103
112
|
return { dashboard, dashboardResults };
|
|
104
113
|
}
|
|
105
114
|
};
|
|
106
115
|
|
|
107
|
-
async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardResultId, result, error, status,
|
|
116
|
+
async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardResultId, result, error, status, dashboardOptions) {
|
|
108
117
|
const dashboardResult = await DashboardResult.findById(dashboardResultId).
|
|
109
|
-
setOptions(
|
|
118
|
+
setOptions(dashboardOptions).
|
|
110
119
|
orFail();
|
|
111
120
|
const finishedEvaluatingAt = new Date();
|
|
112
121
|
dashboardResult.finishedEvaluatingAt = finishedEvaluatingAt;
|
|
@@ -115,7 +124,7 @@ async function completeDashboardEvaluate(Dashboard, DashboardResult, dashboardRe
|
|
|
115
124
|
dashboardResult.status = status;
|
|
116
125
|
await dashboardResult.save();
|
|
117
126
|
await Dashboard.updateOne({ _id: dashboardResult.dashboardId }, { $set: { lastEvaluatedAt: finishedEvaluatingAt } }).
|
|
118
|
-
setOptions(
|
|
127
|
+
setOptions(dashboardOptions);
|
|
119
128
|
return { dashboardResult };
|
|
120
129
|
}
|
|
121
130
|
|
|
@@ -131,7 +140,7 @@ async function startDashboardEvaluate(DashboardResult, dashboardId, workspaceId,
|
|
|
131
140
|
return { dashboardResult };
|
|
132
141
|
}
|
|
133
142
|
|
|
134
|
-
async function getDashboardResults(DashboardResult, dashboardId, workspaceId, authorization, mothershipUrl,
|
|
143
|
+
async function getDashboardResults(DashboardResult, dashboardId, workspaceId, authorization, mothershipUrl, dashboardOptions) {
|
|
135
144
|
const filter = { dashboardId };
|
|
136
145
|
if (workspaceId != null) {
|
|
137
146
|
filter.workspaceId = workspaceId;
|
|
@@ -141,7 +150,7 @@ async function getDashboardResults(DashboardResult, dashboardId, workspaceId, au
|
|
|
141
150
|
DashboardResult.find(filter).
|
|
142
151
|
sort({ _id: -1 }).
|
|
143
152
|
limit(10).
|
|
144
|
-
setOptions(
|
|
153
|
+
setOptions(dashboardOptions),
|
|
145
154
|
getMothershipDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl)
|
|
146
155
|
]);
|
|
147
156
|
|
|
@@ -212,3 +221,6 @@ function addDocumentSchemaPaths(result) {
|
|
|
212
221
|
}
|
|
213
222
|
result.$document.schemaPaths = schemaPaths;
|
|
214
223
|
}
|
|
224
|
+
|
|
225
|
+
module.exports.paramsType = GetDashboardParams;
|
|
226
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
-
const
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
6
|
|
|
7
7
|
const GetDashboardParams = new Archetype({
|
|
8
8
|
roles: {
|
|
9
9
|
$type: ['string']
|
|
10
|
+
},
|
|
11
|
+
maxTimeMS: {
|
|
12
|
+
$type: 'number'
|
|
10
13
|
}
|
|
11
14
|
}).compile('GetDashboardParams');
|
|
12
15
|
|
|
@@ -18,8 +21,11 @@ module.exports = ({ studioConnection, options }) => async function getDashboards
|
|
|
18
21
|
|
|
19
22
|
const dashboards = await Dashboard
|
|
20
23
|
.find()
|
|
21
|
-
.setOptions(
|
|
24
|
+
.setOptions(operationOptions(options, params))
|
|
22
25
|
.sort({ isPinned: -1, createdAt: -1, _id: -1 });
|
|
23
26
|
|
|
24
27
|
return { dashboards };
|
|
25
28
|
};
|
|
29
|
+
|
|
30
|
+
module.exports.paramsType = GetDashboardParams;
|
|
31
|
+
module.exports.tags = ['readOnly'];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
-
const
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
6
|
|
|
7
7
|
const UpdateDashboardParams = new Archetype({
|
|
8
8
|
dashboardId: {
|
|
@@ -23,6 +23,9 @@ const UpdateDashboardParams = new Archetype({
|
|
|
23
23
|
},
|
|
24
24
|
roles: {
|
|
25
25
|
$type: ['string']
|
|
26
|
+
},
|
|
27
|
+
maxTimeMS: {
|
|
28
|
+
$type: 'number'
|
|
26
29
|
}
|
|
27
30
|
}).compile('UpdateDashboardParams');
|
|
28
31
|
|
|
@@ -55,7 +58,10 @@ module.exports = ({ studioConnection, options }) => async function updateDashboa
|
|
|
55
58
|
dashboardId,
|
|
56
59
|
updateObj,
|
|
57
60
|
{ sanitizeFilter: true, returnDocument: 'after' }
|
|
58
|
-
).setOptions(
|
|
61
|
+
).setOptions(operationOptions(options, params));
|
|
59
62
|
|
|
60
63
|
return { doc };
|
|
61
64
|
};
|
|
65
|
+
|
|
66
|
+
module.exports.paramsType = UpdateDashboardParams;
|
|
67
|
+
module.exports.tags = [];
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
|
-
const
|
|
5
|
+
const operationOptions = require('../../helpers/operationOptions');
|
|
6
6
|
|
|
7
7
|
const AddFieldParams = new Archetype({
|
|
8
8
|
model: {
|
|
@@ -23,6 +23,9 @@ const AddFieldParams = new Archetype({
|
|
|
23
23
|
},
|
|
24
24
|
roles: {
|
|
25
25
|
$type: ['string']
|
|
26
|
+
},
|
|
27
|
+
maxTimeMS: {
|
|
28
|
+
$type: 'number'
|
|
26
29
|
}
|
|
27
30
|
}).compile('AddFieldParams');
|
|
28
31
|
|
|
@@ -49,7 +52,10 @@ module.exports = ({ db, options }) => async function addField(params) {
|
|
|
49
52
|
runValidators: false,
|
|
50
53
|
strict: false
|
|
51
54
|
}
|
|
52
|
-
).setOptions(
|
|
55
|
+
).setOptions(operationOptions(options, params));
|
|
53
56
|
|
|
54
57
|
return { doc };
|
|
55
58
|
};
|
|
59
|
+
|
|
60
|
+
module.exports.paramsType = AddFieldParams;
|
|
61
|
+
module.exports.tags = [];
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const Archetype = require('archetype');
|
|
4
4
|
const authorize = require('../../authorize');
|
|
5
5
|
const { Buffer } = require('buffer');
|
|
6
|
-
const
|
|
6
|
+
const readOperationOptions = require('../../helpers/readOperationOptions');
|
|
7
7
|
const validateDocumentWithTimeout = require('../../helpers/validateDocumentWithTimeout');
|
|
8
8
|
|
|
9
9
|
const SAMPLE_SIZE = 1000;
|
|
@@ -16,6 +16,13 @@ const AnalyzeSchemaParams = new Archetype({
|
|
|
16
16
|
},
|
|
17
17
|
roles: {
|
|
18
18
|
$type: ['string']
|
|
19
|
+
},
|
|
20
|
+
maxTimeMS: {
|
|
21
|
+
$type: 'number'
|
|
22
|
+
},
|
|
23
|
+
readPreference: {
|
|
24
|
+
$type: 'string',
|
|
25
|
+
$enum: ['secondary', 'secondaryPreferred', 'primary']
|
|
19
26
|
}
|
|
20
27
|
}).compile('AnalyzeSchemaParams');
|
|
21
28
|
|
|
@@ -29,11 +36,11 @@ module.exports = ({ db, options }) => async function analyzeSchema(params) {
|
|
|
29
36
|
throw new Error(`Model ${model} not found`);
|
|
30
37
|
}
|
|
31
38
|
|
|
32
|
-
const
|
|
33
|
-
const documentCount = await Model.collection.countDocuments({},
|
|
39
|
+
const dbOptions = readOperationOptions(options, params);
|
|
40
|
+
const documentCount = await Model.collection.countDocuments({}, dbOptions);
|
|
34
41
|
const rawDocs = documentCount > SAMPLE_SIZE ?
|
|
35
|
-
await Model.collection.aggregate([{ $sample: { size: SAMPLE_SIZE } }],
|
|
36
|
-
await Model.collection.find({},
|
|
42
|
+
await Model.collection.aggregate([{ $sample: { size: SAMPLE_SIZE } }], dbOptions).toArray() :
|
|
43
|
+
await Model.collection.find({}, dbOptions).toArray();
|
|
37
44
|
|
|
38
45
|
const paths = getSchemaPaths(Model.schema);
|
|
39
46
|
const pathTypeCounts = {};
|
|
@@ -202,3 +209,6 @@ function getType(value) {
|
|
|
202
209
|
}
|
|
203
210
|
return typeof value;
|
|
204
211
|
}
|
|
212
|
+
|
|
213
|
+
module.exports.paramsType = AnalyzeSchemaParams;
|
|
214
|
+
module.exports.tags = ['readOnly'];
|