@mongoosejs/studio 0.2.12 → 0.3.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.
Files changed (92) hide show
  1. package/backend/actions/ChatMessage/executeScript.js +5 -1
  2. package/backend/actions/ChatThread/createChatMessage.js +2 -1
  3. package/backend/actions/ChatThread/streamChatMessage.js +2 -2
  4. package/backend/actions/Model/getEstimatedDocumentCounts.js +38 -0
  5. package/backend/actions/Model/index.js +1 -0
  6. package/backend/actions/Model/streamDocumentChanges.js +8 -7
  7. package/backend/actions/Task/getTasks.js +9 -6
  8. package/backend/authorize.js +1 -0
  9. package/backend/index.js +11 -3
  10. package/eslint.config.js +5 -1
  11. package/express.js +1 -0
  12. package/frontend/public/app.js +25235 -662
  13. package/frontend/public/dark-theme.css +365 -0
  14. package/frontend/public/images/mongoose-studio.svg +4 -0
  15. package/frontend/public/index.html +21 -1
  16. package/frontend/public/style.css +5 -7
  17. package/frontend/public/theme-variables.css +294 -0
  18. package/frontend/public/tw.css +461 -239
  19. package/frontend/src/ace-editor/ace-editor.html +4 -0
  20. package/frontend/src/ace-editor/ace-editor.js +89 -0
  21. package/frontend/src/aceEditor.js +69 -0
  22. package/frontend/src/api.js +6 -0
  23. package/frontend/src/chat/chat-message/chat-message.html +1 -1
  24. package/frontend/src/chat/chat-message/chat-message.js +1 -1
  25. package/frontend/src/chat/chat-message-script/chat-message-script.html +51 -34
  26. package/frontend/src/chat/chat-message-script/chat-message-script.js +12 -55
  27. package/frontend/src/chat/chat.html +72 -37
  28. package/frontend/src/chat/chat.js +26 -2
  29. package/frontend/src/clone-document/clone-document.html +7 -2
  30. package/frontend/src/clone-document/clone-document.js +1 -8
  31. package/frontend/src/create-dashboard/create-dashboard.html +11 -6
  32. package/frontend/src/create-dashboard/create-dashboard.js +0 -7
  33. package/frontend/src/create-document/create-document.html +15 -9
  34. package/frontend/src/create-document/create-document.js +5 -12
  35. package/frontend/src/dashboard/dashboard.html +14 -12
  36. package/frontend/src/dashboard/dashboard.js +12 -4
  37. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.html +13 -7
  38. package/frontend/src/dashboard/edit-dashboard/edit-dashboard.js +13 -21
  39. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.html +19 -17
  40. package/frontend/src/dashboard-result/dashboard-chart/dashboard-chart.js +97 -2
  41. package/frontend/src/dashboard-result/dashboard-map/dashboard-map.js +27 -3
  42. package/frontend/src/dashboard-result/dashboard-result.html +3 -3
  43. package/frontend/src/dashboards/dashboards.html +101 -109
  44. package/frontend/src/dashboards/dashboards.js +25 -1
  45. package/frontend/src/detail-default/detail-default.html +2 -2
  46. package/frontend/src/detail-default/detail-default.js +24 -3
  47. package/frontend/src/document/confirm-changes/confirm-changes.html +1 -1
  48. package/frontend/src/document/confirm-delete/confirm-delete.html +1 -1
  49. package/frontend/src/document/document.css +1 -1
  50. package/frontend/src/document/document.html +53 -27
  51. package/frontend/src/document/document.js +27 -1
  52. package/frontend/src/document/execute-script/execute-script.html +20 -21
  53. package/frontend/src/document/execute-script/execute-script.js +1 -43
  54. package/frontend/src/document-details/document-details.css +4 -9
  55. package/frontend/src/document-details/document-details.html +34 -33
  56. package/frontend/src/document-details/document-details.js +2 -53
  57. package/frontend/src/document-details/document-property/document-property.html +12 -12
  58. package/frontend/src/edit-array/edit-array.html +7 -6
  59. package/frontend/src/edit-array/edit-array.js +10 -50
  60. package/frontend/src/edit-boolean/edit-boolean.html +12 -12
  61. package/frontend/src/edit-date/edit-date.html +2 -2
  62. package/frontend/src/edit-default/edit-default.html +1 -1
  63. package/frontend/src/edit-string/edit-string.html +3 -3
  64. package/frontend/src/edit-subdocument/edit-subdocument.html +5 -3
  65. package/frontend/src/edit-subdocument/edit-subdocument.js +1 -15
  66. package/frontend/src/export-query-results/export-query-results.html +3 -3
  67. package/frontend/src/json-node/json-node.html +3 -3
  68. package/frontend/src/list-json/json-node.html +1 -1
  69. package/frontend/src/models/document-search/document-search.html +3 -3
  70. package/frontend/src/models/model-switcher/model-switcher.html +53 -0
  71. package/frontend/src/models/model-switcher/model-switcher.js +123 -0
  72. package/frontend/src/models/models.css +3 -10
  73. package/frontend/src/models/models.html +146 -74
  74. package/frontend/src/models/models.js +142 -4
  75. package/frontend/src/navbar/navbar.html +157 -97
  76. package/frontend/src/navbar/navbar.js +32 -13
  77. package/frontend/src/routes.js +20 -4
  78. package/frontend/src/splash/splash.html +5 -5
  79. package/frontend/src/task-by-name/task-by-name.html +15 -0
  80. package/frontend/src/task-by-name/task-by-name.js +78 -0
  81. package/frontend/src/task-single/task-single.html +157 -0
  82. package/frontend/src/task-single/task-single.js +116 -0
  83. package/frontend/src/tasks/task-details/task-details.html +124 -73
  84. package/frontend/src/tasks/task-details/task-details.js +166 -10
  85. package/frontend/src/tasks/tasks.html +37 -48
  86. package/frontend/src/tasks/tasks.js +11 -50
  87. package/frontend/src/team/new-invitation/new-invitation.html +8 -8
  88. package/frontend/src/team/team.html +27 -27
  89. package/frontend/src/update-document/update-document.html +7 -2
  90. package/frontend/src/update-document/update-document.js +2 -11
  91. package/package.json +3 -1
  92. package/tailwind.config.js +75 -11
