@mongoosejs/studio 0.3.8 → 0.3.10

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.
@@ -17,9 +17,9 @@ const CreateDashboardParams = new Archetype({
17
17
  }
18
18
  }).compile('CreateDashboardParams');
19
19
 
20
- module.exports = ({ db }) => async function createDashboard(params) {
20
+ module.exports = ({ studioConnection }) => async function createDashboard(params) {
21
21
  const { title, code, roles } = new CreateDashboardParams(params);
22
- const Dashboard = db.model('__Studio_Dashboard');
22
+ const Dashboard = studioConnection.model('__Studio_Dashboard');
23
23
 
24
24
  await authorize('Dashboard.createDashboard', roles);
25
25
 
@@ -13,12 +13,14 @@ const DeleteDashboardParams = new Archetype({
13
13
  }
14
14
  }).compile('DeleteDashboardParams');
15
15
 
16
- module.exports = ({ db }) => async function deleteDashboard(params) {
16
+ module.exports = ({ studioConnection }) => async function deleteDashboard(params) {
17
17
  const { dashboardId, roles } = new DeleteDashboardParams(params);
18
- const Dashboard = db.model('__Studio_Dashboard');
18
+ const Dashboard = studioConnection.model('__Studio_Dashboard');
19
+ const DashboardResult = studioConnection.model('__Studio_DashboardResult');
19
20
 
20
21
  await authorize('Dashboard.deleteDashboard', roles);
21
22
 
22
23
  const result = await Dashboard.deleteOne({ _id: dashboardId }).orFail();
24
+ await DashboardResult.deleteMany({ dashboardId });
23
25
  return { result };
24
26
  };
@@ -1,13 +1,13 @@
1
1
  'use strict';
2
2
 
3
3
  const Archetype = require('archetype');
4
- const vm = require('vm');
5
4
  const authorize = require('../../authorize');
5
+ const mongoose = require('mongoose');
6
6
  const { defaultMothershipURL } = require('../../../constants');
7
7
 
8
8
  const GetDashboardParams = new Archetype({
9
9
  dashboardId: {
10
- $type: 'string',
10
+ $type: mongoose.Types.ObjectId,
11
11
  $required: true
12
12
  },
13
13
  evaluate: {
@@ -17,16 +17,20 @@ const GetDashboardParams = new Archetype({
17
17
  $type: 'string'
18
18
  },
19
19
  $workspaceId: {
20
- $type: 'string'
20
+ $type: mongoose.Types.ObjectId
21
+ },
22
+ userId: {
23
+ $type: mongoose.Types.ObjectId
21
24
  },
22
25
  roles: {
23
26
  $type: ['string']
24
27
  }
25
28
  }).compile('GetDashboardParams');
26
29
 
27
- module.exports = ({ db, options }) => async function getDashboard(params) {
28
- const { $workspaceId, authorization, dashboardId, evaluate, roles } = new GetDashboardParams(params);
29
- const Dashboard = db.model('__Studio_Dashboard');
30
+ module.exports = ({ studioConnection, options }) => async function getDashboard(params) {
31
+ const { $workspaceId, authorization, userId, dashboardId, evaluate, roles } = new GetDashboardParams(params);
32
+ const Dashboard = studioConnection.model('__Studio_Dashboard');
33
+ const DashboardResult = studioConnection.model('__Studio_DashboardResult');
30
34
  const mothershipUrl = options?._mothershipUrl ?? defaultMothershipURL;
31
35
 
32
36
  await authorize('Dashboard.getDashboard', roles);
@@ -34,9 +38,8 @@ module.exports = ({ db, options }) => async function getDashboard(params) {
34
38
  const dashboard = await Dashboard.findOne({ _id: dashboardId });
35
39
  if (evaluate) {
36
40
  let result = null;
37
- const startExec = startDashboardEvaluate(dashboardId, $workspaceId, authorization, mothershipUrl);
38
- // Avoid unhandled promise rejection since we handle the promise later.
39
- startExec.catch(() => {});
41
+ const startExec = startDashboardEvaluate(DashboardResult, dashboardId, $workspaceId, userId);
42
+ startExec.catch(() => {}); // Avoid unhandled promise rejections - we will handle this error later.
40
43
  try {
41
44
  result = await dashboard.evaluate();
42
45
  } catch (error) {
@@ -45,12 +48,11 @@ module.exports = ({ db, options }) => async function getDashboard(params) {
45
48
  return {};
46
49
  }
47
50
  return completeDashboardEvaluate(
51
+ DashboardResult,
48
52
  dashboardResult._id,
49
- $workspaceId,
50
- authorization,
51
53
  null,
52
54
  { message: error.message },
53
- mothershipUrl
55
+ 'failed'
54
56
  );
55
57
  });
56
58
  return { dashboard, dashboardResult, error: { message: error.message } };
@@ -62,12 +64,11 @@ module.exports = ({ db, options }) => async function getDashboard(params) {
62
64
  return {};
63
65
  }
64
66
  return completeDashboardEvaluate(
67
+ DashboardResult,
65
68
  dashboardResult._id,
66
- $workspaceId,
67
- authorization,
68
69
  result,
69
70
  undefined,
70
- mothershipUrl
71
+ 'completed'
71
72
  );
72
73
  });
73
74
 
@@ -76,77 +77,70 @@ module.exports = ({ db, options }) => async function getDashboard(params) {
76
77
  return { dashboard, error: { message: error.message } };
77
78
  }
78
79
  } else {
79
- const { dashboardResults } = await getDashboardResults(dashboardId, $workspaceId, authorization, mothershipUrl);
80
+ const { dashboardResults } = await getDashboardResults(
81
+ DashboardResult,
82
+ dashboardId,
83
+ $workspaceId,
84
+ authorization,
85
+ mothershipUrl
86
+ );
80
87
  return { dashboard, dashboardResults };
81
88
  }
82
89
  };
83
90
 
84
- async function completeDashboardEvaluate(dashboardResultId, workspaceId, authorization, result, error, mothershipUrl) {
85
- if (!workspaceId) {
86
- return {};
87
- }
88
- const headers = { 'Content-Type': 'application/json' };
89
- if (authorization) {
90
- headers.Authorization = authorization;
91
- }
92
- const response = await fetch(`${mothershipUrl}/completeDashboardEvaluate`, {
93
- method: 'POST',
94
- headers,
95
- body: JSON.stringify({
96
- dashboardResultId,
97
- workspaceId,
98
- finishedEvaluatingAt: new Date(),
99
- result,
100
- error
101
- })
102
- }).then(response => {
103
- if (response.status < 200 || response.status >= 400) {
104
- return response.json().then(data => {
105
- throw new Error(`completeDashboardEvaluate error: ${data.message}`);
106
- });
107
- }
108
- return response;
91
+ async function completeDashboardEvaluate(DashboardResult, dashboardResultId, result, error, status) {
92
+ const dashboardResult = await DashboardResult.findById(dashboardResultId).orFail();
93
+ dashboardResult.finishedEvaluatingAt = new Date();
94
+ dashboardResult.result = result;
95
+ dashboardResult.error = error;
96
+ dashboardResult.status = status;
97
+ await dashboardResult.save();
98
+ return { dashboardResult };
99
+ }
100
+
101
+ async function startDashboardEvaluate(DashboardResult, dashboardId, workspaceId, userId) {
102
+ const dashboardResult = await DashboardResult.create({
103
+ dashboardId,
104
+ workspaceId,
105
+ userId,
106
+ startedEvaluatingAt: new Date(),
107
+ status: 'in_progress'
109
108
  });
110
109
 
111
- return await response.json();
110
+ return { dashboardResult };
112
111
  }
113
112
 
114
- async function startDashboardEvaluate(dashboardId, workspaceId, authorization, _mothershipUrl) {
115
- if (!workspaceId) {
116
- return {};
117
- }
118
- const headers = { 'Content-Type': 'application/json' };
119
- if (authorization) {
120
- headers.Authorization = authorization;
113
+ async function getDashboardResults(DashboardResult, dashboardId, workspaceId, authorization, mothershipUrl) {
114
+ const filter = { dashboardId };
115
+ if (workspaceId != null) {
116
+ filter.workspaceId = workspaceId;
121
117
  }
122
- const response = await fetch(`${_mothershipUrl}/startDashboardEvaluate`, {
123
- method: 'POST',
124
- headers,
125
- body: JSON.stringify({
126
- dashboardId,
127
- workspaceId,
128
- startedEvaluatingAt: new Date()
129
- })
130
- }).then(response => {
131
- if (response.status < 200 || response.status >= 400) {
132
- return response.json().then(data => {
133
- throw new Error(`startDashboardEvaluate error: ${data.message}`);
134
- });
135
- }
136
- return response;
137
- });
138
118
 
139
- return await response.json();
119
+ const [localResultsRes, remoteResultsRes] = await Promise.allSettled([
120
+ DashboardResult.find(filter).sort({ _id: -1 }).limit(10),
121
+ getMothershipDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl)
122
+ ]);
123
+
124
+ const localResults = localResultsRes.status === 'fulfilled' ? localResultsRes.value : [];
125
+ const remoteResults = remoteResultsRes.status === 'fulfilled' ? remoteResultsRes.value : [];
126
+
127
+ const dashboardResults = localResults.concat(remoteResults)
128
+ .sort((a, b) => getSortTime(b) - getSortTime(a))
129
+ .slice(0, 10);
130
+
131
+ return { dashboardResults };
140
132
  }
141
133
 
142
- async function getDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl) {
134
+ async function getMothershipDashboardResults(dashboardId, workspaceId, authorization, mothershipUrl) {
143
135
  if (!workspaceId) {
144
- return {};
136
+ return [];
145
137
  }
138
+
146
139
  const headers = { 'Content-Type': 'application/json' };
147
140
  if (authorization) {
148
141
  headers.Authorization = authorization;
149
142
  }
143
+
150
144
  const response = await fetch(`${mothershipUrl}/getDashboardResults`, {
151
145
  method: 'POST',
152
146
  headers,
@@ -154,14 +148,25 @@ async function getDashboardResults(dashboardId, workspaceId, authorization, moth
154
148
  dashboardId,
155
149
  workspaceId
156
150
  })
157
- }).then(response => {
158
- if (response.status < 200 || response.status >= 400) {
159
- return response.json().then(data => {
160
- throw new Error(`getDashboardResults error: ${data.message}`);
161
- });
162
- }
163
- return response;
164
151
  });
165
152
 
166
- return await response.json();
153
+ if (response.status < 200 || response.status >= 400) {
154
+ let message = `getDashboardResults error: ${response.status}`;
155
+ try {
156
+ const data = await response.json();
157
+ message = `getDashboardResults error: ${data.message}`;
158
+ } catch {
159
+ // Ignore parse errors and keep the generic status-based message.
160
+ }
161
+ throw new Error(message);
162
+ }
163
+
164
+ const data = await response.json();
165
+ return Array.isArray(data.dashboardResults) ? data.dashboardResults : [];
166
+ }
167
+
168
+ function getSortTime(result) {
169
+ const candidate = result?.finishedEvaluatingAt ?? result?.startedEvaluatingAt ?? result?.createdAt ?? result?._id;
170
+ const time = new Date(candidate).getTime();
171
+ return Number.isNaN(time) ? 0 : time;
167
172
  }
@@ -9,8 +9,8 @@ const GetDashboardParams = new Archetype({
9
9
  }
10
10
  }).compile('GetDashboardParams');
11
11
 
12
- module.exports = ({ db }) => async function getDashboards(params) {
13
- const Dashboard = db.model('__Studio_Dashboard');
12
+ module.exports = ({ studioConnection }) => async function getDashboards(params) {
13
+ const Dashboard = studioConnection.model('__Studio_Dashboard');
14
14
  const { roles } = new GetDashboardParams(params);
15
15
 
16
16
  await authorize('Dashboard.getDashboards', roles);
@@ -23,10 +23,10 @@ const UpdateDashboardParams = new Archetype({
23
23
  }
24
24
  }).compile('UpdateDashboardParams');
25
25
 
26
- module.exports = ({ db }) => async function updateDashboard(params) {
26
+ module.exports = ({ studioConnection }) => async function updateDashboard(params) {
27
27
  const { dashboardId, code, title, description, roles, evaluate } = new UpdateDashboardParams(params);
28
28
 
29
- const Dashboard = db.models['__Studio_Dashboard'];
29
+ const Dashboard = studioConnection.models['__Studio_Dashboard'];
30
30
 
31
31
  await authorize('Dashboard.updateDashboard', roles);
32
32
 
@@ -0,0 +1,37 @@
1
+ 'use strict';
2
+
3
+ const mongoose = require('mongoose');
4
+
5
+ const dashboardResultSchema = new mongoose.Schema({
6
+ dashboardId: {
7
+ type: mongoose.Schema.Types.ObjectId,
8
+ ref: '__Studio_Dashboard',
9
+ required: true
10
+ },
11
+ workspaceId: {
12
+ type: mongoose.Schema.Types.ObjectId
13
+ },
14
+ userId: {
15
+ type: mongoose.Schema.Types.ObjectId
16
+ },
17
+ startedEvaluatingAt: {
18
+ type: Date
19
+ },
20
+ finishedEvaluatingAt: {
21
+ type: Date
22
+ },
23
+ status: {
24
+ type: String,
25
+ enum: ['in_progress', 'completed', 'failed'],
26
+ required: true
27
+ },
28
+ error: new mongoose.Schema({
29
+ message: String,
30
+ extra: String
31
+ }, { _id: false }),
32
+ result: mongoose.Schema.Types.Mixed
33
+ }, { timestamps: true });
34
+
35
+ dashboardResultSchema.index({ dashboardId: 1 });
36
+
37
+ module.exports = dashboardResultSchema;
package/backend/index.js CHANGED
@@ -7,12 +7,14 @@ const mongoose = require('mongoose');
7
7
  const chatMessageSchema = require('./db/chatMessageSchema');
8
8
  const chatThreadSchema = require('./db/chatThreadSchema');
9
9
  const dashboardSchema = require('./db/dashboardSchema');
10
+ const dashboardResultSchema = require('./db/dashboardResultSchema');
10
11
 
11
12
  module.exports = function backend(db, studioConnection, options) {
12
13
  db = db || mongoose.connection;
13
14
 
14
15
  studioConnection = studioConnection ?? db;
15
16
  const Dashboard = studioConnection.model('__Studio_Dashboard', dashboardSchema, 'studio__dashboards');
17
+ const DashboardResult = studioConnection.model('__Studio_DashboardResult', dashboardResultSchema, 'studio__dashboardResults');
16
18
  const ChatMessage = studioConnection.model('__Studio_ChatMessage', chatMessageSchema, 'studio__chatMessages');
17
19
  const ChatThread = studioConnection.model('__Studio_ChatThread', chatThreadSchema, 'studio__chatThreads');
18
20
 
@@ -62835,7 +62835,7 @@ var src_default = VueToastificationPlugin;
62835
62835
  (module) {
62836
62836
 
62837
62837
  "use strict";
62838
- module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.8","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","time-commando":"1.0.1","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
62838
+ module.exports = /*#__PURE__*/JSON.parse('{"name":"@mongoosejs/studio","version":"0.3.10","description":"A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.","homepage":"https://mongoosestudio.app/","repository":{"type":"git","url":"https://github.com/mongoosejs/studio"},"license":"Apache-2.0","dependencies":{"@ai-sdk/anthropic":"2.x","@ai-sdk/google":"2.x","@ai-sdk/openai":"2.x","ace-builds":"^1.43.6","ai":"5.x","archetype":"0.13.1","csv-stringify":"6.3.0","ejson":"^2.2.3","extrovert":"^0.2.0","marked":"15.0.12","node-inspect-extracted":"3.x","regexp.escape":"^2.0.1","tailwindcss":"3.4.0","vue":"3.x","vue-toastification":"^2.0.0-rc.5","webpack":"5.x","time-commando":"1.0.1","xss":"^1.0.15"},"peerDependencies":{"mongoose":"7.x || 8.x || ^9.0.0"},"optionalPeerDependencies":{"@mongoosejs/task":"0.5.x || 0.6.x"},"devDependencies":{"@masteringjs/eslint-config":"0.1.1","axios":"1.2.2","dedent":"^1.6.0","eslint":"9.30.0","express":"4.x","mocha":"10.2.0","mongodb-memory-server":"^11.0.1","mongoose":"9.x","sinon":"^21.0.1"},"scripts":{"lint":"eslint .","seed":"node seed/index.js","start":"node ./local.js","tailwind":"tailwindcss -o ./frontend/public/tw.css","tailwind:watch":"tailwindcss -o ./frontend/public/tw.css --watch","test":"mocha test/*.test.js","test:frontend":"mocha test/frontend/*.test.js"}}');
62839
62839
 
62840
62840
  /***/ }
62841
62841
 
package/index.d.ts CHANGED
@@ -1,7 +1,12 @@
1
1
  declare module '@mongoosejs/studio' {
2
2
  import { RequestHandler } from 'express';
3
+ import { Connection, Mongoose } from 'mongoose';
3
4
 
4
- const express: (path: string) => RequestHandler;
5
+ const express: (
6
+ path: string,
7
+ connOrMongoose?: Connection | Mongoose,
8
+ options?: { apiKey?: string }
9
+ ) => RequestHandler;
5
10
 
6
11
  const studio: {
7
12
  express: typeof express;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mongoosejs/studio",
3
- "version": "0.3.8",
3
+ "version": "0.3.10",
4
4
  "description": "A Mongoose-native MongoDB UI with schema-aware autocomplete, AI-assisted queries, and dashboards that understand your models - not just your data.",
5
5
  "homepage": "https://mongoosestudio.app/",
6
6
  "repository": {