@@ -43,7 +43,11 @@ module.exports = ({ db, studioConnection }) => async function executeScript(para
43
43
  if (!db.Types) {
44
44
  db.Types = mongoose.Types;
45
45
  }
46
- const sandbox = { db, mongoose, console: {}, ObjectId: mongoose.Types.ObjectId };
46
+ const MongooseStudioChartColors = [
47
+ '#4e79a7', '#e15759', '#59a14f', '#9c755f',
48
+ '#f28e2b', '#b07aa1', '#76b7b2', '#edc948'
49
+ ];
50
+ const sandbox = { db, mongoose, console: {}, ObjectId: mongoose.Types.ObjectId, MongooseStudioChartColors };
47
51
 
48
52
  // Capture console logs
49
53
  sandbox.console.log = function() {
@@ -108,6 +108,7 @@ The following globals are available. Assume no other globals exist.
108
108
  - mongoose: the output of require('mongoose').
109
109
  - ObjectId: MongoDB ObjectId class from mongoose.Types.ObjectId
110
110
  - console: has a stubbed log() function that logs to the console and is accessible in the output.
111
+ - MongooseStudioChartColors: an array of 8 hex color strings for use as chart dataset colors.
111
112
 
112
113
  Keep scripts concise. Avoid unnecessary comments, error handling, and temporary variables.
113
114
 
@@ -129,7 +130,7 @@ Format output as Markdown, including code fences for any scripts the user reques
129
130
 
130
131
  Add a brief text description of what the script does.
131
132
 
132
- If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`. Disable ChartJS animation by default unless user asks for it. Set responsive: true, maintainAspectRatio: false options unless the user explicitly asks.
133
+ If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`. Disable ChartJS animation by default unless user asks for it. Set responsive: true, maintainAspectRatio: false options unless the user explicitly asks. Use MongooseStudioChartColors for dataset backgroundColor and borderColor by default. For line/bar charts, use MongooseStudioChartColors[i] as borderColor and MongooseStudioChartColors[i] + '33' as backgroundColor for each dataset. For pie/doughnut charts, use MongooseStudioChartColors.slice(0, data.length) as backgroundColor. Only use custom colors if the user explicitly requests specific colors.
133
134
 
134
135
  If the user\'s query is best answered by a map, return an object { $featureCollection } which contains a GeoJSON FeatureCollection
135
136
 
@@ -123,7 +123,7 @@ const systemPrompt = `
123
123
 
124
124
  Do not write any imports or require() statements, that will cause the script to break.
125
125
 
126
- If the user approves the script, the script will run in the Node.js server in a sandboxed vm.createContext() call with only 1 global variable: db, which contains the Mongoose connection. The script return value will then send the response via JSON to the client. Be aware that the result of the query will be serialized to JSON before being displayed to the user. MAKE SURE TO RETURN A VALUE FROM THE SCRIPT.
126
+ If the user approves the script, the script will run in the Node.js server in a sandboxed vm.createContext() call with the following globals: db (the Mongoose connection), mongoose, ObjectId (mongoose.Types.ObjectId), console, and MongooseStudioChartColors (an array of 8 hex color strings for chart dataset colors). The script return value will then send the response via JSON to the client. Be aware that the result of the query will be serialized to JSON before being displayed to the user. MAKE SURE TO RETURN A VALUE FROM THE SCRIPT.
127
127
 
128
128
  Optimize scripts for readability first, followed by reliability, followed by performance. Avoid using the aggregation framework unless explicitly requested by the user. Use indexed fields in queries where possible.
129
129
 
@@ -139,7 +139,7 @@ const systemPrompt = `
139
139
 
140
140
  Add a brief text description of what the script does.
141
141
 
142
- If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`. Disable ChartJS animation by default unless user asks for it. Set responsive: true, maintainAspectRatio: false options unless the user explicitly asks.
142
+ If the user's query is best answered with a chart, return a Chart.js 4 configuration as \`return { $chart: chartJSConfig };\`. Disable ChartJS animation by default unless user asks for it. Set responsive: true, maintainAspectRatio: false options unless the user explicitly asks. Use MongooseStudioChartColors for dataset backgroundColor and borderColor by default. For line/bar charts, use MongooseStudioChartColors[i] as borderColor and MongooseStudioChartColors[i] + '33' as backgroundColor for each dataset. For pie/doughnut charts, use MongooseStudioChartColors.slice(0, data.length) as backgroundColor. Only use custom colors if the user explicitly requests specific colors.
143
143
 
144
144
  If the user\'s query is best answered by a map, return an object { $featureCollection } which contains a GeoJSON FeatureCollection
145
145
 
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ const Archetype = require('archetype');
4
+ const authorize = require('../../authorize');
5
+
6
+ const GetEstimatedDocumentCountsParams = new Archetype({
7
+ roles: {
8
+ $type: ['string']
9
+ }
10
+ }).compile('GetEstimatedDocumentCountsParams');
11
+
12
+ module.exports = ({ db }) => async function getEstimatedDocumentCounts(params) {
13
+ const { roles } = new GetEstimatedDocumentCountsParams(params);
14
+ await authorize('Model.getEstimatedDocumentCounts', roles);
15
+
16
+ const modelNames = Object.keys(db.models)
17
+ .filter(key => !key.startsWith('__Studio_'))
18
+ .sort();
19
+
20
+ const results = await Promise.allSettled(
21
+ modelNames.map(name => {
22
+ const Model = db.models[name];
23
+ return Model.estimatedDocumentCount().exec();
24
+ })
25
+ );
26
+
27
+ const counts = {};
28
+ results.forEach((result, index) => {
29
+ const name = modelNames[index];
30
+ if (result.status === 'fulfilled' && typeof result.value === 'number') {
31
+ counts[name] = result.value;
32
+ } else {
33
+ counts[name] = null;
34
+ }
35
+ });
36
+
37
+ return { counts };
38
+ };
@@ -13,6 +13,7 @@ exports.getDocuments = require('./getDocuments');
13
13
  exports.getDocumentsStream = require('./getDocumentsStream');
14
14
  exports.getCollectionInfo = require('./getCollectionInfo');
15
15
  exports.getIndexes = require('./getIndexes');
16
+ exports.getEstimatedDocumentCounts = require('./getEstimatedDocumentCounts');
16
17
  exports.listModels = require('./listModels');
17
18
  exports.streamDocumentChanges = require('./streamDocumentChanges');
18
19
  exports.streamChatMessage = require('./streamChatMessage');
@@ -27,7 +27,8 @@ module.exports = ({ db, changeStream }) => async function* streamDocumentChanges
27
27
  throw new Error(`Model ${model} not found`);
28
28
  }
29
29
 
30
- if (!changeStream) {
30
+ const stream = changeStream();
31
+ if (!stream) {
31
32
  throw new Error('Change streams are not enabled');
32
33
  }
33
34
 
@@ -79,9 +80,9 @@ module.exports = ({ db, changeStream }) => async function* streamDocumentChanges
79
80
  enqueue({ type: 'end' });
80
81
  }
81
82
 
82
- changeStream.on('change', handleChange);
83
- changeStream.on('error', handleError);
84
- changeStream.on('end', handleEnd);
83
+ stream.on('change', handleChange);
84
+ stream.on('error', handleError);
85
+ stream.on('end', handleEnd);
85
86
 
86
87
  try {
87
88
  while (true) {
@@ -112,9 +113,9 @@ module.exports = ({ db, changeStream }) => async function* streamDocumentChanges
112
113
  }
113
114
  }
114
115
  } finally {
115
- changeStream.off('change', handleChange);
116
- changeStream.off('error', handleError);
117
- changeStream.off('end', handleEnd);
116
+ stream.off('change', handleChange);
117
+ stream.off('error', handleError);
118
+ stream.off('end', handleEnd);
118
119
  if (resolveQueue) {
119
120
  resolveQueue();
120
121
  resolveQueue = null;
@@ -4,7 +4,8 @@ const Archetype = require('archetype');
4
4
 
5
5
  const GetTasksParams = new Archetype({
6
6
  start: {
7
- $type: Date
7
+ $type: Date,
8
+ $required: true
8
9
  },
9
10
  end: {
10
11
  $type: Date
@@ -31,22 +32,24 @@ module.exports = ({ db }) => async function getTasks(params) {
31
32
  }
32
33
  if (status) {
33
34
  filter.status = status;
35
+ } else {
36
+ filter.status = { $in: ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'] };
34
37
  }
35
38
  if (name) {
36
39
  filter.name = { $regex: name, $options: 'i' };
37
40
  }
38
41
 
39
42
  const tasks = await Task.find(filter);
40
-
43
+
41
44
  // Define all possible statuses
42
45
  const allStatuses = ['pending', 'in_progress', 'succeeded', 'failed', 'cancelled', 'unknown'];
43
-
46
+
44
47
  // Initialize groupedTasks with all statuses
45
48
  const groupedTasks = allStatuses.reduce((groups, status) => {
46
49
  groups[status] = [];
47
50
  return groups;
48
51
  }, {});
49
-
52
+
50
53
  // Group tasks by status
51
54
  tasks.forEach(task => {
52
55
  const taskStatus = task.status || 'unknown';
@@ -54,9 +57,9 @@ module.exports = ({ db }) => async function getTasks(params) {
54
57
  groupedTasks[taskStatus].push(task);
55
58
  }
56
59
  });
57
-
60
+
58
61
  return {
59
62
  tasks,
60
63
  groupedTasks
61
64
  };
62
- };
65
+ };
@@ -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.getEstimatedDocumentCounts': ['owner', 'admin', 'member', 'readonly'],
25
26
  'Model.getIndexes': ['owner', 'admin', 'member', 'readonly'],
26
27
  'Model.listModels': ['owner', 'admin', 'member', 'readonly'],
27
28
  'Model.streamDocumentChanges': ['owner', 'admin', 'member', 'readonly'],
package/backend/index.js CHANGED
@@ -18,10 +18,18 @@ module.exports = function backend(db, studioConnection, options) {
18
18
 
19
19
  let changeStream = null;
20
20
  if (options?.changeStream) {
21
- changeStream = db instanceof mongoose.Mongoose ? db.connection.watch() : db.watch();
21
+ const conn = db instanceof mongoose.Mongoose ? db.connection : db;
22
+ if (conn.readyState !== mongoose.Connection.STATES.connected) {
23
+ conn._waitForConnect().then(() => {
24
+ changeStream = conn.watch();
25
+ });
26
+ } else {
27
+ changeStream = conn.watch();
28
+ }
29
+
22
30
  }
23
31
 
24
- const actions = applySpec(Actions, { db, studioConnection, options, changeStream });
25
- actions.services = { changeStream };
32
+ const actions = applySpec(Actions, { db, studioConnection, options, changeStream: () => changeStream });
33
+ actions.services = { changeStream: () => changeStream };
26
34
  return actions;
27
35
  };
package/eslint.config.js CHANGED
@@ -42,7 +42,11 @@ module.exports = defineConfig([
42
42
  navigator: true,
43
43
  TextDecoder: true,
44
44
  AbortController: true,
45
- clearTimeout: true
45
+ clearTimeout: true,
46
+ requestAnimationFrame: true,
47
+ localStorage: true,
48
+ getComputedStyle: true,
49
+ CustomEvent: true
46
50
  },
47
51
  sourceType: 'commonjs'
48
52
  },
package/express.js CHANGED
@@ -33,6 +33,7 @@ module.exports = async function mongooseStudioExpressApp(apiUrl, conn, options)
33
33
 
34
34
  apiUrl = apiUrl || 'api';
35
35
  const backend = Backend(conn, options.studioConnection, options);
36
+ delete backend.services;
36
37
 
37
38
  router.use(
38
39
  '/api',